From eca0fbf47ca9eb4792f69ead2acb78d834f4cd03 Mon Sep 17 00:00:00 2001 From: nael Date: Sat, 23 Mar 2024 10:02:03 +0100 Subject: [PATCH] :bug: Added webapp + collections --- .../components/Connection/ConnectionTable.tsx | 1 + .../src/components/Connection/columns.tsx | 29 ++- .../components/Connection/data/connection.ts | 13 ++ .../src/components/Connection/data/schema.ts | 3 +- .../ticketing/ticketing.connection.module.ts | 12 ++ .../types/original/original.ticketing.ts | 15 +- packages/api/src/app.module.ts | 2 + .../api/src/ticketing/@utils/@types/index.ts | 15 +- .../collection/collection.controller.ts | 106 ++++++++++ .../ticketing/collection/collection.module.ts | 33 +++ .../collection/services/collection.service.ts | 194 ++++++++++++++++++ .../collection/services/gorgias/index.ts | 35 ++++ .../collection/services/gorgias/mappers.ts | 47 +++++ .../collection/services/gorgias/types.ts | 12 ++ .../collection/services/jira/index.ts | 66 ++++++ .../collection/services/jira/mappers.ts | 47 +++++ .../collection/services/jira/types.ts | 7 + .../collection/services/registry.service.ts | 23 +++ .../ticketing/collection/sync/sync.service.ts | 33 +++ .../src/ticketing/collection/types/index.ts | 32 +++ .../collection/types/mappingsTypes.ts | 16 ++ .../collection/types/model.unified.ts | 35 ++++ .../src/ticketing/collection/utils/index.ts | 1 + .../src/ticketing/comment/comment.module.ts | 4 + .../src/ticketing/contact/contact.module.ts | 2 + packages/api/src/ticketing/tag/tag.module.ts | 4 + .../api/src/ticketing/team/team.module.ts | 4 + .../api/src/ticketing/ticket/ticket.module.ts | 4 + .../api/src/ticketing/ticketing.module.ts | 8 +- .../api/src/ticketing/user/user.module.ts | 4 + 30 files changed, 798 insertions(+), 9 deletions(-) create mode 100644 packages/api/src/ticketing/collection/collection.controller.ts create mode 100644 packages/api/src/ticketing/collection/collection.module.ts create mode 100644 packages/api/src/ticketing/collection/services/collection.service.ts create mode 100644 packages/api/src/ticketing/collection/services/gorgias/index.ts create mode 100644 packages/api/src/ticketing/collection/services/gorgias/mappers.ts create mode 100644 packages/api/src/ticketing/collection/services/gorgias/types.ts create mode 100644 packages/api/src/ticketing/collection/services/jira/index.ts create mode 100644 packages/api/src/ticketing/collection/services/jira/mappers.ts create mode 100644 packages/api/src/ticketing/collection/services/jira/types.ts create mode 100644 packages/api/src/ticketing/collection/services/registry.service.ts create mode 100644 packages/api/src/ticketing/collection/sync/sync.service.ts create mode 100644 packages/api/src/ticketing/collection/types/index.ts create mode 100644 packages/api/src/ticketing/collection/types/mappingsTypes.ts create mode 100644 packages/api/src/ticketing/collection/types/model.unified.ts create mode 100644 packages/api/src/ticketing/collection/utils/index.ts diff --git a/apps/client-ts/src/components/Connection/ConnectionTable.tsx b/apps/client-ts/src/components/Connection/ConnectionTable.tsx index 2b05bf1c1..5b2ac1949 100644 --- a/apps/client-ts/src/components/Connection/ConnectionTable.tsx +++ b/apps/client-ts/src/components/Connection/ConnectionTable.tsx @@ -51,6 +51,7 @@ export default function ConnectionTable() { status: connection.status, linkedUser: connection.id_linked_user, date: new Date().toISOString(), + connectionToken: connection.connection_token! })) diff --git a/apps/client-ts/src/components/Connection/columns.tsx b/apps/client-ts/src/components/Connection/columns.tsx index df60ecd68..990f2b38e 100644 --- a/apps/client-ts/src/components/Connection/columns.tsx +++ b/apps/client-ts/src/components/Connection/columns.tsx @@ -18,6 +18,14 @@ function truncateMiddle(str: string, maxLength: number) { return `${start}...${end}`; } +function insertDots(originalString: string): string { + if(!originalString) return ""; + if (originalString.length <= 50) { + return originalString; + } + return originalString.substring(0, 50 - 3) + '...'; +} + export const columns: ColumnDef[] = [ { id: "select", @@ -95,7 +103,7 @@ export const columns: ColumnDef[] = [ return value.includes(row.getValue(id)) }, }, - { + { accessorKey: "status", header: ({ column }) => ( @@ -157,5 +165,24 @@ export const columns: ColumnDef[] = [ ) }, + }, + { + accessorKey: "connectionToken", + header: ({ column }) => ( + + ), + cell: ({ row }) => { +
+
+ {insertDots(row.getValue("connectionToken"))} +
+
navigator.clipboard.writeText(row.getValue("connectionToken"))} + > + +
+
+ }, } ] \ No newline at end of file diff --git a/apps/client-ts/src/components/Connection/data/connection.ts b/apps/client-ts/src/components/Connection/data/connection.ts index ba6e565f3..df86f4189 100644 --- a/apps/client-ts/src/components/Connection/data/connection.ts +++ b/apps/client-ts/src/components/Connection/data/connection.ts @@ -6,6 +6,7 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, { @@ -16,6 +17,7 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-7839", @@ -25,6 +27,7 @@ export const CONNECTIONS = [ "category": "high", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-5562", @@ -34,6 +37,7 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-8686", @@ -43,6 +47,7 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-1280", @@ -52,6 +57,7 @@ export const CONNECTIONS = [ "category": "high", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-7262", @@ -61,6 +67,7 @@ export const CONNECTIONS = [ "category": "high", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-1138", @@ -70,6 +77,7 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-7184", @@ -79,6 +87,7 @@ export const CONNECTIONS = [ "category": "low", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-5160", @@ -88,6 +97,7 @@ export const CONNECTIONS = [ "category": "high", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-5618", @@ -97,6 +107,7 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-6699", @@ -106,6 +117,7 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, { "linkedUser": "TASK-2858", @@ -115,5 +127,6 @@ export const CONNECTIONS = [ "category": "medium", "date": "", "organisation": "", + "connectionToken": "", }, ] \ No newline at end of file diff --git a/apps/client-ts/src/components/Connection/data/schema.ts b/apps/client-ts/src/components/Connection/data/schema.ts index 7ccf69cc8..ac8453035 100644 --- a/apps/client-ts/src/components/Connection/data/schema.ts +++ b/apps/client-ts/src/components/Connection/data/schema.ts @@ -8,7 +8,8 @@ export const connectionSchema = z.object({ category: z.string(), status: z.string(), linkedUser: z.string(), - date: z.string() + date: z.string(), + connectionToken: z.string() }) export type Connection = z.infer \ No newline at end of file diff --git a/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts b/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts index 67e1b1455..f14594bb5 100644 --- a/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts +++ b/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts @@ -10,6 +10,12 @@ import { TicketingConnectionsService } from './services/ticketing.connection.ser import { ServiceRegistry } from './services/registry.service'; import { FrontConnectionService } from './services/front/front.service'; import { GithubConnectionService } from './services/github/github.service'; +import { JiraConnectionService } from './services/jira/jira.service'; +import { JiraServiceMgmtConnectionService } from './services/jira_service_management/jira.service'; +import { LinearConnectionService } from './services/linear/linear.service'; +import { GitlabConnectionService } from './services/gitlab/gitlab.service'; +import { ClickupConnectionService } from './services/clickup/clickup.service'; +import { GorgiasConnectionService } from './services/gorgias/gorgias.service'; @Module({ imports: [WebhookModule], @@ -25,6 +31,12 @@ import { GithubConnectionService } from './services/github/github.service'; ZendeskConnectionService, FrontConnectionService, GithubConnectionService, + JiraConnectionService, + JiraServiceMgmtConnectionService, + LinearConnectionService, + GitlabConnectionService, + ClickupConnectionService, + GorgiasConnectionService, ], exports: [TicketingConnectionsService], }) diff --git a/packages/api/src/@core/utils/types/original/original.ticketing.ts b/packages/api/src/@core/utils/types/original/original.ticketing.ts index 80e1e1148..613ff98ae 100644 --- a/packages/api/src/@core/utils/types/original/original.ticketing.ts +++ b/packages/api/src/@core/utils/types/original/original.ticketing.ts @@ -129,6 +129,8 @@ import { JiraTagInput, JiraTagOutput, } from '@ticketing/tag/services/jira/types'; +import { GorgiasCollectionOutput } from '@ticketing/collection/services/gorgias/types'; +import { JiraCollectionOutput } from '@ticketing/collection/services/jira/types'; /* INPUT */ @@ -188,6 +190,7 @@ export type OriginalTeamInput = /* attachment */ export type OriginalAttachmentInput = null; +export type OriginalCollectionInput = null; export type TicketingObjectInput = | OriginalTicketInput @@ -197,7 +200,8 @@ export type TicketingObjectInput = | OriginalTagInput | OriginalTeamInput | OriginalContactInput - | OriginalAccountInput; + | OriginalAccountInput + | OriginalCollectionInput; /* OUTPUT */ @@ -261,6 +265,12 @@ export type OriginalAttachmentOutput = | GorgiasAttachmentOutput | JiraAttachmentOutput; +/* collection */ + +export type OriginalCollectionOutput = + | GorgiasCollectionOutput + | JiraCollectionOutput; + export type TicketingObjectOutput = | OriginalTicketOutput | OriginalCommentOutput @@ -269,4 +279,5 @@ export type TicketingObjectOutput = | OriginalTeamOutput | OriginalTagOutput | OriginalContactOutput - | OriginalAccountOutput; + | OriginalAccountOutput + | OriginalCollectionOutput; diff --git a/packages/api/src/app.module.ts b/packages/api/src/app.module.ts index 61b15b6b6..dc8ba2022 100644 --- a/packages/api/src/app.module.ts +++ b/packages/api/src/app.module.ts @@ -18,6 +18,7 @@ import { CoreModule } from '@@core/core.module'; import { BullModule } from '@nestjs/bull'; import { TicketingModule } from '@ticketing/ticketing.module'; import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; +import { ProjectModule } from './project/project.module'; @Module({ imports: [ @@ -67,6 +68,7 @@ import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; port: 6379, }, }), + ProjectModule, ], controllers: [AppController], providers: [ diff --git a/packages/api/src/ticketing/@utils/@types/index.ts b/packages/api/src/ticketing/@utils/@types/index.ts index d31ed4846..f124bbd36 100644 --- a/packages/api/src/ticketing/@utils/@types/index.ts +++ b/packages/api/src/ticketing/@utils/@types/index.ts @@ -45,6 +45,12 @@ import { UnifiedUserInput, UnifiedUserOutput, } from '@ticketing/user/types/model.unified'; +import { ICollectionService } from '@ticketing/collection/types'; +import { + UnifiedCollectionInput, + UnifiedCollectionOutput, +} from '@ticketing/collection/types/model.unified'; +import { collectionUnificationMapping } from '@ticketing/collection/types/mappingsTypes'; export enum TicketingObject { ticket = 'ticket', @@ -55,6 +61,7 @@ export enum TicketingObject { account = 'account', tag = 'tag', team = 'team', + collection = 'collection', } export type UnifiedTicketing = @@ -73,7 +80,9 @@ export type UnifiedTicketing = | UnifiedTagInput | UnifiedTagOutput | UnifiedAttachmentInput - | UnifiedAttachmentOutput; + | UnifiedAttachmentOutput + | UnifiedCollectionInput + | UnifiedCollectionOutput; export const unificationMapping = { [TicketingObject.ticket]: ticketUnificationMapping, @@ -83,6 +92,7 @@ export const unificationMapping = { [TicketingObject.contact]: contactTicketingUnificationMapping, [TicketingObject.team]: teamUnificationMapping, [TicketingObject.tag]: tagUnificationMapping, + [TicketingObject.collection]: collectionUnificationMapping, }; export type ITicketingService = @@ -93,7 +103,8 @@ export type ITicketingService = | IContactService | IAccountService | ITeamService - | ITagService; + | ITagService + | ICollectionService; /*TODO: export all providers */ export * from '../../ticket/services/zendesk/types'; diff --git a/packages/api/src/ticketing/collection/collection.controller.ts b/packages/api/src/ticketing/collection/collection.controller.ts new file mode 100644 index 000000000..48f52b699 --- /dev/null +++ b/packages/api/src/ticketing/collection/collection.controller.ts @@ -0,0 +1,106 @@ +import { + Controller, + Post, + Body, + Query, + Get, + Patch, + Param, + Headers, +} from '@nestjs/common'; +import { LoggerService } from '@@core/logger/logger.service'; +import { + ApiBody, + ApiOperation, + ApiParam, + ApiQuery, + ApiTags, + ApiHeader, +} from '@nestjs/swagger'; +import { ApiCustomResponse } from '@@core/utils/types'; +import { CollectionService } from './services/collection.service'; +import { + UnifiedCollectionInput, + UnifiedCollectionOutput, +} from './types/model.unified'; +import { ConnectionUtils } from '@@core/connections/@utils'; + +@ApiTags('ticketing/collection') +@Controller('ticketing/collection') +export class CollectionController { + private readonly connectionUtils = new ConnectionUtils(); + + constructor( + private readonly collectionService: CollectionService, + private logger: LoggerService, + ) { + this.logger.setContext(CollectionController.name); + } + + @ApiOperation({ + operationId: 'getCollections', + summary: 'List a batch of Collections', + }) + @ApiHeader({ + name: 'x-connection-token', + required: true, + description: 'The connection token', + example: 'b008e199-eda9-4629-bd41-a01b6195864a', + }) + @ApiQuery({ + name: 'remote_data', + required: false, + type: Boolean, + description: + 'Set to true to include data from the original Ticketing software.', + }) + @ApiCustomResponse(UnifiedCollectionOutput) + //@UseGuards(ApiKeyAuthGuard) + @Get() + async getCollections( + @Headers('x-connection-token') connection_token: string, + @Query('remote_data') remote_data?: boolean, + ) { + try { + const { linkedUserId, remoteSource } = + await this.connectionUtils.getConnectionMetadataFromConnectionToken( + connection_token, + ); + return this.collectionService.getCollections( + remoteSource, + linkedUserId, + remote_data, + ); + } catch (error) { + throw new Error(error); + } + } + + @ApiOperation({ + operationId: 'getCollection', + summary: 'Retrieve a Collection', + description: 'Retrieve a collection from any connected Ticketing software', + }) + @ApiParam({ + name: 'id', + required: true, + type: String, + description: 'id of the collection you want to retrieve.', + }) + @ApiQuery({ + name: 'remote_data', + required: false, + type: Boolean, + description: + 'Set to true to include data from the original Ticketing software.', + }) + @ApiCustomResponse(UnifiedCollectionOutput) + //@UseGuards(ApiKeyAuthGuard) + @Get(':id') + getCollection( + @Param('id') id: string, + @Query('remote_data') remote_data?: boolean, + ) { + return this.collectionService.getCollection(id, remote_data); + } +} diff --git a/packages/api/src/ticketing/collection/collection.module.ts b/packages/api/src/ticketing/collection/collection.module.ts new file mode 100644 index 000000000..ac9f45def --- /dev/null +++ b/packages/api/src/ticketing/collection/collection.module.ts @@ -0,0 +1,33 @@ +import { Module } from '@nestjs/common'; +import { CollectionController } from './collection.controller'; +import { SyncService } from './sync/sync.service'; +import { LoggerService } from '@@core/logger/logger.service'; +import { CollectionService } from './services/collection.service'; +import { ServiceRegistry } from './services/registry.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +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'; + +@Module({ + imports: [ + BullModule.registerQueue({ + name: 'webhookDelivery', + }), + ], + controllers: [CollectionController], + providers: [ + CollectionService, + PrismaService, + LoggerService, + SyncService, + WebhookService, + EncryptionService, + FieldMappingService, + ServiceRegistry, + /* PROVIDERS SERVICES */ + ], + exports: [SyncService], +}) +export class CollectionModule {} diff --git a/packages/api/src/ticketing/collection/services/collection.service.ts b/packages/api/src/ticketing/collection/services/collection.service.ts new file mode 100644 index 000000000..e112dcc31 --- /dev/null +++ b/packages/api/src/ticketing/collection/services/collection.service.ts @@ -0,0 +1,194 @@ +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 { 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 { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private webhook: WebhookService, + private fieldMappingService: FieldMappingService, + private serviceRegistry: ServiceRegistry, + ) { + this.logger.setContext(CollectionService.name); + } + async getCollection( + id_ticketing_collection: string, + remote_data?: boolean, + ): Promise { + try { + const collection = await this.prisma.tcg_collections.findUnique({ + where: { + id_tcg_collection: id_ticketing_collection, + }, + }); + + // 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 + }; + + let res: UnifiedCollectionOutput = { + ...unifiedCollection, + }; + + if (remote_data) { + const resp = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: collection.id_tcg_collection, + }, + }); + const remote_data = JSON.parse(resp.data); + + res = { + ...res, + remote_data: remote_data, + }; + } + + return res; + } catch (error) { + handleServiceError(error, this.logger); + } + } + + async getCollections( + integrationId: string, + linkedUserId: string, + remote_data?: boolean, + ): Promise { + try { + const collections = await this.prisma.tcg_collections.findMany({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + }, + }); + + 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 + }; + }), + ); + + let res: UnifiedCollectionOutput[] = unifiedCollections; + + if (remote_data) { + const remote_array_data: UnifiedCollectionOutput[] = await Promise.all( + res.map(async (collection) => { + const resp = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: collection.id, + }, + }); + const remote_data = JSON.parse(resp.data); + return { ...collection, remote_data }; + }), + ); + res = remote_array_data; + } + + const event = await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'ticketing.collection.pulled', + method: 'GET', + url: '/ticketing/collection', + provider: integrationId, + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + }, + }); + + return res; + } catch (error) { + handleServiceError(error, this.logger); + } + } +} diff --git a/packages/api/src/ticketing/collection/services/gorgias/index.ts b/packages/api/src/ticketing/collection/services/gorgias/index.ts new file mode 100644 index 000000000..7070bf16a --- /dev/null +++ b/packages/api/src/ticketing/collection/services/gorgias/index.ts @@ -0,0 +1,35 @@ +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 new file mode 100644 index 000000000..61c3db6ab --- /dev/null +++ b/packages/api/src/ticketing/collection/services/gorgias/mappers.ts @@ -0,0 +1,47 @@ +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 new file mode 100644 index 000000000..89b6f874b --- /dev/null +++ b/packages/api/src/ticketing/collection/services/gorgias/types.ts @@ -0,0 +1,12 @@ +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 new file mode 100644 index 000000000..9e4d82648 --- /dev/null +++ b/packages/api/src/ticketing/collection/services/jira/index.ts @@ -0,0 +1,66 @@ +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 { JiraCollectionOutput } from './types'; + +@Injectable() +export class JiraService implements ICollectionService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + TicketingObject.collection.toUpperCase() + ':' + JiraService.name, + ); + this.registry.registerService('jira', this); + } + + async syncCollections( + linkedUserId: string, + ): Promise> { + try { + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'jira', + }, + }); + + const resp = await axios.get( + `${connection.account_url}/rest/api/3/user/groups`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + this.logger.log(`Synced jira collections !`); + + return { + data: resp.data._results, + message: 'Jira collections retrieved', + statusCode: 200, + }; + } catch (error) { + handleServiceError( + error, + this.logger, + 'Jira', + TicketingObject.collection, + ActionType.GET, + ); + } + } +} diff --git a/packages/api/src/ticketing/collection/services/jira/mappers.ts b/packages/api/src/ticketing/collection/services/jira/mappers.ts new file mode 100644 index 000000000..83582dfdc --- /dev/null +++ b/packages/api/src/ticketing/collection/services/jira/mappers.ts @@ -0,0 +1,47 @@ +import { ICollectionMapper } from '@ticketing/collection/types'; +import { JiraCollectionInput, JiraCollectionOutput } from './types'; +import { + UnifiedCollectionInput, + UnifiedCollectionOutput, +} from '@ticketing/collection/types/model.unified'; + +export class JiraCollectionMapper implements ICollectionMapper { + desunify( + source: UnifiedCollectionInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): JiraCollectionInput { + return; + } + + unify( + source: JiraCollectionOutput | JiraCollectionOutput[], + 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: JiraCollectionOutput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedCollectionOutput { + const unifiedCollection: UnifiedCollectionOutput = { + name: collection.name, + }; + + return unifiedCollection; + } +} diff --git a/packages/api/src/ticketing/collection/services/jira/types.ts b/packages/api/src/ticketing/collection/services/jira/types.ts new file mode 100644 index 000000000..6189b90b5 --- /dev/null +++ b/packages/api/src/ticketing/collection/services/jira/types.ts @@ -0,0 +1,7 @@ +export type JiraCollectionOutput = { + groupId: string; + name: string; + self: string; +}; + +export type JiraCollectionInput = null; diff --git a/packages/api/src/ticketing/collection/services/registry.service.ts b/packages/api/src/ticketing/collection/services/registry.service.ts new file mode 100644 index 000000000..605afb5a4 --- /dev/null +++ b/packages/api/src/ticketing/collection/services/registry.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { ICollectionService } from '../types'; + +@Injectable() +export class ServiceRegistry { + private serviceMap: Map; + + constructor() { + this.serviceMap = new Map(); + } + + registerService(serviceKey: string, service: ICollectionService) { + this.serviceMap.set(serviceKey, service); + } + + getService(integrationId: string): ICollectionService { + const service = this.serviceMap.get(integrationId); + if (!service) { + throw new Error(); + } + return service; + } +} diff --git a/packages/api/src/ticketing/collection/sync/sync.service.ts b/packages/api/src/ticketing/collection/sync/sync.service.ts new file mode 100644 index 000000000..7caf6f1dd --- /dev/null +++ b/packages/api/src/ticketing/collection/sync/sync.service.ts @@ -0,0 +1,33 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +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 { v4 as uuidv4 } from 'uuid'; +import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; +import { ServiceRegistry } from '../services/registry.service'; +import { unify } from '@@core/utils/unification/unify'; +import { TicketingObject } from '@ticketing/@utils/@types'; +import { WebhookService } from '@@core/webhook/webhook.service'; +import { UnifiedCollectionOutput } from '../types/model.unified'; +import { ICollectionService } from '../types'; + +@Injectable() +export class SyncService implements OnModuleInit { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private webhook: WebhookService, + private fieldMappingService: FieldMappingService, + private serviceRegistry: ServiceRegistry, + ) { + this.logger.setContext(SyncService.name); + } + + async onModuleInit() { + // Initialization logic + } + + // Additional methods and logic +} diff --git a/packages/api/src/ticketing/collection/types/index.ts b/packages/api/src/ticketing/collection/types/index.ts new file mode 100644 index 000000000..d6283555a --- /dev/null +++ b/packages/api/src/ticketing/collection/types/index.ts @@ -0,0 +1,32 @@ +import { DesunifyReturnType } from '@@core/utils/types/desunify.input'; +import { + UnifiedCollectionInput, + UnifiedCollectionOutput, +} from './model.unified'; +import { OriginalCollectionOutput } from '@@core/utils/types/original/original.ticketing'; +import { ApiResponse } from '@@core/utils/types'; + +export interface ICollectionService { + syncCollections( + linkedUserId: string, + custom_properties?: string[], + ): Promise>; +} + +export interface ICollectionMapper { + desunify( + source: UnifiedCollectionInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): DesunifyReturnType; + + unify( + source: OriginalCollectionOutput | OriginalCollectionOutput[], + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): UnifiedCollectionOutput | UnifiedCollectionOutput[]; +} diff --git a/packages/api/src/ticketing/collection/types/mappingsTypes.ts b/packages/api/src/ticketing/collection/types/mappingsTypes.ts new file mode 100644 index 000000000..d2c51fba3 --- /dev/null +++ b/packages/api/src/ticketing/collection/types/mappingsTypes.ts @@ -0,0 +1,16 @@ +import { JiraCollectionMapper } from '../services/jira/mappers'; +import { GorgiasCollectionMapper } from '../services/gorgias/mappers'; + +const jiraCollectionMapper = new JiraCollectionMapper(); +const gorgiasCollectionMapper = new GorgiasCollectionMapper(); + +export const collectionUnificationMapping = { + jira: { + unify: jiraCollectionMapper.unify.bind(jiraCollectionMapper), + desunify: jiraCollectionMapper.desunify, + }, + gorgias: { + unify: gorgiasCollectionMapper.unify.bind(gorgiasCollectionMapper), + desunify: gorgiasCollectionMapper.desunify, + }, +}; diff --git a/packages/api/src/ticketing/collection/types/model.unified.ts b/packages/api/src/ticketing/collection/types/model.unified.ts new file mode 100644 index 000000000..f6fe9af06 --- /dev/null +++ b/packages/api/src/ticketing/collection/types/model.unified.ts @@ -0,0 +1,35 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class UnifiedCollectionInput { + @ApiProperty({ + description: 'The name of the collection', + }) + name: string; + + @ApiPropertyOptional({ + description: 'The description of the collection', + }) + description?: string; + + @ApiPropertyOptional({ + description: 'The type of the collection, either PROJECT or LIST ', + }) + collection_type?: string; +} + +export class UnifiedCollectionOutput extends UnifiedCollectionInput { + @ApiPropertyOptional({ description: 'The uuid of the collection' }) + id?: string; + + @ApiPropertyOptional({ + description: 'The id of the collection in the context of the 3rd Party', + }) + remote_id?: string; + + @ApiPropertyOptional({ + type: [{}], + description: + 'The remote data of the collection in the context of the 3rd Party', + }) + remote_data?: Record; +} diff --git a/packages/api/src/ticketing/collection/utils/index.ts b/packages/api/src/ticketing/collection/utils/index.ts new file mode 100644 index 000000000..f849788c1 --- /dev/null +++ b/packages/api/src/ticketing/collection/utils/index.ts @@ -0,0 +1 @@ +/* PUT ALL UTILS FUNCTIONS USED IN YOUR OBJECT METHODS HERE */ diff --git a/packages/api/src/ticketing/comment/comment.module.ts b/packages/api/src/ticketing/comment/comment.module.ts index e33d3d1ae..11bef6ead 100644 --- a/packages/api/src/ticketing/comment/comment.module.ts +++ b/packages/api/src/ticketing/comment/comment.module.ts @@ -13,6 +13,8 @@ import { ServiceRegistry } from './services/registry.service'; import { GithubService } from './services/github'; import { FrontService } from './services/front'; import { HubspotService } from './services/hubspot'; +import { JiraService } from './services/jira'; +import { GorgiasService } from './services/gorgias'; @Module({ imports: [ @@ -35,6 +37,8 @@ import { HubspotService } from './services/hubspot'; HubspotService, FrontService, GithubService, + JiraService, + GorgiasService, ], exports: [SyncService], }) diff --git a/packages/api/src/ticketing/contact/contact.module.ts b/packages/api/src/ticketing/contact/contact.module.ts index a6c3a9c79..4db82645c 100644 --- a/packages/api/src/ticketing/contact/contact.module.ts +++ b/packages/api/src/ticketing/contact/contact.module.ts @@ -12,6 +12,7 @@ import { ContactService } from './services/contact.service'; import { ContactController } from './contact.controller'; import { FrontService } from './services/front'; import { GithubService } from './services/github'; +import { GorgiasService } from './services/gorgias'; @Module({ imports: [ @@ -33,6 +34,7 @@ import { GithubService } from './services/github'; ZendeskService, FrontService, GithubService, + GorgiasService, ], exports: [SyncService], }) diff --git a/packages/api/src/ticketing/tag/tag.module.ts b/packages/api/src/ticketing/tag/tag.module.ts index 03e41f0f9..8c95d440d 100644 --- a/packages/api/src/ticketing/tag/tag.module.ts +++ b/packages/api/src/ticketing/tag/tag.module.ts @@ -12,6 +12,8 @@ import { BullModule } from '@nestjs/bull'; import { FrontService } from './services/front'; import { GithubService } from './services/github'; import { ZendeskService } from './services/zendesk'; +import { JiraService } from './services/jira'; +import { GorgiasService } from './services/gorgias'; @Module({ imports: [ @@ -33,6 +35,8 @@ import { ZendeskService } from './services/zendesk'; ZendeskService, FrontService, GithubService, + JiraService, + GorgiasService, ], exports: [SyncService], }) diff --git a/packages/api/src/ticketing/team/team.module.ts b/packages/api/src/ticketing/team/team.module.ts index 5aada8f6e..c24f4403d 100644 --- a/packages/api/src/ticketing/team/team.module.ts +++ b/packages/api/src/ticketing/team/team.module.ts @@ -12,6 +12,8 @@ import { BullModule } from '@nestjs/bull'; import { FrontService } from './services/front'; import { GithubService } from './services/github'; import { ZendeskService } from './services/zendesk'; +import { JiraService } from './services/jira'; +import { GorgiasService } from './services/gorgias'; @Module({ imports: [ @@ -33,6 +35,8 @@ import { ZendeskService } from './services/zendesk'; ZendeskService, FrontService, GithubService, + JiraService, + GorgiasService, ], exports: [SyncService], }) diff --git a/packages/api/src/ticketing/ticket/ticket.module.ts b/packages/api/src/ticketing/ticket/ticket.module.ts index fbe503b50..7a798c6fe 100644 --- a/packages/api/src/ticketing/ticket/ticket.module.ts +++ b/packages/api/src/ticketing/ticket/ticket.module.ts @@ -13,6 +13,8 @@ import { ServiceRegistry } from './services/registry.service'; import { HubspotService } from './services/hubspot'; import { FrontService } from './services/front'; import { GithubService } from './services/github'; +import { JiraService } from './services/jira'; +import { GorgiasService } from './services/gorgias'; @Module({ imports: [ @@ -35,6 +37,8 @@ import { GithubService } from './services/github'; HubspotService, FrontService, GithubService, + JiraService, + GorgiasService, ], exports: [SyncService], }) diff --git a/packages/api/src/ticketing/ticketing.module.ts b/packages/api/src/ticketing/ticketing.module.ts index 4e1cd7302..bcb11d053 100644 --- a/packages/api/src/ticketing/ticketing.module.ts +++ b/packages/api/src/ticketing/ticketing.module.ts @@ -7,6 +7,8 @@ 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({ imports: [ @@ -18,9 +20,9 @@ import { TeamModule } from './team/team.module'; AccountModule, TagModule, TeamModule, + ProjectModule, + CollectionModule, ], providers: [], controllers: [], - exports: [UserModule, AttachmentModule, ContactModule], -}) -export class TicketingModule {} + exports: [export class TicketingModule {} diff --git a/packages/api/src/ticketing/user/user.module.ts b/packages/api/src/ticketing/user/user.module.ts index 833733636..46630a475 100644 --- a/packages/api/src/ticketing/user/user.module.ts +++ b/packages/api/src/ticketing/user/user.module.ts @@ -12,6 +12,8 @@ import { BullModule } from '@nestjs/bull'; import { ZendeskService } from './services/zendesk'; import { FrontService } from './services/front'; import { GithubService } from './services/github'; +import { JiraService } from './services/jira'; +import { GorgiasService } from './services/gorgias'; @Module({ imports: [ @@ -33,6 +35,8 @@ import { GithubService } from './services/github'; ZendeskService, FrontService, GithubService, + JiraService, + GorgiasService, ], exports: [SyncService], })