import { Injectable, Inject } from '@nestjs/common'; import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { DynamoDBDocumentClient, GetCommand, PutCommand, DeleteCommand, TranslateConfig, } from '@aws-sdk/lib-dynamodb'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { v4 as uuid } from 'uuid'; import { DYNAMODB_CONFIG } from './dynamodb.config'; export interface ReferenceColumn { name: string; type: string; } export interface InputDocument { id: string; client_id: string; user_id: string; created_at: string; name: string; description?: string; plugin: string; type: string; tables?: Array<{ name: string; type: string; columns?: string[]; reference_column?: ReferenceColumn; }>; credentials?: Record; } @Injectable() export class DynamoDBService { private documentClient: DynamoDBDocumentClient; private logger: any; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, ) { this.logger = dadosferaLogger.logger; const dynamoConfig = { region: DYNAMODB_CONFIG.region() }; const marshallOptions: TranslateConfig = { marshallOptions: { removeUndefinedValues: true, }, }; const dynamoDb = new DynamoDBClient(dynamoConfig); this.documentClient = DynamoDBDocumentClient.from(dynamoDb, marshallOptions); } async createInput( clientId: string, userId: string, data: { name: string; description?: string; plugin: string; type: string; tables?: Array<{ name: string; type: string; columns?: string[]; reference_column?: ReferenceColumn; }>; }, ): Promise { const tableName = DYNAMODB_CONFIG.inputsTable(); const id = uuid(); const created_at = new Date().toISOString(); const item: InputDocument = { id, client_id: clientId, user_id: userId, created_at, name: data.name, description: data.description, plugin: data.plugin, type: data.type, tables: data.tables, }; this.logger.info('DynamoDB: Creating input', { tableName, inputId: id, plugin: data.plugin, }); const putCommand = new PutCommand({ TableName: tableName, Item: item, }); try { await this.documentClient.send(putCommand); this.logger.info('DynamoDB: Input created successfully', { inputId: id }); return item; } catch (error) { this.logger.error('DynamoDB: Failed to create input', { tableName, inputId: id, region: DYNAMODB_CONFIG.region(), error: error.message, errorName: error.name, }); throw error; } } async findInput(clientId: string, inputId: string): Promise { const tableName = DYNAMODB_CONFIG.inputsTable(); const getCommand = new GetCommand({ TableName: tableName, Key: { id: inputId, client_id: clientId, }, }); try { const { Item } = await this.documentClient.send(getCommand); return Item as InputDocument | null; } catch (error) { this.logger.error('DynamoDB: findInput failed', { inputId, clientId, error: error.message }); throw error; } } async deleteInput(clientId: string, inputId: string): Promise { const tableName = DYNAMODB_CONFIG.inputsTable(); this.logger.info('DynamoDB: Deleting input', { tableName, inputId, }); const deleteCommand = new DeleteCommand({ TableName: tableName, Key: { id: inputId, client_id: clientId, }, }); await this.documentClient.send(deleteCommand); this.logger.info('DynamoDB: Input deleted successfully', { inputId }); } /** * Update a specific table entry in the input document. * Fetches the current document, updates the matching table, and saves. */ async updateInputTable( clientId: string, inputId: string, tableName: string, changes: { type?: string; columns?: string[]; reference_column?: ReferenceColumn | null; }, ): Promise { const dynamoTableName = DYNAMODB_CONFIG.inputsTable(); this.logger.info('DynamoDB: Updating input table', { inputId, tableName, changes: Object.keys(changes), }); // Get current document const current = await this.findInput(clientId, inputId); if (!current) { this.logger.warn('DynamoDB: Input not found for update', { inputId }); return; } // Find and update the matching table const tables = current.tables || []; const tableIndex = tables.findIndex((t) => t.name === tableName); if (tableIndex === -1) { this.logger.warn('DynamoDB: Table not found in input', { inputId, tableName, }); return; } // Merge changes into the table entry const updatedTable = { ...tables[tableIndex] }; if ('type' in changes) updatedTable.type = changes.type; if ('columns' in changes) updatedTable.columns = changes.columns; if ('reference_column' in changes) { if (changes.reference_column === null) { delete updatedTable.reference_column; } else { updatedTable.reference_column = changes.reference_column; } } tables[tableIndex] = updatedTable; // Save updated document const putCommand = new PutCommand({ TableName: dynamoTableName, Item: { ...current, tables, updated_at: new Date().toISOString(), }, }); try { await this.documentClient.send(putCommand); this.logger.info('DynamoDB: Input table updated successfully', { inputId, tableName, }); } catch (error) { this.logger.error('DynamoDB: Failed to update input table', { inputId, tableName, error: error.message, }); throw error; } } }