source/packages/services/assetlibraryhistory/src/events/events.dao.ts (318 lines of code) (raw):

/********************************************************************************************************************* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. * * * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * * with the License. A copy of the License is located at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * * and limitations under the License. * *********************************************************************************************************************/ import { logger } from '@awssolutions/simple-cdf-logger'; import atob from 'atob'; import AWS from 'aws-sdk'; import { AttributeValue, DocumentClient } from 'aws-sdk/clients/dynamodb'; import btoa from 'btoa'; import { inject, injectable } from 'inversify'; import { TYPES } from '../di/types'; import { StateHistoryListModel, StateHistoryModel } from './events.models'; @injectable() export class EventsDao { private _dc: AWS.DynamoDB.DocumentClient; public constructor( @inject('aws.dynamoDb.tables.events') private eventsTable: string, @inject(TYPES.DocumentClientFactory) documentClientFactory: () => AWS.DynamoDB.DocumentClient ) { this._dc = documentClientFactory(); } public async getLatest(objectId: string): Promise<StateHistoryModel> { logger.debug(`events.dao getLatest: in: objectId:${JSON.stringify(objectId)}`); const params = { TableName: this.eventsTable, Key: { objectId, time: 'latest', }, }; const r = await this._dc.get(params).promise(); if (r.Item === undefined) { logger.debug('events.dao getLatest: exit: undefined'); return undefined; } const i = r.Item; const event = { objectId: i['objectId'], type: i['type'], time: i['time'], event: i['event'], user: i['user'], state: i['state'], }; logger.debug(`events.dao getLatest: exit: event:${JSON.stringify(event)}`); return event; } public async create(model: StateHistoryModel): Promise<void> { logger.debug(`events.dao create: in: model:${JSON.stringify(model)}`); const params: DocumentClient.PutItemInput = { TableName: this.eventsTable, Item: { objectId: model.objectId, type: model.type, time: model.time, event: model.event, user: model.user, state: model.state, }, }; logger.debug(`events.dao create: params:${JSON.stringify(params)}`); await this._dc.put(params).promise(); logger.debug(`events.dao create: exit:`); } public async update(model: StateHistoryModel): Promise<void> { logger.debug(`events.dao update: in: model:${JSON.stringify(model)}`); const params: DocumentClient.UpdateItemInput = { TableName: this.eventsTable, Key: { objectId: model.objectId, time: model.time }, UpdateExpression: '', ExpressionAttributeNames: {}, ExpressionAttributeValues: {}, }; Object.keys(model).forEach((k) => { if ( model.hasOwnProperty(k) && k !== 'objectId' && k !== 'time' && model[k] !== undefined ) { if (params.UpdateExpression === '') { params.UpdateExpression += 'set '; } else { params.UpdateExpression += ', '; } params.UpdateExpression += `#${k} = :${k}`; params.ExpressionAttributeNames[`#${k}`] = k; params.ExpressionAttributeValues[`:${k}`] = model[k]; } }); // TODO add optimistic locking logger.debug(`events.dao update: params:${JSON.stringify(params)}`); await this._dc.update(params).promise(); logger.debug(`events.dao update: exit:`); } public async listCategoryEvents(args: ListCategoryEventsArgs): Promise<StateHistoryListModel> { logger.debug(`events.dao listCategoryEvents: args:${JSON.stringify(args)}`); // initialize the key let keyConditionExpression = '#type = :type'; const expressionAttributeNames: DocumentClient.ExpressionAttributeNameMap = { '#type': 'type', }; const expressionAttributeValues: DocumentClient.ExpressionAttributeValueMap = { ':type': args.category, }; // apply the filters keyConditionExpression = this.applyTimeRangeFilter( keyConditionExpression, expressionAttributeNames, expressionAttributeValues, args.timeFrom, args.timeTo ); let filterExpression: string; filterExpression = this.applyUserFilter( filterExpression, expressionAttributeNames, expressionAttributeValues, args.user ); filterExpression = this.applyEventFilter( filterExpression, expressionAttributeNames, expressionAttributeValues, args.event ); // apply pagination if provided let exclusiveStartKey: { [key: string]: AttributeValue }; if (args.token) { const lastEvaluated: string[] = atob(args.token).split(':::'); exclusiveStartKey = {}; exclusiveStartKey['objectId'] = lastEvaluated[0] as AttributeValue; exclusiveStartKey['time'] = lastEvaluated[1] as AttributeValue; } // the params for the dynamodb call const scanIndexForward = args.sort === SortDirection.asc; const params: DocumentClient.QueryInput = { TableName: this.eventsTable, IndexName: 'type-time-index', KeyConditionExpression: keyConditionExpression, ExpressionAttributeNames: expressionAttributeNames, ExpressionAttributeValues: expressionAttributeValues, FilterExpression: filterExpression, Limit: args.limit, ScanIndexForward: scanIndexForward, ExclusiveStartKey: exclusiveStartKey, }; logger.debug(`events.dao listCategoryEvents: params: ${JSON.stringify(params)}`); const results = await this._dc.query(params).promise(); const response = this.assembleEventList(results); logger.debug(`events.dao listCategoryEvents: exit: response:${JSON.stringify(response)}`); return response; } public async listObjectEvents(args: ListObjectEventsArgs): Promise<StateHistoryListModel> { logger.debug(`events.dao listObjectEvents: args:${JSON.stringify(args)}`); // initialize the key let keyConditionExpression = '#objectId = :objectId'; const expressionAttributeNames: DocumentClient.ExpressionAttributeNameMap = { '#objectId': 'objectId', }; const expressionAttributeValues: DocumentClient.ExpressionAttributeValueMap = { ':objectId': args.objectId, }; // apply the time filter which may be for a specific time, or for a time range let scanIndexForward = args.sort === SortDirection.asc; if (args.timeAt) { keyConditionExpression += ' and #time between :timeFrom and :timeTo'; expressionAttributeNames['#time'] = 'time'; expressionAttributeValues[':timeFrom'] = args.timeAt; expressionAttributeValues[':timeTo'] = '9999'; args.limit = 1; scanIndexForward = true; } else { keyConditionExpression = this.applyTimeRangeFilter( keyConditionExpression, expressionAttributeNames, expressionAttributeValues, args.timeFrom, args.timeTo ); } // apply any other filtering let filterExpression: string; filterExpression = this.applyUserFilter( filterExpression, expressionAttributeNames, expressionAttributeValues, args.user ); filterExpression = this.applyEventFilter( filterExpression, expressionAttributeNames, expressionAttributeValues, args.event ); // apply pagination if provided let exclusiveStartKey: { [key: string]: AttributeValue }; if (args.token) { const lastEvaluated: string[] = atob(args.token).split(':::'); exclusiveStartKey = {}; exclusiveStartKey['objectId'] = lastEvaluated[0] as AttributeValue; exclusiveStartKey['time'] = lastEvaluated[1] as AttributeValue; } // the params for the dynamodb call const params: DocumentClient.QueryInput = { TableName: this.eventsTable, KeyConditionExpression: keyConditionExpression, ExpressionAttributeNames: expressionAttributeNames, ExpressionAttributeValues: expressionAttributeValues, FilterExpression: filterExpression, Limit: args.limit, ScanIndexForward: scanIndexForward, ExclusiveStartKey: exclusiveStartKey, }; logger.debug(`events.dao listObjectEvents: params: ${JSON.stringify(params)}`); const results = await this._dc.query(params).promise(); const response = this.assembleEventList(results); logger.debug(`events.dao listObjectEvents: exit: response:${JSON.stringify(response)}`); return response; } private applyTimeRangeFilter( keyConditionExpression: string, expressionAttributeNames: DocumentClient.ExpressionAttributeNameMap, expressionAttributeValues: DocumentClient.ExpressionAttributeValueMap, timeFrom: string, timeTo: string ): string { if (!timeFrom && !timeTo) { return keyConditionExpression; } keyConditionExpression += ' and #time between :timeFrom and :timeTo'; expressionAttributeNames['#time'] = 'time'; expressionAttributeValues[':timeFrom'] = timeFrom; expressionAttributeValues[':timeTo'] = timeTo; return keyConditionExpression; } private applyUserFilter( filterExpression: string, expressionAttributeNames: DocumentClient.ExpressionAttributeNameMap, expressionAttributeValues: DocumentClient.ExpressionAttributeValueMap, user: string ): string { if (user) { filterExpression = this.appendFilterExpression(filterExpression, '#user = :user'); expressionAttributeNames['#user'] = 'user'; expressionAttributeValues[':user'] = user; } return filterExpression; } private applyEventFilter( filterExpression: string, expressionAttributeNames: DocumentClient.ExpressionAttributeNameMap, expressionAttributeValues: DocumentClient.ExpressionAttributeValueMap, event: string ): string { if (event) { filterExpression = this.appendFilterExpression(filterExpression, '#event = :event'); expressionAttributeNames['#event'] = 'event'; expressionAttributeValues[':event'] = event; } return filterExpression; } private assembleEventList(results: DocumentClient.QueryOutput): StateHistoryListModel { logger.debug(`events.dao assembleEventList: in: results:${JSON.stringify(results)}`); const events: StateHistoryModel[] = []; const response: StateHistoryListModel = { events, }; if (results.Items === undefined) { logger.debug('events.dao assembleEventList: exit: events:undefined'); return undefined; } for (const item of results.Items) { const event = { objectId: item['objectId'], type: item['type'], time: item['time'], event: item['event'], user: item['user'], state: '{}', }; if (item['state'] !== undefined) { event['state'] = JSON.parse(item['state']); } response.events.push(event); } if (results.LastEvaluatedKey) { response.pagination = { token: btoa( `${results.LastEvaluatedKey['objectId']}:::${results.LastEvaluatedKey['time']}` ), limit: 1, }; } logger.debug(`events.dao assembleEventList: exit: events:${JSON.stringify(response)}`); return response; } private appendFilterExpression(existing: string, toAppend: string): string { if (existing === undefined) { return toAppend; } else { return `${existing} and ${toAppend}`; } } } export interface ListCategoryEventsArgs { category: string; timeFrom?: string; timeTo?: string; user?: string; event?: string; sort?: SortDirection; token?: string; limit?: number; } export interface ListObjectEventsArgs { category: string; objectId: string; timeAt?: string; timeFrom?: string; timeTo?: string; user?: string; event?: string; sort?: SortDirection; token?: string; limit?: number; } export enum SortDirection { asc = 'asc', desc = 'desc', }