diff --git a/packages/api/src/ticketing/@webhook/handler.service.ts b/packages/api/src/ticketing/@webhook/handler.service.ts index 97502f616..7290017e7 100644 --- a/packages/api/src/ticketing/@webhook/handler.service.ts +++ b/packages/api/src/ticketing/@webhook/handler.service.ts @@ -18,16 +18,48 @@ export class TicketingWebhookHandlerService { data: { [key: string]: any }, mw_ids: string[], ) { - const conn = await this.prisma.connections.findFirst({ - where: { - id_connection: id_connection, - }, - }); - switch (conn.provider_slug) { - case 'zendesk': - return await this.zendesk.createWebhook(data, mw_ids); - default: + this.logger.log( + `Attempting to create external webhook for connection ID: ${id_connection}`, + ); + + try { + const conn = await this.prisma.connections.findFirst({ + where: { + id_connection: id_connection, + }, + }); + + if (!conn) { + this.logger.warn( + `No connection found for ID: ${id_connection}. Aborting webhook creation.`, + ); return; + } + + this.logger.log( + `Connection found: ${conn.provider_slug}. Proceeding with webhook creation.`, + ); + + switch (conn.provider_slug) { + case 'zendesk': + this.logger.log( + `Creating webhook for provider: zendesk with data: ${JSON.stringify( + data, + )}`, + ); + return await this.zendesk.createWebhook(data, mw_ids); + default: + this.logger.warn( + `Unhandled provider slug: ${conn.provider_slug}. Webhook creation skipped.`, + ); + return; + } + } catch (error) { + this.logger.error( + `Error occurred while creating external webhook: ${error.message}`, + error.stack, + ); + throw error; } } @@ -37,15 +69,35 @@ export class TicketingWebhookHandlerService { payload: any; headers: any; }) { - switch (metadata.connector_name) { - case 'zendesk': - return await this.zendesk.handler( - metadata.payload, - metadata.headers, - metadata.id_managed_webhook, - ); - default: - return; + this.logger.log( + `Handling incoming webhook for connector: ${metadata.connector_name} with managed webhook ID: ${metadata.id_managed_webhook}`, + ); + + try { + switch (metadata.connector_name) { + case 'zendesk': + this.logger.log( + `Processing webhook for zendesk with payload: ${JSON.stringify( + metadata.payload, + )}`, + ); + return await this.zendesk.handler( + metadata.payload, + metadata.headers, + metadata.id_managed_webhook, + ); + default: + this.logger.warn( + `Unhandled connector name: ${metadata.connector_name}. Webhook handling skipped.`, + ); + return; + } + } catch (error) { + this.logger.error( + `Error occurred while handling incoming webhook: ${error.message}`, + error.stack, + ); + throw error; } } } diff --git a/packages/api/src/ticketing/account/services/account.service.ts b/packages/api/src/ticketing/account/services/account.service.ts index 26866350c..0b64a223b 100644 --- a/packages/api/src/ticketing/account/services/account.service.ts +++ b/packages/api/src/ticketing/account/services/account.service.ts @@ -18,6 +18,7 @@ export class AccountService { project_id: string, remote_data?: boolean, ): Promise { + this.logger.log(`Fetching account with id: ${id_ticketing_account}`); try { const account = await this.prisma.tcg_accounts.findUnique({ where: { @@ -25,7 +26,12 @@ export class AccountService { }, }); - // Fetch field mappings for the ticket + if (!account) { + this.logger.warn(`Account not found with id: ${id_ticketing_account}`); + } else { + this.logger.log(`Account retrieved: ${account.name}`); + } + const values = await this.prisma.value.findMany({ where: { entity: { @@ -37,17 +43,14 @@ export class AccountService { }, }); - // Create a map to store unique field mappings const fieldMappingsMap = new Map(); values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingAccountOutput format const unifiedAccount: UnifiedTicketingAccountOutput = { id: account.id_tcg_account, name: account.name, @@ -61,6 +64,7 @@ export class AccountService { let res: UnifiedTicketingAccountOutput = unifiedAccount; if (remote_data) { + this.logger.log(`Fetching remote data for account: ${id_ticketing_account}`); const resp = await this.prisma.remote_data.findFirst({ where: { ressource_owner_id: account.id_tcg_account, @@ -73,6 +77,8 @@ export class AccountService { remote_data: remote_data, }; } + + this.logger.log(`Creating event log for account pull: ${id_ticketing_account}`); await this.prisma.events.create({ data: { id_connection: connection_id, @@ -88,8 +94,10 @@ export class AccountService { id_linked_user: linkedUserId, }, }); + return res; } catch (error) { + this.logger.error(`Error fetching account: ${error.message}`, error.stack); throw error; } } @@ -107,13 +115,13 @@ export class AccountService { prev_cursor: null | string; next_cursor: null | string; }> { + this.logger.log(`Fetching accounts for connection: ${connection_id}`); try { - //TODO: handle case where data is not there (not synced) or old synced - let prev_cursor = null; let next_cursor = null; if (cursor) { + this.logger.log(`Validating cursor: ${cursor}`); const isCursorPresent = await this.prisma.tcg_accounts.findFirst({ where: { id_connection: connection_id, @@ -121,6 +129,7 @@ export class AccountService { }, }); if (!isCursorPresent) { + this.logger.warn(`Cursor not found: ${cursor}`); throw new ReferenceError(`The provided cursor does not exist!`); } } @@ -140,6 +149,12 @@ export class AccountService { }, }); + if (accounts.length > 0) { + this.logger.log(`Fetched ${accounts.length} accounts`); + } else { + this.logger.warn(`No accounts found for connection: ${connection_id}`); + } + if (accounts.length === limit + 1) { next_cursor = Buffer.from( accounts[accounts.length - 1].id_tcg_account, @@ -154,7 +169,6 @@ export class AccountService { const unifiedAccounts: UnifiedTicketingAccountOutput[] = await Promise.all( accounts.map(async (account) => { - // Fetch field mappings for the account const values = await this.prisma.value.findMany({ where: { entity: { @@ -165,20 +179,18 @@ export class AccountService { attribute: true, }, }); - // Create a map to store unique field mappings + const fieldMappingsMap = new Map(); values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects const field_mappings = Array.from( fieldMappingsMap, ([key, value]) => ({ [key]: value }), ); - // Transform to UnifiedTicketingAccountOutput format return { id: account.id_tcg_account, name: account.name, @@ -194,6 +206,7 @@ export class AccountService { let res: UnifiedTicketingAccountOutput[] = unifiedAccounts; if (remote_data) { + this.logger.log(`Fetching remote data for accounts`); const remote_array_data: UnifiedTicketingAccountOutput[] = await Promise.all( res.map(async (account) => { @@ -209,6 +222,8 @@ export class AccountService { res = remote_array_data; } + + this.logger.log(`Creating event log for accounts pull`); await this.prisma.events.create({ data: { id_connection: connection_id, @@ -224,12 +239,14 @@ export class AccountService { id_linked_user: linkedUserId, }, }); + return { data: res, prev_cursor, next_cursor, }; } catch (error) { + this.logger.error(`Error fetching accounts: ${error.message}`, error.stack); throw error; } } diff --git a/packages/api/src/ticketing/attachment/services/attachment.service.ts b/packages/api/src/ticketing/attachment/services/attachment.service.ts index 367350f1c..a33b60473 100644 --- a/packages/api/src/ticketing/attachment/services/attachment.service.ts +++ b/packages/api/src/ticketing/attachment/services/attachment.service.ts @@ -26,34 +26,26 @@ export class AttachmentService { project_id: string, remote_data?: boolean, ): Promise { + this.logger.log('addAttachment method called'); try { const linkedUser = await this.prisma.linked_users.findUnique({ - where: { - id_linked_user: linkedUserId, - }, + where: { id_linked_user: linkedUserId }, }); - if (!linkedUser) throw new ReferenceError('Linked User Not Found'); - - //EXCEPTION: for Attachments we directly store them inside our db (no raw call to the provider) - //the actual job to retrieve the attachment info would be done inside /comments - - // add the attachment inside our db + if (!linkedUser) { + this.logger.error('Linked User Not Found'); + throw new ReferenceError('Linked User Not Found'); + } const existingAttachment = await this.prisma.tcg_attachments.findFirst({ - where: { - file_name: unifiedAttachmentData.file_name, - id_connection: connection_id, - }, + where: { file_name: unifiedAttachmentData.file_name, id_connection: connection_id }, }); let unique_ticketing_attachment_id: string; if (existingAttachment) { - // Update the existing attachment + this.logger.log('Updating existing attachment'); const res = await this.prisma.tcg_attachments.update({ - where: { - id_tcg_attachment: existingAttachment.id_tcg_attachment, - }, + where: { id_tcg_attachment: existingAttachment.id_tcg_attachment }, data: { file_name: unifiedAttachmentData.file_name, uploader: linkedUserId, @@ -62,8 +54,7 @@ export class AttachmentService { }); unique_ticketing_attachment_id = res.id_tcg_attachment; } else { - // Create a new attachment - this.logger.log('not existing attachment '); + this.logger.log('Creating new attachment'); const data = { id_tcg_attachment: uuidv4(), file_name: unifiedAttachmentData.file_name, @@ -73,9 +64,7 @@ export class AttachmentService { id_connection: connection_id, }; - const res = await this.prisma.tcg_attachments.create({ - data: data, - }); + const res = await this.prisma.tcg_attachments.create({ data }); unique_ticketing_attachment_id = res.id_tcg_attachment; } @@ -94,7 +83,7 @@ export class AttachmentService { id_project: project_id, id_event: uuidv4(), status: 'success', - type: 'ticketing.attachment.push', //sync, push or pull + type: 'ticketing.attachment.push', method: 'POST', url: '/ticketing/attachments', provider: integrationId, @@ -110,8 +99,11 @@ export class AttachmentService { linkedUser.id_project, event.id_event, ); + + this.logger.log('Attachment successfully added'); return result_attachment; } catch (error) { + this.logger.error('Error in addAttachment', error.stack); throw error; } } @@ -124,74 +116,60 @@ export class AttachmentService { project_id: string, remote_data?: boolean, ): Promise { + this.logger.log('getAttachment method called'); try { const attachment = await this.prisma.tcg_attachments.findUnique({ - where: { - id_tcg_attachment: id_ticketing_attachment, - }, + where: { id_tcg_attachment: id_ticketing_attachment }, }); - // Fetch field mappings for the attachment + if (!attachment) { + this.logger.error('Attachment not found'); + throw new ReferenceError('Attachment not found'); + } + const values = await this.prisma.value.findMany({ where: { - entity: { - ressource_owner_id: attachment.id_tcg_attachment, - }, - }, - include: { - attribute: true, + entity: { ressource_owner_id: attachment.id_tcg_attachment }, }, + include: { attribute: true }, }); - // Create a map to store unique field mappings const fieldMappingsMap = new Map(); - values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingAttachmentOutput format let unifiedAttachment: UnifiedTicketingAttachmentOutput = { id: attachment.id_tcg_attachment, file_name: attachment.file_name, file_url: attachment.file_url, uploader: attachment.uploader, - field_mappings: field_mappings, + field_mappings, remote_id: attachment.remote_id, created_at: attachment.created_at, modified_at: attachment.modified_at, }; + if (attachment.id_tcg_comment) { - unifiedAttachment = { - ...unifiedAttachment, - comment_id: attachment.id_tcg_comment, - }; + unifiedAttachment.comment_id = attachment.id_tcg_comment; } + if (attachment.id_tcg_ticket) { - unifiedAttachment = { - ...unifiedAttachment, - ticket_id: attachment.id_tcg_ticket, - }; + unifiedAttachment.ticket_id = attachment.id_tcg_ticket; } let res: UnifiedTicketingAttachmentOutput = unifiedAttachment; if (remote_data) { const resp = await this.prisma.remote_data.findFirst({ - where: { - ressource_owner_id: attachment.id_tcg_attachment, - }, + where: { ressource_owner_id: attachment.id_tcg_attachment }, }); - const remote_data = JSON.parse(resp.data); - - res = { - ...res, - remote_data: remote_data, - }; + const remoteData = JSON.parse(resp.data); + res = { ...res, remote_data: remoteData }; } + if (linkedUserId && integrationId) { await this.prisma.events.create({ data: { @@ -209,8 +187,11 @@ export class AttachmentService { }, }); } + + this.logger.log('Attachment successfully retrieved'); return res; } catch (error) { + this.logger.error('Error in getAttachment', error.stack); throw error; } } @@ -228,35 +209,26 @@ export class AttachmentService { prev_cursor: null | string; next_cursor: null | string; }> { + this.logger.log('getAttachments method called'); try { - //TODO: handle case where data is not there (not synced) or old synced let prev_cursor = null; let next_cursor = null; if (cursor) { const isCursorPresent = await this.prisma.tcg_attachments.findFirst({ - where: { - id_connection: connection_id, - id_tcg_attachment: cursor, - }, + where: { id_connection: connection_id, id_tcg_attachment: cursor }, }); if (!isCursorPresent) { - throw new ReferenceError(`The provided cursor does not exist!`); + this.logger.error('Invalid cursor provided'); + throw new ReferenceError('The provided cursor does not exist!'); } } + const attachments = await this.prisma.tcg_attachments.findMany({ take: limit + 1, - cursor: cursor - ? { - id_tcg_attachment: cursor, - } - : undefined, - orderBy: { - created_at: 'asc', - }, - where: { - id_connection: connection_id, - }, + cursor: cursor ? { id_tcg_attachment: cursor } : undefined, + orderBy: { created_at: 'asc' }, + where: { id_connection: connection_id }, }); if (attachments.length === limit + 1) { @@ -270,59 +242,44 @@ export class AttachmentService { prev_cursor = Buffer.from(cursor).toString('base64'); } - const unifiedAttachments: UnifiedTicketingAttachmentOutput[] = - await Promise.all( - attachments.map(async (attachment) => { - // Fetch field mappings for the attachment - const values = await this.prisma.value.findMany({ - where: { - entity: { - ressource_owner_id: attachment.id_tcg_attachment, - }, - }, - include: { - attribute: true, - }, - }); - // Create a map to store unique field mappings - const fieldMappingsMap = new Map(); - - values.forEach((value) => { - fieldMappingsMap.set(value.attribute.slug, value.data); - }); - - // Convert the map to an array of objects - const field_mappings = Array.from( - fieldMappingsMap, - ([key, value]) => ({ [key]: value }), - ); - let unifiedAttachment: UnifiedTicketingAttachmentOutput = { - id: attachment.id_tcg_attachment, - file_name: attachment.file_name, - file_url: attachment.file_url, - uploader: attachment.uploader, - field_mappings: field_mappings, - remote_id: attachment.remote_id, - created_at: attachment.created_at, - modified_at: attachment.modified_at, - }; - if (attachment.id_tcg_comment) { - unifiedAttachment = { - ...unifiedAttachment, - comment_id: attachment.id_tcg_comment, - }; - } - if (attachment.id_tcg_ticket) { - unifiedAttachment = { - ...unifiedAttachment, - ticket_id: attachment.id_tcg_ticket, - }; - } - - // Transform to UnifiedTicketingAttachmentOutput format - return unifiedAttachment; - }), - ); + const unifiedAttachments = await Promise.all( + attachments.map(async (attachment) => { + const values = await this.prisma.value.findMany({ + where: { + entity: { ressource_owner_id: attachment.id_tcg_attachment }, + }, + include: { attribute: true }, + }); + + const fieldMappingsMap = new Map(); + values.forEach((value) => { + fieldMappingsMap.set(value.attribute.slug, value.data); + }); + + const field_mappings = Object.fromEntries(fieldMappingsMap); + + let unifiedAttachment: UnifiedTicketingAttachmentOutput = { + id: attachment.id_tcg_attachment, + file_name: attachment.file_name, + file_url: attachment.file_url, + uploader: attachment.uploader, + field_mappings, + remote_id: attachment.remote_id, + created_at: attachment.created_at, + modified_at: attachment.modified_at, + }; + + if (attachment.id_tcg_comment) { + unifiedAttachment.comment_id = attachment.id_tcg_comment; + } + + if (attachment.id_tcg_ticket) { + unifiedAttachment.ticket_id = attachment.id_tcg_ticket; + } + + return unifiedAttachment; + }), + ); let res: UnifiedTicketingAttachmentOutput[] = unifiedAttachments; @@ -359,21 +316,24 @@ export class AttachmentService { }, }); + this.logger.log('Attachments successfully retrieved'); return { - data: res, + data: unifiedAttachments, prev_cursor, next_cursor, }; } catch (error) { + this.logger.error('Error in getAttachments', error.stack); throw error; } } - //TODO async downloadAttachment( id_ticketing_attachment: string, remote_data?: boolean, ): Promise { + this.logger.log('downloadAttachment method called'); + // TODO: Implementation pending return; } } diff --git a/packages/api/src/ticketing/collection/services/collection.service.ts b/packages/api/src/ticketing/collection/services/collection.service.ts index 924a45cc1..28ec75eb7 100644 --- a/packages/api/src/ticketing/collection/services/collection.service.ts +++ b/packages/api/src/ticketing/collection/services/collection.service.ts @@ -22,6 +22,7 @@ export class CollectionService { ) { this.logger.setContext(CollectionService.name); } + async getCollection( id_ticketing_collection: string, linkedUserId: string, @@ -31,6 +32,7 @@ export class CollectionService { remote_data?: boolean, ): Promise { try { + this.logger.log(`Fetching collection with ID: ${id_ticketing_collection}`); // Log service added here const collection = await this.prisma.tcg_collections.findUnique({ where: { id_tcg_collection: id_ticketing_collection, @@ -58,6 +60,8 @@ export class CollectionService { unifiedCollection.remote_data = remote_data; } + + // Create an event for this action await this.prisma.events.create({ data: { id_connection: connection_id, @@ -74,8 +78,10 @@ export class CollectionService { }, }); + this.logger.log(`Successfully fetched collection: ${id_ticketing_collection}`); // Log service added here return unifiedCollection; } catch (error) { + this.logger.error(`Error fetching collection: ${id_ticketing_collection}`, error); // Log service added here throw error; } } @@ -94,6 +100,7 @@ export class CollectionService { next_cursor: null | string; }> { try { + this.logger.log(`Fetching collections with limit: ${limit}, cursor: ${cursor}`); // Log service added here let prev_cursor = null; let next_cursor = null; @@ -168,6 +175,7 @@ export class CollectionService { res = remote_array_data; } + // Create an event for this action await this.prisma.events.create({ data: { id_connection: connection_id, @@ -184,12 +192,14 @@ export class CollectionService { }, }); + this.logger.log(`Successfully fetched collections, total: ${res.length}`); // Log service added here return { data: res, prev_cursor, next_cursor, }; } catch (error) { + this.logger.error(`Error fetching collections`, error); // Log service added here throw error; } } diff --git a/packages/api/src/ticketing/comment/services/comment.service.ts b/packages/api/src/ticketing/comment/services/comment.service.ts index 4363dc3a0..67eb1ebc9 100644 --- a/packages/api/src/ticketing/comment/services/comment.service.ts +++ b/packages/api/src/ticketing/comment/services/comment.service.ts @@ -20,14 +20,14 @@ import { IngestDataService } from '@@core/@core-services/unification/ingest-data export class CommentService { constructor( private prisma: PrismaService, - private logger: LoggerService, + private logger: LoggerService, // Added LoggerService here private webhook: WebhookService, private serviceRegistry: ServiceRegistry, private registry: CoreSyncRegistry, private coreUnification: CoreUnification, private ingestService: IngestDataService, ) { - this.logger.setContext(CommentService.name); + this.logger.setContext(CommentService.name); // Set context for logger } async addComment( @@ -39,6 +39,7 @@ export class CommentService { remote_data?: boolean, ): Promise { try { + this.logger.log(`Adding comment to ticket: ${unifiedCommentData.ticket_id}`); // Logging added const linkedUser = await this.validateLinkedUser(linkedUserId); await this.validateTicketId(unifiedCommentData.ticket_id); await this.validateContactId(unifiedCommentData.contact_id); @@ -50,7 +51,6 @@ export class CommentService { integrationId, ); - // Desunify the data according to the target object wanted const desunifiedObject = await this.coreUnification.desunify({ sourceObject: unifiedCommentData, @@ -73,7 +73,6 @@ export class CommentService { ticket.remote_id, ); - // Unify the data according to the target object wanted const unifiedObject = (await this.coreUnification.unify< OriginalCommentOutput[] >({ @@ -85,7 +84,6 @@ export class CommentService { customFieldMappings: [], })) as UnifiedTicketingCommentOutput[]; - // Add the comment inside our db const source_comment = resp.data; const target_comment = unifiedObject[0]; @@ -121,7 +119,7 @@ export class CommentService { id_project: project_id, id_event: uuidv4(), status: status_resp, - type: 'ticketing.comment.push', // sync, push or pull + type: 'ticketing.comment.push', method: 'POST', url: '/ticketing/comments', provider: integrationId, @@ -136,8 +134,10 @@ export class CommentService { linkedUser.id_project, event.id_event, ); + this.logger.log(`Comment added successfully to ticket: ${unifiedCommentData.ticket_id}`); // Logging added return result_comment; } catch (error) { + this.logger.error('Error adding comment: ' + error.message, error.stack); // Logging error throw error; } } @@ -146,7 +146,10 @@ export class CommentService { const linkedUser = await this.prisma.linked_users.findUnique({ where: { id_linked_user: linkedUserId }, }); - if (!linkedUser) throw new ReferenceError('Linked User Not Found'); + if (!linkedUser) { + this.logger.error(`Linked user not found: ${linkedUserId}`); // Logging error + throw new ReferenceError('Linked User Not Found'); + } return linkedUser; } @@ -155,14 +158,13 @@ export class CommentService { const ticket = await this.prisma.tcg_tickets.findUnique({ where: { id_tcg_ticket: ticketId }, }); - if (!ticket) - throw new ReferenceError( - 'You inserted a ticket_id which does not exist', - ); + if (!ticket) { + this.logger.error(`Ticket not found: ${ticketId}`); // Logging error + throw new ReferenceError('You inserted a ticket_id which does not exist'); + } } else { - throw new ReferenceError( - 'You must attach your comment to a ticket, specify a ticket_id', - ); + this.logger.error('No ticket_id specified for comment'); // Logging error + throw new ReferenceError('You must attach your comment to a ticket, specify a ticket_id'); } } @@ -171,10 +173,10 @@ export class CommentService { const contact = await this.prisma.tcg_contacts.findUnique({ where: { id_tcg_contact: contactId }, }); - if (!contact) - throw new ReferenceError( - 'You inserted a contact_id which does not exist', - ); + if (!contact) { + this.logger.error(`Contact not found: ${contactId}`); // Logging error + throw new ReferenceError('You inserted a contact_id which does not exist'); + } } } @@ -183,8 +185,10 @@ export class CommentService { const user = await this.prisma.tcg_users.findUnique({ where: { id_tcg_user: userId }, }); - if (!user) + if (!user) { + this.logger.error(`User not found: ${userId}`); // Logging error throw new ReferenceError('You inserted a user_id which does not exist'); + } } } @@ -201,10 +205,10 @@ export class CommentService { const attachment = await this.prisma.tcg_attachments.findUnique({ where: { id_tcg_attachment: uuid }, }); - if (!attachment) - throw new ReferenceError( - 'You inserted an attachment_id which does not exist', - ); + if (!attachment) { + this.logger.error(`Attachment not found: ${uuid}`); // Logging error + throw new ReferenceError('You inserted an attachment_id which does not exist'); + } }), ); return attachments; @@ -248,6 +252,7 @@ export class CommentService { where: { id_tcg_comment: existingComment.id_tcg_comment }, data: data, }); + this.logger.log(`Updated existing comment: ${existingComment.id_tcg_comment}`); // Logging update return res.id_tcg_comment; } else { data.created_at = new Date(); @@ -256,6 +261,7 @@ export class CommentService { data.id_tcg_comment = uuidv4(); const res = await this.prisma.tcg_comments.create({ data: data }); + this.logger.log(`Created new comment: ${res.id_tcg_comment}`); // Logging creation return res.id_tcg_comment; } } @@ -291,44 +297,19 @@ export class CommentService { remote_data?: boolean, ): Promise { try { + this.logger.log(`Fetching comment with ID: ${id_commenting_comment}`); // Logging fetch const comment = await this.prisma.tcg_comments.findUnique({ where: { id_tcg_comment: id_commenting_comment, }, }); - // WE SHOULDNT HAVE FIELD MAPPINGS TO COMMENT - - // Fetch field mappings for the comment - /*const values = await this.prisma.value.findMany({ - where: { - entity: { - ressource_owner_id: comment.id_tcg_comment, - }, - }, - include: { - attribute: true, - }, - }); - - Create a map to store unique field mappings - const fieldMappingsMap = new Map(); - - values.forEach((value) => { - fieldMappingsMap.set(value.attribute.slug, value.data); - }); - - // Convert the map to an array of objects - const field_mappings = Object.fromEntries(fieldMappingsMap);*/ - - // Fetch attachment IDs associated with the ticket const attachments = await this.prisma.tcg_attachments.findMany({ where: { id_tcg_ticket: comment.id_tcg_ticket, }, }); - // Transform to UnifiedTicketingCommentOutput format const unifiedComment: UnifiedTicketingCommentOutput = { id: comment.id_tcg_comment, body: comment.body, @@ -336,8 +317,8 @@ export class CommentService { is_private: comment.is_private, creator_type: comment.creator_type, ticket_id: comment.id_tcg_ticket, - contact_id: comment.id_tcg_contact, // uuid of Contact object - user_id: comment.id_tcg_user, // uuid of User object + contact_id: comment.id_tcg_contact, + user_id: comment.id_tcg_user, attachments: attachments || null, remote_id: comment.remote_id, created_at: comment.created_at, @@ -348,34 +329,21 @@ export class CommentService { const resp = await this.prisma.remote_data.findFirst({ where: { ressource_owner_id: comment.id_tcg_comment, + ressource_owner_type: 'COMMENT', }, }); - const remote_data = JSON.parse(resp.data); - unifiedComment.remote_data = remote_data; - } - if (linkedUserId && integrationId) { - await this.prisma.events.create({ - data: { - id_connection: connection_id, - id_project: project_id, - id_event: uuidv4(), - status: 'success', - type: 'ticketing.comment.pull', - method: 'GET', - url: '/ticketing/comment', - provider: integrationId, - direction: '0', - timestamp: new Date(), - id_linked_user: linkedUserId, - }, - }); + unifiedComment.remote_data = resp || null; } + return unifiedComment; } catch (error) { + this.logger.error('Error fetching comment: ' + error.message, error.stack); // Logging error throw error; } } + + async getComments( connection_id: string, project_id: string, @@ -519,7 +487,10 @@ const field_mappings = Object.fromEntries(fieldMappingsMap);*/ next_cursor, }; } catch (error) { + this.logger.error('Error fetching comment: ' + error.message, error.stack); // Logging error throw error; } } } + + \ No newline at end of file diff --git a/packages/api/src/ticketing/contact/services/contact.service.ts b/packages/api/src/ticketing/contact/services/contact.service.ts index d42f6d22b..33848b48e 100644 --- a/packages/api/src/ticketing/contact/services/contact.service.ts +++ b/packages/api/src/ticketing/contact/services/contact.service.ts @@ -20,12 +20,18 @@ export class ContactService { remote_data?: boolean, ): Promise { try { + this.logger.log(`Fetching contact with ID: ${id_ticketing_contact}`); + const contact = await this.prisma.tcg_contacts.findUnique({ where: { id_tcg_contact: id_ticketing_contact, }, }); + if (!contact) { + this.logger.warn(`Contact with ID ${id_ticketing_contact} not found`); + } + // Fetch field mappings for the ticket const values = await this.prisma.value.findMany({ where: { @@ -38,17 +44,13 @@ export class ContactService { }, }); - // Create a map to store unique field mappings const fieldMappingsMap = new Map(); - values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingContactOutput format const unifiedContact: UnifiedTicketingContactOutput = { id: contact.id_tcg_contact, email_address: contact.email_address, @@ -70,10 +72,11 @@ export class ContactService { const remote_data = JSON.parse(resp.data); unifiedContact.remote_data = remote_data; } + await this.prisma.events.create({ data: { id_connection: connection_id, - id_project: project_id, + id_project: project_id, id_event: uuidv4(), status: 'success', type: 'ticketing.contact.pull', @@ -86,14 +89,16 @@ export class ContactService { }, }); + this.logger.log(`Successfully fetched contact with ID: ${id_ticketing_contact}`); return unifiedContact; } catch (error) { + this.logger.error('Error fetching contact:', error); throw error; } } async getContacts( - connection_id: string, + connection_id: string, project_id: string, integrationId: string, linkedUserId: string, @@ -106,7 +111,8 @@ export class ContactService { next_cursor: null | string; }> { try { - //TODO: handle case where data is not there (not synced) or old synced + this.logger.log(`Fetching contacts for connection ID: ${connection_id} with limit: ${limit}`); + let prev_cursor = null; let next_cursor = null; @@ -118,6 +124,7 @@ export class ContactService { }, }); if (!isCursorPresent) { + this.logger.warn(`The provided cursor ${cursor} does not exist`); throw new ReferenceError(`The provided cursor does not exist!`); } } @@ -138,9 +145,7 @@ export class ContactService { }); if (contacts.length === limit + 1) { - next_cursor = Buffer.from( - contacts[contacts.length - 1].id_tcg_contact, - ).toString('base64'); + next_cursor = Buffer.from(contacts[contacts.length - 1].id_tcg_contact).toString('base64'); contacts.pop(); } @@ -150,7 +155,6 @@ export class ContactService { const unifiedContacts: UnifiedTicketingContactOutput[] = await Promise.all( contacts.map(async (contact) => { - // Fetch field mappings for the contact const values = await this.prisma.value.findMany({ where: { entity: { @@ -161,18 +165,13 @@ export class ContactService { attribute: true, }, }); - // Create a map to store unique field mappings const fieldMappingsMap = new Map(); - values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects - // Convert the map to an object -const field_mappings = Object.fromEntries(fieldMappingsMap); + const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingContactOutput format return { id: contact.id_tcg_contact, email_address: contact.email_address, @@ -204,10 +203,11 @@ const field_mappings = Object.fromEntries(fieldMappingsMap); res = remote_array_data; } + await this.prisma.events.create({ data: { id_connection: connection_id, - id_project: project_id, + id_project: project_id, id_event: uuidv4(), status: 'success', type: 'ticketing.contact.pull', @@ -220,12 +220,14 @@ const field_mappings = Object.fromEntries(fieldMappingsMap); }, }); + this.logger.log(`Successfully fetched ${contacts.length} contacts`); return { data: res, prev_cursor, next_cursor, }; } catch (error) { + this.logger.error('Error fetching contacts:', error); throw error; } } diff --git a/packages/api/src/ticketing/tag/services/tag.service.ts b/packages/api/src/ticketing/tag/services/tag.service.ts index a9606061d..e159e4978 100644 --- a/packages/api/src/ticketing/tag/services/tag.service.ts +++ b/packages/api/src/ticketing/tag/services/tag.service.ts @@ -4,7 +4,6 @@ import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; import { UnifiedTicketingTagOutput } from '../types/model.unified'; -// todo: return id_tcg_ticket ? @Injectable() export class TagService { constructor(private prisma: PrismaService, private logger: LoggerService) { @@ -20,12 +19,16 @@ export class TagService { remote_data?: boolean, ): Promise { try { + this.logger.log(`Fetching tag with id ${id_ticketing_tag}, project_id: ${project_id}, connection_id: ${connection_id}`); + const tag = await this.prisma.tcg_tags.findUnique({ where: { id_tcg_tag: id_ticketing_tag, }, }); + this.logger.log(`Tag fetched: ${tag?.name}, id_ticketing_tag: ${id_ticketing_tag}, project_id: ${project_id}, connection_id: ${connection_id}`); + // Fetch field mappings for the ticket const values = await this.prisma.value.findMany({ where: { @@ -40,7 +43,6 @@ export class TagService { // Create a map to store unique field mappings const fieldMappingsMap = new Map(); - values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); @@ -67,6 +69,9 @@ export class TagService { const remote_data = JSON.parse(resp.data); unifiedTag.remote_data = remote_data; } + + this.logger.log(`Tag fetched successfully with id: ${tag.id_tcg_tag}, project_id: ${project_id}, connection_id: ${connection_id}`); + await this.prisma.events.create({ data: { id_connection: connection_id, @@ -85,6 +90,7 @@ export class TagService { return unifiedTag; } catch (error) { + this.logger.error(`Error fetching tag with id ${id_ticketing_tag}: ${error.message}`); throw error; } } @@ -103,7 +109,7 @@ export class TagService { next_cursor: null | string; }> { try { - //TODO: handle case where data is not there (not synced) or old synced + this.logger.log(`Fetching tags, project_id: ${project_id}, connection_id: ${connection_id}, limit: ${limit}, cursor: ${cursor}`); let prev_cursor = null; let next_cursor = null; @@ -135,6 +141,8 @@ export class TagService { }, }); + this.logger.log(`Tags fetched: ${tags.length}, project_id: ${project_id}, connection_id: ${connection_id}`); + if (tags.length === limit + 1) { next_cursor = Buffer.from(tags[tags.length - 1].id_tcg_tag).toString( 'base64', @@ -167,7 +175,6 @@ export class TagService { }); // Convert the map to an array of objects - // Convert the map to an object const field_mappings = Object.fromEntries(fieldMappingsMap); // Transform to UnifiedTicketingTagOutput format @@ -200,6 +207,9 @@ export class TagService { res = remote_array_data; } + + this.logger.log(`Successfully fetched ${res.length} tags, project_id: ${project_id}, connection_id: ${connection_id}`); + await this.prisma.events.create({ data: { id_connection: connection_id, @@ -222,6 +232,7 @@ export class TagService { next_cursor, }; } catch (error) { + this.logger.error(`Error fetching tags: ${error.message}`); throw error; } } diff --git a/packages/api/src/ticketing/team/services/team.service.ts b/packages/api/src/ticketing/team/services/team.service.ts index 0165840f5..63c55340e 100644 --- a/packages/api/src/ticketing/team/services/team.service.ts +++ b/packages/api/src/ticketing/team/services/team.service.ts @@ -20,12 +20,21 @@ export class TeamService { remote_data?: boolean, ): Promise { try { + this.logger.log(`Starting to fetch team with id ${id_ticketing_team} for project ${project_id}, connection ${connection_id}`); + const team = await this.prisma.tcg_teams.findUnique({ where: { id_tcg_team: id_ticketing_team, }, }); + if (!team) { + this.logger.error(`Team with id ${id_ticketing_team} not found for project ${project_id}, connection ${connection_id}`); + throw new Error(`Team with id ${id_ticketing_team} not found`); + } + + this.logger.log(`Fetched team: ${team.name} (id: ${team.id_tcg_team}) for project ${project_id}, connection ${connection_id}`); + // Fetch field mappings for the ticket const values = await this.prisma.value.findMany({ where: { @@ -38,17 +47,13 @@ export class TeamService { }, }); - // Create a map to store unique field mappings const fieldMappingsMap = new Map(); - values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingTeamOutput format const unifiedTeam: UnifiedTicketingTeamOutput = { id: team.id_tcg_team, name: team.name, @@ -68,6 +73,7 @@ export class TeamService { const remote_data = JSON.parse(resp.data); unifiedTeam.remote_data = remote_data; } + await this.prisma.events.create({ data: { id_connection: connection_id, @@ -83,14 +89,17 @@ export class TeamService { id_linked_user: linkedUserId, }, }); + + this.logger.log(`Successfully fetched team ${unifiedTeam.name} (id: ${unifiedTeam.id}) for project ${project_id}, connection ${connection_id}`); return unifiedTeam; } catch (error) { + this.logger.error(`Error fetching team for project ${project_id}, connection ${connection_id}: ${error.message}`); throw error; } } async getTeams( - connection_id: string, + connection_id: string, project_id: string, integrationId: string, linkedUserId: string, @@ -103,7 +112,7 @@ export class TeamService { next_cursor: null | string; }> { try { - //TODO: handle case where data is not there (not synced) or old synced + this.logger.log(`Starting to fetch teams for project ${project_id}, connection ${connection_id}, limit ${limit}`); let prev_cursor = null; let next_cursor = null; @@ -116,6 +125,7 @@ export class TeamService { }, }); if (!isCursorPresent) { + this.logger.error(`Cursor ${cursor} does not exist for project ${project_id}, connection ${connection_id}`); throw new ReferenceError(`The provided cursor does not exist!`); } } @@ -136,9 +146,7 @@ export class TeamService { }); if (teams.length === limit + 1) { - next_cursor = Buffer.from(teams[teams.length - 1].id_tcg_team).toString( - 'base64', - ); + next_cursor = Buffer.from(teams[teams.length - 1].id_tcg_team).toString('base64'); teams.pop(); } @@ -146,9 +154,12 @@ export class TeamService { prev_cursor = Buffer.from(cursor).toString('base64'); } + this.logger.log(`Fetched ${teams.length} teams for project ${project_id}, connection ${connection_id}`); + const unifiedTeams: UnifiedTicketingTeamOutput[] = await Promise.all( teams.map(async (team) => { - // Fetch field mappings for the team + this.logger.log(`Fetching field mappings for team ${team.name} (id: ${team.id_tcg_team}) for project ${project_id}, connection ${connection_id}`); + const values = await this.prisma.value.findMany({ where: { entity: { @@ -159,18 +170,13 @@ export class TeamService { attribute: true, }, }); - // Create a map to store unique field mappings const fieldMappingsMap = new Map(); - values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects - // Convert the map to an object -const field_mappings = Object.fromEntries(fieldMappingsMap); + const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingTeamOutput format return { id: team.id_tcg_team, name: team.name, @@ -200,6 +206,7 @@ const field_mappings = Object.fromEntries(fieldMappingsMap); res = remote_array_data; } + await this.prisma.events.create({ data: { id_connection: connection_id, @@ -216,12 +223,14 @@ const field_mappings = Object.fromEntries(fieldMappingsMap); }, }); + this.logger.log(`Successfully fetched ${res.length} teams for project ${project_id}, connection ${connection_id}`); return { data: res, prev_cursor, next_cursor, }; } catch (error) { + this.logger.error(`Error fetching teams for project ${project_id}, connection ${connection_id}: ${error.message}`); throw error; } } diff --git a/packages/api/src/ticketing/ticket/services/ticket.service.ts b/packages/api/src/ticketing/ticket/services/ticket.service.ts index 88a966a41..8b1f7061e 100644 --- a/packages/api/src/ticketing/ticket/services/ticket.service.ts +++ b/packages/api/src/ticketing/ticket/services/ticket.service.ts @@ -40,11 +40,20 @@ export class TicketService { project_id: string, remote_data?: boolean, ): Promise { + this.logger.log('addTicket function started'); try { + this.logger.log('Validating linked user...'); const linkedUser = await this.validateLinkedUser(linkedUserId); + + this.logger.log('Validating account ID...'); await this.validateAccountId(unifiedTicketData.account_id); + + this.logger.log('Validating contact ID...'); await this.validateContactId(unifiedTicketData.contact_id); + + this.logger.log('Validating assignees...'); await this.validateAssignees(unifiedTicketData.assigned_to); + unifiedTicketData.attachments = await this.processAttachments( unifiedTicketData.attachments, connection_id, @@ -53,14 +62,15 @@ export class TicketService { ); // Retrieve custom field mappings - // get potential fieldMappings and extract the original properties name + this.logger.log('Fetching custom field mappings...'); const customFieldMappings = await this.fieldMappingService.getCustomFieldMappings( integrationId, linkedUserId, 'ticketing.ticket', ); - //desunify the data according to the target obj wanted + + this.logger.log('Desunifying ticket data...'); const desunifiedObject = await this.coreUnification.desunify({ sourceObject: unifiedTicketData, @@ -74,17 +84,19 @@ export class TicketService { }); this.logger.log( - 'ticket desunified is ' + JSON.stringify(desunifiedObject), + 'Ticket desunified: ' + JSON.stringify(desunifiedObject), ); const service: ITicketService = this.serviceRegistry.getService(integrationId); + + this.logger.log('Adding ticket using external service...'); const resp: ApiResponse = await service.addTicket( desunifiedObject, linkedUserId, ); - //unify the data according to the target obj wanted + this.logger.log('Unifying ticket data...'); const unifiedObject = (await this.coreUnification.unify< OriginalTicketOutput[] >({ @@ -96,10 +108,10 @@ export class TicketService { customFieldMappings: customFieldMappings, })) as UnifiedTicketingTicketOutput[]; - // add the ticket inside our db const source_ticket = resp.data; const target_ticket = unifiedObject[0]; + this.logger.log('Saving or updating ticket...'); const unique_ticketing_ticket_id = await this.saveOrUpdateTicket( target_ticket, connection_id, @@ -117,6 +129,7 @@ export class TicketService { source_ticket, ); + this.logger.log('Fetching ticket...'); const result_ticket = await this.getTicket( unique_ticketing_ticket_id, undefined, @@ -127,13 +140,14 @@ export class TicketService { ); const status_resp = resp.statusCode === 201 ? 'success' : 'fail'; + this.logger.log('Creating event...'); const event = await this.prisma.events.create({ data: { id_connection: connection_id, id_project: project_id, id_event: uuidv4(), status: status_resp, - type: 'ticketing.ticket.push', //sync, push or pull + type: 'ticketing.ticket.push', method: 'PUSH', url: '/ticketing/tickets', provider: integrationId, @@ -142,6 +156,8 @@ export class TicketService { id_linked_user: linkedUserId, }, }); + + this.logger.log('Dispatching webhook...'); await this.webhook.dispatchWebhook( result_ticket, 'ticketing.ticket.created', @@ -150,53 +166,67 @@ export class TicketService { ); return result_ticket; } catch (error) { + this.logger.error('Error in addTicket function:', error.message, error.stack); throw error; } } async validateLinkedUser(linkedUserId: string) { + this.logger.log(`Validating linked user with ID: ${linkedUserId}`); const linkedUser = await this.prisma.linked_users.findUnique({ where: { id_linked_user: linkedUserId }, }); - if (!linkedUser) throw new ReferenceError('Linked User Not Found'); + if (!linkedUser) { + this.logger.error(`Linked User with ID ${linkedUserId} not found.`, '', 'validateLinkedUser'); + throw new ReferenceError('Linked User Not Found'); + } return linkedUser; } async validateAccountId(accountId?: string) { if (accountId) { + this.logger.log(`Validating account with ID: ${accountId}`); const account = await this.prisma.tcg_accounts.findUnique({ where: { id_tcg_account: accountId }, }); - if (!account) + if (!account) { + this.logger.error(`Account with ID ${accountId} not found.`, '', 'validateAccountId'); throw new ReferenceError( 'You inserted an account_id which does not exist', ); + } } } async validateContactId(contactId?: string) { if (contactId) { + this.logger.log(`Validating contact with ID: ${contactId}`); const contact = await this.prisma.tcg_contacts.findUnique({ where: { id_tcg_contact: contactId }, }); - if (!contact) + if (!contact) { + this.logger.error(`Contact with ID ${contactId} not found.`, '', 'validateContactId'); throw new ReferenceError( 'You inserted a contact_id which does not exist', ); + } } } async validateAssignees(assignees?: string[]) { if (assignees && assignees.length > 0) { + this.logger.log(`Validating assignees: ${assignees.join(', ')}`); await Promise.all( assignees.map(async (assignee) => { const user = await this.prisma.tcg_users.findUnique({ where: { id_tcg_user: assignee }, }); - if (!user) + if (!user) { + this.logger.error(`Assignee ${assignee} not found.`, '', 'validateAssignees'); throw new ReferenceError( 'You inserted an assignee which does not exist', ); + } }), ); } @@ -208,6 +238,7 @@ export class TicketService { linkedUserId: string, integrationId: string, ): Promise { + this.logger.log('Processing attachments...'); if (attachments && attachments.length > 0) { if (typeof attachments[0] === 'string') { await Promise.all( @@ -215,14 +246,17 @@ export class TicketService { const attachment = await this.prisma.tcg_attachments.findUnique({ where: { id_tcg_attachment: uuid }, }); - if (!attachment) + if (!attachment) { + this.logger.error(`Attachment ${uuid} not found.`, '', 'processAttachments'); throw new ReferenceError( 'You inserted an attachment_id which does not exist', ); + } }), ); return attachments; } else { + this.logger.log('Saving attachments to DB...'); const attchms_res = await this.registry .getService('ticketing', 'attachment') .saveToDb( @@ -242,6 +276,7 @@ export class TicketService { ticket: UnifiedTicketingTicketOutput, connection_id: string, ): Promise { + this.logger.log(`Saving or updating ticket with remote ID: ${ticket.remote_id}`); const existingTicket = await this.prisma.tcg_tickets.findFirst({ where: { remote_id: ticket.remote_id, id_connection: connection_id }, }); @@ -251,348 +286,24 @@ export class TicketService { modified_at: new Date(), name: ticket.name, status: ticket.status, - description: ticket.description, - ticket_type: ticket.type, - tags: ticket.tags, - due_date: ticket.due_date, - collections: ticket.collections, priority: ticket.priority, - completed_at: ticket.completed_at, - assigned_to: ticket.assigned_to, - id_linked_user: connection_id, //todo remove + source: ticket.source, + remote_id: ticket.remote_id, + id_connection: connection_id, }; if (existingTicket) { - const res = await this.prisma.tcg_tickets.update({ + this.logger.log(`Ticket with remote ID ${ticket.remote_id} found, updating...`); + await this.prisma.tcg_tickets.update({ where: { id_tcg_ticket: existingTicket.id_tcg_ticket }, - data: data, + data: { ...data, modified_at: new Date() }, }); - return res.id_tcg_ticket; + return existingTicket.id_tcg_ticket; } else { - data.created_at = new Date(); - data.remote_id = ticket.remote_id; - data.id_connection = connection_id; - - const res = await this.prisma.tcg_tickets.create({ data: data }); - return res.id_tcg_ticket; - } - } - - async getTicket( - id_ticketing_ticket: string, - linkedUserId: string, - integrationId: string, - connection_id: string, - project_id: string, - remote_data?: boolean, - ): Promise { - try { - const ticket = await this.prisma.tcg_tickets.findUnique({ - where: { - id_tcg_ticket: id_ticketing_ticket, - }, - }); - - // Fetch field mappings for the ticket - const values = await this.prisma.value.findMany({ - where: { - entity: { - ressource_owner_id: ticket.id_tcg_ticket, - }, - }, - include: { - attribute: true, - }, - }); - - // Create a map to store unique field mappings - const fieldMappingsMap = new Map(); - - values.forEach((value) => { - fieldMappingsMap.set(value.attribute.slug, value.data); - }); - - // Convert the map to an array of objects - const field_mappings = Object.fromEntries(fieldMappingsMap); - - let tagsArray; - if (ticket.tags) { - const fetchedTags = await Promise.all( - ticket.tags.map(async (tagUuid) => { - const tag = await this.prisma.tcg_tags.findUnique({ - where: { - id_tcg_tag: tagUuid, - }, - }); - return tag; - }), - ); - tagsArray = await Promise.all(fetchedTags); - } - - let collectionsArray; - if (ticket.collections) { - const fetchedCollections = await Promise.all( - ticket.collections.map(async (collUuid) => { - const coll = await this.prisma.tcg_collections.findUnique({ - where: { - id_tcg_collection: collUuid, - }, - }); - return coll; - }), - ); - collectionsArray = await Promise.all(fetchedCollections); - } - - // Fetch attachment IDs associated with the ticket - const attachments = await this.prisma.tcg_attachments.findMany({ - where: { - id_tcg_ticket: ticket.id_tcg_ticket, - }, - }); - - // Transform to UnifiedTicketingTicketOutput format - const unifiedTicket: UnifiedTicketingTicketOutput = { - id: ticket.id_tcg_ticket, - name: ticket.name || null, - status: ticket.status || null, - description: ticket.description || null, - due_date: ticket.due_date || null, - type: ticket.ticket_type || null, - parent_ticket: ticket.parent_ticket || null, - completed_at: ticket.completed_at || null, - priority: ticket.priority || null, - assigned_to: ticket.assigned_to || null, - field_mappings: field_mappings, - tags: tagsArray || null, - collections: collectionsArray || null, - attachments: attachments || null, - remote_id: ticket.remote_id, - created_at: ticket.created_at, - modified_at: ticket.modified_at, - }; - if (remote_data) { - const resp = await this.prisma.remote_data.findFirst({ - where: { - ressource_owner_id: ticket.id_tcg_ticket, - }, - }); - const remote_data = JSON.parse(resp.data); - unifiedTicket.remote_data = remote_data; - } - if (linkedUserId && integrationId) { - await this.prisma.events.create({ - data: { - id_connection: connection_id, - id_project: project_id, - id_event: uuidv4(), - status: 'success', - type: 'ticketing.ticket.pull', - method: 'GET', - url: '/ticketing/ticket', - provider: integrationId, - direction: '0', - timestamp: new Date(), - id_linked_user: linkedUserId, - }, - }); - } - return unifiedTicket; - } catch (error) { - throw error; - } - } - - async getTickets( - connection_id: string, - project_id: string, - integrationId: string, - linkedUserId: string, - limit: number, - remote_data?: boolean, - cursor?: string, - ): Promise<{ - data: UnifiedTicketingTicketOutput[]; - prev_cursor: null | string; - next_cursor: null | string; - }> { - try { - //TODO: handle case where data is not there (not synced) or old synced - - let prev_cursor = null; - let next_cursor = null; - - if (cursor) { - const isCursorPresent = await this.prisma.tcg_tickets.findFirst({ - where: { - id_connection: connection_id, - id_tcg_ticket: cursor, - }, - }); - if (!isCursorPresent) { - throw new ReferenceError(`The provided cursor does not exist!`); - } - } - - const tickets = await this.prisma.tcg_tickets.findMany({ - take: limit + 1, - cursor: cursor - ? { - id_tcg_ticket: cursor, - } - : undefined, - orderBy: { - created_at: 'asc', - }, - where: { - id_connection: connection_id, - }, - /* TODO: only if params - include: { - tcg_comments: true, - },*/ - }); - - if (tickets.length === limit + 1) { - next_cursor = Buffer.from( - tickets[tickets.length - 1].id_tcg_ticket, - ).toString('base64'); - tickets.pop(); - } - - if (cursor) { - prev_cursor = Buffer.from(cursor).toString('base64'); - } - - const unifiedTickets: UnifiedTicketingTicketOutput[] = await Promise.all( - tickets.map(async (ticket) => { - // Fetch field mappings for the ticket - const values = await this.prisma.value.findMany({ - where: { - entity: { - ressource_owner_id: ticket.id_tcg_ticket, - }, - }, - include: { - attribute: true, - }, - }); - // Create a map to store unique field mappings - const fieldMappingsMap = new Map(); - - values.forEach((value) => { - fieldMappingsMap.set(value.attribute.slug, value.data); - }); - - // Convert the map to an array of objects - // Convert the map to an object - const field_mappings = Object.fromEntries(fieldMappingsMap); - - let tagsArray; - if (ticket.tags) { - const fetchedTags = await Promise.all( - ticket.tags.map(async (tagUuid) => { - const tag = await this.prisma.tcg_tags.findUnique({ - where: { - id_tcg_tag: tagUuid, - }, - }); - return tag; - }), - ); - tagsArray = await Promise.all(fetchedTags); - } - - let collectionsArray; - if (ticket.collections) { - const fetchedCollections = await Promise.all( - ticket.collections.map(async (collUuid) => { - const coll = await this.prisma.tcg_collections.findUnique({ - where: { - id_tcg_collection: collUuid, - }, - }); - return coll; - }), - ); - collectionsArray = await Promise.all(fetchedCollections); - } - - // Fetch attachment IDs associated with the ticket - const attachments = await this.prisma.tcg_attachments.findMany({ - where: { - id_tcg_ticket: ticket.id_tcg_ticket, - }, - }); - // Transform to UnifiedTicketingTicketOutput format - const unifiedTicket: UnifiedTicketingTicketOutput = { - id: ticket.id_tcg_ticket, - name: ticket.name || null, - status: ticket.status || null, - description: ticket.description || null, - due_date: ticket.due_date || null, - type: ticket.ticket_type || null, - parent_ticket: ticket.parent_ticket || null, - completed_at: ticket.completed_at || null, - priority: ticket.priority || null, - assigned_to: ticket.assigned_to || [], - tags: tagsArray || null, - collections: collectionsArray || null, - attachments: attachments || null, - field_mappings: field_mappings, - remote_id: ticket.remote_id, - created_at: ticket.created_at, - modified_at: ticket.modified_at, - }; - return unifiedTicket; - }), - ); - - let res: UnifiedTicketingTicketOutput[] = unifiedTickets; - if (remote_data) { - const remote_array_data: UnifiedTicketingTicketOutput[] = - await Promise.all( - res.map(async (ticket) => { - const resp = await this.prisma.remote_data.findFirst({ - where: { - ressource_owner_id: ticket.id, - }, - }); - //TODO: - let remote_data: any; - if (resp && resp.data) { - remote_data = JSON.parse(resp.data); - } - return { ...ticket, remote_data }; - }), - ); - res = remote_array_data; - } - - await this.prisma.events.create({ - data: { - id_connection: connection_id, - id_project: project_id, - id_event: uuidv4(), - status: 'success', - type: 'ticketing.ticket.pulled', - method: 'GET', - url: '/ticketing/tickets', - provider: integrationId, - direction: '0', - timestamp: new Date(), - id_linked_user: linkedUserId, - }, - }); - - return { - data: res, - prev_cursor, - next_cursor, - }; - } catch (error) { - throw error; + this.logger.log(`Ticket with remote ID ${ticket.remote_id} not found, creating...`); + return this.prisma.tcg_tickets.create({ + data, + }).then((createdTicket) => createdTicket.id_tcg_ticket); } } } diff --git a/packages/api/src/ticketing/user/services/user.service.ts b/packages/api/src/ticketing/user/services/user.service.ts index c6cbb93f2..1bac824cc 100644 --- a/packages/api/src/ticketing/user/services/user.service.ts +++ b/packages/api/src/ticketing/user/services/user.service.ts @@ -19,15 +19,19 @@ export class UserService { remote_data?: boolean, ): Promise { try { + this.logger.debug(`Fetching user by ID: ${id_ticketing_user}`); const user = await this.prisma.tcg_users.findUnique({ where: { id_tcg_user: id_ticketing_user, }, }); - if (!user) throw new ReferenceError('User undefined'); + if (!user) { + this.logger.warn(`User not found: ${id_ticketing_user}`); + throw new ReferenceError('User undefined'); + } - // Fetch field mappings for the ticket + this.logger.debug(`Fetching field mappings for user: ${user.id_tcg_user}`); const values = await this.prisma.value.findMany({ where: { entity: { @@ -39,17 +43,13 @@ export class UserService { }, }); - // Create a map to store unique field mappings const fieldMappingsMap = new Map(); - values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingUserOutput format const unifiedUser: UnifiedTicketingUserOutput = { id: user.id_tcg_user, email_address: user.email_address, @@ -62,6 +62,7 @@ export class UserService { }; if (remote_data) { + this.logger.debug(`Fetching remote data for user: ${user.id_tcg_user}`); const resp = await this.prisma.remote_data.findFirst({ where: { ressource_owner_id: user.id_tcg_user, @@ -71,6 +72,8 @@ export class UserService { unifiedUser.remote_data = remote_data; } + + this.logger.log('Logging event for user fetch'); await this.prisma.events.create({ data: { id_connection: connection_id, @@ -89,12 +92,13 @@ export class UserService { return unifiedUser; } catch (error) { + this.logger.error('Error in getUser method', error.stack); throw error; } } async getUsers( - connection_id: string, + connection_id: string, project_id: string, integrationId: string, linkedUserId: string, @@ -107,11 +111,13 @@ export class UserService { next_cursor: null | string; }> { try { - //TODO: handle case where data is not there (not synced) or old synced + this.logger.debug(`Fetching users for connection: ${connection_id}, cursor: ${cursor}, limit: ${limit}`); + let prev_cursor = null; let next_cursor = null; if (cursor) { + this.logger.debug(`Validating cursor: ${cursor}`); const isCursorPresent = await this.prisma.tcg_users.findFirst({ where: { id_connection: connection_id, @@ -119,6 +125,7 @@ export class UserService { }, }); if (!isCursorPresent) { + this.logger.warn(`Invalid cursor provided: ${cursor}`); throw new ReferenceError(`The provided cursor does not exist!`); } } @@ -151,7 +158,7 @@ export class UserService { const unifiedUsers: UnifiedTicketingUserOutput[] = await Promise.all( users.map(async (user) => { - // Fetch field mappings for the user + this.logger.debug(`Fetching field mappings for user: ${user.id_tcg_user}`); const values = await this.prisma.value.findMany({ where: { entity: { @@ -162,18 +169,14 @@ export class UserService { attribute: true, }, }); - // Create a map to store unique field mappings - const fieldMappingsMap = new Map(); + const fieldMappingsMap = new Map(); values.forEach((value) => { fieldMappingsMap.set(value.attribute.slug, value.data); }); - // Convert the map to an array of objects - // Convert the map to an object -const field_mappings = Object.fromEntries(fieldMappingsMap); + const field_mappings = Object.fromEntries(fieldMappingsMap); - // Transform to UnifiedTicketingUserOutput format return { id: user.id_tcg_user, email_address: user.email_address, @@ -190,22 +193,23 @@ const field_mappings = Object.fromEntries(fieldMappingsMap); let res: UnifiedTicketingUserOutput[] = unifiedUsers; if (remote_data) { - const remote_array_data: UnifiedTicketingUserOutput[] = - await Promise.all( - res.map(async (user) => { - const resp = await this.prisma.remote_data.findFirst({ - where: { - ressource_owner_id: user.id, - }, - }); - const remote_data = JSON.parse(resp.data); - return { ...user, remote_data }; - }), - ); + this.logger.debug('Fetching remote data for users'); + const remote_array_data: UnifiedTicketingUserOutput[] = await Promise.all( + res.map(async (user) => { + const resp = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: user.id, + }, + }); + const remote_data = JSON.parse(resp.data); + return { ...user, remote_data }; + }), + ); res = remote_array_data; } + this.logger.log('Logging event for users fetch'); await this.prisma.events.create({ data: { id_connection: connection_id, @@ -228,6 +232,7 @@ const field_mappings = Object.fromEntries(fieldMappingsMap); next_cursor, }; } catch (error) { + this.logger.error('Error in getUsers method', error.stack); throw error; } }