diff --git a/.env.example b/.env.example index 2e505989f..284837894 100644 --- a/.env.example +++ b/.env.example @@ -68,7 +68,8 @@ GORGIAS_TICKETING_CLOUD_CLIENT_SECRET= GORGIAS_TICKETING_CLOUD_SUBDOMAIN= FRONT_TICKETING_CLOUD_CLIENT_ID= FRONT_TICKETING_CLOUD_CLIENT_SECRET= - +GITLAB_TICKETING_CLOUD_CLIENT_ID= +GITLAB_TICKETING_CLOUD_CLIENT_SECRET= # ================================================ # Webapp settings # Must be set in the perspective of the end user browser diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index 0b92b8aad..b65b22a22 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -141,7 +141,7 @@ model crm_companies { name String? industry String? number_of_employees BigInt? - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_crm_companies") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) remote_id String? remote_platform String? @@ -164,7 +164,7 @@ model crm_contacts { id_crm_contact String @id(map: "pk_crm_contacts") @db.Uuid first_name String last_name String - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_crm_contacts") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) remote_id String remote_platform String @@ -184,8 +184,8 @@ model crm_deals { id_crm_deal String @id(map: "pk_crm_deal") @db.Uuid name String description String - amount BigInt - created_at DateTime @db.Timestamp(6) + amount BigInt? + created_at DateTime @unique(map: "force_createdat_unique_crm_deals") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) remote_id String? remote_platform String? @@ -207,7 +207,7 @@ model crm_deals { model crm_deals_stages { id_crm_deals_stage String @id(map: "pk_crm_deal_stages") @db.Uuid stage_name String? - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_crm_deals_stages") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_linked_user String? @db.Uuid remote_id String? @@ -251,7 +251,7 @@ model crm_engagements { subject String? start_at DateTime? @db.Timestamp(6) end_time DateTime? @db.Timestamp(6) - created_at DateTime? @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_crm_engagements") @default(now()) @db.Timestamp(6) modified_at DateTime? @db.Timestamp(6) remote_id String? id_linked_user String? @db.Uuid @@ -269,7 +269,7 @@ model crm_engagements { model crm_notes { id_crm_note String @id(map: "pk_crm_notes") @db.Uuid content String - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_crm_notes") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_crm_company String? @db.Uuid id_crm_contact String? @db.Uuid @@ -312,7 +312,7 @@ model crm_tasks { status String? due_date DateTime? @db.Timestamp(6) finished_date DateTime? @db.Timestamp(6) - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_crm_tasks") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_crm_user String? @db.Uuid id_crm_company String? @db.Uuid @@ -333,7 +333,7 @@ model crm_users { id_crm_user String @id(map: "pk_crm_users") @db.Uuid name String? email String? - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_crm_users") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_linked_user String? @db.Uuid remote_id String? @@ -457,7 +457,7 @@ model tcg_accounts { name String? domains String[] remote_platform String? - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_accounts") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_linked_user String? @db.Uuid tcg_contacts tcg_contacts[] @@ -471,7 +471,7 @@ model tcg_attachments { file_name String? file_url String? uploader String @db.Uuid - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_attachments") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_linked_user String? @db.Uuid id_tcg_ticket String? @db.Uuid @@ -491,7 +491,7 @@ model tcg_collections { remote_platform String? collection_type String? parent_collection String? @db.Uuid - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_collections") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_linked_user String @db.Uuid } @@ -504,7 +504,7 @@ model tcg_comments { is_private Boolean? remote_id String? remote_platform String? - created_at DateTime? @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_comments") @default(now()) @db.Timestamp(6) modified_at DateTime? @db.Timestamp(6) creator_type String? id_tcg_attachment String[] @@ -530,7 +530,7 @@ model tcg_contacts { details String? remote_id String? remote_platform String? - created_at DateTime? @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_contacts") @default(now()) @db.Timestamp(6) modified_at DateTime? @db.Timestamp(6) id_tcg_account String? @db.Uuid id_linked_user String? @db.Uuid @@ -545,7 +545,7 @@ model tcg_tags { name String? remote_id String? remote_platform String? - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_tags") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_tcg_ticket String? @db.Uuid id_linked_user String? @db.Uuid @@ -560,7 +560,7 @@ model tcg_teams { remote_platform String? name String? description String? - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_teams") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) id_linked_user String? @db.Uuid } @@ -578,7 +578,7 @@ model tcg_tickets { collections String[] completed_at DateTime? @db.Timestamp(6) priority String? - created_at DateTime @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_tickets") @default(now()) @db.Timestamp(6) modified_at DateTime @db.Timestamp(6) assigned_to String[] remote_id String? @@ -601,7 +601,7 @@ model tcg_users { remote_id String? remote_platform String? teams String[] - created_at DateTime? @db.Timestamp(6) + created_at DateTime @unique(map: "force_createdat_unique_tcg_users") @default(now()) @db.Timestamp(6) modified_at DateTime? @db.Timestamp(6) id_linked_user String? @db.Uuid tcg_comments tcg_comments[] diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index d6a8aa9f1..023530d1e 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -104,9 +104,10 @@ CREATE TABLE tcg_users remote_id text NULL, remote_platform text NULL, teams text[] NULL, - created_at timestamp NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NULL, id_linked_user uuid NULL, + CONSTRAINT force_createdAt_unique_tcg_users UNIQUE ( created_at ), CONSTRAINT PK_tcg_users PRIMARY KEY ( id_tcg_user ) ); @@ -127,9 +128,10 @@ CREATE TABLE tcg_teams remote_platform text NULL, name text NULL, description text NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_linked_user uuid NULL, + CONSTRAINT force_createdAt_unique_tcg_teams UNIQUE ( created_at ), CONSTRAINT PK_tcg_teams PRIMARY KEY ( id_tcg_team ) ); @@ -151,9 +153,10 @@ CREATE TABLE tcg_collections remote_platform text NULL, collection_type text NULL, parent_collection uuid NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_linked_user uuid NOT NULL, + CONSTRAINT force_createdAt_unique_tcg_collections UNIQUE ( created_at ), CONSTRAINT PK_tcg_collections PRIMARY KEY ( id_tcg_collection ) ); @@ -173,9 +176,10 @@ CREATE TABLE tcg_accounts name text NULL, domains text[] NULL, remote_platform text NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_linked_user uuid NULL, + CONSTRAINT force_createdAt_unique_tcg_accounts UNIQUE ( created_at ), CONSTRAINT PK_tcg_account PRIMARY KEY ( id_tcg_account ) ); @@ -355,11 +359,12 @@ CREATE TABLE crm_users id_crm_user uuid NOT NULL, name text NULL, email text NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_linked_user uuid NULL, remote_id text NULL, remote_platform text NULL, + CONSTRAINT force_createdAt_unique_crm_users UNIQUE ( created_at ), CONSTRAINT PK_crm_users PRIMARY KEY ( id_crm_user ) ); @@ -370,11 +375,12 @@ CREATE TABLE crm_deals_stages ( id_crm_deals_stage uuid NOT NULL, stage_name text NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_linked_user uuid NULL, remote_id text NULL, remote_platform text NULL, + CONSTRAINT force_createdAt_unique_crm_deals_stages UNIQUE ( created_at ), CONSTRAINT PK_crm_deal_stages PRIMARY KEY ( id_crm_deals_stage ) ); @@ -439,7 +445,7 @@ CREATE TABLE tcg_tickets collections text[] NULL, completed_at timestamp NULL, priority text NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, assigned_to text[] NULL, remote_id text NULL, @@ -447,6 +453,7 @@ CREATE TABLE tcg_tickets creator_type text NULL, id_tcg_user uuid NULL, id_linked_user uuid NOT NULL, + CONSTRAINT force_createdAt_unique_tcg_tickets UNIQUE ( created_at ), CONSTRAINT PK_tcg_tickets PRIMARY KEY ( id_tcg_ticket ) ); @@ -480,11 +487,12 @@ CREATE TABLE tcg_contacts details text NULL, remote_id text NULL, remote_platform text NULL, - created_at timestamp NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NULL, id_tcg_account uuid NULL, id_linked_user uuid NULL, CONSTRAINT PK_tcg_contact PRIMARY KEY ( id_tcg_contact ), + CONSTRAINT force_createdAt_unique_tcg_contacts UNIQUE ( created_at ), CONSTRAINT FK_49 FOREIGN KEY ( id_tcg_account ) REFERENCES tcg_accounts ( id_tcg_account ) ); @@ -565,13 +573,14 @@ CREATE TABLE crm_contacts id_crm_contact uuid NOT NULL, first_name text NOT NULL, last_name text NOT NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, remote_id text NOT NULL, remote_platform text NOT NULL, id_crm_user uuid NULL, id_linked_user uuid NULL, CONSTRAINT PK_crm_contacts PRIMARY KEY ( id_crm_contact ), + CONSTRAINT force_createdAt_unique_crm_contacts UNIQUE ( created_at ), CONSTRAINT FK_23 FOREIGN KEY ( id_crm_user ) REFERENCES crm_users ( id_crm_user ) ); @@ -596,13 +605,14 @@ CREATE TABLE crm_companies name text NULL, industry text NULL, number_of_employees bigint NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, remote_id text NULL, remote_platform text NULL, id_crm_user uuid NULL, id_linked_user uuid NULL, CONSTRAINT PK_crm_companies PRIMARY KEY ( id_crm_company ), + CONSTRAINT force_createdAt_unique_crm_companies UNIQUE ( created_at ), CONSTRAINT FK_24 FOREIGN KEY ( id_crm_user ) REFERENCES crm_users ( id_crm_user ) ); @@ -704,11 +714,12 @@ CREATE TABLE tcg_tags name text NULL, remote_id text NULL, remote_platform text NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_tcg_ticket uuid NULL, id_linked_user uuid NULL, CONSTRAINT PK_tcg_tags PRIMARY KEY ( id_tcg_tag ), + CONSTRAINT force_createdAt_unique_tcg_tags UNIQUE ( created_at ), CONSTRAINT FK_48 FOREIGN KEY ( id_tcg_ticket ) REFERENCES tcg_tickets ( id_tcg_ticket ) ); @@ -734,7 +745,7 @@ CREATE TABLE tcg_comments is_private boolean NULL, remote_id text NULL, remote_platform text NULL, - created_at timestamp NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NULL, creator_type text NULL, id_tcg_attachment text[] NULL, @@ -743,6 +754,7 @@ CREATE TABLE tcg_comments id_tcg_user uuid NULL, id_linked_user uuid NULL, CONSTRAINT PK_tcg_comments PRIMARY KEY ( id_tcg_comment ), + CONSTRAINT force_createdAt_unique_tcg_comments UNIQUE ( created_at ), CONSTRAINT FK_41 FOREIGN KEY ( id_tcg_contact ) REFERENCES tcg_contacts ( id_tcg_contact ), CONSTRAINT FK_40_1 FOREIGN KEY ( id_tcg_ticket ) REFERENCES tcg_tickets ( id_tcg_ticket ), CONSTRAINT FK_42 FOREIGN KEY ( id_tcg_user ) REFERENCES tcg_users ( id_tcg_user ) @@ -878,7 +890,7 @@ CREATE TABLE crm_engagements subject text NULL, start_at timestamp NULL, end_time timestamp NULL, - created_at timestamp NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NULL, remote_id text NULL, id_linked_user uuid NULL, @@ -886,6 +898,7 @@ CREATE TABLE crm_engagements id_crm_company uuid NULL, id_crm_user uuid NULL, CONSTRAINT PK_crm_engagement PRIMARY KEY ( id_crm_engagement ), + CONSTRAINT force_createdAt_unique_crm_engagements UNIQUE ( created_at ), CONSTRAINT FK_crm_engagement_crm_user FOREIGN KEY ( id_crm_user ) REFERENCES crm_users ( id_crm_user ), CONSTRAINT FK_29 FOREIGN KEY ( id_crm_company ) REFERENCES crm_companies ( id_crm_company ) ); @@ -952,8 +965,8 @@ CREATE TABLE crm_deals id_crm_deal uuid NOT NULL, name text NOT NULL, description text NOT NULL, - amount bigint NOT NULL, - created_at timestamp NOT NULL, + amount bigint NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, remote_id text NULL, remote_platform text NULL, @@ -962,6 +975,7 @@ CREATE TABLE crm_deals id_linked_user uuid NULL, id_crm_company uuid NULL, CONSTRAINT PK_crm_deal PRIMARY KEY ( id_crm_deal ), + CONSTRAINT force_createdAt_unique_crm_deals UNIQUE ( created_at ), CONSTRAINT FK_22 FOREIGN KEY ( id_crm_user ) REFERENCES crm_users ( id_crm_user ), CONSTRAINT FK_21 FOREIGN KEY ( id_crm_deals_stage ) REFERENCES crm_deals_stages ( id_crm_deals_stage ), CONSTRAINT FK_47_1 FOREIGN KEY ( id_crm_company ) REFERENCES crm_companies ( id_crm_company ) @@ -1075,12 +1089,13 @@ CREATE TABLE tcg_attachments file_name text NULL, file_url text NULL, uploader uuid NOT NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_linked_user uuid NULL, id_tcg_ticket uuid NULL, id_tcg_comment uuid NULL, CONSTRAINT PK_tcg_attachments PRIMARY KEY ( id_tcg_attachment ), + CONSTRAINT force_createdAt_unique_tcg_attachments UNIQUE ( created_at ), CONSTRAINT FK_51 FOREIGN KEY ( id_tcg_comment ) REFERENCES tcg_comments ( id_tcg_comment ), CONSTRAINT FK_50 FOREIGN KEY ( id_tcg_ticket ) REFERENCES tcg_tickets ( id_tcg_ticket ) ); @@ -1170,7 +1185,7 @@ CREATE TABLE crm_tasks status text NULL, due_date timestamp NULL, finished_date timestamp NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_crm_user uuid NULL, id_crm_company uuid NULL, @@ -1179,6 +1194,7 @@ CREATE TABLE crm_tasks remote_id text NULL, remote_platform text NULL, CONSTRAINT PK_crm_task PRIMARY KEY ( id_crm_task ), + CONSTRAINT force_createdAt_unique_crm_tasks UNIQUE ( created_at ), CONSTRAINT FK_26 FOREIGN KEY ( id_crm_company ) REFERENCES crm_companies ( id_crm_company ), CONSTRAINT FK_25 FOREIGN KEY ( id_crm_user ) REFERENCES crm_users ( id_crm_user ), CONSTRAINT FK_27 FOREIGN KEY ( id_crm_deal ) REFERENCES crm_deals ( id_crm_deal ) @@ -1212,7 +1228,7 @@ CREATE TABLE crm_notes ( id_crm_note uuid NOT NULL, content text NOT NULL, - created_at timestamp NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), modified_at timestamp NOT NULL, id_crm_company uuid NULL, id_crm_contact uuid NULL, @@ -1222,6 +1238,7 @@ CREATE TABLE crm_notes remote_platform text NULL, id_crm_user uuid NULL, CONSTRAINT PK_crm_notes PRIMARY KEY ( id_crm_note ), + CONSTRAINT force_createdAt_unique_crm_notes UNIQUE ( created_at ), CONSTRAINT FK_19 FOREIGN KEY ( id_crm_contact ) REFERENCES crm_contacts ( id_crm_contact ), CONSTRAINT FK_18 FOREIGN KEY ( id_crm_company ) REFERENCES crm_companies ( id_crm_company ), CONSTRAINT FK_20 FOREIGN KEY ( id_crm_deal ) REFERENCES crm_deals ( id_crm_deal ) diff --git a/packages/api/src/@core/utils/dtos/fetch-objects-query.dto.ts b/packages/api/src/@core/utils/dtos/fetch-objects-query.dto.ts new file mode 100644 index 000000000..7da1509fc --- /dev/null +++ b/packages/api/src/@core/utils/dtos/fetch-objects-query.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty, ApiQuery } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsBoolean, IsNumber, IsOptional, IsUUID } from 'class-validator' + + +// To provide a default pageSize +const DEFAULT_PAGE_SIZE = 50; + +export class FetchObjectsQueryDto { + + @ApiProperty({ + name: 'remote_data', + description: 'Set to true to include data from the original software.', + required: false + }) + @IsOptional() + @Transform(({ value }) => value === 'true' ? true : value === 'false' ? false : value) + @IsBoolean() + remote_data: boolean; + + @ApiProperty({ + name: 'pageSize', + required: false, + description: 'Set to get the number of records.' + }) + @IsOptional() + @IsNumber() + @Transform(p => Number(p.value)) + pageSize: number = DEFAULT_PAGE_SIZE; + + @ApiProperty({ + name: 'cursor', + required: false, + description: 'Set to get the number of records after this cursor.' + }) + @IsOptional() + @Transform(p => Buffer.from(p.value, 'base64').toString()) + @IsUUID() + cursor: string; + +} + + + diff --git a/packages/api/src/crm/@lib/@utils/index.ts b/packages/api/src/crm/@lib/@utils/index.ts index b8dfc67eb..921fa64c1 100644 --- a/packages/api/src/crm/@lib/@utils/index.ts +++ b/packages/api/src/crm/@lib/@utils/index.ts @@ -84,7 +84,8 @@ export class Utils { id_crm_user: uuid, }, }); - if (!res) throw new Error(`crm_user not found for uuid ${uuid}`); + // if (!res) throw new Error(`crm_user not found for uuid ${uuid}`); + if (!res) return; return res.remote_id; } catch (error) { throw new Error(error); @@ -98,7 +99,8 @@ export class Utils { id_crm_user: uuid, }, }); - if (!res) throw new Error(`crm_user not found for uuid ${uuid}`); + // if (!res) throw new Error(`crm_user not found for uuid ${uuid}`); + if (!res) return; return res; } catch (error) { throw new Error(error); @@ -114,9 +116,10 @@ export class Utils { }, }); if (!res) { - throw new Error( - `crm_user not found for remote_id ${remote_id} and integration ${remote_platform}`, - ); + // throw new Error( + // `crm_user not found for remote_id ${remote_id} and integration ${remote_platform}`, + // ); + return; } return res.id_crm_user; } catch (error) { @@ -166,7 +169,8 @@ export class Utils { }, }); if (!res) { - throw new Error(`crm_companies not found for uuid ${uuid}`); + // throw new Error(`crm_companies not found for uuid ${uuid}`); + return; } return res.remote_id; } catch (error) { @@ -183,9 +187,10 @@ export class Utils { }, }); if (!res) { - throw new Error( - `crm_companies not found for remote_id ${remote_id} and integration ${remote_platform}`, - ); + return; + // throw new Error( + // `crm_companies not found for remote_id ${remote_id} and integration ${remote_platform}`, + // ); } return res.id_crm_company; } catch (error) { @@ -200,7 +205,8 @@ export class Utils { id_crm_deals_stage: uuid, }, }); - if (!res) throw new Error(`crm_deals_stages not found for uuid ${uuid}`); + // if (!res) throw new Error(`crm_deals_stages not found for uuid ${uuid}`); + if (!res) return; return res.remote_id; } catch (error) { throw new Error(error); @@ -216,9 +222,10 @@ export class Utils { }, }); if (!res) { - throw new Error( - `crm_deals_stages not found for remote_id ${remote_id} and integration ${remote_platform}`, - ); + return; + // throw new Error( + // `crm_deals_stages not found for remote_id ${remote_id} and integration ${remote_platform}`, + // ); } return res.id_crm_deals_stage; } catch (error) { @@ -233,7 +240,8 @@ export class Utils { id_crm_contact: uuid, }, }); - if (!res) throw new Error(`crm_contacts not found for uuid ${uuid}`); + // if (!res) throw new Error(`crm_contacts not found for uuid ${uuid}`); + if (!res) return; return res.remote_id; } catch (error) { throw new Error(error); @@ -248,10 +256,11 @@ export class Utils { remote_platform: remote_platform, }, }); - if (!res) - throw new Error( - `crm_contacts not found for remote_id ${remote_id} and integration ${remote_platform}`, - ); + // if (!res) + // throw new Error( + // `crm_contacts not found for remote_id ${remote_id} and integration ${remote_platform}`, + // ); + if (!res) return; return res.id_crm_contact; } catch (error) { throw new Error(error); @@ -265,7 +274,8 @@ export class Utils { id_crm_deal: uuid, }, }); - if (!res) throw new Error(`crm_deals not found for uuid ${uuid}`); + // if (!res) throw new Error(`crm_deals not found for uuid ${uuid}`); + if (!res) return; return res.remote_id; } catch (error) { throw new Error(error); @@ -280,10 +290,11 @@ export class Utils { remote_platform: remote_platform, }, }); - if (!res) - throw new Error( - `crm_deals not found for remote_id ${remote_id} and integration ${remote_platform}`, - ); + // if (!res) + // throw new Error( + // `crm_deals not found for remote_id ${remote_id} and integration ${remote_platform}`, + // ); + if (!res) return; return res.id_crm_deal; } catch (error) { throw new Error(error); @@ -308,7 +319,7 @@ export class Utils { return priority === 'High' ? 'HIGH' : priority === 'Medium' - ? 'MEDIUM' - : 'LOW'; + ? 'MEDIUM' + : 'LOW'; } } diff --git a/packages/api/src/crm/company/company.controller.ts b/packages/api/src/crm/company/company.controller.ts index c11446c7b..a04e3034d 100644 --- a/packages/api/src/crm/company/company.controller.ts +++ b/packages/api/src/crm/company/company.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { LoggerService } from '@@core/logger/logger.service'; @@ -18,6 +20,7 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { CompanyService } from './services/company.service'; @@ -26,7 +29,9 @@ import { UnifiedCompanyOutput, } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('crm/companies') @Controller('crm/companies') export class CompanyController { @@ -49,28 +54,26 @@ export class CompanyController { 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 Crm software.', - }) @ApiCustomResponse(UnifiedCompanyOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getCompanies( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.companyService.getCompanies( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/crm/company/services/company.service.ts b/packages/api/src/crm/company/services/company.service.ts index b86fa027a..156a31cda 100644 --- a/packages/api/src/crm/company/services/company.service.ts +++ b/packages/api/src/crm/company/services/company.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedCompanyInput, @@ -454,10 +454,36 @@ export class CompanyService { async getCompanies( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedCompanyOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const companies = await this.prisma.crm_companies.findMany({ + + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_companies.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_company: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let companies = await this.prisma.crm_companies.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_company: cursor + } : undefined, + orderBy: { + modified_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, @@ -469,6 +495,15 @@ export class CompanyService { }, }); + if (companies.length === (pageSize + 1)) { + next_cursor = Buffer.from(companies[companies.length - 1].id_crm_company).toString('base64'); + companies.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedCompanies: UnifiedCompanyOutput[] = await Promise.all( companies.map(async (company) => { const values = await this.prisma.value.findMany({ @@ -548,7 +583,11 @@ export class CompanyService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/company/services/hubspot/index.ts b/packages/api/src/crm/company/services/hubspot/index.ts index 8187a6c4e..3c2a0b667 100644 --- a/packages/api/src/crm/company/services/hubspot/index.ts +++ b/packages/api/src/crm/company/services/hubspot/index.ts @@ -102,6 +102,7 @@ export class HubspotService implements ICompanyService { }); this.logger.log(`Synced hubspot companies !`); + return { data: resp.data.results, message: 'Hubspot companies retrieved', diff --git a/packages/api/src/crm/company/sync/sync.service.ts b/packages/api/src/crm/company/sync/sync.service.ts index baa7de129..48b59dadb 100644 --- a/packages/api/src/crm/company/sync/sync.service.ts +++ b/packages/api/src/crm/company/sync/sync.service.ts @@ -72,12 +72,12 @@ export class SyncService implements OnModuleInit { this.logger.log(`Syncing companies....`); const users = user_id ? [ - await this.prisma.users.findUnique({ - where: { - id_user: user_id, - }, - }), - ] + await this.prisma.users.findUnique({ + where: { + id_user: user_id, + }, + }), + ] : await this.prisma.users.findMany(); if (users && users.length > 0) { for (const user of users) { @@ -353,7 +353,7 @@ export class SyncService implements OnModuleInit { const uuid = uuidv4(); let data: any = { id_crm_company: uuid, - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/crm/contact/contact.controller.ts b/packages/api/src/crm/contact/contact.controller.ts index b3eaed11a..3518a35fd 100644 --- a/packages/api/src/crm/contact/contact.controller.ts +++ b/packages/api/src/crm/contact/contact.controller.ts @@ -8,6 +8,8 @@ import { Param, UseGuards, Headers, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { ContactService } from './services/contact.service'; import { LoggerService } from '@@core/logger/logger.service'; @@ -22,11 +24,15 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiCustomResponse } from '@@core/utils/types'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; + +@ApiBearerAuth('JWT') @ApiTags('crm/contacts') @Controller('crm/contacts') export class ContactController { @@ -49,28 +55,26 @@ export class ContactController { 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 CRM software.', - }) @ApiCustomResponse(UnifiedContactOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getContacts( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.contactService.getContacts( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/crm/contact/services/contact.service.ts b/packages/api/src/crm/contact/services/contact.service.ts index 9bfeaaebe..e3f17000a 100644 --- a/packages/api/src/crm/contact/services/contact.service.ts +++ b/packages/api/src/crm/contact/services/contact.service.ts @@ -11,7 +11,7 @@ import { UnifiedContactOutput, } from '@crm/contact/types/model.unified'; import { ApiResponse } from '@@core/utils/types'; -import { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; import { WebhookService } from '@@core/webhook/webhook.service'; import { OriginalContactOutput } from '@@core/utils/types/original/original.crm'; @@ -486,12 +486,37 @@ export class ContactService { async getContacts( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedContactOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const contacts = await this.prisma.crm_contacts.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_contacts.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_contact: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let contacts = await this.prisma.crm_contacts.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_contact: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, @@ -503,6 +528,15 @@ export class ContactService { }, }); + if (contacts.length === (pageSize + 1)) { + next_cursor = Buffer.from(contacts[contacts.length - 1].id_crm_contact).toString('base64'); + contacts.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedContacts: UnifiedContactOutput[] = await Promise.all( contacts.map(async (contact) => { // Fetch field mappings for the contact @@ -581,7 +615,11 @@ export class ContactService { id_linked_user: linkedUserId, }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/contact/sync/sync.service.ts b/packages/api/src/crm/contact/sync/sync.service.ts index e8799ca65..0adc59b52 100644 --- a/packages/api/src/crm/contact/sync/sync.service.ts +++ b/packages/api/src/crm/contact/sync/sync.service.ts @@ -85,12 +85,12 @@ export class SyncService implements OnModuleInit { const users = user_id ? [ - await this.prisma.users.findUnique({ - where: { - id_user: user_id, - }, - }), - ] + await this.prisma.users.findUnique({ + where: { + id_user: user_id, + }, + }), + ] : await this.prisma.users.findMany(); if (users && users.length > 0) { for (const user of users) { @@ -369,7 +369,7 @@ export class SyncService implements OnModuleInit { id_crm_contact: uuid, first_name: '', last_name: '', - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/crm/deal/deal.controller.ts b/packages/api/src/crm/deal/deal.controller.ts index 2c464ac9b..5d5a6c2bf 100644 --- a/packages/api/src/crm/deal/deal.controller.ts +++ b/packages/api/src/crm/deal/deal.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { LoggerService } from '@@core/logger/logger.service'; @@ -18,12 +20,15 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { DealService } from './services/deal.service'; import { UnifiedDealInput, UnifiedDealOutput } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('crm/deals') @Controller('crm/deals') export class DealController { @@ -46,25 +51,21 @@ export class DealController { 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 Crm software.', - }) @ApiCustomResponse(UnifiedDealOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getDeals( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); - return this.dealService.getDeals(remoteSource, linkedUserId, remote_data); + const { remote_data, pageSize, cursor } = query; + return this.dealService.getDeals(remoteSource, linkedUserId, pageSize, remote_data, cursor); } catch (error) { throw new Error(error); } diff --git a/packages/api/src/crm/deal/services/deal.service.ts b/packages/api/src/crm/deal/services/deal.service.ts index b73c13194..64d3d30dc 100644 --- a/packages/api/src/crm/deal/services/deal.service.ts +++ b/packages/api/src/crm/deal/services/deal.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedDealInput, UnifiedDealOutput } from '../types/model.unified'; import { desunify } from '@@core/utils/unification/desunify'; @@ -318,16 +318,51 @@ export class DealService { async getDeals( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedDealOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const deals = await this.prisma.crm_deals.findMany({ + + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_deals.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_deal: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let deals = await this.prisma.crm_deals.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_deal: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (deals.length === (pageSize + 1)) { + next_cursor = Buffer.from(deals[deals.length - 1].id_crm_deal).toString('base64'); + deals.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedDeals: UnifiedDealOutput[] = await Promise.all( deals.map(async (deal) => { // Fetch field mappings for the ticket @@ -398,7 +433,11 @@ export class DealService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/deal/services/hubspot/index.ts b/packages/api/src/crm/deal/services/hubspot/index.ts index 7e9672693..5c9ed96d3 100644 --- a/packages/api/src/crm/deal/services/hubspot/index.ts +++ b/packages/api/src/crm/deal/services/hubspot/index.ts @@ -53,8 +53,11 @@ export class HubspotService implements IDealService { }, }, ); + + this.logger.log(`Synced hubspot deals !`); + return { - data: resp.data, + data: resp.data.results, message: 'Hubspot deal created', statusCode: 201, }; diff --git a/packages/api/src/crm/deal/services/hubspot/mappers.ts b/packages/api/src/crm/deal/services/hubspot/mappers.ts index 2172e042c..ba2eae95b 100644 --- a/packages/api/src/crm/deal/services/hubspot/mappers.ts +++ b/packages/api/src/crm/deal/services/hubspot/mappers.ts @@ -101,11 +101,17 @@ export class HubspotDealMapper implements IDealMapper { } } + if (deal.properties.amount) { + opts = { + ...opts, + amount: parseFloat(deal.properties.amount) + } + } + return { remote_id: deal.id, name: deal.properties.dealname, description: deal.properties.dealname, // Placeholder if there's no direct mapping - amount: parseFloat(deal.properties.amount), //TODO; stage_id: deal.properties.dealstage, field_mappings, ...opts, diff --git a/packages/api/src/crm/deal/sync/sync.service.ts b/packages/api/src/crm/deal/sync/sync.service.ts index 9c3fa9b48..ebd069aec 100644 --- a/packages/api/src/crm/deal/sync/sync.service.ts +++ b/packages/api/src/crm/deal/sync/sync.service.ts @@ -166,7 +166,7 @@ export class SyncService implements OnModuleInit { ); const sourceObject: OriginalDealOutput[] = resp.data; - //this.logger.log('SOURCE OBJECT DATA = ' + JSON.stringify(sourceObject)); + // this.logger.log('SOURCE OBJECT DATA = ' + JSON.stringify(sourceObject)); //unify the data according to the target obj wanted const unifiedObject = (await unify({ sourceObject, @@ -272,11 +272,10 @@ export class SyncService implements OnModuleInit { this.logger.log('deal not exists'); let data: any = { id_crm_deal: uuidv4(), - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, description: '', - amount: deal.amount, remote_id: originId, remote_platform: originSource, }; diff --git a/packages/api/src/crm/engagement/engagement.controller.ts b/packages/api/src/crm/engagement/engagement.controller.ts index 57daa5a10..dd869c283 100644 --- a/packages/api/src/crm/engagement/engagement.controller.ts +++ b/packages/api/src/crm/engagement/engagement.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; @@ -18,6 +20,7 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { EngagementService } from './services/engagement.service'; @@ -26,7 +29,9 @@ import { UnifiedEngagementOutput, } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('crm/engagements') @Controller('crm/engagements') export class EngagementController { @@ -49,28 +54,27 @@ export class EngagementController { 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 Crm software.', - }) @ApiCustomResponse(UnifiedEngagementOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getEngagements( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; + return this.engagementService.getEngagements( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/crm/engagement/services/engagement.service.ts b/packages/api/src/crm/engagement/services/engagement.service.ts index 70f895d93..83921540c 100644 --- a/packages/api/src/crm/engagement/services/engagement.service.ts +++ b/packages/api/src/crm/engagement/services/engagement.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedEngagementInput, @@ -124,8 +124,8 @@ export class EngagementService { type === 'CALL' ? CrmObject.engagement_call : type === 'MEETING' - ? CrmObject.engagement_meeting - : CrmObject.engagement_email; + ? CrmObject.engagement_meeting + : CrmObject.engagement_email; //unify the data according to the target obj wanted const unifiedObject = (await unify({ @@ -370,16 +370,50 @@ export class EngagementService { async getEngagements( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedEngagementOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const engagements = await this.prisma.crm_engagements.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_engagements.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_engagement: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let engagements = await this.prisma.crm_engagements.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_engagement: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (engagements.length === (pageSize + 1)) { + next_cursor = Buffer.from(engagements[engagements.length - 1].id_crm_engagement).toString('base64'); + engagements.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedEngagements: UnifiedEngagementOutput[] = await Promise.all( engagements.map(async (engagement) => { // Fetch field mappings for the ticket @@ -458,7 +492,11 @@ export class EngagementService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/engagement/sync/sync.service.ts b/packages/api/src/crm/engagement/sync/sync.service.ts index bc535c672..034c7de98 100644 --- a/packages/api/src/crm/engagement/sync/sync.service.ts +++ b/packages/api/src/crm/engagement/sync/sync.service.ts @@ -280,7 +280,7 @@ export class SyncService implements OnModuleInit { this.logger.log('engagement not exists'); let data: any = { id_crm_engagement: uuidv4(), - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/crm/note/note.controller.ts b/packages/api/src/crm/note/note.controller.ts index 76572964e..036e39aa8 100644 --- a/packages/api/src/crm/note/note.controller.ts +++ b/packages/api/src/crm/note/note.controller.ts @@ -7,6 +7,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { LoggerService } from '@@core/logger/logger.service'; @@ -17,12 +19,15 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { NoteService } from './services/note.service'; import { UnifiedNoteInput, UnifiedNoteOutput } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('crm/notes') @Controller('crm/notes') export class NoteController { @@ -45,25 +50,27 @@ export class NoteController { 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 Crm software.', - }) @ApiCustomResponse(UnifiedNoteOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getNotes( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); - return this.noteService.getNotes(remoteSource, linkedUserId, remote_data); + const { remote_data, pageSize, cursor } = query; + return this.noteService.getNotes( + remoteSource, + linkedUserId, + pageSize, + remote_data, + cursor + ); } catch (error) { throw new Error(error); } diff --git a/packages/api/src/crm/note/services/note.service.ts b/packages/api/src/crm/note/services/note.service.ts index 7157bd1a9..3e23c8fc6 100644 --- a/packages/api/src/crm/note/services/note.service.ts +++ b/packages/api/src/crm/note/services/note.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedNoteInput, UnifiedNoteOutput } from '../types/model.unified'; import { desunify } from '@@core/utils/unification/desunify'; @@ -333,16 +333,50 @@ export class NoteService { async getNotes( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedNoteOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const notes = await this.prisma.crm_notes.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_notes.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_note: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let notes = await this.prisma.crm_notes.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_note: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (notes.length === (pageSize + 1)) { + next_cursor = Buffer.from(notes[notes.length - 1].id_crm_note).toString('base64'); + notes.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedNotes: UnifiedNoteOutput[] = await Promise.all( notes.map(async (note) => { // Fetch field mappings for the ticket @@ -413,7 +447,11 @@ export class NoteService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/note/sync/sync.service.ts b/packages/api/src/crm/note/sync/sync.service.ts index 9ce345aa0..73ce8de09 100644 --- a/packages/api/src/crm/note/sync/sync.service.ts +++ b/packages/api/src/crm/note/sync/sync.service.ts @@ -260,7 +260,7 @@ export class SyncService implements OnModuleInit { this.logger.log('note not exists'); let data: any = { id_crm_note: uuidv4(), - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/crm/stage/services/stage.service.ts b/packages/api/src/crm/stage/services/stage.service.ts index a7d4d4c48..f34d839cb 100644 --- a/packages/api/src/crm/stage/services/stage.service.ts +++ b/packages/api/src/crm/stage/services/stage.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedStageOutput } from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; @@ -82,16 +82,50 @@ export class StageService { async getStages( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedStageOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const stages = await this.prisma.crm_deals_stages.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_deals_stages.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_deals_stage: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let stages = await this.prisma.crm_deals_stages.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_deals_stage: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (stages.length === (pageSize + 1)) { + next_cursor = Buffer.from(stages[stages.length - 1].id_crm_deals_stage).toString('base64'); + stages.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedStages: UnifiedStageOutput[] = await Promise.all( stages.map(async (stage) => { // Fetch field mappings for the ticket @@ -158,7 +192,11 @@ export class StageService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/stage/stage.controller.ts b/packages/api/src/crm/stage/stage.controller.ts index 47bfdb12a..c75945ef8 100644 --- a/packages/api/src/crm/stage/stage.controller.ts +++ b/packages/api/src/crm/stage/stage.controller.ts @@ -5,6 +5,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; @@ -14,12 +16,15 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { StageService } from './services/stage.service'; import { UnifiedStageOutput } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('crm/stages') @Controller('crm/stages') export class StageController { @@ -42,28 +47,26 @@ export class StageController { 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 Crm software.', - }) @ApiCustomResponse(UnifiedStageOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getStages( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.stageService.getStages( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/crm/stage/sync/sync.service.ts b/packages/api/src/crm/stage/sync/sync.service.ts index 1c3974ed1..a427405f1 100644 --- a/packages/api/src/crm/stage/sync/sync.service.ts +++ b/packages/api/src/crm/stage/sync/sync.service.ts @@ -290,7 +290,7 @@ export class SyncService implements OnModuleInit { this.logger.log('stage not exists'); let data: any = { id_crm_deals_stage: uuidv4(), - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId || '', diff --git a/packages/api/src/crm/task/services/task.service.ts b/packages/api/src/crm/task/services/task.service.ts index b0d8c3b71..a28c05923 100644 --- a/packages/api/src/crm/task/services/task.service.ts +++ b/packages/api/src/crm/task/services/task.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedTaskInput, UnifiedTaskOutput } from '../types/model.unified'; import { desunify } from '@@core/utils/unification/desunify'; @@ -341,16 +341,50 @@ export class TaskService { async getTasks( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedTaskOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const tasks = await this.prisma.crm_tasks.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_tasks.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_task: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let tasks = await this.prisma.crm_tasks.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_task: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (tasks.length === (pageSize + 1)) { + next_cursor = Buffer.from(tasks[tasks.length - 1].id_crm_task).toString('base64'); + tasks.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedTasks: UnifiedTaskOutput[] = await Promise.all( tasks.map(async (task) => { // Fetch field mappings for the ticket @@ -423,7 +457,11 @@ export class TaskService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/task/sync/sync.service.ts b/packages/api/src/crm/task/sync/sync.service.ts index e33555cf5..01d11fea9 100644 --- a/packages/api/src/crm/task/sync/sync.service.ts +++ b/packages/api/src/crm/task/sync/sync.service.ts @@ -269,7 +269,7 @@ export class SyncService implements OnModuleInit { this.logger.log('task not exists'); let data: any = { id_crm_task: uuidv4(), - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/crm/task/task.controller.ts b/packages/api/src/crm/task/task.controller.ts index 52e20f9ce..2be474389 100644 --- a/packages/api/src/crm/task/task.controller.ts +++ b/packages/api/src/crm/task/task.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -17,13 +19,16 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { TaskService } from './services/task.service'; import { UnifiedTaskInput, UnifiedTaskOutput } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('crm/tasks') @Controller('crm/tasks') export class TaskController { @@ -46,25 +51,28 @@ export class TaskController { 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 Crm software.', - }) @ApiCustomResponse(UnifiedTaskOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getTasks( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); - return this.taskService.getTasks(remoteSource, linkedUserId, remote_data); + const { remote_data, pageSize, cursor } = query; + + return this.taskService.getTasks( + remoteSource, + linkedUserId, + pageSize, + remote_data, + cursor + ); } catch (error) { throw new Error(error); } diff --git a/packages/api/src/crm/user/services/user.service.ts b/packages/api/src/crm/user/services/user.service.ts index 93b1fb52f..38f47183a 100644 --- a/packages/api/src/crm/user/services/user.service.ts +++ b/packages/api/src/crm/user/services/user.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedUserOutput } from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; @@ -89,16 +89,51 @@ export class UserService { async getUsers( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedUserOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const users = await this.prisma.crm_users.findMany({ + + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.crm_users.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_crm_user: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let users = await this.prisma.crm_users.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_crm_user: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (users.length === (pageSize + 1)) { + next_cursor = Buffer.from(users[users.length - 1].id_crm_user).toString('base64'); + users.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedUsers: UnifiedUserOutput[] = await Promise.all( users.map(async (user) => { // Fetch field mappings for the ticket @@ -166,7 +201,11 @@ export class UserService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/crm/user/sync/sync.service.ts b/packages/api/src/crm/user/sync/sync.service.ts index f5832a08c..a4917c417 100644 --- a/packages/api/src/crm/user/sync/sync.service.ts +++ b/packages/api/src/crm/user/sync/sync.service.ts @@ -252,7 +252,7 @@ export class SyncService implements OnModuleInit { this.logger.log('user not exists'); let data: any = { id_crm_user: uuidv4(), - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/crm/user/user.controller.ts b/packages/api/src/crm/user/user.controller.ts index cd21c6cfe..e23c60371 100644 --- a/packages/api/src/crm/user/user.controller.ts +++ b/packages/api/src/crm/user/user.controller.ts @@ -5,6 +5,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -13,13 +15,16 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { UserService } from './services/user.service'; import { UnifiedUserOutput } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('crm/users') @Controller('crm/users') export class UserController { @@ -42,25 +47,27 @@ export class UserController { 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 Crm software.', - }) @ApiCustomResponse(UnifiedUserOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getUsers( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); - return this.userService.getUsers(remoteSource, linkedUserId, remote_data); + const { remote_data, pageSize, cursor } = query; + return this.userService.getUsers( + remoteSource, + linkedUserId, + pageSize, + remote_data, + cursor + ); } catch (error) { throw new Error(error); } diff --git a/packages/api/src/main.ts b/packages/api/src/main.ts index 61b1ccaf2..2decb23af 100644 --- a/packages/api/src/main.ts +++ b/packages/api/src/main.ts @@ -13,6 +13,10 @@ async function bootstrap() { .setTitle('Unified Panora API') .setDescription('The Panora API description') .setVersion('1.0') + .addBearerAuth( + { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, + 'JWT' + ) .build(); const document = SwaggerModule.createDocument(app, config); diff --git a/packages/api/src/ticketing/@lib/@utils/index.ts b/packages/api/src/ticketing/@lib/@utils/index.ts index 307bb5912..625e4f968 100644 --- a/packages/api/src/ticketing/@lib/@utils/index.ts +++ b/packages/api/src/ticketing/@lib/@utils/index.ts @@ -37,7 +37,8 @@ export class Utils { id_tcg_user: uuid, }, }); - if (!res) throw new Error(`tcg_user not found for uuid ${uuid}`); + // if (!res) throw new Error(`tcg_user not found for uuid ${uuid}`); + if (!res) return; return res.remote_id; } catch (error) { throw new Error(error); @@ -66,7 +67,8 @@ export class Utils { id_tcg_contact: uuid, }, }); - if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`); + // if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`); + if (!res) return; return res.remote_id; } catch (error) { throw new Error(error); @@ -97,7 +99,8 @@ export class Utils { id_tcg_user: uuid, }, }); - if (!res) throw new Error(`tcg_user not found for uuid ${uuid}`); + // if (!res) throw new Error(`tcg_user not found for uuid ${uuid}`); + if (!res) return; return res.email_address; } catch (error) { throw new Error(error); @@ -158,7 +161,8 @@ export class Utils { id_tcg_ticket: uuid, }, }); - if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`); + // if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`); + if (!res) return; return res.remote_id; } catch (error) { throw new Error(error); diff --git a/packages/api/src/ticketing/account/account.controller.ts b/packages/api/src/ticketing/account/account.controller.ts index 2bfa586a1..f7ce70933 100644 --- a/packages/api/src/ticketing/account/account.controller.ts +++ b/packages/api/src/ticketing/account/account.controller.ts @@ -5,6 +5,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -13,13 +15,16 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { AccountService } from './services/account.service'; import { ConnectionUtils } from '@@core/connections/@utils'; import { UnifiedAccountOutput } from './types/model.unified'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/accounts') @Controller('ticketing/accounts') export class AccountController { @@ -42,29 +47,26 @@ export class AccountController { 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(UnifiedAccountOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getAccounts( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.accountService.getAccounts( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/ticketing/account/services/account.service.ts b/packages/api/src/ticketing/account/services/account.service.ts index 529a89239..b9cedab96 100644 --- a/packages/api/src/ticketing/account/services/account.service.ts +++ b/packages/api/src/ticketing/account/services/account.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { UnifiedAccountOutput } from '../types/model.unified'; @Injectable() @@ -79,18 +79,52 @@ export class AccountService { async getAccounts( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedAccountOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const accounts = await this.prisma.tcg_accounts.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_accounts.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_account: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let accounts = await this.prisma.tcg_accounts.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_account: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (accounts.length === (pageSize + 1)) { + next_cursor = Buffer.from(accounts[accounts.length - 1].id_tcg_account).toString('base64'); + accounts.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedAccounts: UnifiedAccountOutput[] = await Promise.all( accounts.map(async (account) => { // Fetch field mappings for the account @@ -157,7 +191,11 @@ export class AccountService { id_linked_user: linkedUserId, }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/account/sync/sync.service.ts b/packages/api/src/ticketing/account/sync/sync.service.ts index ca6ac5e12..d97246851 100644 --- a/packages/api/src/ticketing/account/sync/sync.service.ts +++ b/packages/api/src/ticketing/account/sync/sync.service.ts @@ -245,7 +245,7 @@ export class SyncService implements OnModuleInit { id_tcg_account: uuidv4(), name: account.name, domains: account.domains, - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/ticketing/attachment/attachment.controller.ts b/packages/api/src/ticketing/attachment/attachment.controller.ts index 0d3d08c07..b92e3bb62 100644 --- a/packages/api/src/ticketing/attachment/attachment.controller.ts +++ b/packages/api/src/ticketing/attachment/attachment.controller.ts @@ -7,6 +7,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -16,6 +18,7 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { AttachmentService } from './services/attachment.service'; @@ -25,7 +28,9 @@ import { } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/attachments') @Controller('ticketing/attachments') export class AttachmentController { @@ -48,29 +53,27 @@ export class AttachmentController { 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(UnifiedAttachmentOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getAttachments( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; + return this.attachmentService.getAttachments( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/ticketing/attachment/services/attachment.service.ts b/packages/api/src/ticketing/attachment/services/attachment.service.ts index ea1649abf..8736e2fbc 100644 --- a/packages/api/src/ticketing/attachment/services/attachment.service.ts +++ b/packages/api/src/ticketing/attachment/services/attachment.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedAttachmentInput, @@ -205,17 +205,50 @@ export class AttachmentService { async getAttachments( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedAttachmentOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const attachments = await this.prisma.tcg_attachments.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_attachments.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_attachment: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + let attachments = await this.prisma.tcg_attachments.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_attachment: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (attachments.length === (pageSize + 1)) { + next_cursor = Buffer.from(attachments[attachments.length - 1].id_tcg_attachment).toString('base64'); + attachments.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedAttachments: UnifiedAttachmentOutput[] = await Promise.all( attachments.map(async (attachment) => { // Fetch field mappings for the attachment @@ -285,7 +318,11 @@ export class AttachmentService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/collection/collection.controller.ts b/packages/api/src/ticketing/collection/collection.controller.ts index f895182ce..4ff55e22c 100644 --- a/packages/api/src/ticketing/collection/collection.controller.ts +++ b/packages/api/src/ticketing/collection/collection.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -17,6 +19,7 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { CollectionService } from './services/collection.service'; @@ -26,7 +29,9 @@ import { } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/collections') @Controller('ticketing/collections') export class CollectionController { @@ -49,29 +54,26 @@ export class CollectionController { 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() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getCollections( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.collectionService.getCollections( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/ticketing/collection/services/collection.service.ts b/packages/api/src/ticketing/collection/services/collection.service.ts index 58d23f808..243ec0f97 100644 --- a/packages/api/src/ticketing/collection/services/collection.service.ts +++ b/packages/api/src/ticketing/collection/services/collection.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedCollectionOutput } from '../types/model.unified'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; @@ -65,17 +65,51 @@ export class CollectionService { async getCollections( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedCollectionOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - console.log('In collection service : ', integrationId); - const collections = await this.prisma.tcg_collections.findMany({ + + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_collections.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_collection: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let collections = await this.prisma.tcg_collections.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_collection: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (collections.length === (pageSize + 1)) { + next_cursor = Buffer.from(collections[collections.length - 1].id_tcg_collection).toString('base64'); + collections.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedCollections: UnifiedCollectionOutput[] = await Promise.all( collections.map(async (collection) => { return { @@ -118,7 +152,11 @@ export class CollectionService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/collection/sync/sync.service.ts b/packages/api/src/ticketing/collection/sync/sync.service.ts index 84b70ddb8..c591cfd25 100644 --- a/packages/api/src/ticketing/collection/sync/sync.service.ts +++ b/packages/api/src/ticketing/collection/sync/sync.service.ts @@ -237,7 +237,7 @@ export class SyncService implements OnModuleInit { name: collection.name, description: collection.description, collection_type: collection.collection_type, - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/ticketing/comment/comment.controller.ts b/packages/api/src/ticketing/comment/comment.controller.ts index 3127d2ba1..cc8e0b760 100644 --- a/packages/api/src/ticketing/comment/comment.controller.ts +++ b/packages/api/src/ticketing/comment/comment.controller.ts @@ -7,6 +7,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -16,6 +18,7 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { CommentService } from './services/comment.service'; import { @@ -25,7 +28,9 @@ import { import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiCustomResponse } from '@@core/utils/types'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/comments') @Controller('ticketing/comments') export class CommentController { @@ -48,29 +53,26 @@ export class CommentController { 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(UnifiedCommentOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getComments( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.commentService.getComments( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/ticketing/comment/services/comment.service.ts b/packages/api/src/ticketing/comment/services/comment.service.ts index cd7d41984..fd34b2f5e 100644 --- a/packages/api/src/ticketing/comment/services/comment.service.ts +++ b/packages/api/src/ticketing/comment/services/comment.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedCommentInput, @@ -176,13 +176,13 @@ export class CommentService { const opts = target_comment.creator_type === 'contact' ? { - id_tcg_contact: unifiedCommentData.contact_id, - } + id_tcg_contact: unifiedCommentData.contact_id, + } : target_comment.creator_type === 'user' - ? { + ? { id_tcg_user: unifiedCommentData.user_id, } - : {}; //case where nothing is passed for creator or a not authorized value; + : {}; //case where nothing is passed for creator or a not authorized value; if (existingComment) { // Update the existing comment @@ -373,16 +373,50 @@ export class CommentService { async getComments( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedCommentOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { - const comments = await this.prisma.tcg_comments.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_comments.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_comment: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let comments = await this.prisma.tcg_comments.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_comment: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (comments.length === (pageSize + 1)) { + next_cursor = Buffer.from(comments[comments.length - 1].id_tcg_comment).toString('base64'); + comments.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedComments: UnifiedCommentOutput[] = await Promise.all( comments.map(async (comment) => { //WE SHOULDNT HAVE FIELD MAPPINGS FOR COMMENT @@ -455,7 +489,11 @@ export class CommentService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/comment/sync/sync.service.ts b/packages/api/src/ticketing/comment/sync/sync.service.ts index 98f13c20a..a1ed3423c 100644 --- a/packages/api/src/ticketing/comment/sync/sync.service.ts +++ b/packages/api/src/ticketing/comment/sync/sync.service.ts @@ -350,7 +350,7 @@ export class SyncService implements OnModuleInit { file_name: attchmt.file_name, file_url: attchmt.file_url, id_tcg_comment: unique_ticketing_comment_id, - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), uploader: linkedUserId, //TODO id_tcg_ticket: id_ticket, diff --git a/packages/api/src/ticketing/contact/contact.controller.ts b/packages/api/src/ticketing/contact/contact.controller.ts index 7de05fe31..a6ff6d586 100644 --- a/packages/api/src/ticketing/contact/contact.controller.ts +++ b/packages/api/src/ticketing/contact/contact.controller.ts @@ -5,6 +5,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -13,13 +15,16 @@ import { ApiQuery, ApiHeader, ApiTags, + ApiBearerAuth, } from '@nestjs/swagger'; import { ContactService } from './services/contact.service'; import { ConnectionUtils } from '@@core/connections/@utils'; import { UnifiedContactOutput } from './types/model.unified'; import { ApiCustomResponse } from '@@core/utils/types'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/contacts') @Controller('ticketing/contacts') export class ContactController { @@ -42,29 +47,26 @@ export class ContactController { 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(UnifiedContactOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getContacts( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.contactService.getContacts( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/ticketing/contact/services/contact.service.ts b/packages/api/src/ticketing/contact/services/contact.service.ts index 345bd59ab..907b3d415 100644 --- a/packages/api/src/ticketing/contact/services/contact.service.ts +++ b/packages/api/src/ticketing/contact/services/contact.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { UnifiedContactOutput } from '../types/model.unified'; @Injectable() @@ -81,17 +81,51 @@ export class ContactService { async getContacts( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedContactOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const contacts = await this.prisma.tcg_contacts.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_contacts.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_contact: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let contacts = await this.prisma.tcg_contacts.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_contact: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (contacts.length === (pageSize + 1)) { + next_cursor = Buffer.from(contacts[contacts.length - 1].id_tcg_contact).toString('base64'); + contacts.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedContacts: UnifiedContactOutput[] = await Promise.all( contacts.map(async (contact) => { // Fetch field mappings for the contact @@ -161,7 +195,11 @@ export class ContactService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/contact/sync/sync.service.ts b/packages/api/src/ticketing/contact/sync/sync.service.ts index 937cb48bf..c2a9fbe71 100644 --- a/packages/api/src/ticketing/contact/sync/sync.service.ts +++ b/packages/api/src/ticketing/contact/sync/sync.service.ts @@ -278,7 +278,7 @@ export class SyncService implements OnModuleInit { email_address: contact.email_address, phone_number: contact.phone_number, details: contact.details, - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/ticketing/tag/services/tag.service.ts b/packages/api/src/ticketing/tag/services/tag.service.ts index e30eea4e6..70af5063d 100644 --- a/packages/api/src/ticketing/tag/services/tag.service.ts +++ b/packages/api/src/ticketing/tag/services/tag.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { UnifiedTagOutput } from '../types/model.unified'; @Injectable() @@ -78,17 +78,52 @@ export class TagService { async getTags( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedTagOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const tags = await this.prisma.tcg_tags.findMany({ + + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_tags.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_tag: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let tags = await this.prisma.tcg_tags.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_tag: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (tags.length === (pageSize + 1)) { + next_cursor = Buffer.from(tags[tags.length - 1].id_tcg_tag).toString('base64'); + tags.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedTags: UnifiedTagOutput[] = await Promise.all( tags.map(async (tag) => { // Fetch field mappings for the tag @@ -155,7 +190,11 @@ export class TagService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/tag/sync/sync.service.ts b/packages/api/src/ticketing/tag/sync/sync.service.ts index be264f495..5fc9bc83a 100644 --- a/packages/api/src/ticketing/tag/sync/sync.service.ts +++ b/packages/api/src/ticketing/tag/sync/sync.service.ts @@ -263,7 +263,7 @@ export class SyncService implements OnModuleInit { const data = { id_tcg_tag: uuidv4(), name: tag.name, - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_tcg_ticket: id_ticket, id_linked_user: linkedUserId, diff --git a/packages/api/src/ticketing/tag/tag.controller.ts b/packages/api/src/ticketing/tag/tag.controller.ts index 30a84d857..6e133146c 100644 --- a/packages/api/src/ticketing/tag/tag.controller.ts +++ b/packages/api/src/ticketing/tag/tag.controller.ts @@ -5,6 +5,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -13,13 +15,16 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { TagService } from './services/tag.service'; import { ConnectionUtils } from '@@core/connections/@utils'; import { UnifiedTagOutput } from './types/model.unified'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/tags') @Controller('ticketing/tags') export class TagController { @@ -42,26 +47,27 @@ export class TagController { 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(UnifiedTagOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getTags( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); - return this.tagService.getTags(remoteSource, linkedUserId, remote_data); + const { remote_data, pageSize, cursor } = query; + return this.tagService.getTags( + remoteSource, + linkedUserId, + pageSize, + remote_data, + cursor + ); } catch (error) { throw new Error(error); } diff --git a/packages/api/src/ticketing/team/services/team.service.ts b/packages/api/src/ticketing/team/services/team.service.ts index ee1112b38..faefa11a5 100644 --- a/packages/api/src/ticketing/team/services/team.service.ts +++ b/packages/api/src/ticketing/team/services/team.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { UnifiedTeamOutput } from '../types/model.unified'; @Injectable() @@ -79,18 +79,52 @@ export class TeamService { async getTeams( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedTeamOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const teams = await this.prisma.tcg_teams.findMany({ + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_teams.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_team: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let teams = await this.prisma.tcg_teams.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_team: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (teams.length === (pageSize + 1)) { + next_cursor = Buffer.from(teams[teams.length - 1].id_tcg_team).toString('base64'); + teams.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedTeams: UnifiedTeamOutput[] = await Promise.all( teams.map(async (team) => { // Fetch field mappings for the team @@ -158,7 +192,11 @@ export class TeamService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/team/sync/sync.service.ts b/packages/api/src/ticketing/team/sync/sync.service.ts index 024da3505..cc82edac2 100644 --- a/packages/api/src/ticketing/team/sync/sync.service.ts +++ b/packages/api/src/ticketing/team/sync/sync.service.ts @@ -247,7 +247,7 @@ export class SyncService implements OnModuleInit { id_tcg_team: uuidv4(), name: team.name, description: team.description, - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/ticketing/team/team.controller.ts b/packages/api/src/ticketing/team/team.controller.ts index 5131451ac..0d2eb2626 100644 --- a/packages/api/src/ticketing/team/team.controller.ts +++ b/packages/api/src/ticketing/team/team.controller.ts @@ -5,6 +5,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -13,13 +15,16 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { TeamService } from './services/team.service'; import { ConnectionUtils } from '@@core/connections/@utils'; import { UnifiedTeamOutput } from './types/model.unified'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/teams') @Controller('ticketing/teams') export class TeamController { @@ -42,26 +47,27 @@ export class TeamController { 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(UnifiedTeamOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getTeams( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); - return this.teamService.getTeams(remoteSource, linkedUserId, remote_data); + const { remote_data, pageSize, cursor } = query; + return this.teamService.getTeams( + remoteSource, + linkedUserId, + pageSize, + remote_data, + cursor + ); } catch (error) { throw new Error(error); } diff --git a/packages/api/src/ticketing/ticket/services/ticket.service.ts b/packages/api/src/ticketing/ticket/services/ticket.service.ts index 459476de7..9c90f862e 100644 --- a/packages/api/src/ticketing/ticket/services/ticket.service.ts +++ b/packages/api/src/ticketing/ticket/services/ticket.service.ts @@ -3,7 +3,7 @@ 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 { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { WebhookService } from '@@core/webhook/webhook.service'; import { UnifiedTicketInput, @@ -412,11 +412,37 @@ export class TicketService { async getTickets( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedTicketOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const tickets = await this.prisma.tcg_tickets.findMany({ + + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_tickets.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_ticket: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let tickets = await this.prisma.tcg_tickets.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_ticket: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, @@ -427,6 +453,15 @@ export class TicketService { },*/ }); + if (tickets.length === (pageSize + 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: UnifiedTicketOutput[] = await Promise.all( tickets.map(async (ticket) => { // Fetch field mappings for the ticket @@ -506,7 +541,11 @@ export class TicketService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/ticket/sync/sync.service.ts b/packages/api/src/ticketing/ticket/sync/sync.service.ts index 815e914c7..d63dd428f 100644 --- a/packages/api/src/ticketing/ticket/sync/sync.service.ts +++ b/packages/api/src/ticketing/ticket/sync/sync.service.ts @@ -270,7 +270,7 @@ export class SyncService implements OnModuleInit { let data: any = { id_tcg_ticket: uuidv4(), - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, remote_id: originId, diff --git a/packages/api/src/ticketing/ticket/ticket.controller.ts b/packages/api/src/ticketing/ticket/ticket.controller.ts index aff73ebcb..a47199c9c 100644 --- a/packages/api/src/ticketing/ticket/ticket.controller.ts +++ b/packages/api/src/ticketing/ticket/ticket.controller.ts @@ -8,6 +8,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -17,13 +19,16 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { TicketService } from './services/ticket.service'; import { UnifiedTicketInput, UnifiedTicketOutput } from './types/model.unified'; import { ConnectionUtils } from '@@core/connections/@utils'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/tickets') @Controller('ticketing/tickets') export class TicketController { @@ -46,29 +51,26 @@ export class TicketController { 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(UnifiedTicketOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getTickets( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); + const { remote_data, pageSize, cursor } = query; return this.ticketService.getTickets( remoteSource, linkedUserId, + pageSize, remote_data, + cursor ); } catch (error) { throw new Error(error); diff --git a/packages/api/src/ticketing/user/services/user.service.ts b/packages/api/src/ticketing/user/services/user.service.ts index 329643462..8a4493c93 100644 --- a/packages/api/src/ticketing/user/services/user.service.ts +++ b/packages/api/src/ticketing/user/services/user.service.ts @@ -2,7 +2,7 @@ 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 { handleServiceError } from '@@core/utils/errors'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { UnifiedUserOutput } from '../types/model.unified'; @Injectable() @@ -80,17 +80,52 @@ export class UserService { async getUsers( integrationId: string, linkedUserId: string, + pageSize: number, remote_data?: boolean, - ): Promise { + cursor?: string + ): Promise<{ data: UnifiedUserOutput[], prev_cursor: null | string, next_cursor: null | string }> { try { //TODO: handle case where data is not there (not synced) or old synced - const users = await this.prisma.tcg_users.findMany({ + + let prev_cursor = null; + let next_cursor = null; + + if (cursor) { + const isCursorPresent = await this.prisma.tcg_users.findFirst({ + where: { + remote_platform: integrationId.toLowerCase(), + id_linked_user: linkedUserId, + id_tcg_user: cursor + } + }); + if (!isCursorPresent) { + throw new NotFoundError(`The provided cursor does not exist!`); + } + } + + let users = await this.prisma.tcg_users.findMany({ + take: pageSize + 1, + cursor: cursor ? { + id_tcg_user: cursor + } : undefined, + orderBy: { + created_at: 'asc' + }, where: { remote_platform: integrationId.toLowerCase(), id_linked_user: linkedUserId, }, }); + if (users.length === (pageSize + 1)) { + next_cursor = Buffer.from(users[users.length - 1].id_tcg_user).toString('base64'); + users.pop(); + } + + if (cursor) { + prev_cursor = Buffer.from(cursor).toString('base64'); + } + const unifiedUsers: UnifiedUserOutput[] = await Promise.all( users.map(async (user) => { // Fetch field mappings for the user @@ -160,7 +195,11 @@ export class UserService { }, }); - return res; + return { + data: res, + prev_cursor, + next_cursor + }; } catch (error) { handleServiceError(error, this.logger); } diff --git a/packages/api/src/ticketing/user/sync/sync.service.ts b/packages/api/src/ticketing/user/sync/sync.service.ts index 06129515d..c017a83ba 100644 --- a/packages/api/src/ticketing/user/sync/sync.service.ts +++ b/packages/api/src/ticketing/user/sync/sync.service.ts @@ -251,7 +251,7 @@ export class SyncService implements OnModuleInit { name: user.name, email_address: user.email_address, teams: user.teams || [], - created_at: new Date(), + // created_at: new Date(), modified_at: new Date(), id_linked_user: linkedUserId, // id_tcg_account: user.account_id || '', diff --git a/packages/api/src/ticketing/user/user.controller.ts b/packages/api/src/ticketing/user/user.controller.ts index ea4c4132c..089b28dd9 100644 --- a/packages/api/src/ticketing/user/user.controller.ts +++ b/packages/api/src/ticketing/user/user.controller.ts @@ -5,6 +5,8 @@ import { Param, Headers, UseGuards, + UsePipes, + ValidationPipe, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { @@ -13,13 +15,16 @@ import { ApiQuery, ApiTags, ApiHeader, + ApiBearerAuth, } from '@nestjs/swagger'; import { ApiCustomResponse } from '@@core/utils/types'; import { UserService } from './services/user.service'; import { ConnectionUtils } from '@@core/connections/@utils'; import { UnifiedUserOutput } from './types/model.unified'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; +import { FetchObjectsQueryDto } from '@@core/utils/dtos/fetch-objects-query.dto'; +@ApiBearerAuth('JWT') @ApiTags('ticketing/users') @Controller('ticketing/users') export class UserController { @@ -42,26 +47,27 @@ export class UserController { 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(UnifiedUserOutput) @UseGuards(ApiKeyAuthGuard) @Get() + @UsePipes(new ValidationPipe({ transform: true, disableErrorMessages: true })) async getUsers( @Headers('x-connection-token') connection_token: string, - @Query('remote_data') remote_data?: boolean, + @Query() query: FetchObjectsQueryDto, ) { try { const { linkedUserId, remoteSource } = await this.connectionUtils.getConnectionMetadataFromConnectionToken( connection_token, ); - return this.userService.getUsers(remoteSource, linkedUserId, remote_data); + const { remote_data, pageSize, cursor } = query; + return this.userService.getUsers( + remoteSource, + linkedUserId, + pageSize, + remote_data, + cursor + ); } catch (error) { throw new Error(error); } diff --git a/packages/api/swagger/swagger-spec.json b/packages/api/swagger/swagger-spec.json index 50d72e3cd..5e42f2956 100644 --- a/packages/api/swagger/swagger-spec.json +++ b/packages/api/swagger/swagger-spec.json @@ -617,10 +617,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -648,6 +667,11 @@ }, "tags": [ "crm/companies" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -719,6 +743,11 @@ }, "tags": [ "crm/companies" + ], + "security": [ + { + "JWT": [] + } ] }, "patch": { @@ -759,6 +788,11 @@ }, "tags": [ "crm/companies" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -812,6 +846,11 @@ }, "tags": [ "crm/companies" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -890,6 +929,11 @@ }, "tags": [ "crm/companies" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -911,10 +955,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original CRM software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -942,6 +1005,11 @@ }, "tags": [ "crm/contacts" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -1013,6 +1081,11 @@ }, "tags": [ "crm/contacts" + ], + "security": [ + { + "JWT": [] + } ] }, "patch": { @@ -1042,6 +1115,11 @@ }, "tags": [ "crm/contacts" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1095,6 +1173,11 @@ }, "tags": [ "crm/contacts" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1173,6 +1256,11 @@ }, "tags": [ "crm/contacts" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1194,10 +1282,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -1225,6 +1332,11 @@ }, "tags": [ "crm/deals" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -1296,6 +1408,11 @@ }, "tags": [ "crm/deals" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1349,6 +1466,11 @@ }, "tags": [ "crm/deals" + ], + "security": [ + { + "JWT": [] + } ] }, "patch": { @@ -1389,6 +1511,11 @@ }, "tags": [ "crm/deals" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1467,6 +1594,11 @@ }, "tags": [ "crm/deals" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1488,10 +1620,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -1519,6 +1670,11 @@ }, "tags": [ "crm/engagements" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -1590,6 +1746,11 @@ }, "tags": [ "crm/engagements" + ], + "security": [ + { + "JWT": [] + } ] }, "patch": { @@ -1630,6 +1791,11 @@ }, "tags": [ "crm/engagements" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1683,6 +1849,11 @@ }, "tags": [ "crm/engagements" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1761,6 +1932,11 @@ }, "tags": [ "crm/engagements" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1782,10 +1958,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -1813,6 +2008,11 @@ }, "tags": [ "crm/notes" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -1884,6 +2084,11 @@ }, "tags": [ "crm/notes" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -1937,6 +2142,11 @@ }, "tags": [ "crm/notes" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2015,6 +2225,11 @@ }, "tags": [ "crm/notes" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2036,10 +2251,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -2067,6 +2301,11 @@ }, "tags": [ "crm/stages" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2120,6 +2359,11 @@ }, "tags": [ "crm/stages" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2141,10 +2385,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -2172,6 +2435,11 @@ }, "tags": [ "crm/tasks" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -2243,6 +2511,11 @@ }, "tags": [ "crm/tasks" + ], + "security": [ + { + "JWT": [] + } ] }, "patch": { @@ -2283,6 +2556,11 @@ }, "tags": [ "crm/tasks" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2336,6 +2614,11 @@ }, "tags": [ "crm/tasks" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2414,6 +2697,11 @@ }, "tags": [ "crm/tasks" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2435,10 +2723,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -2466,6 +2773,11 @@ }, "tags": [ "crm/users" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2519,6 +2831,11 @@ }, "tags": [ "crm/users" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2540,10 +2857,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -2571,6 +2907,11 @@ }, "tags": [ "ticketing/accounts" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2624,6 +2965,11 @@ }, "tags": [ "ticketing/accounts" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2645,10 +2991,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -2676,6 +3041,11 @@ }, "tags": [ "ticketing/collections" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2729,6 +3099,11 @@ }, "tags": [ "ticketing/collections" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2750,10 +3125,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -2781,6 +3175,11 @@ }, "tags": [ "ticketing/comments" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -2852,6 +3251,11 @@ }, "tags": [ "ticketing/comments" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2905,6 +3309,11 @@ }, "tags": [ "ticketing/comments" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -2983,6 +3392,11 @@ }, "tags": [ "ticketing/comments" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3004,10 +3418,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -3035,6 +3468,11 @@ }, "tags": [ "ticketing/contacts" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3088,6 +3526,11 @@ }, "tags": [ "ticketing/contacts" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3109,10 +3552,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -3140,6 +3602,11 @@ }, "tags": [ "ticketing/tags" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3193,6 +3660,11 @@ }, "tags": [ "ticketing/tags" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3214,10 +3686,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -3245,6 +3736,11 @@ }, "tags": [ "ticketing/teams" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3298,6 +3794,11 @@ }, "tags": [ "ticketing/teams" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3319,10 +3820,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -3350,6 +3870,11 @@ }, "tags": [ "ticketing/tickets" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -3421,6 +3946,11 @@ }, "tags": [ "ticketing/tickets" + ], + "security": [ + { + "JWT": [] + } ] }, "patch": { @@ -3450,6 +3980,11 @@ }, "tags": [ "ticketing/tickets" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3503,6 +4038,11 @@ }, "tags": [ "ticketing/tickets" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3581,6 +4121,11 @@ }, "tags": [ "ticketing/tickets" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3602,10 +4147,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -3633,6 +4197,11 @@ }, "tags": [ "ticketing/users" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -3686,6 +4255,11 @@ }, "tags": [ "ticketing/users" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -4532,10 +5106,29 @@ "name": "remote_data", "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", + "description": "Set to true to include data from the original software.", "schema": { "type": "boolean" } + }, + { + "name": "pageSize", + "required": false, + "in": "query", + "description": "Set to get the number of records.", + "schema": { + "default": 50, + "type": "number" + } + }, + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Set to get the number of records after this cursor.", + "schema": { + "type": "string" + } } ], "responses": { @@ -4563,6 +5156,11 @@ }, "tags": [ "ticketing/attachments" + ], + "security": [ + { + "JWT": [] + } ] }, "post": { @@ -4634,6 +5232,11 @@ }, "tags": [ "ticketing/attachments" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -4687,6 +5290,11 @@ }, "tags": [ "ticketing/attachments" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -4740,6 +5348,11 @@ }, "tags": [ "ticketing/attachments" + ], + "security": [ + { + "JWT": [] + } ] } }, @@ -4818,6 +5431,11 @@ }, "tags": [ "ticketing/attachments" + ], + "security": [ + { + "JWT": [] + } ] } } @@ -4831,6 +5449,13 @@ "tags": [], "servers": [], "components": { + "securitySchemes": { + "JWT": { + "scheme": "bearer", + "bearerFormat": "JWT", + "type": "http" + } + }, "schemas": { "CreateUserDto": { "type": "object",