/*! * @license * Copyright Squiz Australia Pty Ltd. All Rights Reserved. */ import { AttributeValue, DynamoDBClient, PutItemCommand, QueryCommand, QueryCommandInput, UpdateItemCommand, UpdateItemCommandInput, } from '@aws-sdk/client-dynamodb'; import { inject } from 'inversify'; import { provideTransient } from '../../transientProvider'; import { Repository } from '../AbstractRepository'; import { ENTITY_TYPES } from '../../constants/Repository.constants'; import { parsePartitionKey } from '../../ParsePartitionKey'; import { CreateJobContextRequest, JobContext, JobContextDto, JobContextSk, UpdateJobContextRequest, } from '../../model/JobContext'; import { decodeTokenString, PAGINATION_DIRECTION_TYPE_BEFORE, PaginationInfo, PaginationQueryResponse, } from '../../core/pagination'; import { InjectTokens } from '../../constants/InjectTokens'; @provideTransient(JobContextRepository) export class JobContextRepository extends Repository { public constructor( @inject(DynamoDBClient) dynamoDbClient: DynamoDBClient, @inject(InjectTokens.DynamoTableName) tableName: string, @inject(InjectTokens.Tenant) tenant: string, ) { super(dynamoDbClient, tableName, `${ENTITY_TYPES.context}~${tenant}`); } public async create(input: CreateJobContextRequest): Promise { const newJobContext: JobContext = { ...input, type: ENTITY_TYPES.context, }; const dynamoDbRecord = this.convertToDynamoDbItem(newJobContext); await this.dynamoDbClient.send( new PutItemCommand({ Item: dynamoDbRecord, TableName: this.tableName, }), ); return newJobContext; } /** * Get list of records from database. * @abstract * @returns {Promise>} The retrieved records. * @memberof Repository */ async getContexts(paginationInfo: PaginationInfo): Promise> { const { token, direction } = paginationInfo; const params: QueryCommandInput = { ExpressionAttributeValues: { ':pk': { S: this.pk }, }, KeyConditionExpression: 'pk = :pk', TableName: this.tableName, }; if (token) { params.ExclusiveStartKey = decodeTokenString(token); } if (direction === PAGINATION_DIRECTION_TYPE_BEFORE) { params.ScanIndexForward = false; } const response = await this.dynamoDbClient.send(new QueryCommand(params)); let items: Array = []; if (response.Items && response.Items.length) { items = response.Items.map((item) => this.convertToItem(item as JobContextDto)); } return { items, lastEvaluatedKey: response.LastEvaluatedKey, }; } async update(sk: JobContextSk, input: UpdateJobContextRequest): Promise { // Initialise update command const updateParams: UpdateItemCommandInput = { Key: { pk: { S: this.pk }, sk: { S: this.buildSk(sk) }, }, ReturnValues: 'ALL_NEW', TableName: this.tableName, }; const expressionAttributeNames: Record = {}; const expressionAttributeValues: Record = {}; // To avoid conflict with reserved keywords, refer to attributes with an additional '#' prefix const environmentMap: Record = {}; if (input.environment) { for (const key of Object.keys(input.environment)) { environmentMap[key] = { S: input.environment[key] }; } expressionAttributeNames['#environment'] = 'environment'; expressionAttributeValues[':environment'] = { M: environmentMap }; } if (input.description != null) { expressionAttributeNames['#description'] = 'description'; expressionAttributeValues[':description'] = { S: input.description }; } const updateExpressionPairs = Object.keys(input).map((key) => `#${key} = :${key}`); const updateExpression = `SET ${updateExpressionPairs.join(', ')}`; updateParams.ExpressionAttributeNames = expressionAttributeNames; updateParams.ExpressionAttributeValues = expressionAttributeValues; updateParams.UpdateExpression = updateExpression; const updateItemCommand = new UpdateItemCommand(updateParams); const updatedJob = await this.dynamoDbClient.send(updateItemCommand); /* istanbul ignore next */ return this.convertToItem(updatedJob.Attributes as JobContextDto); } convertToDynamoDbItem(item: JobContext): JobContextDto { const environmentMap: Record = {}; for (const key of Object.keys(item.environment)) { environmentMap[key] = { S: item.environment[key] }; } return { pk: { S: this.pk }, sk: { S: this.buildSk(item) }, description: { S: item.description }, environment: { M: environmentMap }, }; } convertToItem(dbItem: JobContextDto): JobContext { const { pk, sk, description } = dbItem; const environment = dbItem.environment as AttributeValue.MMember; const environmentMap = environment.M; const environmentRecord: Record = {}; for (const key of Object.keys(environmentMap)) { environmentRecord[key] = environmentMap[key].S; } return { contextName: sk.S as string, type: parsePartitionKey(pk.S as string).entityType, description: description.S as string, environment: environmentRecord, }; } protected buildSk(sk: JobContextSk): string { return sk.contextName; } }