From da3bd0c11fee425aff0c89a39777667ec814e743 Mon Sep 17 00:00:00 2001 From: nael Date: Sat, 23 Mar 2024 16:10:41 +0100 Subject: [PATCH] :bug: Fix --- .../ticketing/collection/collection.module.ts | 2 + .../collection/services/collection.service.ts | 83 +------ .../collection/services/gorgias/index.ts | 35 --- .../collection/services/gorgias/mappers.ts | 47 ---- .../collection/services/gorgias/types.ts | 12 - .../collection/services/jira/index.ts | 4 +- .../collection/services/jira/mappers.ts | 2 + .../collection/services/jira/types.ts | 27 ++- .../ticketing/collection/sync/sync.service.ts | 222 +++++++++++++++++- .../api/src/ticketing/ticketing.module.ts | 16 +- .../src/ticketing/user/services/jira/index.ts | 3 - 11 files changed, 271 insertions(+), 182 deletions(-) delete mode 100644 packages/api/src/ticketing/collection/services/gorgias/index.ts delete mode 100644 packages/api/src/ticketing/collection/services/gorgias/mappers.ts delete mode 100644 packages/api/src/ticketing/collection/services/gorgias/types.ts diff --git a/packages/api/src/ticketing/collection/collection.module.ts b/packages/api/src/ticketing/collection/collection.module.ts index ac9f45def..1be92ac99 100644 --- a/packages/api/src/ticketing/collection/collection.module.ts +++ b/packages/api/src/ticketing/collection/collection.module.ts @@ -9,6 +9,7 @@ import { FieldMappingService } from '@@core/field-mapping/field-mapping.service' import { PrismaService } from '@@core/prisma/prisma.service'; import { WebhookService } from '@@core/webhook/webhook.service'; import { BullModule } from '@nestjs/bull'; +import { JiraService } from './services/jira'; @Module({ imports: [ @@ -27,6 +28,7 @@ import { BullModule } from '@nestjs/bull'; FieldMappingService, ServiceRegistry, /* PROVIDERS SERVICES */ + JiraService, ], exports: [SyncService], }) diff --git a/packages/api/src/ticketing/collection/services/collection.service.ts b/packages/api/src/ticketing/collection/services/collection.service.ts index e112dcc31..e7c4cbad9 100644 --- a/packages/api/src/ticketing/collection/services/collection.service.ts +++ b/packages/api/src/ticketing/collection/services/collection.service.ts @@ -2,20 +2,11 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '@@core/prisma/prisma.service'; import { LoggerService } from '@@core/logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; -import { ApiResponse } from '@@core/utils/types'; import { handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; -import { - UnifiedCollectionInput, - UnifiedCollectionOutput, -} from '../types/model.unified'; -import { desunify } from '@@core/utils/unification/desunify'; -import { TicketingObject } from '@ticketing/@utils/@types'; +import { UnifiedCollectionOutput } from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from './registry.service'; -import { OriginalCollectionOutput } from '@@core/utils/types/original/original.ticketing'; -import { unify } from '@@core/utils/unification/unify'; -import { ICollectionService } from '../types'; @Injectable() export class CollectionService { @@ -39,42 +30,12 @@ export class CollectionService { }, }); - // WE SHOULDNT HAVE FIELD MAPPINGS TO COMMENT - - // Fetch field mappings for the collection - /*const values = await this.prisma.value.findMany({ - where: { - entity: { - ressource_owner_id: collection.id_tcg_collection, - }, - }, - 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, - }));*/ - // Transform to UnifiedCollectionOutput format const unifiedCollection: UnifiedCollectionOutput = { id: collection.id_tcg_collection, - body: collection.body, - html_body: collection.html_body, - is_private: collection.is_private, - creator_type: collection.creator_type, - ticket_id: collection.id_tcg_ticket, - contact_id: collection.id_tcg_contact, // uuid of Contact object - user_id: collection.id_tcg_user, // uuid of User object + name: collection.name, + description: collection.description, + collection_type: collection.collection_type, }; let res: UnifiedCollectionOutput = { @@ -116,41 +77,11 @@ export class CollectionService { const unifiedCollections: UnifiedCollectionOutput[] = await Promise.all( collections.map(async (collection) => { - //WE SHOULDNT HAVE FIELD MAPPINGS FOR COMMENT - // Fetch field mappings for the ticket - /*const values = await this.prisma.value.findMany({ - where: { - entity: { - ressource_owner_id: collection.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 = Array.from( - fieldMappingsMap, - ([key, value]) => ({ [key]: value }), - );*/ - - // Transform to UnifiedCollectionOutput format return { id: collection.id_tcg_collection, - body: collection.body, - html_body: collection.html_body, - is_private: collection.is_private, - creator_type: collection.creator_type, - ticket_id: collection.id_tcg_ticket, - contact_id: collection.id_tcg_contact, // uuid of Contact object - user_id: collection.id_tcg_user, // uuid of User object + name: collection.name, + description: collection.description, + collection_type: collection.collection_type, }; }), ); diff --git a/packages/api/src/ticketing/collection/services/gorgias/index.ts b/packages/api/src/ticketing/collection/services/gorgias/index.ts deleted file mode 100644 index 7070bf16a..000000000 --- a/packages/api/src/ticketing/collection/services/gorgias/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { LoggerService } from '@@core/logger/logger.service'; -import { PrismaService } from '@@core/prisma/prisma.service'; -import { EncryptionService } from '@@core/encryption/encryption.service'; -import { TicketingObject } from '@ticketing/@utils/@types'; -import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handleServiceError } from '@@core/utils/errors'; -import { ServiceRegistry } from '../registry.service'; -import { ICollectionService } from '@ticketing/collection/types'; -import { GorgiasCollectionOutput } from './types'; -import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; -import { OriginalCollectionOutput } from '@@core/utils/types/original/original.ticketing'; - -@Injectable() -export class GorgiasService implements ICollectionService { - constructor( - private prisma: PrismaService, - private logger: LoggerService, - private cryptoService: EncryptionService, - private registry: ServiceRegistry, - ) { - this.logger.setContext( - TicketingObject.collection.toUpperCase() + ':' + GorgiasService.name, - ); - this.registry.registerService('gorgias', this); - } - - syncCollections( - linkedUserId: string, - custom_properties?: string[], - ): Promise> { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/api/src/ticketing/collection/services/gorgias/mappers.ts b/packages/api/src/ticketing/collection/services/gorgias/mappers.ts deleted file mode 100644 index 61c3db6ab..000000000 --- a/packages/api/src/ticketing/collection/services/gorgias/mappers.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ICollectionMapper } from '@ticketing/collection/types'; -import { GorgiasCollectionInput, GorgiasCollectionOutput } from './types'; -import { - UnifiedCollectionInput, - UnifiedCollectionOutput, -} from '@ticketing/collection/types/model.unified'; - -export class GorgiasCollectionMapper implements ICollectionMapper { - desunify( - source: UnifiedCollectionInput, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): GorgiasCollectionInput { - return; - } - - unify( - source: GorgiasCollectionOutput | GorgiasCollectionOutput[], - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): UnifiedCollectionOutput | UnifiedCollectionOutput[] { - // If the source is not an array, convert it to an array for mapping - const sourcesArray = Array.isArray(source) ? source : [source]; - - return sourcesArray.map((collection) => - this.mapSingleCollectionToUnified(collection, customFieldMappings), - ); - } - - private mapSingleCollectionToUnified( - collection: GorgiasCollectionOutput, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): UnifiedCollectionOutput { - const unifiedCollection: UnifiedCollectionOutput = { - name: collection.name, - }; - - return unifiedCollection; - } -} diff --git a/packages/api/src/ticketing/collection/services/gorgias/types.ts b/packages/api/src/ticketing/collection/services/gorgias/types.ts deleted file mode 100644 index 89b6f874b..000000000 --- a/packages/api/src/ticketing/collection/services/gorgias/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type GorgiasCollectionInput = null; - -export type GorgiasCollectionOutput = { - id: number; - uri: string; - name: string; - description: string; - decoration: { - emoji: string; - }; - created_datetime: string; -}; diff --git a/packages/api/src/ticketing/collection/services/jira/index.ts b/packages/api/src/ticketing/collection/services/jira/index.ts index 9e4d82648..7cc36f0bf 100644 --- a/packages/api/src/ticketing/collection/services/jira/index.ts +++ b/packages/api/src/ticketing/collection/services/jira/index.ts @@ -36,7 +36,7 @@ export class JiraService implements ICollectionService { }); const resp = await axios.get( - `${connection.account_url}/rest/api/3/user/groups`, + `${connection.account_url}/rest/api/3/project/search`, { headers: { 'Content-Type': 'application/json', @@ -49,7 +49,7 @@ export class JiraService implements ICollectionService { this.logger.log(`Synced jira collections !`); return { - data: resp.data._results, + data: resp.data, message: 'Jira collections retrieved', statusCode: 200, }; diff --git a/packages/api/src/ticketing/collection/services/jira/mappers.ts b/packages/api/src/ticketing/collection/services/jira/mappers.ts index 83582dfdc..65c0264dd 100644 --- a/packages/api/src/ticketing/collection/services/jira/mappers.ts +++ b/packages/api/src/ticketing/collection/services/jira/mappers.ts @@ -40,6 +40,8 @@ export class JiraCollectionMapper implements ICollectionMapper { ): UnifiedCollectionOutput { const unifiedCollection: UnifiedCollectionOutput = { name: collection.name, + description: collection.name, + collection_type: 'PROJECT', }; return unifiedCollection; diff --git a/packages/api/src/ticketing/collection/services/jira/types.ts b/packages/api/src/ticketing/collection/services/jira/types.ts index 6189b90b5..de9b1f2f3 100644 --- a/packages/api/src/ticketing/collection/services/jira/types.ts +++ b/packages/api/src/ticketing/collection/services/jira/types.ts @@ -1,5 +1,30 @@ export type JiraCollectionOutput = { - groupId: string; + avatarUrls: AvatarUrls; + id: string; + insight: Insight; + key: string; + name: string; + projectCategory: ProjectCategory; + self: string; + simplified: boolean; + style: string; +}; + +type AvatarUrls = { + '16x16': string; + '24x24': string; + '32x32': string; + '48x48': string; +}; + +type Insight = { + lastIssueUpdateTime: string; // Consider using Date type if appropriate + totalIssueCount: number; +}; + +type ProjectCategory = { + description: string; + id: string; // Or number if the ID is always numeric name: string; self: string; }; diff --git a/packages/api/src/ticketing/collection/sync/sync.service.ts b/packages/api/src/ticketing/collection/sync/sync.service.ts index 7caf6f1dd..327b2972e 100644 --- a/packages/api/src/ticketing/collection/sync/sync.service.ts +++ b/packages/api/src/ticketing/collection/sync/sync.service.ts @@ -3,7 +3,7 @@ import { LoggerService } from '@@core/logger/logger.service'; import { PrismaService } from '@@core/prisma/prisma.service'; import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { Cron } from '@nestjs/schedule'; -import { ApiResponse } from '@@core/utils/types'; +import { ApiResponse, TICKETING_PROVIDERS } from '@@core/utils/types'; import { v4 as uuidv4 } from 'uuid'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { ServiceRegistry } from '../services/registry.service'; @@ -12,6 +12,8 @@ import { TicketingObject } from '@ticketing/@utils/@types'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedCollectionOutput } from '../types/model.unified'; import { ICollectionService } from '../types'; +import { OriginalCollectionOutput } from '@@core/utils/types/original/original.ticketing'; +import { tcg_collections as TicketingCollection } from '@prisma/client'; @Injectable() export class SyncService implements OnModuleInit { @@ -26,8 +28,222 @@ export class SyncService implements OnModuleInit { } async onModuleInit() { - // Initialization logic + try { + await this.syncCollections(); + } catch (error) { + handleServiceError(error, this.logger); + } } - // Additional methods and logic + @Cron('*/20 * * * *') + //function used by sync worker which populate our tcg_collections table + //its role is to fetch all collections from providers 3rd parties and save the info inside our db + async syncCollections() { + try { + this.logger.log(`Syncing collections....`); + const defaultOrg = await this.prisma.organizations.findFirst({ + where: { + name: 'Acme Inc', + }, + }); + + const defaultProject = await this.prisma.projects.findFirst({ + where: { + id_organization: defaultOrg.id_organization, + name: 'Project 1', + }, + }); + const id_project = defaultProject.id_project; + const linkedUsers = await this.prisma.linked_users.findMany({ + where: { + id_project: id_project, + }, + }); + linkedUsers.map(async (linkedUser) => { + try { + const providers = TICKETING_PROVIDERS; + for (const provider of providers) { + try { + await this.syncCollectionsForLinkedUser( + provider, + linkedUser.id_linked_user, + id_project, + ); + } catch (error) { + handleServiceError(error, this.logger); + } + } + } catch (error) { + handleServiceError(error, this.logger); + } + }); + } catch (error) { + handleServiceError(error, this.logger); + } + } + + //todo: HANDLE DATA REMOVED FROM PROVIDER + async syncCollectionsForLinkedUser( + integrationId: string, + linkedUserId: string, + id_project: string, + ) { + try { + this.logger.log( + `Syncing ${integrationId} collections for linkedUser ${linkedUserId}`, + ); + // check if linkedTeam has a connection if not just stop sync + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: integrationId, + }, + }); + if (!connection) { + this.logger.warn( + `Skipping collections syncing... No ${integrationId} connection was found for linked user ${linkedUserId} `, + ); + return; + } + + const service: ICollectionService = + this.serviceRegistry.getService(integrationId); + const resp: ApiResponse = + await service.syncCollections(linkedUserId); + + const sourceObject: OriginalCollectionOutput[] = resp.data; + //this.logger.log('SOURCE OBJECT DATA = ' + JSON.stringify(sourceObject)); + //unify the data according to the target obj wanted + const unifiedObject = (await unify({ + sourceObject, + targetType: TicketingObject.collection, + providerName: integrationId, + customFieldMappings: [], + })) as UnifiedCollectionOutput[]; + + //TODO + const collectionIds = sourceObject.map((collection) => + 'id' in collection ? String(collection.id) : undefined, + ); + + //insert the data in the DB with the fieldMappings (value table) + const collection_data = await this.saveCollectionsInDb( + linkedUserId, + unifiedObject, + collectionIds, + integrationId, + sourceObject, + ); + + const event = await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'ticketing.collection.pulled', + method: 'PULL', + url: '/pull', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + }, + }); + + await this.webhook.handleWebhook( + collection_data, + 'ticketing.collection.pulled', + id_project, + event.id_event, + ); + } catch (error) { + handleServiceError(error, this.logger); + } + } + + async saveCollectionsInDb( + linkedUserId: string, + collections: UnifiedCollectionOutput[], + originIds: string[], + originSource: string, + remote_data: Record[], + ): Promise { + try { + let collections_results: TicketingCollection[] = []; + for (let i = 0; i < collections.length; i++) { + const collection = collections[i]; + const originId = originIds[i]; + + if (!originId || originId == '') { + throw new NotFoundError(`Origin id not there, found ${originId}`); + } + + const existingTeam = await this.prisma.tcg_collections.findFirst({ + where: { + remote_id: originId, + remote_platform: originSource, + id_linked_user: linkedUserId, + }, + }); + + let unique_ticketing_collection_id: string; + + if (existingTeam) { + // Update the existing ticket + const res = await this.prisma.tcg_collections.update({ + where: { + id_tcg_collection: existingTeam.id_tcg_collection, + }, + data: { + name: existingTeam.name, + description: collection.description, + type: collection.collection_type, + modified_at: new Date(), + }, + }); + unique_ticketing_collection_id = res.id_tcg_collection; + collections_results = [...collections_results, res]; + } else { + // Create a new collection + this.logger.log('not existing collection ' + collection.name); + const data = { + id_tcg_collection: uuidv4(), + name: collection.name, + description: collection.description, + type: collection.collection_type, + created_at: new Date(), + modified_at: new Date(), + id_linked_user: linkedUserId, + remote_id: originId, + remote_platform: originSource, + }; + const res = await this.prisma.tcg_collections.create({ + data: data, + }); + collections_results = [...collections_results, res]; + unique_ticketing_collection_id = res.id_tcg_collection; + } + + //insert remote_data in db + await this.prisma.remote_data.upsert({ + where: { + ressource_owner_id: unique_ticketing_collection_id, + }, + create: { + id_remote_data: uuidv4(), + ressource_owner_id: unique_ticketing_collection_id, + format: 'json', + data: JSON.stringify(remote_data[i]), + created_at: new Date(), + }, + update: { + data: JSON.stringify(remote_data[i]), + created_at: new Date(), + }, + }); + } + return collections_results; + } catch (error) { + handleServiceError(error, this.logger); + } + } } diff --git a/packages/api/src/ticketing/ticketing.module.ts b/packages/api/src/ticketing/ticketing.module.ts index bcb11d053..cedaa0e3f 100644 --- a/packages/api/src/ticketing/ticketing.module.ts +++ b/packages/api/src/ticketing/ticketing.module.ts @@ -7,7 +7,6 @@ import { ContactModule } from './contact/contact.module'; import { AccountModule } from './account/account.module'; import { TagModule } from './tag/tag.module'; import { TeamModule } from './team/team.module'; -import { ProjectModule } from './project/project.module'; import { CollectionModule } from './collection/collection.module'; @Module({ @@ -20,9 +19,20 @@ import { CollectionModule } from './collection/collection.module'; AccountModule, TagModule, TeamModule, - ProjectModule, CollectionModule, ], providers: [], controllers: [], - exports: [export class TicketingModule {} + exports: [ + TicketModule, + CommentModule, + UserModule, + AttachmentModule, + ContactModule, + AccountModule, + TagModule, + TeamModule, + CollectionModule, + ], +}) +export class TicketingModule {} diff --git a/packages/api/src/ticketing/user/services/jira/index.ts b/packages/api/src/ticketing/user/services/jira/index.ts index bc348e6ab..e45e18aac 100644 --- a/packages/api/src/ticketing/user/services/jira/index.ts +++ b/packages/api/src/ticketing/user/services/jira/index.ts @@ -34,9 +34,6 @@ export class JiraService implements IUserService { provider_slug: 'jira', }, }); - // TODO: sync projects first inside /projects - // /rest/api/3/project/search - const resp = await axios.get( `${connection.account_url}/rest/api/3/users/search`, {