From e2ffec2c3c56fe386defdd8b1ec16be52de15002 Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 29 Apr 2024 19:43:32 +0200 Subject: [PATCH 01/11] :ambulance: Fixes on unified objects :wq --- .env.example | 2 + packages/api/package.json | 5 +- packages/api/src/app.controller.ts | 6 + .../src/crm/company/services/attio/mappers.ts | 13 -- packages/api/src/crm/company/utils/index.ts | 208 ++++++++++++++++++ .../engagement/services/hubspot/mappers.ts | 4 +- .../src/crm/engagement/types/model.unified.ts | 2 +- .../src/crm/task/services/hubspot/mappers.ts | 34 +-- .../api/src/crm/task/types/model.unified.ts | 2 +- packages/api/src/crm/task/utils/index.ts | 20 ++ .../collection/types/model.unified.ts | 3 +- .../comment/services/front/mappers.ts | 2 +- .../comment/services/gorgias/mappers.ts | 2 +- .../comment/services/jira/mappers.ts | 2 +- .../comment/services/zendesk/mappers.ts | 13 +- .../ticketing/comment/types/model.unified.ts | 5 +- .../ticket/services/gorgias/mappers.ts | 4 +- .../ticket/services/hubspot/mappers.ts | 14 +- .../ticket/services/zendesk/mappers.ts | 16 +- .../ticketing/ticket/types/model.unified.ts | 8 +- pnpm-lock.yaml | 62 ++++++ 21 files changed, 340 insertions(+), 87 deletions(-) diff --git a/.env.example b/.env.example index cfdbdc290..b7c839a8e 100644 --- a/.env.example +++ b/.env.example @@ -27,6 +27,8 @@ POSTGRES_USER=my_user POSTGRES_DB=panora_db POSTGRES_HOST=postgres +OPENAI_API_KEY= + # Each Provider is of form PROVIDER_TICKETING_SOFTWAREMODE_ATTRIBUTE # ================================================ # Integration Providers diff --git a/packages/api/package.json b/packages/api/package.json index 00958abad..cdfca4498 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -39,6 +39,7 @@ "@nestjs/swagger": "^7.1.14", "@nestjs/throttler": "^5.1.1", "@ntegral/nestjs-sentry": "^4.0.0", + "@panora/shared": "workspace:*", "@prisma/client": "^5.4.2", "@sentry/node": "^7.80.0", "@sentry/tracing": "^7.80.0", @@ -54,6 +55,7 @@ "install": "^0.13.0", "js-yaml": "^4.1.0", "nestjs-pino": "^3.5.0", + "openai": "^4.38.5", "passport": "^0.6.0", "passport-headerapikey": "^1.2.2", "passport-jwt": "^4.0.1", @@ -63,8 +65,7 @@ "rxjs": "^7.8.1", "stytch": "^10.5.0", "uuid": "^9.0.1", - "yargs": "^17.7.2", - "@panora/shared": "workspace:*" + "yargs": "^17.7.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/packages/api/src/app.controller.ts b/packages/api/src/app.controller.ts index 2c23905e1..6cb31f359 100644 --- a/packages/api/src/app.controller.ts +++ b/packages/api/src/app.controller.ts @@ -3,6 +3,7 @@ import { AppService } from './app.service'; import { LoggerService } from '@@core/logger/logger.service'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { ApiOperation } from '@nestjs/swagger'; +import { mapCompanyIndustryToRemote } from '@crm/company/utils'; @Controller() export class AppController { @@ -25,6 +26,11 @@ export class AppController { return 200; } + @Get('gpt') + async gptTest() { + return await mapCompanyIndustryToRemote('Technology', 'hubspot'); + } + @UseGuards(ApiKeyAuthGuard) @ApiOperation({ operationId: 'getHelloProtected' }) @Get('protected') diff --git a/packages/api/src/crm/company/services/attio/mappers.ts b/packages/api/src/crm/company/services/attio/mappers.ts index b406f28d0..96912c3db 100644 --- a/packages/api/src/crm/company/services/attio/mappers.ts +++ b/packages/api/src/crm/company/services/attio/mappers.ts @@ -35,20 +35,7 @@ export class AttioCompanyMapper implements ICompanyMapper { }, ]; } - // const result: AttioCompanyInput = { - // city: '', - // name: source.name, - // phone: '', - // state: '', - // domain: '', - // industry: source.industry, - // }; - // Assuming 'phone_numbers' array contains at least one phone number - // const primaryPhone = source.phone_numbers?.[0]?.phone_number; - // if (primaryPhone) { - // result.values = primaryPhone; - // } if (source.addresses) { const address = source.addresses[0]; if (address) { diff --git a/packages/api/src/crm/company/utils/index.ts b/packages/api/src/crm/company/utils/index.ts index 4ba67b5ec..d37b6eb36 100644 --- a/packages/api/src/crm/company/utils/index.ts +++ b/packages/api/src/crm/company/utils/index.ts @@ -1,5 +1,11 @@ import { Address } from '@crm/@utils/@types'; import { v4 as uuidv4 } from 'uuid'; +import { OpenAI } from 'openai'; + +// OpenAIApi initialization +const openai = new OpenAI({ + apiKey: process.env['OPENAI_API_KEY'], // This is the default and can be omitted +}); export function normalizeAddresses(addresses: Address[]) { const normalizedAddresses = addresses.map((addy) => ({ @@ -13,3 +19,205 @@ export function normalizeAddresses(addresses: Address[]) { return normalizedAddresses; } + +//These arrays are to maintain the history of the conversation +const conversationContext = []; +const currentMessages = []; + +export async function mapCompanyIndustryToRemote( + unified_industry_value: string, + provider_name: string, +) { + //call gpt 3.5 to associate the closest industry value based on provider api defined value + // for instance hubspot has 150 pre-defined values for industry field, we want gpt to give us the mapping + try { + switch (provider_name.toLowerCase()) { + case 'hubspot': + return await Hubspot_mapCompanyIndustryToRemote(unified_industry_value); + default: + throw new Error('provider not supported for custom industry mapping'); + } + } catch (error) { + throw new Error(error); + } +} + +async function Hubspot_mapCompanyIndustryToRemote( + unified_industry_value: string, +) { + try { + const prompt = `I have a value which defines an industry of a company. + This is someone's input and I'd love to get the closest value of this input from the following list. + Here are the 150 pre-defined values of the list. \n + ACCOUNTING\n + AIRLINES_AVIATION\n + ALTERNATIVE_DISPUTE_RESOLUTION\n + ALTERNATIVE_MEDICINE\n + ANIMATION\n + APPAREL_FASHION\n + ARCHITECTURE_PLANNING\n + ARTS_AND_CRAFTS\n + AUTOMOTIVE\n + AVIATION_AEROSPACE\n + BANKING\n + BIOTECHNOLOGY\n + BROADCAST_MEDIA\n + BUILDING_MATERIALS\n + BUSINESS_SUPPLIES_AND_EQUIPMENT\n + CAPITAL_MARKETS\n + CHEMICALS\n + CIVIC_SOCIAL_ORGANIZATION\n + CIVIL_ENGINEERING\n + COMMERCIAL_REAL_ESTATE\n + COMPUTER_NETWORK_SECURITY\n + COMPUTER_GAMES\n + COMPUTER_HARDWARE\n + COMPUTER_NETWORKING\n + COMPUTER_SOFTWARE\n + INTERNET\n + CONSTRUCTION\n + CONSUMER_ELECTRONICS\n + CONSUMER_GOODS\n + CONSUMER_SERVICES\n + COSMETICS\n + DAIRY\n + DEFENSE_SPACE\n + DESIGN\n + EDUCATION_MANAGEMENT\n + E_LEARNING\n + ELECTRICAL_ELECTRONIC_MANUFACTURING\n + ENTERTAINMENT\n + ENVIRONMENTAL_SERVICES\n + EVENTS_SERVICES\n + EXECUTIVE_OFFICE\n + FACILITIES_SERVICES\n + FARMING\n + FINANCIAL_SERVICES\n + FINE_ART\n + FISHERY\n + FOOD_BEVERAGES\n + FOOD_PRODUCTION\n + FUND_RAISING\n + FURNITURE\n + GAMBLING_CASINOS\n + GLASS_CERAMICS_CONCRETE\n + GOVERNMENT_ADMINISTRATION\n + GOVERNMENT_RELATIONS\n + GRAPHIC_DESIGN\n + HEALTH_WELLNESS_AND_FITNESS\n + HIGHER_EDUCATION\n + HOSPITAL_HEALTH_CARE\n + HOSPITALITY\n + HUMAN_RESOURCES\n + IMPORT_AND_EXPORT\n + INDIVIDUAL_FAMILY_SERVICES\n + INDUSTRIAL_AUTOMATION\n + INFORMATION_SERVICES\n + INFORMATION_TECHNOLOGY_AND_SERVICES\n + INSURANCE\n + INTERNATIONAL_AFFAIRS\n + INTERNATIONAL_TRADE_AND_DEVELOPMENT\n + INVESTMENT_BANKING\n + INVESTMENT_MANAGEMENT\n + JUDICIARY\n + LAW_ENFORCEMENT\n + LAW_PRACTICE\n + LEGAL_SERVICES\n + LEGISLATIVE_OFFICE\n + LEISURE_TRAVEL_TOURISM\n + LIBRARIES\n + LOGISTICS_AND_SUPPLY_CHAIN\n + LUXURY_GOODS_JEWELRY\n + MACHINERY\n + MANAGEMENT_CONSULTING\n + MARITIME\n + MARKET_RESEARCH\n + MARKETING_AND_ADVERTISING\n + MECHANICAL_OR_INDUSTRIAL_ENGINEERING\n + MEDIA_PRODUCTION\n + MEDICAL_DEVICES\n + MEDICAL_PRACTICE\n + MENTAL_HEALTH_CARE\n + MILITARY\n + MINING_METALS\n + MOTION_PICTURES_AND_FILM\n + MUSEUMS_AND_INSTITUTIONS\n + MUSIC\n + NANOTECHNOLOGY\n + NEWSPAPERS\n + NON_PROFIT_ORGANIZATION_MANAGEMENT\n + OIL_ENERGY\n + ONLINE_MEDIA\n + OUTSOURCING_OFFSHORING\n + PACKAGE_FREIGHT_DELIVERY\n + PACKAGING_AND_CONTAINERS\n + PAPER_FOREST_PRODUCTS\n + PERFORMING_ARTS\n + PHARMACEUTICALS\n + PHILANTHROPY\n + PHOTOGRAPHY\n + PLASTICS\n + POLITICAL_ORGANIZATION\n + PRIMARY_SECONDARY_EDUCATION\n + PRINTING\n + PROFESSIONAL_TRAINING_COACHING\n + PROGRAM_DEVELOPMENT\n + PUBLIC_POLICY\n + PUBLIC_RELATIONS_AND_COMMUNICATIONS\n + PUBLIC_SAFETY\n + PUBLISHING\n + RAILROAD_MANUFACTURE\n + RANCHING\n + REAL_ESTATE\n + RECREATIONAL_FACILITIES_AND_SERVICES\n + RELIGIOUS_INSTITUTIONS\n + RENEWABLES_ENVIRONMENT\n + RESEARCH\n + RESTAURANTS\n + RETAIL\n + SECURITY_AND_INVESTIGATIONS\n + SEMICONDUCTORS\n + SHIPBUILDING\n + SPORTING_GOODS\n + SPORTS\n + STAFFING_AND_RECRUITING\n + SUPERMARKETS\n + TELECOMMUNICATIONS\n + TEXTILES\n + THINK_TANKS\n + TOBACCO\n + TRANSLATION_AND_LOCALIZATION\n + TRANSPORTATION_TRUCKING_RAILROAD\n + UTILITIES\n + VENTURE_CAPITAL_PRIVATE_EQUITY\n + VETERINARY\n + WAREHOUSING\n + WHOLESALE\n + WINE_AND_SPIRITS\n + WIRELESS\n + WRITING_AND_EDITING\n + I want you to just return 1 word amongst this huge list which is closest to the input I'm giving you : ${unified_industry_value}\n + I repeat, you MUST answer just the right word, don't do any sentence. + `; + const modelId = 'gpt-3.5-turbo'; + const promptText = `${prompt}\n\nResponse:`; + + // Restore the previous context + for (const [inputText, responseText] of conversationContext) { + currentMessages.push({ role: 'user', content: inputText }); + currentMessages.push({ role: 'assistant', content: responseText }); + } + + // Stores the new message + currentMessages.push({ role: 'user', content: promptText }); + + const result = await openai.chat.completions.create({ + model: modelId, + messages: currentMessages, + }); + + const responseText = result.choices.shift().message.content; + conversationContext.push([promptText, responseText]); + return responseText; + } catch (error) {} +} diff --git a/packages/api/src/crm/engagement/services/hubspot/mappers.ts b/packages/api/src/crm/engagement/services/hubspot/mappers.ts index 2bef68acf..c5044552a 100644 --- a/packages/api/src/crm/engagement/services/hubspot/mappers.ts +++ b/packages/api/src/crm/engagement/services/hubspot/mappers.ts @@ -57,7 +57,7 @@ export class HubspotEngagementMapper implements IEngagementMapper { // Assuming direction is used to determine call status hs_call_status: '', hs_call_duration: '', // Needs appropriate mapping - hs_call_direction: source.direction || '', // Needs appropriate mapping + hs_call_direction: source.direction || '', // Needs appropriate mapping hubspot_owner_id: '', hs_call_to_number: '', // Needs appropriate mapping hs_call_from_number: '', // Needs appropriate mapping @@ -146,7 +146,7 @@ export class HubspotEngagementMapper implements IEngagementMapper { ? 'INCOMING_EMAIL' : source.direction === 'OUTBOUND' ? 'FORWARDED_EMAIL' - : '', + : '', hs_email_to_lastname: '', // Placeholder, needs appropriate mapping hs_email_sender_email: '', // Placeholder, needs appropriate mapping hs_email_to_firstname: '', // Placeholder, needs appropriate mapping diff --git a/packages/api/src/crm/engagement/types/model.unified.ts b/packages/api/src/crm/engagement/types/model.unified.ts index 0008e23b1..8895d5605 100644 --- a/packages/api/src/crm/engagement/types/model.unified.ts +++ b/packages/api/src/crm/engagement/types/model.unified.ts @@ -4,7 +4,7 @@ export class UnifiedEngagementInput { @ApiPropertyOptional({ description: 'The content of the engagement' }) content?: string; - @ApiPropertyOptional({ description: 'The direction of the engagement' }) + @ApiPropertyOptional({ description: 'The direction of the engagement. Authorized values are INBOUND or OUTBOUND' }) direction?: string; @ApiPropertyOptional({ description: 'The subject of the engagement' }) diff --git a/packages/api/src/crm/task/services/hubspot/mappers.ts b/packages/api/src/crm/task/services/hubspot/mappers.ts index c8b22923d..dbc32f171 100644 --- a/packages/api/src/crm/task/services/hubspot/mappers.ts +++ b/packages/api/src/crm/task/services/hubspot/mappers.ts @@ -23,7 +23,7 @@ export class HubspotTaskMapper implements ITaskMapper { const result: HubspotTaskInput = { hs_task_subject: source.subject || '', hs_task_body: source.content || '', - hs_task_status: this.mapStatus(source.status), + hs_task_status: source.status, hs_task_priority: '', hs_timestamp: source.due_date ? source.due_date.toISOString() @@ -97,7 +97,7 @@ export class HubspotTaskMapper implements ITaskMapper { return { subject: task.properties.hs_task_subject, content: task.properties.hs_task_body, - status: this.mapStatusReverse(task.properties.hs_task_status), + status: task.properties.hs_task_status, due_date: new Date(task.properties.hs_timestamp), field_mappings, ...opts, @@ -105,34 +105,4 @@ export class HubspotTaskMapper implements ITaskMapper { }; } - private mapStatus(status?: string): string { - // Map UnifiedTaskInput status to HubspotTaskInput status - // Adjust this method according to your specific status mapping logic - return status || 'WAITING'; - } - - private mapPriority(priority?: string): 'HIGH' | 'MEDIUM' | 'LOW' { - // Map UnifiedTaskInput priority to HubspotTaskInput priority - // Adjust this method according to your specific priority mapping logic - return priority === 'High' - ? 'HIGH' - : priority === 'Medium' - ? 'MEDIUM' - : 'LOW'; - } - - private mapStatusReverse(status: string): string { - // Reverse map HubspotTaskOutput status to UnifiedTaskOutput status - // Adjust this method according to your specific status reverse mapping logic - switch (status) { - case 'WAITING': - return 'Pending'; - case 'COMPLETED': - return 'Completed'; - case 'IN_PROGRESS': - return 'In Progress'; - default: - return 'Unknown'; - } - } } diff --git a/packages/api/src/crm/task/types/model.unified.ts b/packages/api/src/crm/task/types/model.unified.ts index fb4abc11b..2c6d33579 100644 --- a/packages/api/src/crm/task/types/model.unified.ts +++ b/packages/api/src/crm/task/types/model.unified.ts @@ -9,7 +9,7 @@ export class UnifiedTaskInput { @ApiProperty({ description: - 'The status of the task. Authorized values are "Completed" and "Not Completed" ', + 'The status of the task. Authorized values are PENDING, COMPLETED.', }) status: string; diff --git a/packages/api/src/crm/task/utils/index.ts b/packages/api/src/crm/task/utils/index.ts index fffeb8f1d..47ae31b90 100644 --- a/packages/api/src/crm/task/utils/index.ts +++ b/packages/api/src/crm/task/utils/index.ts @@ -134,4 +134,24 @@ export class Utils { throw new Error(error); } } + + mapStatus(status: string, provider_name: string): string { + try{ + switch(provider_name.toLowerCase()){ + default: + throw new Error('Provider not supported for status custom task mapping') + } + }catch(error){ + throw new Error(error); + } + } + + // not currently in use, but might be in the future + mapPriority(priority?: string): 'HIGH' | 'MEDIUM' | 'LOW' { + return priority === 'High' + ? 'HIGH' + : priority === 'Medium' + ? 'MEDIUM' + : 'LOW'; + } } diff --git a/packages/api/src/ticketing/collection/types/model.unified.ts b/packages/api/src/ticketing/collection/types/model.unified.ts index f6fe9af06..71f844652 100644 --- a/packages/api/src/ticketing/collection/types/model.unified.ts +++ b/packages/api/src/ticketing/collection/types/model.unified.ts @@ -12,7 +12,8 @@ export class UnifiedCollectionInput { description?: string; @ApiPropertyOptional({ - description: 'The type of the collection, either PROJECT or LIST ', + description: + 'The type of the collection. Authorized values are either PROJECT or LIST ', }) collection_type?: string; } diff --git a/packages/api/src/ticketing/comment/services/front/mappers.ts b/packages/api/src/ticketing/comment/services/front/mappers.ts index 1e28303b8..b48a9b593 100644 --- a/packages/api/src/ticketing/comment/services/front/mappers.ts +++ b/packages/api/src/ticketing/comment/services/front/mappers.ts @@ -80,7 +80,7 @@ export class FrontCommentMapper implements ICommentMapper { if (user_id) { // we must always fall here for Front - opts = { user_id: user_id, creator_type: 'user' }; + opts = { user_id: user_id, creator_type: 'USER' }; } } diff --git a/packages/api/src/ticketing/comment/services/gorgias/mappers.ts b/packages/api/src/ticketing/comment/services/gorgias/mappers.ts index 3b3d5b74f..cd41e92dd 100644 --- a/packages/api/src/ticketing/comment/services/gorgias/mappers.ts +++ b/packages/api/src/ticketing/comment/services/gorgias/mappers.ts @@ -96,7 +96,7 @@ export class GorgiasCommentMapper implements ICommentMapper { 'gorgias', ); if (contact_id) { - opts = { creator_type: 'contact', contact_id: contact_id }; + opts = { creator_type: 'CONTACT', contact_id: contact_id }; } } } diff --git a/packages/api/src/ticketing/comment/services/jira/mappers.ts b/packages/api/src/ticketing/comment/services/jira/mappers.ts index 7e16c588a..c25798828 100644 --- a/packages/api/src/ticketing/comment/services/jira/mappers.ts +++ b/packages/api/src/ticketing/comment/services/jira/mappers.ts @@ -73,7 +73,7 @@ export class JiraCommentMapper implements ICommentMapper { if (user_id) { // we must always fall here for Jira - opts = { user_id: user_id, creator_type: 'user' }; + opts = { user_id: user_id, creator_type: 'USER' }; } } diff --git a/packages/api/src/ticketing/comment/services/zendesk/mappers.ts b/packages/api/src/ticketing/comment/services/zendesk/mappers.ts index 25e8553ea..d42534d82 100644 --- a/packages/api/src/ticketing/comment/services/zendesk/mappers.ts +++ b/packages/api/src/ticketing/comment/services/zendesk/mappers.ts @@ -30,10 +30,11 @@ export class ZendeskCommentMapper implements ICommentMapper { type: 'Comment', }; - if (source.contact_id) { - result.author_id = source.contact_id - ? Number(await this.utils.getContactRemoteIdFromUuid(source.contact_id)) - : Number(await this.utils.getUserRemoteIdFromUuid(source.user_id)); + if(source.creator_type === "USER"){ + result.author_id = Number(await this.utils.getUserRemoteIdFromUuid(source.user_id)); + } + if(source.creator_type === "CONTACT"){ + result.author_id = Number(await this.utils.getContactRemoteIdFromUuid(source.contact_id)); } if (source.attachments) { @@ -92,14 +93,14 @@ export class ZendeskCommentMapper implements ICommentMapper { ); if (user_id) { - opts = { user_id: user_id, creator_type: 'user' }; + opts = { user_id: user_id, creator_type: 'USER' }; } else { const contact_id = await this.utils.getContactUuidFromRemoteId( String(comment.author_id), 'zendesk', ); if (contact_id) { - opts = { creator_type: 'contact', contact_id: contact_id }; + opts = { creator_type: 'CONTACT', contact_id: contact_id }; } } } diff --git a/packages/api/src/ticketing/comment/types/model.unified.ts b/packages/api/src/ticketing/comment/types/model.unified.ts index 56030c0ca..abf62b18c 100644 --- a/packages/api/src/ticketing/comment/types/model.unified.ts +++ b/packages/api/src/ticketing/comment/types/model.unified.ts @@ -15,9 +15,10 @@ export class UnifiedCommentInput { is_private?: boolean; @ApiPropertyOptional({ - description: 'The creator type of the comment (either user or contact)', + description: + 'The creator type of the comment. Authorized values are either USER or CONTACT', }) - creator_type?: 'user' | 'contact'; + creator_type?: string; @ApiPropertyOptional({ description: 'The uuid of the ticket the comment is tied to', diff --git a/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts b/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts index 832efae98..4949ae775 100644 --- a/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts @@ -48,11 +48,11 @@ export class GorgiasTicketMapper implements ITicketMapper { } : null, }, - ], + ], }; if (source.status) { - result.status = source.status; + result.status = source.status.toLowerCase(); } if (source.assigned_to && source.assigned_to.length > 0) { diff --git a/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts b/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts index 008ab0128..3920ec241 100644 --- a/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts @@ -15,9 +15,9 @@ export class HubspotTicketMapper implements ITicketMapper { ): HubspotTicketInput { const result = { subject: source.name, - hs_pipeline: source.type || '', - hubspot_owner_id: '', // TODO Replace 'default' with actual owner ID - hs_pipeline_stage: source.status || '', + hs_pipeline: '', + hubspot_owner_id: '', + hs_pipeline_stage: '', hs_ticket_priority: source.priority || 'MEDIUM', }; @@ -65,11 +65,11 @@ export class HubspotTicketMapper implements ITicketMapper { } return { - name: ticket.properties.name, //TODO - status: ticket.properties.hs_pipeline_stage, - description: ticket.properties.description, //TODO + name: ticket.properties.name, + status: "", // hs_pipeline_stage: '', + description: ticket.properties.description, due_date: new Date(ticket.properties.createdate), - type: ticket.properties.hs_pipeline, + type: "", //ticket.properties.hs_pipeline, parent_ticket: '', // Define how you determine the parent ticket completed_at: new Date(ticket.properties.hs_lastmodifieddate), priority: ticket.properties.hs_ticket_priority, diff --git a/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts b/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts index b1fc033c2..e7531e373 100644 --- a/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts @@ -41,22 +41,16 @@ export class ZendeskTicketMapper implements ITicketMapper { result.due_at = source.due_date?.toISOString(); } if (source.priority) { - result.priority = source.priority as 'urgent' | 'high' | 'normal' | 'low'; + result.priority = (source.priority == "MEDIUM" ? 'normal' : source.priority.toLowerCase()) as 'urgent' | 'high' | 'normal' | 'low'; } if (source.status) { - result.status = source.status as - | 'new' - | 'open' - | 'pending' - | 'hold' - | 'solved' - | 'closed'; + result.status = source.status.toLowerCase() as 'open' | 'closed'; } if (source.tags) { result.tags = source.tags; } if (source.type) { - result.type = source.type as 'problem' | 'incident' | 'question' | 'task'; + result.type = source.type.toLowerCase() as 'problem' | 'incident' | 'question' | 'task'; } if (customFieldMappings && source.field_mappings) { @@ -128,10 +122,10 @@ export class ZendeskTicketMapper implements ITicketMapper { const unifiedTicket: UnifiedTicketOutput = { name: ticket.subject, - status: ticket.status, + status: ticket.status === "new" || ticket.status === "open" ? 'OPEN' : "CLOSED", // todo: handle pending status ? description: ticket.description, due_date: ticket.due_at ? new Date(ticket.due_at) : undefined, - type: ticket.type, + type: ticket.type === "incident" ? "PROBLEM" : ticket.type.toUpperCase(), parent_ticket: undefined, // If available, add logic to map parent ticket tags: ticket.tags, completed_at: new Date(ticket.updated_at), diff --git a/packages/api/src/ticketing/ticket/types/model.unified.ts b/packages/api/src/ticketing/ticket/types/model.unified.ts index d704c086e..38f8c1d18 100644 --- a/packages/api/src/ticketing/ticket/types/model.unified.ts +++ b/packages/api/src/ticketing/ticket/types/model.unified.ts @@ -8,7 +8,7 @@ export class UnifiedTicketInput { name: string; @ApiPropertyOptional({ - description: 'The status of the ticket', + description: 'The status of the ticket. Authorized values are OPEN or CLOSED.', }) status?: string; @@ -25,7 +25,7 @@ export class UnifiedTicketInput { due_date?: Date; @ApiPropertyOptional({ - description: 'The type of the ticket', + description: 'The type of the ticket. Authorized values are PROBLEM, QUESTION, or TASK', }) type?: string; @@ -53,9 +53,9 @@ export class UnifiedTicketInput { completed_at?: Date; @ApiPropertyOptional({ - description: 'The priority of the ticket', + description: 'The priority of the ticket. Authorized values are HIGH, MEDIUM or LOW.', }) - priority?: string; + priority?: string; @ApiPropertyOptional({ type: [String], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c68e13426..9870b9e8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -402,6 +402,9 @@ importers: nestjs-pino: specifier: ^3.5.0 version: 3.5.0(@nestjs/common@10.3.7)(pino-http@8.6.1) + openai: + specifier: ^4.38.5 + version: 4.38.5 passport: specifier: ^0.6.0 version: 0.6.0 @@ -4658,10 +4661,23 @@ packages: resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} dev: false + /@types/node-fetch@2.6.11: + resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} + dependencies: + '@types/node': 20.12.7 + form-data: 4.0.0 + dev: false + /@types/node@12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: false + /@types/node@18.19.31: + resolution: {integrity: sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==} + dependencies: + undici-types: 5.26.5 + dev: false + /@types/node@20.12.7: resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} dependencies: @@ -5234,6 +5250,13 @@ packages: - supports-color dev: false + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + /ajv-formats@2.1.1(ajv@8.12.0): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -7804,6 +7827,10 @@ packages: webpack: 5.90.1 dev: true + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + /form-data-encoder@2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} @@ -7817,6 +7844,14 @@ packages: combined-stream: 1.0.8 mime-types: 2.1.35 + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + /formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -8332,6 +8367,12 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + /husky@8.0.3: resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} engines: {node: '>=14'} @@ -10298,6 +10339,22 @@ packages: dependencies: mimic-fn: 4.0.0 + /openai@4.38.5: + resolution: {integrity: sha512-Ym5GJL98ZhLJJ7enBx53jjG3vwN/fsB+Ozh46nnRZZS9W1NiYqbwkJ+sXd3dkCIiWIgcyyOPL2Zr8SQAzbpj3g==} + hasBin: true + dependencies: + '@types/node': 18.19.31 + '@types/node-fetch': 2.6.11 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + web-streams-polyfill: 3.3.3 + transitivePeerDependencies: + - encoding + dev: false + /optional@0.1.4: resolution: {integrity: sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==} dev: false @@ -13551,6 +13608,11 @@ packages: engines: {node: '>= 8'} dev: false + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} From 3ba8b8d05d01caacf28f8947e3c00aa7f94fe71f Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 29 Apr 2024 20:32:07 +0200 Subject: [PATCH 02/11] :fire: Added validation pipes to inoput unified typse --- packages/api/package.json | 2 +- .../src/crm/company/types/model.unified.ts | 31 ++++++++-- .../src/crm/contact/types/model.unified.ts | 20 +++++-- .../api/src/crm/deal/types/model.unified.ts | 34 +++++++++-- .../src/crm/engagement/types/model.unified.ts | 46 +++++++++++++-- .../api/src/crm/note/types/model.unified.ts | 36 +++++++++-- .../api/src/crm/stage/types/model.unified.ts | 15 ++++- .../api/src/crm/task/types/model.unified.ts | 42 +++++++++++-- .../api/src/crm/user/types/model.unified.ts | 18 ++++-- .../ticketing/account/types/model.unified.ts | 16 ++++- .../attachment/types/model.unified.ts | 28 +++++++-- .../collection/types/model.unified.ts | 24 +++++++- .../ticketing/comment/types/model.unified.ts | 39 ++++++++++-- .../ticketing/contact/types/model.unified.ts | 22 ++++++- .../src/ticketing/tag/types/model.unified.ts | 14 ++++- .../src/ticketing/team/types/model.unified.ts | 17 +++++- .../ticketing/ticket/types/model.unified.ts | 59 ++++++++++++++++--- .../src/ticketing/user/types/model.unified.ts | 23 ++++++-- pnpm-lock.yaml | 2 +- 19 files changed, 418 insertions(+), 70 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index cdfca4498..e15ec2531 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -47,7 +47,7 @@ "bcrypt": "^5.1.1", "bull": "^4.11.5", "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", + "class-validator": "^0.14.1", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "crypto": "^1.0.1", diff --git a/packages/api/src/crm/company/types/model.unified.ts b/packages/api/src/crm/company/types/model.unified.ts index 89203d42d..399cc6f77 100644 --- a/packages/api/src/crm/company/types/model.unified.ts +++ b/packages/api/src/crm/company/types/model.unified.ts @@ -1,39 +1,55 @@ import { Address, Email, Phone } from '@crm/@utils/@types'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; export class UnifiedCompanyInput { - @ApiProperty({ description: 'The name of the company' }) + @ApiProperty({ type: String, description: 'The name of the company' }) + @IsString() name: string; - @ApiPropertyOptional({ description: 'The industry of the company' }) + @ApiPropertyOptional({ + type: String, + description: 'The industry of the company', + }) + @IsString() + @IsOptional() industry?: string; @ApiPropertyOptional({ + type: Number, description: 'The number of employees of the company', }) + @IsNumber() + @IsOptional() number_of_employees?: number; @ApiPropertyOptional({ + type: String, description: 'The uuid of the user who owns the company', }) + @IsString() + @IsOptional() user_id?: string; @ApiPropertyOptional({ description: 'The email addresses of the company', type: [Email], }) + @IsOptional() email_addresses?: Email[]; @ApiPropertyOptional({ description: 'The addresses of the company', type: [Address], }) + @IsOptional() addresses?: Address[]; @ApiPropertyOptional({ description: 'The phone numbers of the company', type: [Phone], }) + @IsOptional() phone_numbers?: Phone[]; @ApiPropertyOptional({ @@ -41,22 +57,29 @@ export class UnifiedCompanyInput { description: 'The custom field mappings of the company between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedCompanyOutput extends UnifiedCompanyInput { - @ApiPropertyOptional({ description: 'The uuid of the company' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the company' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the company in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the company in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/crm/contact/types/model.unified.ts b/packages/api/src/crm/contact/types/model.unified.ts index 83182e2bf..4df599ec6 100644 --- a/packages/api/src/crm/contact/types/model.unified.ts +++ b/packages/api/src/crm/contact/types/model.unified.ts @@ -1,11 +1,14 @@ import { Address, Email, Phone } from '@crm/@utils/@types'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedContactInput { - @ApiProperty({ description: 'The first name of the contact' }) + @ApiProperty({ type: String, description: 'The first name of the contact' }) + @IsString() first_name: string; - @ApiProperty({ description: 'The last name of the contact' }) + @ApiProperty({ type: String, description: 'The last name of the contact' }) + @IsString() last_name: string; @ApiPropertyOptional({ @@ -30,6 +33,8 @@ export class UnifiedContactInput { type: String, description: 'The uuid of the user who owns the contact', }) + @IsString() + @IsOptional() user_id?: string; @ApiPropertyOptional({ @@ -37,22 +42,29 @@ export class UnifiedContactInput { description: 'The custom field mappings of the contact between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedContactOutput extends UnifiedContactInput { - @ApiPropertyOptional({ description: 'The uuid of the contact' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the contact' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the contact in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the contact in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/crm/deal/types/model.unified.ts b/packages/api/src/crm/deal/types/model.unified.ts index 27a4b4854..b4d9afe25 100644 --- a/packages/api/src/crm/deal/types/model.unified.ts +++ b/packages/api/src/crm/deal/types/model.unified.ts @@ -1,26 +1,41 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; export class UnifiedDealInput { - @ApiProperty({ description: 'The name of the deal' }) + @ApiProperty({ type: String, description: 'The name of the deal' }) + @IsString() name: string; - @ApiProperty({ description: 'The description of the deal' }) + @ApiProperty({ type: String, description: 'The description of the deal' }) + @IsString() description: string; - @ApiProperty({ description: 'The amount of the deal' }) + @ApiProperty({ type: Number, description: 'The amount of the deal' }) + @IsNumber() amount: number; @ApiPropertyOptional({ + type: String, description: 'The uuid of the user who is on the deal', }) + @IsString() + @IsOptional() user_id?: string; - @ApiPropertyOptional({ description: 'The uuid of the stage of the deal' }) + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the stage of the deal', + }) + @IsString() + @IsOptional() stage_id?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid of the company tied to the deal', }) + @IsString() + @IsOptional() company_id?: string; @ApiPropertyOptional({ @@ -28,22 +43,29 @@ export class UnifiedDealInput { description: 'The custom field mappings of the company between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedDealOutput extends UnifiedDealInput { - @ApiPropertyOptional({ description: 'The uuid of the deal' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the deal' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the deal in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the deal in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/crm/engagement/types/model.unified.ts b/packages/api/src/crm/engagement/types/model.unified.ts index 8895d5605..6ebc3d19b 100644 --- a/packages/api/src/crm/engagement/types/model.unified.ts +++ b/packages/api/src/crm/engagement/types/model.unified.ts @@ -1,40 +1,69 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsIn, IsOptional, IsString } from 'class-validator'; export class UnifiedEngagementInput { - @ApiPropertyOptional({ description: 'The content of the engagement' }) + @ApiPropertyOptional({ type: String, description: 'The content of the engagement' }) + @IsString() + @IsOptional() content?: string; - @ApiPropertyOptional({ description: 'The direction of the engagement. Authorized values are INBOUND or OUTBOUND' }) + @ApiPropertyOptional({ + type: String, + description: 'The direction of the engagement. Authorized values are INBOUND or OUTBOUND' + }) + @IsIn(['INBOUND', 'OUTBOUND'], { + message: "Direction must be either INBOUND or OUTBOUND" + }) + @IsOptional() direction?: string; - @ApiPropertyOptional({ description: 'The subject of the engagement' }) + @ApiPropertyOptional({ + type: String, + description: 'The subject of the engagement' + }) + @IsString() + @IsOptional() subject?: string; @ApiPropertyOptional({ description: 'The start time of the engagement' }) + @IsOptional() start_at?: Date; @ApiPropertyOptional({ description: 'The end time of the engagement' }) + @IsOptional() end_time?: Date; @ApiProperty({ + type: String, description: 'The type of the engagement. Authorized values are EMAIL, CALL or MEETING', }) + @IsIn(['EMAIL', 'CALL', 'MEETING'], { + message: "Type must be either EMAIL, CALL or MEETING" + }) type: string; @ApiPropertyOptional({ + type: String, description: 'The uuid of the user tied to the engagement', }) + @IsString() + @IsOptional() user_id?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid of the company tied to the engagement', }) + @IsString() + @IsOptional() company_id?: string; // uuid of Company object @ApiPropertyOptional({ + type: [String], description: 'The uuids of contacts tied to the engagement object', }) + @IsOptional() contacts?: string[]; // array of uuids of Engagement Contacts objects @ApiPropertyOptional({ @@ -42,22 +71,29 @@ export class UnifiedEngagementInput { description: 'The custom field mappings of the engagement between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedEngagementOutput extends UnifiedEngagementInput { - @ApiPropertyOptional({ description: 'The uuid of the engagement' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the engagement' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the engagement in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the engagement in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/crm/note/types/model.unified.ts b/packages/api/src/crm/note/types/model.unified.ts index 8658020e3..6e2d4a46f 100644 --- a/packages/api/src/crm/note/types/model.unified.ts +++ b/packages/api/src/crm/note/types/model.unified.ts @@ -1,23 +1,41 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedNoteInput { - @ApiProperty({ description: 'The content of the note' }) + @ApiProperty({ type: String, description: 'The content of the note' }) + @IsString() content: string; - @ApiPropertyOptional({ description: 'The uuid of the user tied the note' }) + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the user tied the note', + }) + @IsString() + @IsOptional() user_id?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid of the company tied to the note', }) + @IsString() + @IsOptional() company_id?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid fo the contact tied to the note', }) + @IsString() + @IsOptional() contact_id?: string; - @ApiPropertyOptional({ description: 'The uuid of the deal tied to the note' }) + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the deal tied to the note', + }) + @IsString() + @IsOptional() deal_id?: string; @ApiPropertyOptional({ @@ -25,22 +43,30 @@ export class UnifiedNoteInput { description: 'The custom field mappings of the note between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedNoteOutput extends UnifiedNoteInput { - @ApiPropertyOptional({ description: 'The uuid of the note' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the note' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, + description: 'The id of the note in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the note in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/crm/stage/types/model.unified.ts b/packages/api/src/crm/stage/types/model.unified.ts index 3b5bc408b..44bcdfa4d 100644 --- a/packages/api/src/crm/stage/types/model.unified.ts +++ b/packages/api/src/crm/stage/types/model.unified.ts @@ -1,7 +1,9 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedStageInput { - @ApiProperty({ description: 'The name of the stage' }) + @ApiProperty({ type: String, description: 'The name of the stage' }) + @IsString() stage_name: string; @ApiPropertyOptional({ @@ -9,22 +11,29 @@ export class UnifiedStageInput { description: 'The custom field mappings of the stage between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedStageOutput extends UnifiedStageInput { - @ApiPropertyOptional({ description: 'The uuid of the stage' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the stage' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the stage in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the stage in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/crm/task/types/model.unified.ts b/packages/api/src/crm/task/types/model.unified.ts index 2c6d33579..d7939fdef 100644 --- a/packages/api/src/crm/task/types/model.unified.ts +++ b/packages/api/src/crm/task/types/model.unified.ts @@ -1,33 +1,55 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsIn, IsOptional, IsString } from 'class-validator'; export class UnifiedTaskInput { - @ApiProperty({ description: 'The subject of the task' }) + @ApiProperty({ type: String, description: 'The subject of the task' }) + @IsString() subject: string; - @ApiProperty({ description: 'The content of the task' }) + @ApiProperty({ type: String, description: 'The content of the task' }) + @IsString() content: string; @ApiProperty({ + type: String, description: 'The status of the task. Authorized values are PENDING, COMPLETED.', }) + @IsIn(['PENDING', 'COMPLETED'], { + message: 'Type must be either PENDING or COMPLETED', + }) status: string; @ApiPropertyOptional({ description: 'The due date of the task' }) + @IsOptional() due_date?: Date; @ApiPropertyOptional({ description: 'The finished date of the task' }) + @IsOptional() finished_date?: Date; - @ApiPropertyOptional({ description: 'The uuid of the user tied to the task' }) + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the user tied to the task', + }) + @IsString() + @IsOptional() user_id?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid fo the company tied to the task', }) + @IsString() + @IsOptional() company_id?: string; - @ApiPropertyOptional({ description: 'The uuid of the deal tied to the task' }) + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the deal tied to the task', + }) + @IsString() + @IsOptional() deal_id?: string; @ApiPropertyOptional({ @@ -35,22 +57,30 @@ export class UnifiedTaskInput { description: 'The custom field mappings of the task between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedTaskOutput extends UnifiedTaskInput { - @ApiPropertyOptional({ description: 'The uuid of the task' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the task' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, + description: 'The id of the task in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the task in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/crm/user/types/model.unified.ts b/packages/api/src/crm/user/types/model.unified.ts index 71dee8151..fda3bcbf8 100644 --- a/packages/api/src/crm/user/types/model.unified.ts +++ b/packages/api/src/crm/user/types/model.unified.ts @@ -1,10 +1,13 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedUserInput { - @ApiProperty({ description: 'The name of the user' }) + @ApiProperty({ type: String, description: 'The name of the user' }) + @IsString() name: string; - @ApiProperty({ description: 'The email of the user' }) + @ApiProperty({ type: String, description: 'The email of the user' }) + @IsString() email: string; @ApiPropertyOptional({ @@ -12,22 +15,29 @@ export class UnifiedUserInput { description: 'The custom field mappings of the user between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedUserOutput extends UnifiedUserInput { - @ApiPropertyOptional({ description: 'The uuid of the user' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the user' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the user in the context of the Crm 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the user in the context of the Crm 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/account/types/model.unified.ts b/packages/api/src/ticketing/account/types/model.unified.ts index 9652b2cb7..63ca08413 100644 --- a/packages/api/src/ticketing/account/types/model.unified.ts +++ b/packages/api/src/ticketing/account/types/model.unified.ts @@ -1,13 +1,16 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedAccountInput { - @ApiProperty({ description: 'The name of the account' }) + @ApiProperty({ type: String, description: 'The name of the account' }) + @IsString() name: string; @ApiPropertyOptional({ type: [String], description: 'The domains of the account', }) + @IsOptional() domains?: string[]; @ApiPropertyOptional({ @@ -15,22 +18,29 @@ export class UnifiedAccountInput { description: 'The custom field mappings of the account between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedAccountOutput extends UnifiedAccountInput { - @ApiPropertyOptional({ description: 'The uuid of the account' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the account' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the account in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the account in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/attachment/types/model.unified.ts b/packages/api/src/ticketing/attachment/types/model.unified.ts index 354150082..4d903488f 100644 --- a/packages/api/src/ticketing/attachment/types/model.unified.ts +++ b/packages/api/src/ticketing/attachment/types/model.unified.ts @@ -1,13 +1,21 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedAttachmentInput { - @ApiProperty({ description: 'The file name of the attachment' }) + @ApiProperty({ type: String, description: 'The file name of the attachment' }) + @IsString() file_name: string; - @ApiProperty({ description: 'The file url of the attachment' }) + @ApiProperty({ type: String, description: 'The file url of the attachment' }) + @IsString() file_url: string; - @ApiProperty({ description: "The uploader's uuid of the attachment" }) + @ApiProperty({ + type: String, + description: "The uploader's uuid of the attachment", + }) + @IsString() + @IsOptional() uploader?: string; @ApiPropertyOptional({ @@ -15,22 +23,32 @@ export class UnifiedAttachmentInput { description: 'The custom field mappings of the attachment between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedAttachmentOutput extends UnifiedAttachmentInput { - @ApiPropertyOptional({ description: 'The uuid of the attachment' }) + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the attachment', + }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the attachment in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the attachment in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/collection/types/model.unified.ts b/packages/api/src/ticketing/collection/types/model.unified.ts index 71f844652..09a425772 100644 --- a/packages/api/src/ticketing/collection/types/model.unified.ts +++ b/packages/api/src/ticketing/collection/types/model.unified.ts @@ -1,36 +1,56 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsIn, IsOptional, IsString } from 'class-validator'; export class UnifiedCollectionInput { @ApiProperty({ + type: String, description: 'The name of the collection', }) + @IsString() name: string; @ApiPropertyOptional({ + type: String, description: 'The description of the collection', }) + @IsString() + @IsOptional() description?: string; @ApiPropertyOptional({ + type: String, description: 'The type of the collection. Authorized values are either PROJECT or LIST ', }) + @IsIn(['PROJECT', 'LIST'], { + message: 'Type must be either PROJECT or LIST', + }) + @IsOptional() collection_type?: string; } export class UnifiedCollectionOutput extends UnifiedCollectionInput { - @ApiPropertyOptional({ description: 'The uuid of the collection' }) + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the collection', + }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the collection in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the collection in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/comment/types/model.unified.ts b/packages/api/src/ticketing/comment/types/model.unified.ts index abf62b18c..203ef7866 100644 --- a/packages/api/src/ticketing/comment/types/model.unified.ts +++ b/packages/api/src/ticketing/comment/types/model.unified.ts @@ -1,68 +1,99 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { UnifiedAttachmentOutput } from '@ticketing/attachment/types/model.unified'; +import { IsBoolean, IsIn, IsOptional, IsString } from 'class-validator'; export class UnifiedCommentInput { - @ApiProperty({ description: 'The body of the comment' }) + @ApiProperty({ type: String, description: 'The body of the comment' }) + @IsString() body: string; - @ApiPropertyOptional({ description: 'The html body of the comment' }) + @ApiPropertyOptional({ + type: String, + description: 'The html body of the comment', + }) + @IsString() + @IsOptional() html_body?: string; @ApiPropertyOptional({ type: Boolean, description: 'The public status of the comment', }) + @IsOptional() + @IsBoolean() is_private?: boolean; @ApiPropertyOptional({ + type: String, description: 'The creator type of the comment. Authorized values are either USER or CONTACT', }) + @IsIn(['USER', 'CONTACT'], { + message: 'Type must be either USER or CONTACT', + }) + @IsOptional() creator_type?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid of the ticket the comment is tied to', }) + @IsString() + @IsOptional() ticket_id?: string; // uuid of Ticket object @ApiPropertyOptional({ + type: String, description: 'The uuid of the contact which the comment belongs to (if no user_id specified)', }) + @IsString() + @IsOptional() contact_id?: string; // uuid of Contact object @ApiPropertyOptional({ + type: String, description: 'The uuid of the user which the comment belongs to (if no contact_id specified)', }) + @IsString() + @IsOptional() user_id?: string; // uuid of User object @ApiPropertyOptional({ type: [String], description: 'The attachements uuids tied to the comment', }) + @IsOptional() attachments?: any[]; //uuids of Attachments objects } export class UnifiedCommentOutput extends UnifiedCommentInput { - @ApiPropertyOptional({ description: 'The uuid of the comment' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the comment' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the comment in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the comment in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; @ApiPropertyOptional({ type: [UnifiedAttachmentOutput], description: 'The attachemnets tied to the comment', }) + @IsOptional() attachments?: UnifiedAttachmentOutput[]; // Attachments objects } diff --git a/packages/api/src/ticketing/contact/types/model.unified.ts b/packages/api/src/ticketing/contact/types/model.unified.ts index a404c6157..c7ba326ba 100644 --- a/packages/api/src/ticketing/contact/types/model.unified.ts +++ b/packages/api/src/ticketing/contact/types/model.unified.ts @@ -1,24 +1,35 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedContactInput { @ApiProperty({ + type: String, description: 'The name of the contact', }) + @IsString() name: string; @ApiProperty({ + type: String, description: 'The email address of the contact', }) + @IsString() email_address: string; @ApiPropertyOptional({ + type: String, description: 'The phone number of the contact', }) + @IsString() + @IsOptional() phone_number?: string; @ApiPropertyOptional({ + type: String, description: 'The details of the contact', }) + @IsOptional() + @IsString() details?: string; @ApiPropertyOptional({ @@ -26,22 +37,29 @@ export class UnifiedContactInput { description: 'The custom field mappings of the contact between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedContactOutput extends UnifiedContactInput { - @ApiPropertyOptional({ description: 'The uuid of the contact' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the contact' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the contact in the context of the 3rd Party', }) + @IsOptional() + @IsString() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the contact in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/tag/types/model.unified.ts b/packages/api/src/ticketing/tag/types/model.unified.ts index 5380a2e32..0e60f0b6c 100644 --- a/packages/api/src/ticketing/tag/types/model.unified.ts +++ b/packages/api/src/ticketing/tag/types/model.unified.ts @@ -1,9 +1,12 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedTagInput { @ApiProperty({ + type: String, description: 'The name of the tag', }) + @IsString() name: string; @ApiPropertyOptional({ @@ -11,21 +14,28 @@ export class UnifiedTagInput { description: 'The custom field mappings of the tag between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedTagOutput extends UnifiedTagInput { - @ApiPropertyOptional({ description: 'The uuid of the tag' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the tag' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the tag in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the tag in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/team/types/model.unified.ts b/packages/api/src/ticketing/team/types/model.unified.ts index ce85bfff5..e0cf61bd6 100644 --- a/packages/api/src/ticketing/team/types/model.unified.ts +++ b/packages/api/src/ticketing/team/types/model.unified.ts @@ -1,14 +1,20 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedTeamInput { @ApiProperty({ + type: String, description: 'The name of the team', }) + @IsString() name: string; @ApiPropertyOptional({ + type: String, description: 'The description of the team', }) + @IsString() + @IsOptional() description?: string; @ApiPropertyOptional({ @@ -16,21 +22,28 @@ export class UnifiedTeamInput { description: 'The custom field mappings of the team between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedTeamOutput extends UnifiedTeamInput { - @ApiPropertyOptional({ description: 'The uuid of the team' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the team' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the team in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the team in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/ticket/types/model.unified.ts b/packages/api/src/ticketing/ticket/types/model.unified.ts index 38f8c1d18..9abcf9839 100644 --- a/packages/api/src/ticketing/ticket/types/model.unified.ts +++ b/packages/api/src/ticketing/ticket/types/model.unified.ts @@ -1,82 +1,120 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { UnifiedCommentInput } from '@ticketing/comment/types/model.unified'; +import { IsIn, IsOptional, IsString } from 'class-validator'; export class UnifiedTicketInput { @ApiProperty({ + type: String, description: 'The name of the ticket', }) + @IsString() name: string; @ApiPropertyOptional({ - description: 'The status of the ticket. Authorized values are OPEN or CLOSED.', + type: String, + description: + 'The status of the ticket. Authorized values are OPEN or CLOSED.', }) + @IsIn(['OPEN', 'CLOSED'], { + message: 'Type must be either OPEN or CLOSED', + }) + @IsOptional() status?: string; @ApiProperty({ - type: [String], + type: String, description: 'The description of the ticket', }) + @IsString() description: string; @ApiPropertyOptional({ type: Date, description: 'The date the ticket is due', }) + @IsOptional() due_date?: Date; @ApiPropertyOptional({ - description: 'The type of the ticket. Authorized values are PROBLEM, QUESTION, or TASK', + type: String, + description: + 'The type of the ticket. Authorized values are PROBLEM, QUESTION, or TASK', + }) + @IsIn(['PROBLEM', 'QUESTION', 'TASK'], { + message: 'Type must be either PROBLEM, QUESTION or TASK', }) + @IsOptional() type?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid of the parent ticket', }) + @IsString() + @IsOptional() parent_ticket?: string; @ApiPropertyOptional({ type: String, description: 'The uuid of the project the ticket belongs to', }) + @IsString() + @IsOptional() project_id?: string; @ApiPropertyOptional({ type: [String], description: 'The tags names of the ticket', }) + @IsOptional() tags?: string[]; // tags names @ApiPropertyOptional({ type: Date, description: 'The date the ticket has been completed', }) + @IsOptional() completed_at?: Date; @ApiPropertyOptional({ - description: 'The priority of the ticket. Authorized values are HIGH, MEDIUM or LOW.', + type: String, + description: + 'The priority of the ticket. Authorized values are HIGH, MEDIUM or LOW.', + }) + @IsIn(['HIGH', 'MEDIUM', 'LOW'], { + message: 'Type must be either HIGH, MEDIUM or LOW', }) - priority?: string; + @IsOptional() + priority?: string; @ApiPropertyOptional({ type: [String], description: 'The users uuids the ticket is assigned to', }) + @IsOptional() assigned_to?: string[]; //uuid of Users objects ? @ApiPropertyOptional({ type: UnifiedCommentInput, description: 'The comment of the ticket', }) + @IsOptional() comment?: UnifiedCommentInput; @ApiPropertyOptional({ + type: String, description: 'The uuid of the account which the ticket belongs to', }) + @IsString() + @IsOptional() account_id?: string; @ApiPropertyOptional({ + type: String, description: 'The uuid of the contact which the ticket belongs to', }) + @IsString() + @IsOptional() contact_id?: string; @ApiPropertyOptional({ @@ -84,21 +122,28 @@ export class UnifiedTicketInput { description: 'The custom field mappings of the ticket between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedTicketOutput extends UnifiedTicketInput { - @ApiPropertyOptional({ description: 'The uuid of the ticket' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the ticket' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the ticket in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the ticket in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/packages/api/src/ticketing/user/types/model.unified.ts b/packages/api/src/ticketing/user/types/model.unified.ts index f6af2c595..2e5cd2bfa 100644 --- a/packages/api/src/ticketing/user/types/model.unified.ts +++ b/packages/api/src/ticketing/user/types/model.unified.ts @@ -1,49 +1,64 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; export class UnifiedUserInput { @ApiProperty({ + type: String, description: 'The name of the user', }) + @IsString() name: string; @ApiProperty({ + type: String, description: 'The email address of the user', }) + @IsString() email_address: string; @ApiPropertyOptional({ type: [String], description: 'The teams whose the user is part of', }) + @IsOptional() teams?: string[]; //TODO @ApiPropertyOptional({ - type: [String], + type: String, description: 'The account or organization the user is part of', }) - account_id?: string[]; + @IsString() + @IsOptional() + account_id?: string; @ApiProperty({ type: {}, description: 'The custom field mappings of the user between the remote 3rd party & Panora', }) + @IsOptional() field_mappings?: Record; } export class UnifiedUserOutput extends UnifiedUserInput { - @ApiPropertyOptional({ description: 'The uuid of the user' }) + @ApiPropertyOptional({ type: String, description: 'The uuid of the user' }) + @IsString() + @IsOptional() id?: string; @ApiPropertyOptional({ + type: String, description: 'The id of the user in the context of the 3rd Party', }) + @IsString() + @IsOptional() remote_id?: string; @ApiPropertyOptional({ - type: [{}], + type: {}, description: 'The remote data of the user in the context of the 3rd Party', }) + @IsOptional() remote_data?: Record; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9870b9e8e..e6aaea3a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -379,7 +379,7 @@ importers: specifier: ^0.5.1 version: 0.5.1 class-validator: - specifier: ^0.14.0 + specifier: ^0.14.1 version: 0.14.1 cookie-parser: specifier: ^1.4.6 From 2790f922eeefc49e0ca588195517e2ce1c345e4a Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 29 Apr 2024 23:31:09 +0200 Subject: [PATCH 03/11] :art: Improve code --- .env.example | 1 - packages/api/src/app.controller.ts | 6 - packages/api/src/crm/@utils/@types/index.ts | 335 ++++++++++++++---- .../src/crm/company/types/model.unified.ts | 19 +- packages/api/src/crm/company/utils/index.ts | 208 ----------- .../src/crm/contact/services/attio/mappers.ts | 17 +- .../src/crm/contact/services/attio/types.ts | 2 +- .../src/crm/contact/types/model.unified.ts | 6 +- packages/api/src/crm/contact/utils/index.ts | 22 +- .../api/src/crm/deal/types/model.unified.ts | 10 +- .../engagement/services/hubspot/mappers.ts | 4 +- .../src/crm/engagement/types/model.unified.ts | 29 +- .../api/src/crm/note/types/model.unified.ts | 12 +- .../api/src/crm/stage/types/model.unified.ts | 4 +- .../src/crm/task/services/hubspot/mappers.ts | 1 - .../api/src/crm/task/types/model.unified.ts | 8 +- .../api/src/crm/user/types/model.unified.ts | 4 +- .../ticketing/account/types/model.unified.ts | 4 +- .../attachment/types/model.unified.ts | 4 +- .../collection/types/model.unified.ts | 4 +- .../comment/services/zendesk/mappers.ts | 12 +- .../ticketing/comment/types/model.unified.ts | 10 +- .../ticketing/contact/types/model.unified.ts | 4 +- .../src/ticketing/tag/types/model.unified.ts | 4 +- .../src/ticketing/team/types/model.unified.ts | 4 +- .../ticket/services/gorgias/mappers.ts | 2 +- .../ticket/services/hubspot/mappers.ts | 6 +- .../ticket/services/zendesk/mappers.ts | 15 +- .../ticketing/ticket/types/model.unified.ts | 14 +- .../src/ticketing/user/types/model.unified.ts | 6 +- 30 files changed, 391 insertions(+), 386 deletions(-) diff --git a/.env.example b/.env.example index b7c839a8e..c6f1c0cab 100644 --- a/.env.example +++ b/.env.example @@ -27,7 +27,6 @@ POSTGRES_USER=my_user POSTGRES_DB=panora_db POSTGRES_HOST=postgres -OPENAI_API_KEY= # Each Provider is of form PROVIDER_TICKETING_SOFTWAREMODE_ATTRIBUTE # ================================================ diff --git a/packages/api/src/app.controller.ts b/packages/api/src/app.controller.ts index 6cb31f359..2c23905e1 100644 --- a/packages/api/src/app.controller.ts +++ b/packages/api/src/app.controller.ts @@ -3,7 +3,6 @@ import { AppService } from './app.service'; import { LoggerService } from '@@core/logger/logger.service'; import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; import { ApiOperation } from '@nestjs/swagger'; -import { mapCompanyIndustryToRemote } from '@crm/company/utils'; @Controller() export class AppController { @@ -26,11 +25,6 @@ export class AppController { return 200; } - @Get('gpt') - async gptTest() { - return await mapCompanyIndustryToRemote('Technology', 'hubspot'); - } - @UseGuards(ApiKeyAuthGuard) @ApiOperation({ operationId: 'getHelloProtected' }) @Get('protected') diff --git a/packages/api/src/crm/@utils/@types/index.ts b/packages/api/src/crm/@utils/@types/index.ts index ecb0bd329..5ffb30a7e 100644 --- a/packages/api/src/crm/@utils/@types/index.ts +++ b/packages/api/src/crm/@utils/@types/index.ts @@ -48,6 +48,7 @@ import { UnifiedUserInput, UnifiedUserOutput, } from '@crm/user/types/model.unified'; +import { IsEnum, IsIn, IsOptional, IsString } from 'class-validator'; export enum CrmObject { company = 'company', @@ -103,85 +104,243 @@ export type ICrmService = | IStageService | ICompanyService; -/* contact */ -/* -export * from '../../contact/services/freshsales/types'; -export * from '../../contact/services/zendesk/types'; -export * from '../../contact/services/hubspot/types'; -export * from '../../contact/services/zoho/types'; -export * from '../../contact/services/pipedrive/types'; -export * from '../../contact/services/attio/types' -*/ -/* user */ -/* -export * from '../../user/services/freshsales/types'; -export * from '../../user/services/zendesk/types'; -export * from '../../user/services/hubspot/types'; -export * from '../../user/services/zoho/types'; -export * from '../../user/services/pipedrive/types'; -*/ -/* engagement */ -/* -export * from '../../engagement/services/freshsales/types'; -export * from '../../engagement/services/zendesk/types'; -export * from '../../engagement/services/hubspot/types'; -export * from '../../engagement/services/zoho/types'; -export * from '../../engagement/services/pipedrive/types'; -*/ -/* note */ -/* -export * from '../../note/services/freshsales/types'; -export * from '../../note/services/zendesk/types'; -export * from '../../note/services/hubspot/types'; -export * from '../../note/services/zoho/types'; -export * from '../../note/services/pipedrive/types'; -*/ -/* deal */ -/* -export * from '../../deal/services/freshsales/types'; -export * from '../../deal/services/zendesk/types'; -export * from '../../deal/services/hubspot/types'; -export * from '../../deal/services/zoho/types'; -export * from '../../deal/services/pipedrive/types'; -*/ -/* task */ -/* -export * from '../../task/services/freshsales/types'; -export * from '../../task/services/zendesk/types'; -export * from '../../task/services/hubspot/types'; -export * from '../../task/services/zoho/types'; -export * from '../../task/services/pipedrive/types'; -*/ -/* stage */ -/* -export * from '../../stage/services/freshsales/types'; -export * from '../../stage/services/zendesk/types'; -export * from '../../stage/services/hubspot/types'; -export * from '../../stage/services/zoho/types'; -export * from '../../stage/services/pipedrive/types'; -*/ -/* company */ -/*export * from '../../company/services/freshsales/types'; -export * from '../../company/services/zendesk/types'; -export * from '../../company/services/hubspot/types'; -export * from '../../company/services/zoho/types'; -export * from '../../company/services/pipedrive/types'; -*/ - -/* engagementType */ +export enum Industry { + ACCOUNTING = 'ACCOUNTING', + AIRLINES_AVIATION = 'AIRLINES_AVIATION', + ALTERNATIVE_DISPUTE_RESOLUTION = 'ALTERNATIVE_DISPUTE_RESOLUTION', + ALTERNATIVE_MEDICINE = 'ALTERNATIVE_MEDICINE', + ANIMATION = 'ANIMATION', + APPAREL_FASHION = 'APPAREL_FASHION', + ARCHITECTURE_PLANNING = 'ARCHITECTURE_PLANNING', + ARTS_AND_CRAFTS = 'ARTS_AND_CRAFTS', + AUTOMOTIVE = 'AUTOMOTIVE', + AVIATION_AEROSPACE = 'AVIATION_AEROSPACE', + BANKING = 'BANKING', + BIOTECHNOLOGY = 'BIOTECHNOLOGY', + BROADCAST_MEDIA = 'BROADCAST_MEDIA', + BUILDING_MATERIALS = 'BUILDING_MATERIALS', + BUSINESS_SUPPLIES_AND_EQUIPMENT = 'BUSINESS_SUPPLIES_AND_EQUIPMENT', + CAPITAL_MARKETS = 'CAPITAL_MARKETS', + CHEMICALS = 'CHEMICALS', + CIVIC_SOCIAL_ORGANIZATION = 'CIVIC_SOCIAL_ORGANIZATION', + CIVIL_ENGINEERING = 'CIVIL_ENGINEERING', + COMMERCIAL_REAL_ESTATE = 'COMMERCIAL_REAL_ESTATE', + COMPUTER_NETWORK_SECURITY = 'COMPUTER_NETWORK_SECURITY', + COMPUTER_GAMES = 'COMPUTER_GAMES', + COMPUTER_HARDWARE = 'COMPUTER_HARDWARE', + COMPUTER_NETWORKING = 'COMPUTER_NETWORKING', + COMPUTER_SOFTWARE = 'COMPUTER_SOFTWARE', + INTERNET = 'INTERNET', + CONSTRUCTION = 'CONSTRUCTION', + CONSUMER_ELECTRONICS = 'CONSUMER_ELECTRONICS', + CONSUMER_GOODS = 'CONSUMER_GOODS', + CONSUMER_SERVICES = 'CONSUMER_SERVICES', + COSMETICS = 'COSMETICS', + DAIRY = 'DAIRY', + DEFENSE_SPACE = 'DEFENSE_SPACE', + DESIGN = 'DESIGN', + EDUCATION_MANAGEMENT = 'EDUCATION_MANAGEMENT', + E_LEARNING = 'E_LEARNING', + ELECTRICAL_ELECTRONIC_MANUFACTURING = 'ELECTRICAL_ELECTRONIC_MANUFACTURING', + ENTERTAINMENT = 'ENTERTAINMENT', + ENVIRONMENTAL_SERVICES = 'ENVIRONMENTAL_SERVICES', + EVENTS_SERVICES = 'EVENTS_SERVICES', + EXECUTIVE_OFFICE = 'EXECUTIVE_OFFICE', + FACILITIES_SERVICES = 'FACILITIES_SERVICES', + FARMING = 'FARMING', + FINANCIAL_SERVICES = 'FINANCIAL_SERVICES', + FINE_ART = 'FINE_ART', + FISHERY = 'FISHERY', + FOOD_BEVERAGES = 'FOOD_BEVERAGES', + FOOD_PRODUCTION = 'FOOD_PRODUCTION', + FUND_RAISING = 'FUND_RAISING', + FURNITURE = 'FURNITURE', + GAMBLING_CASINOS = 'GAMBLING_CASINOS', + GLASS_CERAMICS_CONCRETE = 'GLASS_CERAMICS_CONCRETE', + GOVERNMENT_ADMINISTRATION = 'GOVERNMENT_ADMINISTRATION', + GOVERNMENT_RELATIONS = 'GOVERNMENT_RELATIONS', + GRAPHIC_DESIGN = 'GRAPHIC_DESIGN', + HEALTH_WELLNESS_AND_FITNESS = 'HEALTH_WELLNESS_AND_FITNESS', + HIGHER_EDUCATION = 'HIGHER_EDUCATION', + HOSPITAL_HEALTH_CARE = 'HOSPITAL_HEALTH_CARE', + HOSPITALITY = 'HOSPITALITY', + HUMAN_RESOURCES = 'HUMAN_RESOURCES', + IMPORT_AND_EXPORT = 'IMPORT_AND_EXPORT', + INDIVIDUAL_FAMILY_SERVICES = 'INDIVIDUAL_FAMILY_SERVICES', + INDUSTRIAL_AUTOMATION = 'INDUSTRIAL_AUTOMATION', + INFORMATION_SERVICES = 'INFORMATION_SERVICES', + INFORMATION_TECHNOLOGY_AND_SERVICES = 'INFORMATION_TECHNOLOGY_AND_SERVICES', + INSURANCE = 'INSURANCE', + INTERNATIONAL_AFFAIRS = 'INTERNATIONAL_AFFAIRS', + INTERNATIONAL_TRADE_AND_DEVELOPMENT = 'INTERNATIONAL_TRADE_AND_DEVELOPMENT', + INVESTMENT_BANKING = 'INVESTMENT_BANKING', + INVESTMENT_MANAGEMENT = 'INVESTMENT_MANAGEMENT', + JUDICIARY = 'JUDICIARY', + LAW_ENFORCEMENT = 'LAW_ENFORCEMENT', + LAW_PRACTICE = 'LAW_PRACTICE', + LEGAL_SERVICES = 'LEGAL_SERVICES', + LEGISLATIVE_OFFICE = 'LEGISLATIVE_OFFICE', + LEISURE_TRAVEL_TOURISM = 'LEISURE_TRAVEL_TOURISM', + LIBRARIES = 'LIBRARIES', + LOGISTICS_AND_SUPPLY_CHAIN = 'LOGISTICS_AND_SUPPLY_CHAIN', + LUXURY_GOODS_JEWELRY = 'LUXURY_GOODS_JEWELRY', + MACHINERY = 'MACHINERY', + MANAGEMENT_CONSULTING = 'MANAGEMENT_CONSULTING', + MARITIME = 'MARITIME', + MARKET_RESEARCH = 'MARKET_RESEARCH', + MARKETING_AND_ADVERTISING = 'MARKETING_AND_ADVERTISING', + MECHANICAL_OR_INDUSTRIAL_ENGINEERING = 'MECHANICAL_OR_INDUSTRIAL_ENGINEERING', + MEDIA_PRODUCTION = 'MEDIA_PRODUCTION', + MEDICAL_DEVICES = 'MEDICAL_DEVICES', + MEDICAL_PRACTICE = 'MEDICAL_PRACTICE', + MENTAL_HEALTH_CARE = 'MENTAL_HEALTH_CARE', + MILITARY = 'MILITARY', + MINING_METALS = 'MINING_METALS', + MOTION_PICTURES_AND_FILM = 'MOTION_PICTURES_AND_FILM', + MUSEUMS_AND_INSTITUTIONS = 'MUSEUMS_AND_INSTITUTIONS', + MUSIC = 'MUSIC', + NANOTECHNOLOGY = 'NANOTECHNOLOGY', + NEWSPAPERS = 'NEWSPAPERS', + NON_PROFIT_ORGANIZATION_MANAGEMENT = 'NON_PROFIT_ORGANIZATION_MANAGEMENT', + OIL_ENERGY = 'OIL_ENERGY', + ONLINE_MEDIA = 'ONLINE_MEDIA', + OUTSOURCING_OFFSHORING = 'OUTSOURCING_OFFSHORING', + PACKAGE_FREIGHT_DELIVERY = 'PACKAGE_FREIGHT_DELIVERY', + PACKAGING_AND_CONTAINERS = 'PACKAGING_AND_CONTAINERS', + PAPER_FOREST_PRODUCTS = 'PAPER_FOREST_PRODUCTS', + PERFORMING_ARTS = 'PERFORMING_ARTS', + PHARMACEUTICALS = 'PHARMACEUTICALS', + PHILANTHROPY = 'PHILANTHROPY', + PHOTOGRAPHY = 'PHOTOGRAPHY', + PLASTICS = 'PLASTICS', + POLITICAL_ORGANIZATION = 'POLITICAL_ORGANIZATION', + PRIMARY_SECONDARY_EDUCATION = 'PRIMARY_SECONDARY_EDUCATION', + PRINTING = 'PRINTING', + PROFESSIONAL_TRAINING_COACHING = 'PROFESSIONAL_TRAINING_COACHING', + PROGRAM_DEVELOPMENT = 'PROGRAM_DEVELOPMENT', + PUBLIC_POLICY = 'PUBLIC_POLICY', + PUBLIC_RELATIONS_AND_COMMUNICATIONS = 'PUBLIC_RELATIONS_AND_COMMUNICATIONS', + PUBLIC_SAFETY = 'PUBLIC_SAFETY', + PUBLISHING = 'PUBLISHING', + RAILROAD_MANUFACTURE = 'RAILROAD_MANUFACTURE', + RANCHING = 'RANCHING', + REAL_ESTATE = 'REAL_ESTATE', + RECREATIONAL_FACILITIES_AND_SERVICES = 'RECREATIONAL_FACILITIES_AND_SERVICES', + RELIGIOUS_INSTITUTIONS = 'RELIGIOUS_INSTITUTIONS', + RENEWABLES_ENVIRONMENT = 'RENEWABLES_ENVIRONMENT', + RESEARCH = 'RESEARCH', + RESTAURANTS = 'RESTAURANTS', + RETAIL = 'RETAIL', + SECURITY_AND_INVESTIGATIONS = 'SECURITY_AND_INVESTIGATIONS', + SEMICONDUCTORS = 'SEMICONDUCTORS', + SHIPBUILDING = 'SHIPBUILDING', + SPORTING_GOODS = 'SPORTING_GOODS', + SPORTS = 'SPORTS', + STAFFING_AND_RECRUITING = 'STAFFING_AND_RECRUITING', + SUPERMARKETS = 'SUPERMARKETS', + TELECOMMUNICATIONS = 'TELECOMMUNICATIONS', + TEXTILES = 'TEXTILES', + THINK_TANKS = 'THINK_TANKS', + TOBACCO = 'TOBACCO', + TRANSLATION_AND_LOCALIZATION = 'TRANSLATION_AND_LOCALIZATION', + TRANSPORTATION_TRUCKING_RAILROAD = 'TRANSPORTATION_TRUCKING_RAILROAD', + UTILITIES = 'UTILITIES', + VENTURE_CAPITAL_PRIVATE_EQUITY = 'VENTURE_CAPITAL_PRIVATE_EQUITY', + VETERINARY = 'VETERINARY', + WAREHOUSING = 'WAREHOUSING', + WHOLESALE = 'WHOLESALE', + WINE_AND_SPIRITS = 'WINE_AND_SPIRITS', + WIRELESS = 'WIRELESS', + WRITING_AND_EDITING = 'WRITING_AND_EDITING', +} + +export const countryPhoneFormats: { [countryCode: string]: string } = { + '+1': 'NNN-NNN-NNNN', // USA + '+44': 'NNNN NNNNNN', // UK + '+49': 'NNN NNNNNNN', // Germany + '+33': 'N NN NN NN NN', // France + '+81': 'NNN NNNN NNNN', // Japan + '+91': 'NNNNN NNNNNN', // India + '+86': 'NNN NNNN NNNN', // China + '+7': 'NNN NNN-NN-NN', // Russia + '+55': 'NN NNNNN-NNNN', // Brazil + '+61': 'N NNNN NNNN', // Australia + '+39': 'NNN NNNN NNNN', // Italy + '+34': 'N NNN NNNN', // Spain + '+62': 'NNN NNN-NNNN', // Indonesia + '+27': 'NNN NNN NNNN', // South Africa + '+82': 'NNN-NNNN-NNNN', // South Korea + '+52': 'NN NNNN NNNN', // Mexico + '+31': 'NN NNN NNNN', // Netherlands + '+90': 'NNN NNN NN NN', // Turkey + '+966': 'N NNN NNNN', // Saudi Arabia + '+48': 'NN NNN NN NN', // Poland + '+47': 'NNN NN NNN', // Norway + '+46': 'NNN-NNN NN NN', // Sweden + '+41': 'NNN NNN NN NN', // Switzerland + '+60': 'NN NNN NNNN', // Malaysia + '+66': 'N NNN NNNN', // Thailand + '+63': 'NNN NNN NNNN', // Philippines + '+64': 'NN NNN NNNN', // New Zealand + '+358': 'NNN NNNNNNN', // Finland + '+32': 'NNN NN NN NN', // Belgium + '+43': 'NNN NNNNNNN', // Austria + '+20': 'NNN NNNN NNNN', // Egypt + '+98': 'NNN NNN NNNN', // Iran + '+54': 'NN NNNN-NNNN', // Argentina + '+84': 'NNN NNNN NNNN', // Vietnam + '+380': 'NN NNN NNNN', // Ukraine + '+234': 'NNN NNN NNNN', // Nigeria + '+92': 'NNN NNNNNNN', // Pakistan + '+880': 'NNNN NNNNNN', // Bangladesh + '+30': 'NNN NNN NNNN', // Greece + '+351': 'NN NNN NNNN', // Portugal + '+36': 'NNN NNN NNN', // Hungary + '+40': 'NNN NNN NNN', // Romania + '+56': 'N NNNN NNNN', // Chile + '+94': 'NN NNN NNNN', // Sri Lanka + '+65': 'NNNN NNNN', // Singapore + '+375': 'NNN NN-NN-NN', // Belarus + '+353': 'NN NNN NNNN', // Ireland + '+45': 'NN NN NN NN', // Denmark + '+421': 'NNN NNN NNN', // Slovakia + '+386': 'NNN NNN NNN', // Slovenia + '+971': 'NN NNN NNNN', // UAE + '+972': 'NNN NNN NNNN', // Israel + '+852': 'NNNN NNNN', // Hong Kong + '+385': 'NNN NNNN', // Croatia + '+387': 'NNN NNNN', // Bosnia and Herzegovina + '+389': 'NN NNN NNN', // North Macedonia + '+381': 'NNN NNNN', // Serbia + '+373': 'NNN NNNN', // Moldova + '+995': 'NNN NNN NNN', // Georgia + '+374': 'NN NNNNNN', // Armenia + '+993': 'NNN NNNNN', // Turkmenistan + '+996': 'NNN NNNNNN', // Kyrgyzstan + '+998': 'NN NNN NNNN', // Uzbekistan + '+976': 'NN NNN NNNN', // Mongolia + '+855': 'NNN NNN NNN', // Cambodia + '+856': 'NNN NNN NNNN', // Laos +}; export class Email { @ApiProperty({ + type: String, description: 'The email address', }) email_address: string; @ApiProperty({ - description: 'The email address type', + type: String, + description: + 'The email address type. Authorized values are either PERSONAL or WORK.', }) + @IsIn(['PERSONAL', 'WORK']) + @IsString() email_address_type: string; @ApiPropertyOptional({ + type: String, description: 'The owner type of an email', }) owner_type?: string; @@ -189,57 +348,89 @@ export class Email { export class Phone { @ApiProperty({ - description: 'The phone number', + type: String, + description: + 'The phone number starting with a plus (+) followed by the country code (e.g +336676778890 for France)', }) + @IsString() phone_number: string; @ApiProperty({ - description: 'The phone type', + type: String, + description: 'The phone type. Authorized values are either MOBILE or WORK', }) + @IsIn(['MOBILE', 'WORK']) + @IsString() phone_type: string; - @ApiPropertyOptional({ description: 'The owner type of a phone number' }) + @ApiPropertyOptional({ + type: String, + description: 'The owner type of a phone number', + }) + @IsString() + @IsOptional() owner_type?: string; } export class Address { @ApiProperty({ + type: String, description: 'The street', }) + @IsString() street_1: string; @ApiProperty({ + type: String, description: 'More information about the street ', }) + @IsString() + @IsOptional() street_2?: string; @ApiProperty({ + type: String, description: 'The city', }) + @IsString() city: string; @ApiProperty({ + type: String, description: 'The state', }) + @IsString() state: string; @ApiProperty({ + type: String, description: 'The postal code', }) + @IsString() postal_code: string; @ApiProperty({ - description: 'The country', + type: String, + description: 'The country.', }) + @IsString() country: string; @ApiProperty({ - description: 'The address type', + type: String, + description: + 'The address type. Authorized values are either PERSONAL or WORK.', }) + @IsIn(['PERSONAL', 'WORK']) + @IsOptional() + @IsString() address_type?: string; @ApiProperty({ + type: String, description: 'The owner type of the address', }) + @IsOptional() + @IsString() owner_type?: string; } diff --git a/packages/api/src/crm/company/types/model.unified.ts b/packages/api/src/crm/company/types/model.unified.ts index 399cc6f77..f0635f997 100644 --- a/packages/api/src/crm/company/types/model.unified.ts +++ b/packages/api/src/crm/company/types/model.unified.ts @@ -1,6 +1,12 @@ -import { Address, Email, Phone } from '@crm/@utils/@types'; +import { Address, Email, Industry, Phone } from '@crm/@utils/@types'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNumber, IsOptional, IsString } from 'class-validator'; +import { + IsEnum, + IsNumber, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; export class UnifiedCompanyInput { @ApiProperty({ type: String, description: 'The name of the company' }) @@ -9,9 +15,10 @@ export class UnifiedCompanyInput { @ApiPropertyOptional({ type: String, - description: 'The industry of the company', + description: + 'The industry of the company. Authorized values can be found in the Industry enum.', }) - @IsString() + @IsEnum(Industry) @IsOptional() industry?: string; @@ -27,8 +34,8 @@ export class UnifiedCompanyInput { type: String, description: 'The uuid of the user who owns the company', }) - @IsString() @IsOptional() + @IsUUID() user_id?: string; @ApiPropertyOptional({ @@ -63,7 +70,7 @@ export class UnifiedCompanyInput { export class UnifiedCompanyOutput extends UnifiedCompanyInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the company' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/crm/company/utils/index.ts b/packages/api/src/crm/company/utils/index.ts index d37b6eb36..4ba67b5ec 100644 --- a/packages/api/src/crm/company/utils/index.ts +++ b/packages/api/src/crm/company/utils/index.ts @@ -1,11 +1,5 @@ import { Address } from '@crm/@utils/@types'; import { v4 as uuidv4 } from 'uuid'; -import { OpenAI } from 'openai'; - -// OpenAIApi initialization -const openai = new OpenAI({ - apiKey: process.env['OPENAI_API_KEY'], // This is the default and can be omitted -}); export function normalizeAddresses(addresses: Address[]) { const normalizedAddresses = addresses.map((addy) => ({ @@ -19,205 +13,3 @@ export function normalizeAddresses(addresses: Address[]) { return normalizedAddresses; } - -//These arrays are to maintain the history of the conversation -const conversationContext = []; -const currentMessages = []; - -export async function mapCompanyIndustryToRemote( - unified_industry_value: string, - provider_name: string, -) { - //call gpt 3.5 to associate the closest industry value based on provider api defined value - // for instance hubspot has 150 pre-defined values for industry field, we want gpt to give us the mapping - try { - switch (provider_name.toLowerCase()) { - case 'hubspot': - return await Hubspot_mapCompanyIndustryToRemote(unified_industry_value); - default: - throw new Error('provider not supported for custom industry mapping'); - } - } catch (error) { - throw new Error(error); - } -} - -async function Hubspot_mapCompanyIndustryToRemote( - unified_industry_value: string, -) { - try { - const prompt = `I have a value which defines an industry of a company. - This is someone's input and I'd love to get the closest value of this input from the following list. - Here are the 150 pre-defined values of the list. \n - ACCOUNTING\n - AIRLINES_AVIATION\n - ALTERNATIVE_DISPUTE_RESOLUTION\n - ALTERNATIVE_MEDICINE\n - ANIMATION\n - APPAREL_FASHION\n - ARCHITECTURE_PLANNING\n - ARTS_AND_CRAFTS\n - AUTOMOTIVE\n - AVIATION_AEROSPACE\n - BANKING\n - BIOTECHNOLOGY\n - BROADCAST_MEDIA\n - BUILDING_MATERIALS\n - BUSINESS_SUPPLIES_AND_EQUIPMENT\n - CAPITAL_MARKETS\n - CHEMICALS\n - CIVIC_SOCIAL_ORGANIZATION\n - CIVIL_ENGINEERING\n - COMMERCIAL_REAL_ESTATE\n - COMPUTER_NETWORK_SECURITY\n - COMPUTER_GAMES\n - COMPUTER_HARDWARE\n - COMPUTER_NETWORKING\n - COMPUTER_SOFTWARE\n - INTERNET\n - CONSTRUCTION\n - CONSUMER_ELECTRONICS\n - CONSUMER_GOODS\n - CONSUMER_SERVICES\n - COSMETICS\n - DAIRY\n - DEFENSE_SPACE\n - DESIGN\n - EDUCATION_MANAGEMENT\n - E_LEARNING\n - ELECTRICAL_ELECTRONIC_MANUFACTURING\n - ENTERTAINMENT\n - ENVIRONMENTAL_SERVICES\n - EVENTS_SERVICES\n - EXECUTIVE_OFFICE\n - FACILITIES_SERVICES\n - FARMING\n - FINANCIAL_SERVICES\n - FINE_ART\n - FISHERY\n - FOOD_BEVERAGES\n - FOOD_PRODUCTION\n - FUND_RAISING\n - FURNITURE\n - GAMBLING_CASINOS\n - GLASS_CERAMICS_CONCRETE\n - GOVERNMENT_ADMINISTRATION\n - GOVERNMENT_RELATIONS\n - GRAPHIC_DESIGN\n - HEALTH_WELLNESS_AND_FITNESS\n - HIGHER_EDUCATION\n - HOSPITAL_HEALTH_CARE\n - HOSPITALITY\n - HUMAN_RESOURCES\n - IMPORT_AND_EXPORT\n - INDIVIDUAL_FAMILY_SERVICES\n - INDUSTRIAL_AUTOMATION\n - INFORMATION_SERVICES\n - INFORMATION_TECHNOLOGY_AND_SERVICES\n - INSURANCE\n - INTERNATIONAL_AFFAIRS\n - INTERNATIONAL_TRADE_AND_DEVELOPMENT\n - INVESTMENT_BANKING\n - INVESTMENT_MANAGEMENT\n - JUDICIARY\n - LAW_ENFORCEMENT\n - LAW_PRACTICE\n - LEGAL_SERVICES\n - LEGISLATIVE_OFFICE\n - LEISURE_TRAVEL_TOURISM\n - LIBRARIES\n - LOGISTICS_AND_SUPPLY_CHAIN\n - LUXURY_GOODS_JEWELRY\n - MACHINERY\n - MANAGEMENT_CONSULTING\n - MARITIME\n - MARKET_RESEARCH\n - MARKETING_AND_ADVERTISING\n - MECHANICAL_OR_INDUSTRIAL_ENGINEERING\n - MEDIA_PRODUCTION\n - MEDICAL_DEVICES\n - MEDICAL_PRACTICE\n - MENTAL_HEALTH_CARE\n - MILITARY\n - MINING_METALS\n - MOTION_PICTURES_AND_FILM\n - MUSEUMS_AND_INSTITUTIONS\n - MUSIC\n - NANOTECHNOLOGY\n - NEWSPAPERS\n - NON_PROFIT_ORGANIZATION_MANAGEMENT\n - OIL_ENERGY\n - ONLINE_MEDIA\n - OUTSOURCING_OFFSHORING\n - PACKAGE_FREIGHT_DELIVERY\n - PACKAGING_AND_CONTAINERS\n - PAPER_FOREST_PRODUCTS\n - PERFORMING_ARTS\n - PHARMACEUTICALS\n - PHILANTHROPY\n - PHOTOGRAPHY\n - PLASTICS\n - POLITICAL_ORGANIZATION\n - PRIMARY_SECONDARY_EDUCATION\n - PRINTING\n - PROFESSIONAL_TRAINING_COACHING\n - PROGRAM_DEVELOPMENT\n - PUBLIC_POLICY\n - PUBLIC_RELATIONS_AND_COMMUNICATIONS\n - PUBLIC_SAFETY\n - PUBLISHING\n - RAILROAD_MANUFACTURE\n - RANCHING\n - REAL_ESTATE\n - RECREATIONAL_FACILITIES_AND_SERVICES\n - RELIGIOUS_INSTITUTIONS\n - RENEWABLES_ENVIRONMENT\n - RESEARCH\n - RESTAURANTS\n - RETAIL\n - SECURITY_AND_INVESTIGATIONS\n - SEMICONDUCTORS\n - SHIPBUILDING\n - SPORTING_GOODS\n - SPORTS\n - STAFFING_AND_RECRUITING\n - SUPERMARKETS\n - TELECOMMUNICATIONS\n - TEXTILES\n - THINK_TANKS\n - TOBACCO\n - TRANSLATION_AND_LOCALIZATION\n - TRANSPORTATION_TRUCKING_RAILROAD\n - UTILITIES\n - VENTURE_CAPITAL_PRIVATE_EQUITY\n - VETERINARY\n - WAREHOUSING\n - WHOLESALE\n - WINE_AND_SPIRITS\n - WIRELESS\n - WRITING_AND_EDITING\n - I want you to just return 1 word amongst this huge list which is closest to the input I'm giving you : ${unified_industry_value}\n - I repeat, you MUST answer just the right word, don't do any sentence. - `; - const modelId = 'gpt-3.5-turbo'; - const promptText = `${prompt}\n\nResponse:`; - - // Restore the previous context - for (const [inputText, responseText] of conversationContext) { - currentMessages.push({ role: 'user', content: inputText }); - currentMessages.push({ role: 'assistant', content: responseText }); - } - - // Stores the new message - currentMessages.push({ role: 'user', content: promptText }); - - const result = await openai.chat.completions.create({ - model: modelId, - messages: currentMessages, - }); - - const responseText = result.choices.shift().message.content; - conversationContext.push([promptText, responseText]); - return responseText; - } catch (error) {} -} diff --git a/packages/api/src/crm/contact/services/attio/mappers.ts b/packages/api/src/crm/contact/services/attio/mappers.ts index a78ae25e6..63398e15a 100644 --- a/packages/api/src/crm/contact/services/attio/mappers.ts +++ b/packages/api/src/crm/contact/services/attio/mappers.ts @@ -42,24 +42,9 @@ export class AttioContactMapper implements IContactMapper { } if (primaryPhone) { - result.values.phone_numbers = [{ original_phone_number: primaryPhone }]; + result.values.phone_numbers = [primaryPhone]; } - // if (source.user_id) { - // const owner = await this.utils.getUser(source.user_id); - // if (owner) { - // result.id = { - // object_id: Number(owner.remote_id), - // name: owner.name, - // email: owner.email, - // has_pic: 0, - // pic_hash: '', - // active_flag: false, - // value: 0, - // }; - // } - // } - if (customFieldMappings && source.field_mappings) { for (const [k, v] of Object.entries(source.field_mappings)) { const mapping = customFieldMappings.find( diff --git a/packages/api/src/crm/contact/services/attio/types.ts b/packages/api/src/crm/contact/services/attio/types.ts index 950da6ac9..c4bb4d423 100644 --- a/packages/api/src/crm/contact/services/attio/types.ts +++ b/packages/api/src/crm/contact/services/attio/types.ts @@ -120,7 +120,7 @@ export interface AttioContact { twitter_follower_count?: NumberValueItem[]; instagram?: TextValueItem[]; first_email_interaction?: InteractionValueItem[]; - phone_numbers?: PhoneValueItem[]; + phone_numbers?: PhoneValueItem[] | string[]; }; } diff --git a/packages/api/src/crm/contact/types/model.unified.ts b/packages/api/src/crm/contact/types/model.unified.ts index 4df599ec6..b4fcd6c3d 100644 --- a/packages/api/src/crm/contact/types/model.unified.ts +++ b/packages/api/src/crm/contact/types/model.unified.ts @@ -1,6 +1,6 @@ import { Address, Email, Phone } from '@crm/@utils/@types'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedContactInput { @ApiProperty({ type: String, description: 'The first name of the contact' }) @@ -33,7 +33,7 @@ export class UnifiedContactInput { type: String, description: 'The uuid of the user who owns the contact', }) - @IsString() + @IsUUID() @IsOptional() user_id?: string; @@ -48,7 +48,7 @@ export class UnifiedContactInput { export class UnifiedContactOutput extends UnifiedContactInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the contact' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/crm/contact/utils/index.ts b/packages/api/src/crm/contact/utils/index.ts index e7ced2ea9..4af036fa9 100644 --- a/packages/api/src/crm/contact/utils/index.ts +++ b/packages/api/src/crm/contact/utils/index.ts @@ -1,4 +1,4 @@ -import { Email, Phone } from '@crm/@utils/@types'; +import { countryPhoneFormats, Email, Phone } from '@crm/@utils/@types'; import { v4 as uuidv4 } from 'uuid'; import { PrismaClient } from '@prisma/client'; @@ -44,6 +44,26 @@ export class Utils { normalizedPhones, }; } + + extractPhoneDetails(phone_number: string): { + country_code: string; + base_number: string; + } { + let country_code = ''; + let base_number: string = phone_number; + + // Find the matching country code + for (const [countryCode, _] of Object.entries(countryPhoneFormats)) { + if (phone_number.startsWith(countryCode)) { + country_code = countryCode; + base_number = phone_number.substring(countryCode.length); + break; + } + } + + return { country_code, base_number }; + } + async getRemoteIdFromUserUuid(uuid: string) { try { const res = await this.prisma.crm_users.findFirst({ diff --git a/packages/api/src/crm/deal/types/model.unified.ts b/packages/api/src/crm/deal/types/model.unified.ts index b4d9afe25..1a7c86767 100644 --- a/packages/api/src/crm/deal/types/model.unified.ts +++ b/packages/api/src/crm/deal/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedDealInput { @ApiProperty({ type: String, description: 'The name of the deal' }) @@ -18,7 +18,7 @@ export class UnifiedDealInput { type: String, description: 'The uuid of the user who is on the deal', }) - @IsString() + @IsUUID() @IsOptional() user_id?: string; @@ -26,7 +26,7 @@ export class UnifiedDealInput { type: String, description: 'The uuid of the stage of the deal', }) - @IsString() + @IsUUID() @IsOptional() stage_id?: string; @@ -34,7 +34,7 @@ export class UnifiedDealInput { type: String, description: 'The uuid of the company tied to the deal', }) - @IsString() + @IsUUID() @IsOptional() company_id?: string; @@ -49,7 +49,7 @@ export class UnifiedDealInput { export class UnifiedDealOutput extends UnifiedDealInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the deal' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/crm/engagement/services/hubspot/mappers.ts b/packages/api/src/crm/engagement/services/hubspot/mappers.ts index c5044552a..2bef68acf 100644 --- a/packages/api/src/crm/engagement/services/hubspot/mappers.ts +++ b/packages/api/src/crm/engagement/services/hubspot/mappers.ts @@ -57,7 +57,7 @@ export class HubspotEngagementMapper implements IEngagementMapper { // Assuming direction is used to determine call status hs_call_status: '', hs_call_duration: '', // Needs appropriate mapping - hs_call_direction: source.direction || '', // Needs appropriate mapping + hs_call_direction: source.direction || '', // Needs appropriate mapping hubspot_owner_id: '', hs_call_to_number: '', // Needs appropriate mapping hs_call_from_number: '', // Needs appropriate mapping @@ -146,7 +146,7 @@ export class HubspotEngagementMapper implements IEngagementMapper { ? 'INCOMING_EMAIL' : source.direction === 'OUTBOUND' ? 'FORWARDED_EMAIL' - : '', + : '', hs_email_to_lastname: '', // Placeholder, needs appropriate mapping hs_email_sender_email: '', // Placeholder, needs appropriate mapping hs_email_to_firstname: '', // Placeholder, needs appropriate mapping diff --git a/packages/api/src/crm/engagement/types/model.unified.ts b/packages/api/src/crm/engagement/types/model.unified.ts index 6ebc3d19b..40fb989d2 100644 --- a/packages/api/src/crm/engagement/types/model.unified.ts +++ b/packages/api/src/crm/engagement/types/model.unified.ts @@ -1,25 +1,29 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsIn, IsOptional, IsString } from 'class-validator'; +import { IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedEngagementInput { - @ApiPropertyOptional({ type: String, description: 'The content of the engagement' }) + @ApiPropertyOptional({ + type: String, + description: 'The content of the engagement', + }) @IsString() @IsOptional() content?: string; - @ApiPropertyOptional({ + @ApiPropertyOptional({ type: String, - description: 'The direction of the engagement. Authorized values are INBOUND or OUTBOUND' + description: + 'The direction of the engagement. Authorized values are INBOUND or OUTBOUND', }) @IsIn(['INBOUND', 'OUTBOUND'], { - message: "Direction must be either INBOUND or OUTBOUND" + message: 'Direction must be either INBOUND or OUTBOUND', }) @IsOptional() direction?: string; @ApiPropertyOptional({ type: String, - description: 'The subject of the engagement' + description: 'The subject of the engagement', }) @IsString() @IsOptional() @@ -39,7 +43,7 @@ export class UnifiedEngagementInput { 'The type of the engagement. Authorized values are EMAIL, CALL or MEETING', }) @IsIn(['EMAIL', 'CALL', 'MEETING'], { - message: "Type must be either EMAIL, CALL or MEETING" + message: 'Type must be either EMAIL, CALL or MEETING', }) type: string; @@ -47,7 +51,7 @@ export class UnifiedEngagementInput { type: String, description: 'The uuid of the user tied to the engagement', }) - @IsString() + @IsUUID() @IsOptional() user_id?: string; @@ -55,7 +59,7 @@ export class UnifiedEngagementInput { type: String, description: 'The uuid of the company tied to the engagement', }) - @IsString() + @IsUUID() @IsOptional() company_id?: string; // uuid of Company object @@ -76,8 +80,11 @@ export class UnifiedEngagementInput { } export class UnifiedEngagementOutput extends UnifiedEngagementInput { - @ApiPropertyOptional({ type: String, description: 'The uuid of the engagement' }) - @IsString() + @ApiPropertyOptional({ + type: String, + description: 'The uuid of the engagement', + }) + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/crm/note/types/model.unified.ts b/packages/api/src/crm/note/types/model.unified.ts index 6e2d4a46f..1a6fb3fda 100644 --- a/packages/api/src/crm/note/types/model.unified.ts +++ b/packages/api/src/crm/note/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedNoteInput { @ApiProperty({ type: String, description: 'The content of the note' }) @@ -10,7 +10,7 @@ export class UnifiedNoteInput { type: String, description: 'The uuid of the user tied the note', }) - @IsString() + @IsUUID() @IsOptional() user_id?: string; @@ -18,7 +18,7 @@ export class UnifiedNoteInput { type: String, description: 'The uuid of the company tied to the note', }) - @IsString() + @IsUUID() @IsOptional() company_id?: string; @@ -26,7 +26,7 @@ export class UnifiedNoteInput { type: String, description: 'The uuid fo the contact tied to the note', }) - @IsString() + @IsUUID() @IsOptional() contact_id?: string; @@ -34,7 +34,7 @@ export class UnifiedNoteInput { type: String, description: 'The uuid of the deal tied to the note', }) - @IsString() + @IsUUID() @IsOptional() deal_id?: string; @@ -49,7 +49,7 @@ export class UnifiedNoteInput { export class UnifiedNoteOutput extends UnifiedNoteInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the note' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/crm/stage/types/model.unified.ts b/packages/api/src/crm/stage/types/model.unified.ts index 44bcdfa4d..96831e68c 100644 --- a/packages/api/src/crm/stage/types/model.unified.ts +++ b/packages/api/src/crm/stage/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedStageInput { @ApiProperty({ type: String, description: 'The name of the stage' }) @@ -17,7 +17,7 @@ export class UnifiedStageInput { export class UnifiedStageOutput extends UnifiedStageInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the stage' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/crm/task/services/hubspot/mappers.ts b/packages/api/src/crm/task/services/hubspot/mappers.ts index dbc32f171..eac091fe5 100644 --- a/packages/api/src/crm/task/services/hubspot/mappers.ts +++ b/packages/api/src/crm/task/services/hubspot/mappers.ts @@ -104,5 +104,4 @@ export class HubspotTaskMapper implements ITaskMapper { // Additional fields mapping based on UnifiedTaskOutput structure }; } - } diff --git a/packages/api/src/crm/task/types/model.unified.ts b/packages/api/src/crm/task/types/model.unified.ts index d7939fdef..d3e278676 100644 --- a/packages/api/src/crm/task/types/model.unified.ts +++ b/packages/api/src/crm/task/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsIn, IsOptional, IsString } from 'class-validator'; +import { IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedTaskInput { @ApiProperty({ type: String, description: 'The subject of the task' }) @@ -32,7 +32,7 @@ export class UnifiedTaskInput { type: String, description: 'The uuid of the user tied to the task', }) - @IsString() + @IsUUID() @IsOptional() user_id?: string; @@ -40,7 +40,7 @@ export class UnifiedTaskInput { type: String, description: 'The uuid fo the company tied to the task', }) - @IsString() + @IsUUID() @IsOptional() company_id?: string; @@ -63,7 +63,7 @@ export class UnifiedTaskInput { export class UnifiedTaskOutput extends UnifiedTaskInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the task' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/crm/user/types/model.unified.ts b/packages/api/src/crm/user/types/model.unified.ts index fda3bcbf8..0758a8598 100644 --- a/packages/api/src/crm/user/types/model.unified.ts +++ b/packages/api/src/crm/user/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedUserInput { @ApiProperty({ type: String, description: 'The name of the user' }) @@ -21,7 +21,7 @@ export class UnifiedUserInput { export class UnifiedUserOutput extends UnifiedUserInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the user' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/account/types/model.unified.ts b/packages/api/src/ticketing/account/types/model.unified.ts index 63ca08413..d0e70293b 100644 --- a/packages/api/src/ticketing/account/types/model.unified.ts +++ b/packages/api/src/ticketing/account/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedAccountInput { @ApiProperty({ type: String, description: 'The name of the account' }) @@ -24,7 +24,7 @@ export class UnifiedAccountInput { export class UnifiedAccountOutput extends UnifiedAccountInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the account' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/attachment/types/model.unified.ts b/packages/api/src/ticketing/attachment/types/model.unified.ts index 4d903488f..710bbed29 100644 --- a/packages/api/src/ticketing/attachment/types/model.unified.ts +++ b/packages/api/src/ticketing/attachment/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedAttachmentInput { @ApiProperty({ type: String, description: 'The file name of the attachment' }) @@ -32,7 +32,7 @@ export class UnifiedAttachmentOutput extends UnifiedAttachmentInput { type: String, description: 'The uuid of the attachment', }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/collection/types/model.unified.ts b/packages/api/src/ticketing/collection/types/model.unified.ts index 09a425772..4329514c6 100644 --- a/packages/api/src/ticketing/collection/types/model.unified.ts +++ b/packages/api/src/ticketing/collection/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsIn, IsOptional, IsString } from 'class-validator'; +import { IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedCollectionInput { @ApiProperty({ @@ -34,7 +34,7 @@ export class UnifiedCollectionOutput extends UnifiedCollectionInput { type: String, description: 'The uuid of the collection', }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/comment/services/zendesk/mappers.ts b/packages/api/src/ticketing/comment/services/zendesk/mappers.ts index d42534d82..ea6e567e7 100644 --- a/packages/api/src/ticketing/comment/services/zendesk/mappers.ts +++ b/packages/api/src/ticketing/comment/services/zendesk/mappers.ts @@ -30,11 +30,15 @@ export class ZendeskCommentMapper implements ICommentMapper { type: 'Comment', }; - if(source.creator_type === "USER"){ - result.author_id = Number(await this.utils.getUserRemoteIdFromUuid(source.user_id)); + if (source.creator_type === 'USER') { + result.author_id = Number( + await this.utils.getUserRemoteIdFromUuid(source.user_id), + ); } - if(source.creator_type === "CONTACT"){ - result.author_id = Number(await this.utils.getContactRemoteIdFromUuid(source.contact_id)); + if (source.creator_type === 'CONTACT') { + result.author_id = Number( + await this.utils.getContactRemoteIdFromUuid(source.contact_id), + ); } if (source.attachments) { diff --git a/packages/api/src/ticketing/comment/types/model.unified.ts b/packages/api/src/ticketing/comment/types/model.unified.ts index 203ef7866..5f5e4a3f3 100644 --- a/packages/api/src/ticketing/comment/types/model.unified.ts +++ b/packages/api/src/ticketing/comment/types/model.unified.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { UnifiedAttachmentOutput } from '@ticketing/attachment/types/model.unified'; -import { IsBoolean, IsIn, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedCommentInput { @ApiProperty({ type: String, description: 'The body of the comment' }) @@ -38,7 +38,7 @@ export class UnifiedCommentInput { type: String, description: 'The uuid of the ticket the comment is tied to', }) - @IsString() + @IsUUID() @IsOptional() ticket_id?: string; // uuid of Ticket object @@ -47,7 +47,7 @@ export class UnifiedCommentInput { description: 'The uuid of the contact which the comment belongs to (if no user_id specified)', }) - @IsString() + @IsUUID() @IsOptional() contact_id?: string; // uuid of Contact object @@ -56,7 +56,7 @@ export class UnifiedCommentInput { description: 'The uuid of the user which the comment belongs to (if no contact_id specified)', }) - @IsString() + @IsUUID() @IsOptional() user_id?: string; // uuid of User object @@ -70,7 +70,7 @@ export class UnifiedCommentInput { export class UnifiedCommentOutput extends UnifiedCommentInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the comment' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/contact/types/model.unified.ts b/packages/api/src/ticketing/contact/types/model.unified.ts index c7ba326ba..90fdc5c34 100644 --- a/packages/api/src/ticketing/contact/types/model.unified.ts +++ b/packages/api/src/ticketing/contact/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedContactInput { @ApiProperty({ @@ -43,7 +43,7 @@ export class UnifiedContactInput { export class UnifiedContactOutput extends UnifiedContactInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the contact' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/tag/types/model.unified.ts b/packages/api/src/ticketing/tag/types/model.unified.ts index 0e60f0b6c..3693535c3 100644 --- a/packages/api/src/ticketing/tag/types/model.unified.ts +++ b/packages/api/src/ticketing/tag/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedTagInput { @ApiProperty({ @@ -20,7 +20,7 @@ export class UnifiedTagInput { export class UnifiedTagOutput extends UnifiedTagInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the tag' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/team/types/model.unified.ts b/packages/api/src/ticketing/team/types/model.unified.ts index e0cf61bd6..b6b9e4013 100644 --- a/packages/api/src/ticketing/team/types/model.unified.ts +++ b/packages/api/src/ticketing/team/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedTeamInput { @ApiProperty({ @@ -28,7 +28,7 @@ export class UnifiedTeamInput { export class UnifiedTeamOutput extends UnifiedTeamInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the team' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts b/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts index 4949ae775..1abf8dd1b 100644 --- a/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/gorgias/mappers.ts @@ -48,7 +48,7 @@ export class GorgiasTicketMapper implements ITicketMapper { } : null, }, - ], + ], }; if (source.status) { diff --git a/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts b/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts index 3920ec241..4958c6f7a 100644 --- a/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/hubspot/mappers.ts @@ -66,10 +66,10 @@ export class HubspotTicketMapper implements ITicketMapper { return { name: ticket.properties.name, - status: "", // hs_pipeline_stage: '', - description: ticket.properties.description, + status: '', // hs_pipeline_stage: '', + description: ticket.properties.description, due_date: new Date(ticket.properties.createdate), - type: "", //ticket.properties.hs_pipeline, + type: '', //ticket.properties.hs_pipeline, parent_ticket: '', // Define how you determine the parent ticket completed_at: new Date(ticket.properties.hs_lastmodifieddate), priority: ticket.properties.hs_ticket_priority, diff --git a/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts b/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts index e7531e373..0d9e4f4f4 100644 --- a/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/zendesk/mappers.ts @@ -41,7 +41,9 @@ export class ZendeskTicketMapper implements ITicketMapper { result.due_at = source.due_date?.toISOString(); } if (source.priority) { - result.priority = (source.priority == "MEDIUM" ? 'normal' : source.priority.toLowerCase()) as 'urgent' | 'high' | 'normal' | 'low'; + result.priority = ( + source.priority == 'MEDIUM' ? 'normal' : source.priority.toLowerCase() + ) as 'urgent' | 'high' | 'normal' | 'low'; } if (source.status) { result.status = source.status.toLowerCase() as 'open' | 'closed'; @@ -50,7 +52,11 @@ export class ZendeskTicketMapper implements ITicketMapper { result.tags = source.tags; } if (source.type) { - result.type = source.type.toLowerCase() as 'problem' | 'incident' | 'question' | 'task'; + result.type = source.type.toLowerCase() as + | 'problem' + | 'incident' + | 'question' + | 'task'; } if (customFieldMappings && source.field_mappings) { @@ -122,10 +128,11 @@ export class ZendeskTicketMapper implements ITicketMapper { const unifiedTicket: UnifiedTicketOutput = { name: ticket.subject, - status: ticket.status === "new" || ticket.status === "open" ? 'OPEN' : "CLOSED", // todo: handle pending status ? + status: + ticket.status === 'new' || ticket.status === 'open' ? 'OPEN' : 'CLOSED', // todo: handle pending status ? description: ticket.description, due_date: ticket.due_at ? new Date(ticket.due_at) : undefined, - type: ticket.type === "incident" ? "PROBLEM" : ticket.type.toUpperCase(), + type: ticket.type === 'incident' ? 'PROBLEM' : ticket.type.toUpperCase(), parent_ticket: undefined, // If available, add logic to map parent ticket tags: ticket.tags, completed_at: new Date(ticket.updated_at), diff --git a/packages/api/src/ticketing/ticket/types/model.unified.ts b/packages/api/src/ticketing/ticket/types/model.unified.ts index 9abcf9839..7c3c31b34 100644 --- a/packages/api/src/ticketing/ticket/types/model.unified.ts +++ b/packages/api/src/ticketing/ticket/types/model.unified.ts @@ -1,6 +1,6 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { UnifiedCommentInput } from '@ticketing/comment/types/model.unified'; -import { IsIn, IsOptional, IsString } from 'class-validator'; +import { IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedTicketInput { @ApiProperty({ @@ -50,15 +50,15 @@ export class UnifiedTicketInput { type: String, description: 'The uuid of the parent ticket', }) - @IsString() + @IsUUID() @IsOptional() parent_ticket?: string; @ApiPropertyOptional({ type: String, - description: 'The uuid of the project the ticket belongs to', + description: 'The uuid of the collection (project) the ticket belongs to', }) - @IsString() + @IsUUID() @IsOptional() project_id?: string; @@ -105,7 +105,7 @@ export class UnifiedTicketInput { type: String, description: 'The uuid of the account which the ticket belongs to', }) - @IsString() + @IsUUID() @IsOptional() account_id?: string; @@ -113,7 +113,7 @@ export class UnifiedTicketInput { type: String, description: 'The uuid of the contact which the ticket belongs to', }) - @IsString() + @IsUUID() @IsOptional() contact_id?: string; @@ -127,7 +127,7 @@ export class UnifiedTicketInput { } export class UnifiedTicketOutput extends UnifiedTicketInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the ticket' }) - @IsString() + @IsUUID() @IsOptional() id?: string; diff --git a/packages/api/src/ticketing/user/types/model.unified.ts b/packages/api/src/ticketing/user/types/model.unified.ts index 2e5cd2bfa..a4f2447cd 100644 --- a/packages/api/src/ticketing/user/types/model.unified.ts +++ b/packages/api/src/ticketing/user/types/model.unified.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsUUID } from 'class-validator'; export class UnifiedUserInput { @ApiProperty({ @@ -28,7 +28,7 @@ export class UnifiedUserInput { type: String, description: 'The account or organization the user is part of', }) - @IsString() + @IsUUID() @IsOptional() account_id?: string; @@ -43,7 +43,7 @@ export class UnifiedUserInput { export class UnifiedUserOutput extends UnifiedUserInput { @ApiPropertyOptional({ type: String, description: 'The uuid of the user' }) - @IsString() + @IsUUID() @IsOptional() id?: string; From d801fb53bd88febea7dee6ad6990dc588cb5a90d Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 30 Apr 2024 21:07:33 +0200 Subject: [PATCH 04/11] :ambulance: Sync functions --- packages/api/src/@core/core.module.ts | 3 + .../api/src/@core/sync/sync.controller.ts | 40 +++++++ packages/api/src/@core/sync/sync.module.ts | 47 ++++++++ packages/api/src/@core/sync/sync.service.ts | 105 ++++++++++++++++++ packages/api/src/crm/@utils/@types/index.ts | 2 +- .../api/src/crm/company/sync/sync.service.ts | 27 ++++- .../api/src/crm/contact/contact.module.ts | 6 +- .../api/src/crm/contact/sync/sync.service.ts | 21 +++- packages/api/src/crm/crm.module.ts | 1 - .../api/src/crm/deal/sync/sync.service.ts | 16 ++- .../src/crm/engagement/sync/sync.service.ts | 14 ++- .../api/src/crm/note/sync/sync.service.ts | 16 ++- .../api/src/crm/stage/sync/sync.service.ts | 16 ++- .../api/src/crm/task/sync/sync.service.ts | 16 ++- .../api/src/crm/user/sync/sync.service.ts | 16 ++- .../ticketing/account/sync/sync.service.ts | 16 ++- .../ticketing/collection/sync/sync.service.ts | 16 ++- .../ticketing/comment/sync/sync.service.ts | 16 ++- .../ticketing/contact/sync/sync.service.ts | 16 ++- .../src/ticketing/tag/sync/sync.service.ts | 16 ++- .../src/ticketing/team/sync/sync.service.ts | 16 ++- .../src/ticketing/ticket/sync/sync.service.ts | 16 ++- .../src/ticketing/user/sync/sync.service.ts | 16 ++- packages/shared/src/utils.ts | 3 +- 24 files changed, 405 insertions(+), 72 deletions(-) create mode 100644 packages/api/src/@core/sync/sync.controller.ts create mode 100644 packages/api/src/@core/sync/sync.module.ts create mode 100644 packages/api/src/@core/sync/sync.service.ts diff --git a/packages/api/src/@core/core.module.ts b/packages/api/src/@core/core.module.ts index 8a6c04a3b..cb2233734 100644 --- a/packages/api/src/@core/core.module.ts +++ b/packages/api/src/@core/core.module.ts @@ -12,6 +12,7 @@ import { WebhookModule } from './webhook/webhook.module'; import { EnvironmentModule } from './environment/environment.module'; import { EncryptionService } from './encryption/encryption.service'; import { ConnectionsStrategiesModule } from './connections-strategies/connections-strategies.module'; +import { SyncModule } from './sync/sync.module'; @Module({ imports: [ @@ -27,6 +28,7 @@ import { ConnectionsStrategiesModule } from './connections-strategies/connection WebhookModule, EnvironmentModule, ConnectionsStrategiesModule, + SyncModule, ], exports: [ AuthModule, @@ -41,6 +43,7 @@ import { ConnectionsStrategiesModule } from './connections-strategies/connection WebhookModule, EnvironmentModule, ConnectionsStrategiesModule, + SyncModule, ], providers: [EncryptionService], }) diff --git a/packages/api/src/@core/sync/sync.controller.ts b/packages/api/src/@core/sync/sync.controller.ts new file mode 100644 index 000000000..7bd7a0d6c --- /dev/null +++ b/packages/api/src/@core/sync/sync.controller.ts @@ -0,0 +1,40 @@ +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { CoreSyncService } from './sync.service'; +import { LoggerService } from '../logger/logger.service'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiKeyAuthGuard } from '@@core/auth/guards/api-key.guard'; + +@ApiTags('sync') +@Controller('sync') +export class SyncController { + constructor( + private readonly syncService: CoreSyncService, + private logger: LoggerService, + ) { + this.logger.setContext(SyncController.name); + } + + @ApiOperation({ + operationId: 'getSyncStatus', + summary: 'Retrieve sync status of a certain vertical', + }) + @ApiResponse({ status: 200 }) + @Get('status/:vertical') + getSyncStatus(@Param('vertical') vertical: string) { + return this.syncService.getSyncStatus(vertical); + } + + //this route must be protected for premium users (regular sync is one every 24 hours) + @ApiOperation({ + operationId: 'resync', + summary: 'Resync common objects across a vertical', + }) + @ApiResponse({ status: 200 }) + @UseGuards(ApiKeyAuthGuard) + @Get('resync/:vertical') + resync(@Param('vertical') vertical: string) { + // TODO: get the right user_id of the premium user using the api key + const user_id = ''; + return this.syncService.resync(vertical, user_id); + } +} diff --git a/packages/api/src/@core/sync/sync.module.ts b/packages/api/src/@core/sync/sync.module.ts new file mode 100644 index 000000000..5859c2764 --- /dev/null +++ b/packages/api/src/@core/sync/sync.module.ts @@ -0,0 +1,47 @@ +import { Module } from '@nestjs/common'; +import { CoreSyncService } from './sync.service'; +import { SyncController } from './sync.controller'; +import { LoggerService } from '../logger/logger.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { SyncService as CrmCompanySyncService } from '@crm/company/sync/sync.service'; +import { SyncService as CrmContactSyncService } from '@crm/contact/sync/sync.service'; +import { SyncService as CrmDealSyncService } from '@crm/deal/sync/sync.service'; +import { SyncService as CrmEngagementSyncService } from '@crm/engagement/sync/sync.service'; +import { SyncService as CrmNoteSyncService } from '@crm/note/sync/sync.service'; +import { SyncService as CrmStageSyncService } from '@crm/stage/sync/sync.service'; +import { SyncService as CrmTaskSyncService } from '@crm/task/sync/sync.service'; +import { SyncService as CrmUserSyncService } from '@crm/user/sync/sync.service'; +import { SyncService as TicketingAccountSyncService } from '@ticketing/account/sync/sync.service'; +import { SyncService as TicketingCollectionSyncService } from '@ticketing/collection/sync/sync.service'; +import { SyncService as TicketingCommentSyncService } from '@ticketing/comment/sync/sync.service'; +import { SyncService as TicketingContactSyncService } from '@ticketing/contact/sync/sync.service'; +import { SyncService as TicketingTagSyncService } from '@ticketing/tag/sync/sync.service'; +import { SyncService as TicketingTeamSyncService } from '@ticketing/team/sync/sync.service'; +import { SyncService as TicketingTicketSyncService } from '@ticketing/ticket/sync/sync.service'; +import { SyncService as TicketingUserSyncService } from '@ticketing/user/sync/sync.service'; + +@Module({ + providers: [ + CoreSyncService, + LoggerService, + PrismaService, + CrmCompanySyncService, + CrmContactSyncService, + CrmDealSyncService, + CrmEngagementSyncService, + CrmNoteSyncService, + CrmStageSyncService, + CrmTaskSyncService, + CrmUserSyncService, + TicketingAccountSyncService, + TicketingCollectionSyncService, + TicketingCommentSyncService, + TicketingContactSyncService, + TicketingTagSyncService, + TicketingTeamSyncService, + TicketingTicketSyncService, + TicketingUserSyncService, + ], + controllers: [SyncController], +}) +export class SyncModule {} diff --git a/packages/api/src/@core/sync/sync.service.ts b/packages/api/src/@core/sync/sync.service.ts new file mode 100644 index 000000000..c5463ef69 --- /dev/null +++ b/packages/api/src/@core/sync/sync.service.ts @@ -0,0 +1,105 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { LoggerService } from '../logger/logger.service'; +import { handleServiceError } from '@@core/utils/errors'; +import { SyncService as CrmCompanySyncService } from '@crm/company/sync/sync.service'; +import { SyncService as CrmContactSyncService } from '@crm/contact/sync/sync.service'; +import { SyncService as CrmDealSyncService } from '@crm/deal/sync/sync.service'; +import { SyncService as CrmEngagementSyncService } from '@crm/engagement/sync/sync.service'; +import { SyncService as CrmNoteSyncService } from '@crm/note/sync/sync.service'; +import { SyncService as CrmStageSyncService } from '@crm/stage/sync/sync.service'; +import { SyncService as CrmTaskSyncService } from '@crm/task/sync/sync.service'; +import { SyncService as CrmUserSyncService } from '@crm/user/sync/sync.service'; +import { SyncService as TicketingAccountSyncService } from '@ticketing/account/sync/sync.service'; +import { SyncService as TicketingCollectionSyncService } from '@ticketing/collection/sync/sync.service'; +import { SyncService as TicketingCommentSyncService } from '@ticketing/comment/sync/sync.service'; +import { SyncService as TicketingContactSyncService } from '@ticketing/contact/sync/sync.service'; +import { SyncService as TicketingTagSyncService } from '@ticketing/tag/sync/sync.service'; +import { SyncService as TicketingTeamSyncService } from '@ticketing/team/sync/sync.service'; +import { SyncService as TicketingTicketSyncService } from '@ticketing/ticket/sync/sync.service'; +import { SyncService as TicketingUserSyncService } from '@ticketing/user/sync/sync.service'; + +@Injectable() +export class CoreSyncService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private CrmCompanySyncService: CrmCompanySyncService, + private CrmContactSyncService: CrmContactSyncService, + private CrmDealSyncService: CrmDealSyncService, + private CrmEngagementSyncService: CrmEngagementSyncService, + private CrmNoteSyncService: CrmNoteSyncService, + private CrmStageSyncService: CrmStageSyncService, + private CrmTaskSyncService: CrmTaskSyncService, + private CrmUserSyncService: CrmUserSyncService, + private TicketingAccountSyncService: TicketingAccountSyncService, + private TicketingCollectionSyncService: TicketingCollectionSyncService, + private TicketingCommentSyncService: TicketingCommentSyncService, + private TicketingContactSyncService: TicketingContactSyncService, + private TicketingTagSyncService: TicketingTagSyncService, + private TicketingTeamSyncService: TicketingTeamSyncService, + private TicketingTicketSyncService: TicketingTicketSyncService, + private TicketingUserSyncService: TicketingUserSyncService, + ) { + this.logger.setContext(CoreSyncService.name); + } + + // we must have a sync_jobs table with 7 (verticals) rows, one of each is syncing details + async getSyncStatus(vertical: string) { + try { + } catch (error) { + handleServiceError(error, this.logger); + } + } + + // todo: test behaviour + async resync(vertical: string, user_id: string) { + // premium feature + // trigger a resync for the vertical but only for linked_users who belong to user_id account + const tasks = []; + try { + switch (vertical.toLowerCase()) { + case 'crm': + tasks.push(this.CrmCompanySyncService.syncCompanies(user_id)); + tasks.push(this.CrmContactSyncService.syncContacts(user_id)); + tasks.push(this.CrmDealSyncService.syncDeals(user_id)); + tasks.push(this.CrmEngagementSyncService.syncEngagements(user_id)); + tasks.push(this.CrmNoteSyncService.syncNotes(user_id)); + tasks.push(this.CrmStageSyncService.syncStages(user_id)); + tasks.push(this.CrmTaskSyncService.syncTasks(user_id)); + tasks.push(this.CrmUserSyncService.syncUsers(user_id)); + break; + case 'ticketing': + tasks.push(this.TicketingAccountSyncService.syncAccounts(user_id)); + tasks.push( + this.TicketingCollectionSyncService.syncCollections(user_id), + ); + tasks.push(this.TicketingCommentSyncService.syncComments(user_id)); + tasks.push(this.TicketingContactSyncService.syncContacts(user_id)); + tasks.push(this.TicketingTagSyncService.syncTags(user_id)); + tasks.push(this.TicketingTeamSyncService.syncTeams(user_id)); + tasks.push(this.TicketingTicketSyncService.syncTickets(user_id)); + tasks.push(this.TicketingUserSyncService.syncUsers(user_id)); + break; + } + return { + timestamp: new Date(), + vertical: vertical, + status: `SYNCING`, + }; + } catch (error) { + handleServiceError(error, this.logger); + } finally { + // Handle background tasks completion + Promise.allSettled(tasks).then((results) => { + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + console.log(`Task ${index} completed successfully.`); + } else if (result.status === 'rejected') { + console.log(`Task ${index} failed:`, result.reason); + } + }); + }); + } + } +} diff --git a/packages/api/src/crm/@utils/@types/index.ts b/packages/api/src/crm/@utils/@types/index.ts index 5ffb30a7e..a632ea48f 100644 --- a/packages/api/src/crm/@utils/@types/index.ts +++ b/packages/api/src/crm/@utils/@types/index.ts @@ -48,7 +48,7 @@ import { UnifiedUserInput, UnifiedUserOutput, } from '@crm/user/types/model.unified'; -import { IsEnum, IsIn, IsOptional, IsString } from 'class-validator'; +import { IsIn, IsOptional, IsString } from 'class-validator'; export enum CrmObject { company = 'company', diff --git a/packages/api/src/crm/company/sync/sync.service.ts b/packages/api/src/crm/company/sync/sync.service.ts index c3aadca02..bff4c8a47 100644 --- a/packages/api/src/crm/company/sync/sync.service.ts +++ b/packages/api/src/crm/company/sync/sync.service.ts @@ -35,19 +35,38 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncCompanies(); + //await this.syncCompanies(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_companies table //its role is to fetch all companies from providers 3rd parties and save the info inside our db - async syncCompanies() { + async syncCompanies(user_id?: string) { try { this.logger.log(`Syncing companies....`); - const users = await this.prisma.users.findMany(); + // TODO: insert inside sync_jobs table ? + /* + { + "common_object": "company", + "vertical": "crm", + "last_sync_start": "", + "next_sync_start": "", + "status": "SYNCING", + "is_initial_sync": true, + } + */ + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/crm/contact/contact.module.ts b/packages/api/src/crm/contact/contact.module.ts index a8fae3a18..787c7d71c 100644 --- a/packages/api/src/crm/contact/contact.module.ts +++ b/packages/api/src/crm/contact/contact.module.ts @@ -9,7 +9,7 @@ import { PipedriveService } from './services/pipedrive'; import { HubspotService } from './services/hubspot'; import { LoggerService } from '@@core/logger/logger.service'; import { FieldMappingService } from '@@core/field-mapping/field-mapping.service'; -import { SyncContactsService } from './sync/sync.service'; +import { SyncService } from './sync/sync.service'; import { WebhookService } from '@@core/webhook/webhook.service'; import { BullModule } from '@nestjs/bull'; import { EncryptionService } from '@@core/encryption/encryption.service'; @@ -27,7 +27,7 @@ import { ServiceRegistry } from './services/registry.service'; PrismaService, LoggerService, FieldMappingService, - SyncContactsService, + SyncService, WebhookService, EncryptionService, ServiceRegistry, @@ -38,6 +38,6 @@ import { ServiceRegistry } from './services/registry.service'; PipedriveService, HubspotService, ], - exports: [SyncContactsService], + exports: [SyncService], }) export class ContactModule {} diff --git a/packages/api/src/crm/contact/sync/sync.service.ts b/packages/api/src/crm/contact/sync/sync.service.ts index f37a6f303..da34a3b78 100644 --- a/packages/api/src/crm/contact/sync/sync.service.ts +++ b/packages/api/src/crm/contact/sync/sync.service.ts @@ -19,7 +19,7 @@ import { Utils } from '../utils'; import { CRM_PROVIDERS } from '@panora/shared'; @Injectable() -export class SyncContactsService implements OnModuleInit { +export class SyncService implements OnModuleInit { private readonly utils: Utils; constructor( @@ -29,25 +29,34 @@ export class SyncContactsService implements OnModuleInit { private webhook: WebhookService, private serviceRegistry: ServiceRegistry, ) { - this.logger.setContext(SyncContactsService.name); + this.logger.setContext(SyncService.name); this.utils = new Utils(); } async onModuleInit() { try { - await this.syncContacts(); + //await this.syncContacts(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_contacts table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db - async syncContacts() { + async syncContacts(user_id?: string) { try { this.logger.log(`Syncing contacts....`); - const users = await this.prisma.users.findMany(); + + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/crm/crm.module.ts b/packages/api/src/crm/crm.module.ts index c2d058b21..3edf3c72a 100644 --- a/packages/api/src/crm/crm.module.ts +++ b/packages/api/src/crm/crm.module.ts @@ -20,7 +20,6 @@ import { CompanyModule } from './company/company.module'; UserModule, ], providers: [], - controllers: [], exports: [ ContactModule, DealModule, diff --git a/packages/api/src/crm/deal/sync/sync.service.ts b/packages/api/src/crm/deal/sync/sync.service.ts index cf38c5059..d93ba4559 100644 --- a/packages/api/src/crm/deal/sync/sync.service.ts +++ b/packages/api/src/crm/deal/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncDeals(); + //await this.syncDeals(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_deals table //its role is to fetch all deals from providers 3rd parties and save the info inside our db - async syncDeals() { + async syncDeals(user_id?: string) { try { this.logger.log(`Syncing deals....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/crm/engagement/sync/sync.service.ts b/packages/api/src/crm/engagement/sync/sync.service.ts index 05bb08ada..efec5ed0e 100644 --- a/packages/api/src/crm/engagement/sync/sync.service.ts +++ b/packages/api/src/crm/engagement/sync/sync.service.ts @@ -38,13 +38,21 @@ export class SyncService implements OnModuleInit { } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_engagements table //its role is to fetch all engagements from providers 3rd parties and save the info inside our db - async syncEngagements() { + async syncEngagements(user_id?: string) { try { this.logger.log(`Syncing engagements....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/crm/note/sync/sync.service.ts b/packages/api/src/crm/note/sync/sync.service.ts index 237790323..871a17a8a 100644 --- a/packages/api/src/crm/note/sync/sync.service.ts +++ b/packages/api/src/crm/note/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncNotes(); + //await this.syncNotes(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_notes table //its role is to fetch all notes from providers 3rd parties and save the info inside our db - async syncNotes() { + async syncNotes(user_id?: string) { try { this.logger.log(`Syncing notes....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/crm/stage/sync/sync.service.ts b/packages/api/src/crm/stage/sync/sync.service.ts index 344f18657..f1e790880 100644 --- a/packages/api/src/crm/stage/sync/sync.service.ts +++ b/packages/api/src/crm/stage/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncStages(); + //await this.syncStages(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_stages table //its role is to fetch all stages from providers 3rd parties and save the info inside our db - async syncStages() { + async syncStages(user_id?: string) { try { this.logger.log(`Syncing stages....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/crm/task/sync/sync.service.ts b/packages/api/src/crm/task/sync/sync.service.ts index ddc5a0e76..de827bbfc 100644 --- a/packages/api/src/crm/task/sync/sync.service.ts +++ b/packages/api/src/crm/task/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncTasks(); + //await this.syncTasks(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_tasks table //its role is to fetch all tasks from providers 3rd parties and save the info inside our db - async syncTasks() { + async syncTasks(user_id?: string) { try { this.logger.log(`Syncing tasks....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/crm/user/sync/sync.service.ts b/packages/api/src/crm/user/sync/sync.service.ts index af19920ac..8afc42021 100644 --- a/packages/api/src/crm/user/sync/sync.service.ts +++ b/packages/api/src/crm/user/sync/sync.service.ts @@ -29,19 +29,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncUsers(); + //await this.syncUsers(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our crm_users table //its role is to fetch all users from providers 3rd parties and save the info inside our db - async syncUsers() { + async syncUsers(user_id?: string) { try { this.logger.log(`Syncing users....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/account/sync/sync.service.ts b/packages/api/src/ticketing/account/sync/sync.service.ts index eb9462bbb..48e813529 100644 --- a/packages/api/src/ticketing/account/sync/sync.service.ts +++ b/packages/api/src/ticketing/account/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncAccounts(); + //await this.syncAccounts(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_accounts table //its role is to fetch all accounts from providers 3rd parties and save the info inside our db - async syncAccounts() { + async syncAccounts(user_id?: string) { try { this.logger.log(`Syncing accounts....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/collection/sync/sync.service.ts b/packages/api/src/ticketing/collection/sync/sync.service.ts index b8c4af4f8..ce28de1a2 100644 --- a/packages/api/src/ticketing/collection/sync/sync.service.ts +++ b/packages/api/src/ticketing/collection/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncCollections(); + //await this.syncCollections(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_collections table //its role is to fetch all collections from providers 3rd parties and save the info inside our db - async syncCollections() { + async syncCollections(user_id?: string) { try { this.logger.log(`Syncing collections....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/comment/sync/sync.service.ts b/packages/api/src/ticketing/comment/sync/sync.service.ts index 0b80c8d67..3871f87c3 100644 --- a/packages/api/src/ticketing/comment/sync/sync.service.ts +++ b/packages/api/src/ticketing/comment/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncComments(); + //await this.syncComments(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_comments table //its role is to fetch all comments from providers 3rd parties and save the info inside our db - async syncComments() { + async syncComments(user_id?: string) { try { this.logger.log(`Syncing comments....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/contact/sync/sync.service.ts b/packages/api/src/ticketing/contact/sync/sync.service.ts index 7f0ae33ec..180cba077 100644 --- a/packages/api/src/ticketing/contact/sync/sync.service.ts +++ b/packages/api/src/ticketing/contact/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncContacts(); + //await this.syncContacts(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_contacts table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db - async syncContacts() { + async syncContacts(user_id?: string) { try { this.logger.log(`Syncing contacts....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/tag/sync/sync.service.ts b/packages/api/src/ticketing/tag/sync/sync.service.ts index 980ede2fc..12b9c57b9 100644 --- a/packages/api/src/ticketing/tag/sync/sync.service.ts +++ b/packages/api/src/ticketing/tag/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncTags(); + //await this.syncTags(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_tags table //its role is to fetch all tags from providers 3rd parties and save the info inside our db - async syncTags() { + async syncTags(user_id?: string) { try { this.logger.log(`Syncing tags....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/team/sync/sync.service.ts b/packages/api/src/ticketing/team/sync/sync.service.ts index 32107f61a..73ebd1a50 100644 --- a/packages/api/src/ticketing/team/sync/sync.service.ts +++ b/packages/api/src/ticketing/team/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncTeams(); + //await this.syncTeams(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_teams table //its role is to fetch all teams from providers 3rd parties and save the info inside our db - async syncTeams() { + async syncTeams(user_id?: string) { try { this.logger.log(`Syncing teams....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/ticket/sync/sync.service.ts b/packages/api/src/ticketing/ticket/sync/sync.service.ts index 8f4520118..3503c472e 100644 --- a/packages/api/src/ticketing/ticket/sync/sync.service.ts +++ b/packages/api/src/ticketing/ticket/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncTickets(); + //await this.syncTickets(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_tickets table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db - async syncTickets() { + async syncTickets(user_id?: string) { try { this.logger.log(`Syncing tickets....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/api/src/ticketing/user/sync/sync.service.ts b/packages/api/src/ticketing/user/sync/sync.service.ts index 3b95a4606..7582184c6 100644 --- a/packages/api/src/ticketing/user/sync/sync.service.ts +++ b/packages/api/src/ticketing/user/sync/sync.service.ts @@ -30,19 +30,27 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - await this.syncUsers(); + //await this.syncUsers(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('*/20 * * * *') + @Cron('0 1 * * *') //function used by sync worker which populate our tcg_users table //its role is to fetch all users from providers 3rd parties and save the info inside our db - async syncUsers() { + async syncUsers(user_id?: string) { try { this.logger.log(`Syncing users....`); - const users = await this.prisma.users.findMany(); + const users = 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) { const projects = await this.prisma.projects.findMany({ diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 65a3d83f7..de2af40e8 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -323,9 +323,8 @@ export const providersConfig: ProvidersConfig = { }, 'ticketing': { 'front': { - scopes: '', urls: { - docsUrl: '', + docsUrl: 'https://dev.frontapp.com/docs/welcome', authBaseUrl: 'https://app.frontapp.com/oauth/authorize', apiUrl: 'https://api2.frontapp.com', }, From 4063f0223fbb4747eb30849dbf359449b09ca974 Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 1 May 2024 00:52:44 +0200 Subject: [PATCH 05/11] :sparkles: Sync features for horizontal scaling --- apps/magic-link/src/App.tsx | 9 ------- .../api/src/crm/company/company.module.ts | 7 ++--- .../src/crm/company/sync/sync.processor.ts | 17 ++++++++++++ .../api/src/crm/company/sync/sync.service.ts | 26 +++++++++++++++++-- .../api/src/crm/contact/contact.module.ts | 9 ++++--- .../src/crm/contact/sync/sync.processor.ts | 17 ++++++++++++ .../api/src/crm/contact/sync/sync.service.ts | 26 +++++++++++++++++-- packages/api/src/crm/deal/deal.module.ts | 9 ++++--- .../api/src/crm/deal/sync/sync.processor.ts | 17 ++++++++++++ .../api/src/crm/deal/sync/sync.service.ts | 25 ++++++++++++++++-- .../src/crm/engagement/engagement.module.ts | 9 ++++--- .../src/crm/engagement/sync/sync.processor.ts | 17 ++++++++++++ .../src/crm/engagement/sync/sync.service.ts | 26 ++++++++++++++++--- packages/api/src/crm/note/note.module.ts | 9 ++++--- .../api/src/crm/note/sync/sync.processor.ts | 17 ++++++++++++ .../api/src/crm/note/sync/sync.service.ts | 25 ++++++++++++++++-- packages/api/src/crm/stage/stage.module.ts | 9 ++++--- .../api/src/crm/stage/sync/sync.processor.ts | 17 ++++++++++++ .../api/src/crm/stage/sync/sync.service.ts | 25 ++++++++++++++++-- .../api/src/crm/task/sync/sync.processor.ts | 17 ++++++++++++ .../api/src/crm/task/sync/sync.service.ts | 25 ++++++++++++++++-- packages/api/src/crm/task/task.module.ts | 9 ++++--- .../api/src/crm/user/sync/sync.processor.ts | 17 ++++++++++++ .../api/src/crm/user/sync/sync.service.ts | 26 +++++++++++++++++-- packages/api/src/crm/user/user.module.ts | 9 ++++--- .../src/ticketing/account/account.module.ts | 9 ++++--- .../ticketing/account/sync/sync.processor.ts | 17 ++++++++++++ .../ticketing/account/sync/sync.service.ts | 25 ++++++++++++++++-- .../ticketing/attachment/attachment.module.ts | 9 ++++--- .../ticketing/collection/collection.module.ts | 9 ++++--- .../collection/sync/sync.processor.ts | 17 ++++++++++++ .../ticketing/collection/sync/sync.service.ts | 26 ++++++++++++++++--- .../src/ticketing/comment/comment.module.ts | 9 ++++--- .../ticketing/comment/sync/sync.processor.ts | 17 ++++++++++++ .../ticketing/comment/sync/sync.service.ts | 25 ++++++++++++++++-- .../src/ticketing/contact/contact.module.ts | 9 ++++--- .../ticketing/contact/sync/sync.processor.ts | 17 ++++++++++++ .../ticketing/contact/sync/sync.service.ts | 25 ++++++++++++++++-- .../src/ticketing/tag/sync/sync.processor.ts | 17 ++++++++++++ .../src/ticketing/tag/sync/sync.service.ts | 25 ++++++++++++++++-- packages/api/src/ticketing/tag/tag.module.ts | 9 ++++--- .../src/ticketing/team/sync/sync.processor.ts | 17 ++++++++++++ .../src/ticketing/team/sync/sync.service.ts | 25 ++++++++++++++++-- .../api/src/ticketing/team/team.module.ts | 9 ++++--- .../ticketing/ticket/sync/sync.processor.ts | 17 ++++++++++++ .../src/ticketing/ticket/sync/sync.service.ts | 25 ++++++++++++++++-- .../api/src/ticketing/ticket/ticket.module.ts | 9 ++++--- .../src/ticketing/user/sync/sync.processor.ts | 17 ++++++++++++ .../src/ticketing/user/sync/sync.service.ts | 25 ++++++++++++++++-- .../api/src/ticketing/user/user.module.ts | 9 ++++--- packages/shared/src/enum.ts | 1 - packages/shared/src/providers.ts | 1 - 52 files changed, 743 insertions(+), 96 deletions(-) create mode 100644 packages/api/src/crm/company/sync/sync.processor.ts create mode 100644 packages/api/src/crm/contact/sync/sync.processor.ts create mode 100644 packages/api/src/crm/deal/sync/sync.processor.ts create mode 100644 packages/api/src/crm/engagement/sync/sync.processor.ts create mode 100644 packages/api/src/crm/note/sync/sync.processor.ts create mode 100644 packages/api/src/crm/stage/sync/sync.processor.ts create mode 100644 packages/api/src/crm/task/sync/sync.processor.ts create mode 100644 packages/api/src/crm/user/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/account/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/collection/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/comment/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/contact/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/tag/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/team/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/ticket/sync/sync.processor.ts create mode 100644 packages/api/src/ticketing/user/sync/sync.processor.ts diff --git a/apps/magic-link/src/App.tsx b/apps/magic-link/src/App.tsx index d4037e076..bb4967ee6 100644 --- a/apps/magic-link/src/App.tsx +++ b/apps/magic-link/src/App.tsx @@ -1,16 +1,7 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import './App.css' import ProviderModal from './lib/ProviderModal' -import { PanoraSDK } from "@panora/typescript-sdk"; function App() { - const sdk = new PanoraSDK({accessToken: 'YOUR_ACCESS_TOKEN'}); - (async () => { - const result = await sdk.main - .appControllerGetHello(); - console.log(result); - })(); - return (
diff --git a/packages/api/src/crm/company/company.module.ts b/packages/api/src/crm/company/company.module.ts index 990390470..bb99a7ac7 100644 --- a/packages/api/src/crm/company/company.module.ts +++ b/packages/api/src/crm/company/company.module.ts @@ -17,9 +17,10 @@ import { AttioService } from './services/attio'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { name: 'webhookDelivery' }, + { name: 'syncTasks' }, + ), ], controllers: [CompanyController], providers: [ diff --git a/packages/api/src/crm/company/sync/sync.processor.ts b/packages/api/src/crm/company/sync/sync.processor.ts new file mode 100644 index 000000000..42823a445 --- /dev/null +++ b/packages/api/src/crm/company/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-companies') + async handleSyncCompanies(job: Job) { + try { + console.log(`Processing queue -> crm-sync-companies ${job.id}`); + await this.syncService.syncCompanies(); + } catch (error) { + console.error('Error syncing crm companies', error); + } + } +} diff --git a/packages/api/src/crm/company/sync/sync.service.ts b/packages/api/src/crm/company/sync/sync.service.ts index bff4c8a47..09d9b1e57 100644 --- a/packages/api/src/crm/company/sync/sync.service.ts +++ b/packages/api/src/crm/company/sync/sync.service.ts @@ -17,6 +17,8 @@ import { crm_companies as CrmCompany } from '@prisma/client'; import { normalizeAddresses } from '../utils'; import { Utils } from '@crm/contact/utils'; import { CRM_PROVIDERS } from '@panora/shared'; +import { Queue } from 'bull'; +import { InjectQueue } from '@nestjs/bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -28,6 +30,7 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); this.utils = new Utils(); @@ -35,13 +38,32 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - //await this.syncCompanies(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-companies'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } + //function used by sync worker which populate our crm_companies table //its role is to fetch all companies from providers 3rd parties and save the info inside our db async syncCompanies(user_id?: string) { diff --git a/packages/api/src/crm/contact/contact.module.ts b/packages/api/src/crm/contact/contact.module.ts index 787c7d71c..ff1bf0fa0 100644 --- a/packages/api/src/crm/contact/contact.module.ts +++ b/packages/api/src/crm/contact/contact.module.ts @@ -17,9 +17,12 @@ import { ServiceRegistry } from './services/registry.service'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [ContactController], providers: [ diff --git a/packages/api/src/crm/contact/sync/sync.processor.ts b/packages/api/src/crm/contact/sync/sync.processor.ts new file mode 100644 index 000000000..e45130ce4 --- /dev/null +++ b/packages/api/src/crm/contact/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-contacts') + async handleSyncCompanies(job: Job) { + try { + console.log(`Processing queue -> crm-sync-contacts ${job.id}`); + await this.syncService.syncContacts(); + } catch (error) { + console.error('Error syncing crm contacts', error); + } + } +} diff --git a/packages/api/src/crm/contact/sync/sync.service.ts b/packages/api/src/crm/contact/sync/sync.service.ts index da34a3b78..e2e81dac1 100644 --- a/packages/api/src/crm/contact/sync/sync.service.ts +++ b/packages/api/src/crm/contact/sync/sync.service.ts @@ -17,6 +17,8 @@ import { ServiceRegistry } from '../services/registry.service'; import { normalizeAddresses } from '@crm/company/utils'; import { Utils } from '../utils'; import { CRM_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -28,6 +30,7 @@ export class SyncService implements OnModuleInit { private fieldMappingService: FieldMappingService, private webhook: WebhookService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); this.utils = new Utils(); @@ -35,13 +38,32 @@ export class SyncService implements OnModuleInit { async onModuleInit() { try { - //await this.syncContacts(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-contacts'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } + //function used by sync worker which populate our crm_contacts table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db async syncContacts(user_id?: string) { diff --git a/packages/api/src/crm/deal/deal.module.ts b/packages/api/src/crm/deal/deal.module.ts index 5c3dbfd97..073979694 100644 --- a/packages/api/src/crm/deal/deal.module.ts +++ b/packages/api/src/crm/deal/deal.module.ts @@ -16,9 +16,12 @@ import { ZohoService } from './services/zoho'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [DealController], providers: [ diff --git a/packages/api/src/crm/deal/sync/sync.processor.ts b/packages/api/src/crm/deal/sync/sync.processor.ts new file mode 100644 index 000000000..edcc09b42 --- /dev/null +++ b/packages/api/src/crm/deal/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-deals') + async handleSyncDeals(job: Job) { + try { + console.log(`Processing queue -> crm-sync-deals ${job.id}`); + await this.syncService.syncDeals(); + } catch (error) { + console.error('Error syncing crm deals', error); + } + } +} diff --git a/packages/api/src/crm/deal/sync/sync.service.ts b/packages/api/src/crm/deal/sync/sync.service.ts index d93ba4559..5140823ac 100644 --- a/packages/api/src/crm/deal/sync/sync.service.ts +++ b/packages/api/src/crm/deal/sync/sync.service.ts @@ -15,6 +15,8 @@ import { IDealService } from '../types'; import { OriginalDealOutput } from '@@core/utils/types/original/original.crm'; import { crm_deals as CrmDeal } from '@prisma/client'; import { CRM_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncDeals(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-deals'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our crm_deals table //its role is to fetch all deals from providers 3rd parties and save the info inside our db async syncDeals(user_id?: string) { diff --git a/packages/api/src/crm/engagement/engagement.module.ts b/packages/api/src/crm/engagement/engagement.module.ts index d6b7ad081..e7461d249 100644 --- a/packages/api/src/crm/engagement/engagement.module.ts +++ b/packages/api/src/crm/engagement/engagement.module.ts @@ -16,9 +16,12 @@ import { ZohoService } from './services/zoho'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [EngagementController], providers: [ diff --git a/packages/api/src/crm/engagement/sync/sync.processor.ts b/packages/api/src/crm/engagement/sync/sync.processor.ts new file mode 100644 index 000000000..2359a9933 --- /dev/null +++ b/packages/api/src/crm/engagement/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-engagements') + async handleSyncEngagements(job: Job) { + try { + console.log(`Processing queue -> crm-sync-engagements ${job.id}`); + await this.syncService.syncEngagements(); + } catch (error) { + console.error('Error syncing crm engagements', error); + } + } +} diff --git a/packages/api/src/crm/engagement/sync/sync.service.ts b/packages/api/src/crm/engagement/sync/sync.service.ts index efec5ed0e..010b9a923 100644 --- a/packages/api/src/crm/engagement/sync/sync.service.ts +++ b/packages/api/src/crm/engagement/sync/sync.service.ts @@ -16,6 +16,8 @@ import { crm_engagements as CrmEngagement } from '@prisma/client'; import { OriginalEngagementOutput } from '@@core/utils/types/original/original.crm'; import { ENGAGEMENTS_TYPE } from '../utils'; import { CRM_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -25,20 +27,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //TODO: to test after - //await this.syncEngagements(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-engagements'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our crm_engagements table //its role is to fetch all engagements from providers 3rd parties and save the info inside our db async syncEngagements(user_id?: string) { diff --git a/packages/api/src/crm/note/note.module.ts b/packages/api/src/crm/note/note.module.ts index 24db7f527..f175da205 100644 --- a/packages/api/src/crm/note/note.module.ts +++ b/packages/api/src/crm/note/note.module.ts @@ -16,9 +16,12 @@ import { ZohoService } from './services/zoho'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [NoteController], providers: [ diff --git a/packages/api/src/crm/note/sync/sync.processor.ts b/packages/api/src/crm/note/sync/sync.processor.ts new file mode 100644 index 000000000..002e5f562 --- /dev/null +++ b/packages/api/src/crm/note/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-notes') + async handleSyncNotes(job: Job) { + try { + console.log(`Processing queue -> crm-sync-notes ${job.id}`); + await this.syncService.syncNotes(); + } catch (error) { + console.error('Error syncing crm notes', error); + } + } +} diff --git a/packages/api/src/crm/note/sync/sync.service.ts b/packages/api/src/crm/note/sync/sync.service.ts index 871a17a8a..b81edf25e 100644 --- a/packages/api/src/crm/note/sync/sync.service.ts +++ b/packages/api/src/crm/note/sync/sync.service.ts @@ -15,6 +15,8 @@ import { INoteService } from '../types'; import { crm_notes as CrmNote } from '@prisma/client'; import { OriginalNoteOutput } from '@@core/utils/types/original/original.crm'; import { CRM_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncNotes(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-notes'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our crm_notes table //its role is to fetch all notes from providers 3rd parties and save the info inside our db async syncNotes(user_id?: string) { diff --git a/packages/api/src/crm/stage/stage.module.ts b/packages/api/src/crm/stage/stage.module.ts index 6376d5286..e1152061e 100644 --- a/packages/api/src/crm/stage/stage.module.ts +++ b/packages/api/src/crm/stage/stage.module.ts @@ -16,9 +16,12 @@ import { ZohoService } from './services/zoho'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [StageController], providers: [ diff --git a/packages/api/src/crm/stage/sync/sync.processor.ts b/packages/api/src/crm/stage/sync/sync.processor.ts new file mode 100644 index 000000000..403915529 --- /dev/null +++ b/packages/api/src/crm/stage/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-stages') + async handleSyncStages(job: Job) { + try { + console.log(`Processing queue -> crm-sync-stages ${job.id}`); + await this.syncService.syncStages(); + } catch (error) { + console.error('Error syncing crm stages', error); + } + } +} diff --git a/packages/api/src/crm/stage/sync/sync.service.ts b/packages/api/src/crm/stage/sync/sync.service.ts index f1e790880..4e9264eb9 100644 --- a/packages/api/src/crm/stage/sync/sync.service.ts +++ b/packages/api/src/crm/stage/sync/sync.service.ts @@ -15,6 +15,8 @@ import { IStageService } from '../types'; import { crm_deals_stages as CrmStage } from '@prisma/client'; import { OriginalStageOutput } from '@@core/utils/types/original/original.crm'; import { CRM_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncStages(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-stages'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our crm_stages table //its role is to fetch all stages from providers 3rd parties and save the info inside our db async syncStages(user_id?: string) { diff --git a/packages/api/src/crm/task/sync/sync.processor.ts b/packages/api/src/crm/task/sync/sync.processor.ts new file mode 100644 index 000000000..f0e17540c --- /dev/null +++ b/packages/api/src/crm/task/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-tasks') + async handleSyncTasks(job: Job) { + try { + console.log(`Processing queue -> crm-sync-tasks ${job.id}`); + await this.syncService.syncTasks(); + } catch (error) { + console.error('Error syncing crm tasks', error); + } + } +} diff --git a/packages/api/src/crm/task/sync/sync.service.ts b/packages/api/src/crm/task/sync/sync.service.ts index de827bbfc..41c91bf49 100644 --- a/packages/api/src/crm/task/sync/sync.service.ts +++ b/packages/api/src/crm/task/sync/sync.service.ts @@ -15,6 +15,8 @@ import { ITaskService } from '../types'; import { crm_tasks as CrmTask } from '@prisma/client'; import { OriginalTaskOutput } from '@@core/utils/types/original/original.crm'; import { CRM_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncTasks(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-tasks'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our crm_tasks table //its role is to fetch all tasks from providers 3rd parties and save the info inside our db async syncTasks(user_id?: string) { diff --git a/packages/api/src/crm/task/task.module.ts b/packages/api/src/crm/task/task.module.ts index e30347cad..e587eaf5a 100644 --- a/packages/api/src/crm/task/task.module.ts +++ b/packages/api/src/crm/task/task.module.ts @@ -16,9 +16,12 @@ import { ZohoService } from './services/zoho'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [TaskController], providers: [ diff --git a/packages/api/src/crm/user/sync/sync.processor.ts b/packages/api/src/crm/user/sync/sync.processor.ts new file mode 100644 index 000000000..d48368a23 --- /dev/null +++ b/packages/api/src/crm/user/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('crm-sync-users') + async handleSyncUsers(job: Job) { + try { + console.log(`Processing queue -> crm-sync-users ${job.id}`); + await this.syncService.syncUsers(); + } catch (error) { + console.error('Error syncing crm users', error); + } + } +} diff --git a/packages/api/src/crm/user/sync/sync.service.ts b/packages/api/src/crm/user/sync/sync.service.ts index 8afc42021..2e545b23f 100644 --- a/packages/api/src/crm/user/sync/sync.service.ts +++ b/packages/api/src/crm/user/sync/sync.service.ts @@ -15,6 +15,9 @@ import { IUserService } from '../types'; import { crm_users as CrmUser } from '@prisma/client'; import { OriginalUserOutput } from '@@core/utils/types/original/original.crm'; import { CRM_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; + @Injectable() export class SyncService implements OnModuleInit { constructor( @@ -23,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncUsers(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'crm-sync-users'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our crm_users table //its role is to fetch all users from providers 3rd parties and save the info inside our db async syncUsers(user_id?: string) { diff --git a/packages/api/src/crm/user/user.module.ts b/packages/api/src/crm/user/user.module.ts index 5ddfd8b7e..2f5fea9bd 100644 --- a/packages/api/src/crm/user/user.module.ts +++ b/packages/api/src/crm/user/user.module.ts @@ -16,9 +16,12 @@ import { ZohoService } from './services/zoho'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [UserController], providers: [ diff --git a/packages/api/src/ticketing/account/account.module.ts b/packages/api/src/ticketing/account/account.module.ts index b8c7862f7..272f5745f 100644 --- a/packages/api/src/ticketing/account/account.module.ts +++ b/packages/api/src/ticketing/account/account.module.ts @@ -14,9 +14,12 @@ import { FrontService } from './services/front'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [AccountController], providers: [ diff --git a/packages/api/src/ticketing/account/sync/sync.processor.ts b/packages/api/src/ticketing/account/sync/sync.processor.ts new file mode 100644 index 000000000..325d66090 --- /dev/null +++ b/packages/api/src/ticketing/account/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-accounts') + async handleSyncAccounts(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-accounts ${job.id}`); + await this.syncService.syncAccounts(); + } catch (error) { + console.error('Error syncing ticketing accounts', error); + } + } +} diff --git a/packages/api/src/ticketing/account/sync/sync.service.ts b/packages/api/src/ticketing/account/sync/sync.service.ts index 48e813529..4a7b5bff4 100644 --- a/packages/api/src/ticketing/account/sync/sync.service.ts +++ b/packages/api/src/ticketing/account/sync/sync.service.ts @@ -15,6 +15,8 @@ import { IAccountService } from '../types'; import { OriginalAccountOutput } from '@@core/utils/types/original/original.ticketing'; import { tcg_accounts as TicketingAccount } from '@prisma/client'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncAccounts(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-accounts'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_accounts table //its role is to fetch all accounts from providers 3rd parties and save the info inside our db async syncAccounts(user_id?: string) { diff --git a/packages/api/src/ticketing/attachment/attachment.module.ts b/packages/api/src/ticketing/attachment/attachment.module.ts index 935a8bb6a..de94b589d 100644 --- a/packages/api/src/ticketing/attachment/attachment.module.ts +++ b/packages/api/src/ticketing/attachment/attachment.module.ts @@ -11,9 +11,12 @@ import { BullModule } from '@nestjs/bull'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [AttachmentController], providers: [ diff --git a/packages/api/src/ticketing/collection/collection.module.ts b/packages/api/src/ticketing/collection/collection.module.ts index 1be92ac99..45637df10 100644 --- a/packages/api/src/ticketing/collection/collection.module.ts +++ b/packages/api/src/ticketing/collection/collection.module.ts @@ -13,9 +13,12 @@ import { JiraService } from './services/jira'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [CollectionController], providers: [ diff --git a/packages/api/src/ticketing/collection/sync/sync.processor.ts b/packages/api/src/ticketing/collection/sync/sync.processor.ts new file mode 100644 index 000000000..a2304ea9d --- /dev/null +++ b/packages/api/src/ticketing/collection/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-collections') + async handleSyncCollections(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-collections ${job.id}`); + await this.syncService.syncCollections(); + } catch (error) { + console.error('Error syncing ticketing collections', error); + } + } +} diff --git a/packages/api/src/ticketing/collection/sync/sync.service.ts b/packages/api/src/ticketing/collection/sync/sync.service.ts index ce28de1a2..674cff9e7 100644 --- a/packages/api/src/ticketing/collection/sync/sync.service.ts +++ b/packages/api/src/ticketing/collection/sync/sync.service.ts @@ -15,6 +15,8 @@ import { ICollectionService } from '../types'; import { OriginalCollectionOutput } from '@@core/utils/types/original/original.ticketing'; import { tcg_collections as TicketingCollection } from '@prisma/client'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -22,21 +24,39 @@ export class SyncService implements OnModuleInit { private prisma: PrismaService, private logger: LoggerService, private webhook: WebhookService, - private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncCollections(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-collections'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_collections table //its role is to fetch all collections from providers 3rd parties and save the info inside our db async syncCollections(user_id?: string) { diff --git a/packages/api/src/ticketing/comment/comment.module.ts b/packages/api/src/ticketing/comment/comment.module.ts index 0747d1023..841c56337 100644 --- a/packages/api/src/ticketing/comment/comment.module.ts +++ b/packages/api/src/ticketing/comment/comment.module.ts @@ -16,9 +16,12 @@ import { GorgiasService } from './services/gorgias'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [CommentController], providers: [ diff --git a/packages/api/src/ticketing/comment/sync/sync.processor.ts b/packages/api/src/ticketing/comment/sync/sync.processor.ts new file mode 100644 index 000000000..ea8ee9b5b --- /dev/null +++ b/packages/api/src/ticketing/comment/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-comments') + async handleSyncComments(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-comments ${job.id}`); + await this.syncService.syncComments(); + } catch (error) { + console.error('Error syncing ticketing comments', error); + } + } +} diff --git a/packages/api/src/ticketing/comment/sync/sync.service.ts b/packages/api/src/ticketing/comment/sync/sync.service.ts index 3871f87c3..69b72c19b 100644 --- a/packages/api/src/ticketing/comment/sync/sync.service.ts +++ b/packages/api/src/ticketing/comment/sync/sync.service.ts @@ -15,6 +15,8 @@ import { ICommentService } from '../types'; import { OriginalCommentOutput } from '@@core/utils/types/original/original.ticketing'; import { ServiceRegistry } from '../services/registry.service'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncComments(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-comments'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_comments table //its role is to fetch all comments from providers 3rd parties and save the info inside our db async syncComments(user_id?: string) { diff --git a/packages/api/src/ticketing/contact/contact.module.ts b/packages/api/src/ticketing/contact/contact.module.ts index ad2e375cd..ed06cab23 100644 --- a/packages/api/src/ticketing/contact/contact.module.ts +++ b/packages/api/src/ticketing/contact/contact.module.ts @@ -15,9 +15,12 @@ import { GorgiasService } from './services/gorgias'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [ContactController], providers: [ diff --git a/packages/api/src/ticketing/contact/sync/sync.processor.ts b/packages/api/src/ticketing/contact/sync/sync.processor.ts new file mode 100644 index 000000000..e810ef014 --- /dev/null +++ b/packages/api/src/ticketing/contact/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-contacts') + async handleSyncContacts(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-contacts ${job.id}`); + await this.syncService.syncContacts(); + } catch (error) { + console.error('Error syncing ticketing contacts', error); + } + } +} diff --git a/packages/api/src/ticketing/contact/sync/sync.service.ts b/packages/api/src/ticketing/contact/sync/sync.service.ts index 180cba077..2cb80862c 100644 --- a/packages/api/src/ticketing/contact/sync/sync.service.ts +++ b/packages/api/src/ticketing/contact/sync/sync.service.ts @@ -15,6 +15,8 @@ import { ServiceRegistry } from '../services/registry.service'; import { tcg_contacts as TicketingContact } from '@prisma/client'; import { OriginalContactOutput } from '@@core/utils/types/original/original.ticketing'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncContacts(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-contacts'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_contacts table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db async syncContacts(user_id?: string) { diff --git a/packages/api/src/ticketing/tag/sync/sync.processor.ts b/packages/api/src/ticketing/tag/sync/sync.processor.ts new file mode 100644 index 000000000..47ecea372 --- /dev/null +++ b/packages/api/src/ticketing/tag/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-tags') + async handleSyncTags(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-tags ${job.id}`); + await this.syncService.syncTags(); + } catch (error) { + console.error('Error syncing ticketing tags', error); + } + } +} diff --git a/packages/api/src/ticketing/tag/sync/sync.service.ts b/packages/api/src/ticketing/tag/sync/sync.service.ts index 12b9c57b9..01d1104e3 100644 --- a/packages/api/src/ticketing/tag/sync/sync.service.ts +++ b/packages/api/src/ticketing/tag/sync/sync.service.ts @@ -15,6 +15,8 @@ import { ITagService } from '../types'; import { OriginalTagOutput } from '@@core/utils/types/original/original.ticketing'; import { tcg_tags as TicketingTag } from '@prisma/client'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncTags(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-tags'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_tags table //its role is to fetch all tags from providers 3rd parties and save the info inside our db async syncTags(user_id?: string) { diff --git a/packages/api/src/ticketing/tag/tag.module.ts b/packages/api/src/ticketing/tag/tag.module.ts index 9e2cb2904..216c7772a 100644 --- a/packages/api/src/ticketing/tag/tag.module.ts +++ b/packages/api/src/ticketing/tag/tag.module.ts @@ -16,9 +16,12 @@ import { GorgiasService } from './services/gorgias'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [TagController], providers: [ diff --git a/packages/api/src/ticketing/team/sync/sync.processor.ts b/packages/api/src/ticketing/team/sync/sync.processor.ts new file mode 100644 index 000000000..c31a7bbc0 --- /dev/null +++ b/packages/api/src/ticketing/team/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-teams') + async handleSyncTeams(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-teams ${job.id}`); + await this.syncService.syncTeams(); + } catch (error) { + console.error('Error syncing ticketing teams', error); + } + } +} diff --git a/packages/api/src/ticketing/team/sync/sync.service.ts b/packages/api/src/ticketing/team/sync/sync.service.ts index 73ebd1a50..297c56554 100644 --- a/packages/api/src/ticketing/team/sync/sync.service.ts +++ b/packages/api/src/ticketing/team/sync/sync.service.ts @@ -15,6 +15,8 @@ import { ITeamService } from '../types'; import { tcg_teams as TicketingTeam } from '@prisma/client'; import { OriginalTeamOutput } from '@@core/utils/types/original/original.ticketing'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncTeams(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-teams'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_teams table //its role is to fetch all teams from providers 3rd parties and save the info inside our db async syncTeams(user_id?: string) { diff --git a/packages/api/src/ticketing/team/team.module.ts b/packages/api/src/ticketing/team/team.module.ts index aba6d0e58..770e9adc7 100644 --- a/packages/api/src/ticketing/team/team.module.ts +++ b/packages/api/src/ticketing/team/team.module.ts @@ -16,9 +16,12 @@ import { GorgiasService } from './services/gorgias'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [TeamController], providers: [ diff --git a/packages/api/src/ticketing/ticket/sync/sync.processor.ts b/packages/api/src/ticketing/ticket/sync/sync.processor.ts new file mode 100644 index 000000000..ba071968d --- /dev/null +++ b/packages/api/src/ticketing/ticket/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-tickets') + async handleSyncTickets(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-tickets ${job.id}`); + await this.syncService.syncTickets(); + } catch (error) { + console.error('Error syncing ticketing tickets', error); + } + } +} diff --git a/packages/api/src/ticketing/ticket/sync/sync.service.ts b/packages/api/src/ticketing/ticket/sync/sync.service.ts index 3503c472e..7a27e861b 100644 --- a/packages/api/src/ticketing/ticket/sync/sync.service.ts +++ b/packages/api/src/ticketing/ticket/sync/sync.service.ts @@ -15,6 +15,8 @@ import { ITicketService } from '../types'; import { OriginalTicketOutput } from '@@core/utils/types/original/original.ticketing'; import { ServiceRegistry } from '../services/registry.service'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncTickets(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-tickets'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_tickets table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db async syncTickets(user_id?: string) { diff --git a/packages/api/src/ticketing/ticket/ticket.module.ts b/packages/api/src/ticketing/ticket/ticket.module.ts index 7a798c6fe..7734697ad 100644 --- a/packages/api/src/ticketing/ticket/ticket.module.ts +++ b/packages/api/src/ticketing/ticket/ticket.module.ts @@ -18,9 +18,12 @@ import { GorgiasService } from './services/gorgias'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [TicketController], providers: [ diff --git a/packages/api/src/ticketing/user/sync/sync.processor.ts b/packages/api/src/ticketing/user/sync/sync.processor.ts new file mode 100644 index 000000000..66fd65fb8 --- /dev/null +++ b/packages/api/src/ticketing/user/sync/sync.processor.ts @@ -0,0 +1,17 @@ +import { Processor, Process } from '@nestjs/bull'; +import { Job } from 'bull'; +import { SyncService } from './sync.service'; + +@Processor('syncTasks') +export class SyncProcessor { + constructor(private syncService: SyncService) {} + @Process('ticketing-sync-users') + async handleSyncUsers(job: Job) { + try { + console.log(`Processing queue -> ticketing-sync-users ${job.id}`); + await this.syncService.syncUsers(); + } catch (error) { + console.error('Error syncing ticketing users', error); + } + } +} diff --git a/packages/api/src/ticketing/user/sync/sync.service.ts b/packages/api/src/ticketing/user/sync/sync.service.ts index 7582184c6..e4d68eba7 100644 --- a/packages/api/src/ticketing/user/sync/sync.service.ts +++ b/packages/api/src/ticketing/user/sync/sync.service.ts @@ -15,6 +15,8 @@ import { OriginalUserOutput } from '@@core/utils/types/original/original.ticketi import { tcg_users as TicketingUser } from '@prisma/client'; import { UnifiedUserOutput } from '../types/model.unified'; import { TICKETING_PROVIDERS } from '@panora/shared'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; @Injectable() export class SyncService implements OnModuleInit { @@ -24,19 +26,38 @@ export class SyncService implements OnModuleInit { private webhook: WebhookService, private fieldMappingService: FieldMappingService, private serviceRegistry: ServiceRegistry, + @InjectQueue('syncTasks') private syncQueue: Queue, ) { this.logger.setContext(SyncService.name); } async onModuleInit() { try { - //await this.syncUsers(); + await this.scheduleSyncJob(); } catch (error) { handleServiceError(error, this.logger); } } - @Cron('0 1 * * *') + private async scheduleSyncJob() { + const jobName = 'ticketing-sync-users'; + + // Remove existing jobs to avoid duplicates in case of application restart + const jobs = await this.syncQueue.getRepeatableJobs(); + for (const job of jobs) { + if (job.name === jobName) { + await this.syncQueue.removeRepeatableByKey(job.key); + } + } + // Add new job to the queue with a CRON expression + await this.syncQueue.add( + jobName, + {}, + { + repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight + }, + ); + } //function used by sync worker which populate our tcg_users table //its role is to fetch all users from providers 3rd parties and save the info inside our db async syncUsers(user_id?: string) { diff --git a/packages/api/src/ticketing/user/user.module.ts b/packages/api/src/ticketing/user/user.module.ts index 6b5f8c481..bf4c644c0 100644 --- a/packages/api/src/ticketing/user/user.module.ts +++ b/packages/api/src/ticketing/user/user.module.ts @@ -16,9 +16,12 @@ import { GorgiasService } from './services/gorgias'; @Module({ imports: [ - BullModule.registerQueue({ - name: 'webhookDelivery', - }), + BullModule.registerQueue( + { + name: 'webhookDelivery', + }, + { name: 'syncTasks' }, + ), ], controllers: [UserController], providers: [ diff --git a/packages/shared/src/enum.ts b/packages/shared/src/enum.ts index 656eb63b4..3d86d97f2 100644 --- a/packages/shared/src/enum.ts +++ b/packages/shared/src/enum.ts @@ -6,7 +6,6 @@ export enum ProviderVertical { Ticketing = 'ticketing', MarketingAutomation = 'marketingautomation', FileStorage = 'filestorage', - Unknown = 'unknown', } export enum CrmProviders { diff --git a/packages/shared/src/providers.ts b/packages/shared/src/providers.ts index abc0a38db..eb3becb31 100644 --- a/packages/shared/src/providers.ts +++ b/packages/shared/src/providers.ts @@ -33,7 +33,6 @@ export function getProviderVertical(providerName: string): ProviderVertical { if (FILESTORAGE_PROVIDERS.includes(providerName)) { return ProviderVertical.FileStorage; } - return ProviderVertical.Unknown; } function mergeAllProviders(...arrays: string[][]): { vertical: string, value: string }[] { From 4ef9c501966657c50981e2c167fd365e6f3e452e Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 1 May 2024 01:00:49 +0200 Subject: [PATCH 06/11] :bug: Fix ci --- packages/api/src/@core/connections/connections.controller.ts | 2 -- packages/api/src/@core/utils/unification/desunify.ts | 2 -- packages/api/src/@core/utils/unification/unify.ts | 2 -- 3 files changed, 6 deletions(-) diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index 2bbdb4053..43613a45f 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -101,8 +101,6 @@ export class ConnectionsController { code, ); break; - case ProviderVertical.Unknown: - break; } res.redirect(returnUrl); } catch (error) { diff --git a/packages/api/src/@core/utils/unification/desunify.ts b/packages/api/src/@core/utils/unification/desunify.ts index 0df893332..e6966a803 100644 --- a/packages/api/src/@core/utils/unification/desunify.ts +++ b/packages/api/src/@core/utils/unification/desunify.ts @@ -56,7 +56,5 @@ export async function desunify({ providerName, customFieldMappings, }); - case ProviderVertical.Unknown: - break; } } diff --git a/packages/api/src/@core/utils/unification/unify.ts b/packages/api/src/@core/utils/unification/unify.ts index bbb004379..a37cfa45a 100644 --- a/packages/api/src/@core/utils/unification/unify.ts +++ b/packages/api/src/@core/utils/unification/unify.ts @@ -57,8 +57,6 @@ export async function unify({ providerName, customFieldMappings, }); - case ProviderVertical.Unknown: - break; } return; } From 34b591be33a54c655f9e9c889072a0035bde04eb Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 1 May 2024 17:59:38 +0200 Subject: [PATCH 07/11] :pencil2: Typo --- packages/shared/src/providers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/shared/src/providers.ts b/packages/shared/src/providers.ts index eb3becb31..8bb661165 100644 --- a/packages/shared/src/providers.ts +++ b/packages/shared/src/providers.ts @@ -33,6 +33,7 @@ export function getProviderVertical(providerName: string): ProviderVertical { if (FILESTORAGE_PROVIDERS.includes(providerName)) { return ProviderVertical.FileStorage; } + return null; } function mergeAllProviders(...arrays: string[][]): { vertical: string, value: string }[] { From 0cee16a94190a7a0dee8093f9864cfcffebd6b28 Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 1 May 2024 18:07:32 +0200 Subject: [PATCH 08/11] :green_heart: Fix build --- .../src/components/Configuration/AddAuthCredentialsForm.tsx | 2 +- packages/shared/src/enum.ts | 1 + packages/shared/src/providers.ts | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx b/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx index 30f2c2406..5b8b082de 100644 --- a/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx +++ b/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx @@ -48,7 +48,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip" import {PasswordInput} from '@/components/ui/password-input' -import { ALL_PROVIDERS,getLogoURL,getProviderVertical,providerToType,AuthStrategy } from "@panora/shared" +import { ALL_PROVIDERS,getLogoURL,providerToType,AuthStrategy } from "@panora/shared" import * as z from "zod" import { cn } from "@/lib/utils" import useProjectStore from "@/state/projectStore" diff --git a/packages/shared/src/enum.ts b/packages/shared/src/enum.ts index 3d86d97f2..b2e8d90ca 100644 --- a/packages/shared/src/enum.ts +++ b/packages/shared/src/enum.ts @@ -6,6 +6,7 @@ export enum ProviderVertical { Ticketing = 'ticketing', MarketingAutomation = 'marketingautomation', FileStorage = 'filestorage', + null = "null" } export enum CrmProviders { diff --git a/packages/shared/src/providers.ts b/packages/shared/src/providers.ts index 8bb661165..382a4e17b 100644 --- a/packages/shared/src/providers.ts +++ b/packages/shared/src/providers.ts @@ -11,7 +11,7 @@ export const TICKETING_PROVIDERS = ['zendesk', 'front', 'github', 'jira', 'gorgi export const MARKETINGAUTOMATION_PROVIDERS = ['']; export const FILESTORAGE_PROVIDERS = ['']; -export function getProviderVertical(providerName: string): ProviderVertical { +/*export function getProviderVertical(providerName: string): ProviderVertical { if (CRM_PROVIDERS.includes(providerName)) { return ProviderVertical.CRM; } @@ -33,8 +33,8 @@ export function getProviderVertical(providerName: string): ProviderVertical { if (FILESTORAGE_PROVIDERS.includes(providerName)) { return ProviderVertical.FileStorage; } - return null; -} + return undefined; +}*/ function mergeAllProviders(...arrays: string[][]): { vertical: string, value: string }[] { const result: { vertical: string, value: string }[] = []; From b930413491ff9efd947caceef2960bde6a45f99b Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 1 May 2024 18:14:02 +0200 Subject: [PATCH 09/11] :pencil2: Lint issue --- packages/api/src/crm/task/utils/index.ts | 10 ++++++---- packages/shared/src/enum.ts | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/api/src/crm/task/utils/index.ts b/packages/api/src/crm/task/utils/index.ts index 47ae31b90..d2437f469 100644 --- a/packages/api/src/crm/task/utils/index.ts +++ b/packages/api/src/crm/task/utils/index.ts @@ -136,12 +136,14 @@ export class Utils { } mapStatus(status: string, provider_name: string): string { - try{ - switch(provider_name.toLowerCase()){ + try { + switch (provider_name.toLowerCase()) { default: - throw new Error('Provider not supported for status custom task mapping') + throw new Error( + 'Provider not supported for status custom task mapping', + ); } - }catch(error){ + } catch (error) { throw new Error(error); } } diff --git a/packages/shared/src/enum.ts b/packages/shared/src/enum.ts index b2e8d90ca..3d86d97f2 100644 --- a/packages/shared/src/enum.ts +++ b/packages/shared/src/enum.ts @@ -6,7 +6,6 @@ export enum ProviderVertical { Ticketing = 'ticketing', MarketingAutomation = 'marketingautomation', FileStorage = 'filestorage', - null = "null" } export enum CrmProviders { From dfa6a5e71e1c8bcdf91a11ddd6a22eb382d8ca58 Mon Sep 17 00:00:00 2001 From: nael Date: Thu, 2 May 2024 00:33:05 +0200 Subject: [PATCH 10/11] :sparkles: Sync with cron --- .../Configuration/AddAuthCredentialsForm.tsx | 14 +- .../shared/data-table-row-actions.tsx | 1 - packages/api/src/@core/sync/sync.module.ts | 39 + packages/api/src/@core/sync/sync.service.ts | 2 - packages/api/src/crm/@utils/@types/index.ts | 3 + .../api/src/crm/company/company.module.ts | 9 +- .../api/src/crm/company/sync/sync.service.ts | 2 + .../api/src/crm/contact/contact.module.ts | 9 +- .../src/crm/contact/sync/sync.processor.ts | 5 +- .../api/src/crm/contact/sync/sync.service.ts | 28 +- .../src/crm/contact/types/model.unified.ts | 9 +- packages/api/src/crm/deal/deal.module.ts | 9 +- .../api/src/crm/deal/sync/sync.service.ts | 2 + .../src/crm/engagement/engagement.module.ts | 9 +- .../src/crm/engagement/sync/sync.service.ts | 2 + packages/api/src/crm/note/note.module.ts | 9 +- .../api/src/crm/note/sync/sync.service.ts | 2 + packages/api/src/crm/stage/stage.module.ts | 9 +- .../api/src/crm/stage/sync/sync.service.ts | 2 + .../api/src/crm/task/sync/sync.service.ts | 2 + packages/api/src/crm/task/task.module.ts | 9 +- .../api/src/crm/user/sync/sync.service.ts | 2 + packages/api/src/crm/user/user.module.ts | 9 +- .../src/ticketing/account/account.module.ts | 9 +- .../ticketing/account/sync/sync.service.ts | 2 + .../ticketing/collection/collection.module.ts | 9 +- .../ticketing/collection/sync/sync.service.ts | 2 + .../src/ticketing/comment/comment.module.ts | 9 +- .../ticketing/comment/sync/sync.service.ts | 2 + .../src/ticketing/contact/contact.module.ts | 9 +- .../ticketing/contact/sync/sync.service.ts | 2 + .../src/ticketing/tag/sync/sync.service.ts | 2 + packages/api/src/ticketing/tag/tag.module.ts | 9 +- .../src/ticketing/team/sync/sync.service.ts | 2 + .../api/src/ticketing/team/team.module.ts | 9 +- .../src/ticketing/ticket/sync/sync.service.ts | 2 + .../api/src/ticketing/ticket/ticket.module.ts | 9 +- .../src/ticketing/user/sync/sync.service.ts | 2 + .../api/src/ticketing/user/user.module.ts | 9 +- packages/api/swagger/swagger-spec.json | 1989 +++++++++-------- 40 files changed, 1248 insertions(+), 1016 deletions(-) diff --git a/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx b/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx index abaa26d4c..99f49ba26 100644 --- a/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx +++ b/apps/client-ts/src/components/Configuration/AddAuthCredentialsForm.tsx @@ -359,7 +359,6 @@ const AddAuthCredentialsForm = (prop : propType) => {
- {/*
*/} {
- ))} - + ))} + - - - {/* - This is the language that will be used in the dashboard. - */} - + )} - /> + /> {/*
*/}
diff --git a/apps/client-ts/src/components/shared/data-table-row-actions.tsx b/apps/client-ts/src/components/shared/data-table-row-actions.tsx index fbd98aede..b9f7b2075 100644 --- a/apps/client-ts/src/components/shared/data-table-row-actions.tsx +++ b/apps/client-ts/src/components/shared/data-table-row-actions.tsx @@ -42,7 +42,6 @@ export function DataTableRowActions({ Edit Make a copy Favorite - {row.id} Labels diff --git a/packages/api/src/@core/sync/sync.module.ts b/packages/api/src/@core/sync/sync.module.ts index 5859c2764..e8d5e682d 100644 --- a/packages/api/src/@core/sync/sync.module.ts +++ b/packages/api/src/@core/sync/sync.module.ts @@ -19,8 +19,47 @@ import { SyncService as TicketingTagSyncService } from '@ticketing/tag/sync/sync import { SyncService as TicketingTeamSyncService } from '@ticketing/team/sync/sync.service'; import { SyncService as TicketingTicketSyncService } from '@ticketing/ticket/sync/sync.service'; import { SyncService as TicketingUserSyncService } from '@ticketing/user/sync/sync.service'; +import { BullModule } from '@nestjs/bull'; +import { CompanyModule } from '@crm/company/company.module'; +import { ContactModule } from '@crm/contact/contact.module'; +import { DealModule } from '@crm/deal/deal.module'; +import { EngagementModule } from '@crm/engagement/engagement.module'; +import { NoteModule } from '@crm/note/note.module'; +import { StageModule } from '@crm/stage/stage.module'; +import { TaskModule } from '@crm/task/task.module'; +import { UserModule } from '@crm/user/user.module'; +import { AccountModule } from '@ticketing/account/account.module'; +import { CollectionModule } from '@ticketing/collection/collection.module'; +import { CommentModule } from '@ticketing/comment/comment.module'; +import { ContactModule as TContactModule } from '@ticketing/contact/contact.module'; +import { TagModule } from '@ticketing/tag/tag.module'; +import { TeamModule } from '@ticketing/team/team.module'; +import { TicketModule } from '@ticketing/ticket/ticket.module'; +import { UserModule as TUserModule } from '@ticketing/user/user.module'; @Module({ + imports: [ + BullModule.registerQueue( + { name: 'webhookDelivery' }, + { name: 'syncTasks' }, + ), + CompanyModule, + ContactModule, + DealModule, + EngagementModule, + NoteModule, + StageModule, + TaskModule, + UserModule, + AccountModule, + CollectionModule, + CommentModule, + TContactModule, + TagModule, + TeamModule, + TicketModule, + TUserModule, + ], providers: [ CoreSyncService, LoggerService, diff --git a/packages/api/src/@core/sync/sync.service.ts b/packages/api/src/@core/sync/sync.service.ts index c5463ef69..8686107f1 100644 --- a/packages/api/src/@core/sync/sync.service.ts +++ b/packages/api/src/@core/sync/sync.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; -import { PrismaService } from '../prisma/prisma.service'; import { LoggerService } from '../logger/logger.service'; import { handleServiceError } from '@@core/utils/errors'; import { SyncService as CrmCompanySyncService } from '@crm/company/sync/sync.service'; @@ -22,7 +21,6 @@ import { SyncService as TicketingUserSyncService } from '@ticketing/user/sync/sy @Injectable() export class CoreSyncService { constructor( - private prisma: PrismaService, private logger: LoggerService, private CrmCompanySyncService: CrmCompanySyncService, private CrmContactSyncService: CrmContactSyncService, diff --git a/packages/api/src/crm/@utils/@types/index.ts b/packages/api/src/crm/@utils/@types/index.ts index a632ea48f..8e85b11a6 100644 --- a/packages/api/src/crm/@utils/@types/index.ts +++ b/packages/api/src/crm/@utils/@types/index.ts @@ -328,6 +328,7 @@ export class Email { type: String, description: 'The email address', }) + @IsString() email_address: string; @ApiProperty({ @@ -343,6 +344,8 @@ export class Email { type: String, description: 'The owner type of an email', }) + @IsString() + @IsOptional() owner_type?: string; } diff --git a/packages/api/src/crm/company/company.module.ts b/packages/api/src/crm/company/company.module.ts index bb99a7ac7..3c6450280 100644 --- a/packages/api/src/crm/company/company.module.ts +++ b/packages/api/src/crm/company/company.module.ts @@ -39,6 +39,13 @@ import { AttioService } from './services/attio'; HubspotService, AttioService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class CompanyModule {} diff --git a/packages/api/src/crm/company/sync/sync.service.ts b/packages/api/src/crm/company/sync/sync.service.ts index 09d9b1e57..d712bac5f 100644 --- a/packages/api/src/crm/company/sync/sync.service.ts +++ b/packages/api/src/crm/company/sync/sync.service.ts @@ -66,6 +66,8 @@ export class SyncService implements OnModuleInit { //function used by sync worker which populate our crm_companies table //its role is to fetch all companies from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncCompanies(user_id?: string) { try { this.logger.log(`Syncing companies....`); diff --git a/packages/api/src/crm/contact/contact.module.ts b/packages/api/src/crm/contact/contact.module.ts index ff1bf0fa0..5a3de9885 100644 --- a/packages/api/src/crm/contact/contact.module.ts +++ b/packages/api/src/crm/contact/contact.module.ts @@ -41,6 +41,13 @@ import { ServiceRegistry } from './services/registry.service'; PipedriveService, HubspotService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class ContactModule {} diff --git a/packages/api/src/crm/contact/sync/sync.processor.ts b/packages/api/src/crm/contact/sync/sync.processor.ts index e45130ce4..aafd01b46 100644 --- a/packages/api/src/crm/contact/sync/sync.processor.ts +++ b/packages/api/src/crm/contact/sync/sync.processor.ts @@ -5,8 +5,9 @@ import { SyncService } from './sync.service'; @Processor('syncTasks') export class SyncProcessor { constructor(private syncService: SyncService) {} - @Process('crm-sync-contacts') - async handleSyncCompanies(job: Job) { + + @Process({ name: 'crm-sync-contacts', concurrency: 5 }) + async handleSyncContacts(job: Job) { try { console.log(`Processing queue -> crm-sync-contacts ${job.id}`); await this.syncService.syncContacts(); diff --git a/packages/api/src/crm/contact/sync/sync.service.ts b/packages/api/src/crm/contact/sync/sync.service.ts index e2e81dac1..889b0015d 100644 --- a/packages/api/src/crm/contact/sync/sync.service.ts +++ b/packages/api/src/crm/contact/sync/sync.service.ts @@ -49,23 +49,37 @@ export class SyncService implements OnModuleInit { // Remove existing jobs to avoid duplicates in case of application restart const jobs = await this.syncQueue.getRepeatableJobs(); + console.log(`Found ${jobs.length} repeatable jobs.`); for (const job of jobs) { + console.log(`Checking job: ${job.name}`); if (job.name === jobName) { + console.log(`Removing job with key: ${job.key}`); await this.syncQueue.removeRepeatableByKey(job.key); } } + // Add new job to the queue with a CRON expression - await this.syncQueue.add( - jobName, - {}, - { - repeat: { cron: '0 0 * * *' }, // Runs once a day at midnight - }, - ); + console.log(`Adding new job: ${jobName}`); + await this.syncQueue + .add( + jobName, + {}, + { + repeat: { cron: '*/2 * * * *' }, // Runs once a day at midnight + }, + ) + .then(() => { + console.log('Job added successfully'); + }) + .catch((error) => { + console.error('Failed to add job', error); + }); } //function used by sync worker which populate our crm_contacts table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncContacts(user_id?: string) { try { this.logger.log(`Syncing contacts....`); diff --git a/packages/api/src/crm/contact/types/model.unified.ts b/packages/api/src/crm/contact/types/model.unified.ts index b4fcd6c3d..1a1266b1c 100644 --- a/packages/api/src/crm/contact/types/model.unified.ts +++ b/packages/api/src/crm/contact/types/model.unified.ts @@ -15,19 +15,22 @@ export class UnifiedContactInput { type: [Email], description: 'The email addresses of the contact', }) - email_addresses: Email[]; + @IsOptional() + email_addresses?: Email[]; @ApiPropertyOptional({ type: [Phone], description: 'The phone numbers of the contact', }) - phone_numbers: Phone[]; + @IsOptional() + phone_numbers?: Phone[]; @ApiPropertyOptional({ type: [Address], description: 'The addresses of the contact', }) - addresses: Address[]; + @IsOptional() + addresses?: Address[]; @ApiPropertyOptional({ type: String, diff --git a/packages/api/src/crm/deal/deal.module.ts b/packages/api/src/crm/deal/deal.module.ts index 073979694..ba2f66537 100644 --- a/packages/api/src/crm/deal/deal.module.ts +++ b/packages/api/src/crm/deal/deal.module.ts @@ -39,6 +39,13 @@ import { ZohoService } from './services/zoho'; PipedriveService, HubspotService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class DealModule {} diff --git a/packages/api/src/crm/deal/sync/sync.service.ts b/packages/api/src/crm/deal/sync/sync.service.ts index 5140823ac..13565fb4b 100644 --- a/packages/api/src/crm/deal/sync/sync.service.ts +++ b/packages/api/src/crm/deal/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our crm_deals table //its role is to fetch all deals from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncDeals(user_id?: string) { try { this.logger.log(`Syncing deals....`); diff --git a/packages/api/src/crm/engagement/engagement.module.ts b/packages/api/src/crm/engagement/engagement.module.ts index e7461d249..1f068bf0e 100644 --- a/packages/api/src/crm/engagement/engagement.module.ts +++ b/packages/api/src/crm/engagement/engagement.module.ts @@ -39,6 +39,13 @@ import { ZohoService } from './services/zoho'; PipedriveService, HubspotService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class EngagementModule {} diff --git a/packages/api/src/crm/engagement/sync/sync.service.ts b/packages/api/src/crm/engagement/sync/sync.service.ts index 010b9a923..68c1bd0dc 100644 --- a/packages/api/src/crm/engagement/sync/sync.service.ts +++ b/packages/api/src/crm/engagement/sync/sync.service.ts @@ -61,6 +61,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our crm_engagements table //its role is to fetch all engagements from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncEngagements(user_id?: string) { try { this.logger.log(`Syncing engagements....`); diff --git a/packages/api/src/crm/note/note.module.ts b/packages/api/src/crm/note/note.module.ts index f175da205..1c2de8847 100644 --- a/packages/api/src/crm/note/note.module.ts +++ b/packages/api/src/crm/note/note.module.ts @@ -39,6 +39,13 @@ import { ZohoService } from './services/zoho'; PipedriveService, HubspotService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class NoteModule {} diff --git a/packages/api/src/crm/note/sync/sync.service.ts b/packages/api/src/crm/note/sync/sync.service.ts index b81edf25e..09d98ce19 100644 --- a/packages/api/src/crm/note/sync/sync.service.ts +++ b/packages/api/src/crm/note/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our crm_notes table //its role is to fetch all notes from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncNotes(user_id?: string) { try { this.logger.log(`Syncing notes....`); diff --git a/packages/api/src/crm/stage/stage.module.ts b/packages/api/src/crm/stage/stage.module.ts index e1152061e..3105933f1 100644 --- a/packages/api/src/crm/stage/stage.module.ts +++ b/packages/api/src/crm/stage/stage.module.ts @@ -39,6 +39,13 @@ import { ZohoService } from './services/zoho'; PipedriveService, HubspotService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class StageModule {} diff --git a/packages/api/src/crm/stage/sync/sync.service.ts b/packages/api/src/crm/stage/sync/sync.service.ts index 4e9264eb9..3819c18aa 100644 --- a/packages/api/src/crm/stage/sync/sync.service.ts +++ b/packages/api/src/crm/stage/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our crm_stages table //its role is to fetch all stages from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncStages(user_id?: string) { try { this.logger.log(`Syncing stages....`); diff --git a/packages/api/src/crm/task/sync/sync.service.ts b/packages/api/src/crm/task/sync/sync.service.ts index 41c91bf49..65c3b14f7 100644 --- a/packages/api/src/crm/task/sync/sync.service.ts +++ b/packages/api/src/crm/task/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our crm_tasks table //its role is to fetch all tasks from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncTasks(user_id?: string) { try { this.logger.log(`Syncing tasks....`); diff --git a/packages/api/src/crm/task/task.module.ts b/packages/api/src/crm/task/task.module.ts index e587eaf5a..e392a5f94 100644 --- a/packages/api/src/crm/task/task.module.ts +++ b/packages/api/src/crm/task/task.module.ts @@ -39,6 +39,13 @@ import { ZohoService } from './services/zoho'; PipedriveService, HubspotService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class TaskModule {} diff --git a/packages/api/src/crm/user/sync/sync.service.ts b/packages/api/src/crm/user/sync/sync.service.ts index 2e545b23f..c7b552f8b 100644 --- a/packages/api/src/crm/user/sync/sync.service.ts +++ b/packages/api/src/crm/user/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our crm_users table //its role is to fetch all users from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncUsers(user_id?: string) { try { this.logger.log(`Syncing users....`); diff --git a/packages/api/src/crm/user/user.module.ts b/packages/api/src/crm/user/user.module.ts index 2f5fea9bd..46960ac91 100644 --- a/packages/api/src/crm/user/user.module.ts +++ b/packages/api/src/crm/user/user.module.ts @@ -39,6 +39,13 @@ import { ZohoService } from './services/zoho'; PipedriveService, HubspotService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class UserModule {} diff --git a/packages/api/src/ticketing/account/account.module.ts b/packages/api/src/ticketing/account/account.module.ts index 272f5745f..cd34201d9 100644 --- a/packages/api/src/ticketing/account/account.module.ts +++ b/packages/api/src/ticketing/account/account.module.ts @@ -35,6 +35,13 @@ import { FrontService } from './services/front'; ZendeskService, FrontService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class AccountModule {} diff --git a/packages/api/src/ticketing/account/sync/sync.service.ts b/packages/api/src/ticketing/account/sync/sync.service.ts index 4a7b5bff4..5ec6d55ed 100644 --- a/packages/api/src/ticketing/account/sync/sync.service.ts +++ b/packages/api/src/ticketing/account/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_accounts table //its role is to fetch all accounts from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncAccounts(user_id?: string) { try { this.logger.log(`Syncing accounts....`); diff --git a/packages/api/src/ticketing/collection/collection.module.ts b/packages/api/src/ticketing/collection/collection.module.ts index 45637df10..03dd577e7 100644 --- a/packages/api/src/ticketing/collection/collection.module.ts +++ b/packages/api/src/ticketing/collection/collection.module.ts @@ -33,6 +33,13 @@ import { JiraService } from './services/jira'; /* PROVIDERS SERVICES */ JiraService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class CollectionModule {} diff --git a/packages/api/src/ticketing/collection/sync/sync.service.ts b/packages/api/src/ticketing/collection/sync/sync.service.ts index 674cff9e7..fca3711db 100644 --- a/packages/api/src/ticketing/collection/sync/sync.service.ts +++ b/packages/api/src/ticketing/collection/sync/sync.service.ts @@ -59,6 +59,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_collections table //its role is to fetch all collections from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncCollections(user_id?: string) { try { this.logger.log(`Syncing collections....`); diff --git a/packages/api/src/ticketing/comment/comment.module.ts b/packages/api/src/ticketing/comment/comment.module.ts index 841c56337..42df81c12 100644 --- a/packages/api/src/ticketing/comment/comment.module.ts +++ b/packages/api/src/ticketing/comment/comment.module.ts @@ -39,6 +39,13 @@ import { GorgiasService } from './services/gorgias'; JiraService, GorgiasService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class CommentModule {} diff --git a/packages/api/src/ticketing/comment/sync/sync.service.ts b/packages/api/src/ticketing/comment/sync/sync.service.ts index 69b72c19b..f6147b1e9 100644 --- a/packages/api/src/ticketing/comment/sync/sync.service.ts +++ b/packages/api/src/ticketing/comment/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_comments table //its role is to fetch all comments from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncComments(user_id?: string) { try { this.logger.log(`Syncing comments....`); diff --git a/packages/api/src/ticketing/contact/contact.module.ts b/packages/api/src/ticketing/contact/contact.module.ts index ed06cab23..b0858a62f 100644 --- a/packages/api/src/ticketing/contact/contact.module.ts +++ b/packages/api/src/ticketing/contact/contact.module.ts @@ -37,6 +37,13 @@ import { GorgiasService } from './services/gorgias'; FrontService, GorgiasService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class ContactModule {} diff --git a/packages/api/src/ticketing/contact/sync/sync.service.ts b/packages/api/src/ticketing/contact/sync/sync.service.ts index 2cb80862c..1999c7803 100644 --- a/packages/api/src/ticketing/contact/sync/sync.service.ts +++ b/packages/api/src/ticketing/contact/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_contacts table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncContacts(user_id?: string) { try { this.logger.log(`Syncing contacts....`); diff --git a/packages/api/src/ticketing/tag/sync/sync.service.ts b/packages/api/src/ticketing/tag/sync/sync.service.ts index 01d1104e3..13b1d250e 100644 --- a/packages/api/src/ticketing/tag/sync/sync.service.ts +++ b/packages/api/src/ticketing/tag/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_tags table //its role is to fetch all tags from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncTags(user_id?: string) { try { this.logger.log(`Syncing tags....`); diff --git a/packages/api/src/ticketing/tag/tag.module.ts b/packages/api/src/ticketing/tag/tag.module.ts index 216c7772a..1e2983fab 100644 --- a/packages/api/src/ticketing/tag/tag.module.ts +++ b/packages/api/src/ticketing/tag/tag.module.ts @@ -39,6 +39,13 @@ import { GorgiasService } from './services/gorgias'; JiraService, GorgiasService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class TagModule {} diff --git a/packages/api/src/ticketing/team/sync/sync.service.ts b/packages/api/src/ticketing/team/sync/sync.service.ts index 297c56554..e18098e67 100644 --- a/packages/api/src/ticketing/team/sync/sync.service.ts +++ b/packages/api/src/ticketing/team/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_teams table //its role is to fetch all teams from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncTeams(user_id?: string) { try { this.logger.log(`Syncing teams....`); diff --git a/packages/api/src/ticketing/team/team.module.ts b/packages/api/src/ticketing/team/team.module.ts index 770e9adc7..3b206c257 100644 --- a/packages/api/src/ticketing/team/team.module.ts +++ b/packages/api/src/ticketing/team/team.module.ts @@ -39,6 +39,13 @@ import { GorgiasService } from './services/gorgias'; JiraService, GorgiasService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class TeamModule {} diff --git a/packages/api/src/ticketing/ticket/sync/sync.service.ts b/packages/api/src/ticketing/ticket/sync/sync.service.ts index 7a27e861b..ebc004ae0 100644 --- a/packages/api/src/ticketing/ticket/sync/sync.service.ts +++ b/packages/api/src/ticketing/ticket/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_tickets table //its role is to fetch all contacts from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncTickets(user_id?: string) { try { this.logger.log(`Syncing tickets....`); diff --git a/packages/api/src/ticketing/ticket/ticket.module.ts b/packages/api/src/ticketing/ticket/ticket.module.ts index 7734697ad..22195516f 100644 --- a/packages/api/src/ticketing/ticket/ticket.module.ts +++ b/packages/api/src/ticketing/ticket/ticket.module.ts @@ -43,6 +43,13 @@ import { GorgiasService } from './services/gorgias'; JiraService, GorgiasService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class TicketModule {} diff --git a/packages/api/src/ticketing/user/sync/sync.service.ts b/packages/api/src/ticketing/user/sync/sync.service.ts index e4d68eba7..113dcda4e 100644 --- a/packages/api/src/ticketing/user/sync/sync.service.ts +++ b/packages/api/src/ticketing/user/sync/sync.service.ts @@ -60,6 +60,8 @@ export class SyncService implements OnModuleInit { } //function used by sync worker which populate our tcg_users table //its role is to fetch all users from providers 3rd parties and save the info inside our db + //@Cron('*/2 * * * *') // every 2 minutes (for testing) + @Cron('0 */8 * * *') // every 8 hours async syncUsers(user_id?: string) { try { this.logger.log(`Syncing users....`); diff --git a/packages/api/src/ticketing/user/user.module.ts b/packages/api/src/ticketing/user/user.module.ts index bf4c644c0..f00b064e1 100644 --- a/packages/api/src/ticketing/user/user.module.ts +++ b/packages/api/src/ticketing/user/user.module.ts @@ -39,6 +39,13 @@ import { GorgiasService } from './services/gorgias'; JiraService, GorgiasService, ], - exports: [SyncService], + exports: [ + SyncService, + ServiceRegistry, + WebhookService, + FieldMappingService, + LoggerService, + PrismaService, + ], }) export class UserModule {} diff --git a/packages/api/swagger/swagger-spec.json b/packages/api/swagger/swagger-spec.json index 8a93f3700..e8948840e 100644 --- a/packages/api/swagger/swagger-spec.json +++ b/packages/api/swagger/swagger-spec.json @@ -1093,10 +1093,58 @@ ] } }, - "/crm/contacts": { + "/sync/status/{vertical}": { "get": { - "operationId": "getContacts", - "summary": "List a batch of CRM Contacts", + "operationId": "getSyncStatus", + "summary": "Retrieve sync status of a certain vertical", + "parameters": [ + { + "name": "vertical", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "sync" + ] + } + }, + "/sync/resync/{vertical}": { + "get": { + "operationId": "resync", + "summary": "Resync common objects across a vertical", + "parameters": [ + { + "name": "vertical", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "sync" + ] + } + }, + "/crm/companies": { + "get": { + "operationId": "getCompanies", + "summary": "List a batch of Companies", "parameters": [ { "name": "x-connection-token", @@ -1111,7 +1159,7 @@ "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 Crm software.", "schema": { "type": "boolean" } @@ -1130,7 +1178,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "$ref": "#/components/schemas/UnifiedCompanyOutput" } } } @@ -1141,13 +1189,13 @@ } }, "tags": [ - "crm/contacts" + "crm/companies" ] }, "post": { - "operationId": "addContact", - "summary": "Create CRM Contact", - "description": "Create a contact in any supported CRM", + "operationId": "addCompany", + "summary": "Create a Company", + "description": "Create a company in any supported Crm software", "parameters": [ { "name": "x-connection-token", @@ -1162,7 +1210,7 @@ "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 Crm software.", "schema": { "type": "boolean" } @@ -1173,7 +1221,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedContactInput" + "$ref": "#/components/schemas/UnifiedCompanyInput" } } } @@ -1191,7 +1239,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "$ref": "#/components/schemas/UnifiedCompanyOutput" } } } @@ -1205,19 +1253,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "$ref": "#/components/schemas/UnifiedCompanyOutput" } } } } }, "tags": [ - "crm/contacts" + "crm/companies" ] }, "patch": { - "operationId": "updateContact", - "summary": "Update a CRM Contact", + "operationId": "updateCompany", + "summary": "Update a Company", "parameters": [ { "name": "id", @@ -1234,28 +1282,39 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "allOf": [ + { + "$ref": "#/components/schemas/ApiResponse" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/UnifiedCompanyOutput" + } + } + } + ] } } } } }, "tags": [ - "crm/contacts" + "crm/companies" ] } }, - "/crm/contacts/{id}": { + "/crm/companies/{id}": { "get": { - "operationId": "getContact", - "summary": "Retrieve a CRM Contact", - "description": "Retrieve a contact from any connected CRM", + "operationId": "getCompany", + "summary": "Retrieve a Company", + "description": "Retrieve a company from any connected Crm software", "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the `contact` you want to retrive.", + "description": "id of the company you want to retrieve.", "schema": { "type": "string" } @@ -1264,7 +1323,7 @@ "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 Crm software.", "schema": { "type": "boolean" } @@ -1283,7 +1342,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "$ref": "#/components/schemas/UnifiedCompanyOutput" } } } @@ -1294,14 +1353,14 @@ } }, "tags": [ - "crm/contacts" + "crm/companies" ] } }, - "/crm/contacts/batch": { + "/crm/companies/batch": { "post": { - "operationId": "addContacts", - "summary": "Add a batch of CRM Contacts", + "operationId": "addCompanies", + "summary": "Add a batch of Companies", "parameters": [ { "name": "x-connection-token", @@ -1316,7 +1375,7 @@ "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 Crm software.", "schema": { "type": "boolean" } @@ -1329,7 +1388,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedContactInput" + "$ref": "#/components/schemas/UnifiedCompanyInput" } } } @@ -1348,7 +1407,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "$ref": "#/components/schemas/UnifiedCompanyOutput" } } } @@ -1364,7 +1423,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "$ref": "#/components/schemas/UnifiedCompanyOutput" } } } @@ -1372,14 +1431,14 @@ } }, "tags": [ - "crm/contacts" + "crm/companies" ] } }, - "/crm/deals": { + "/crm/contacts": { "get": { - "operationId": "getDeals", - "summary": "List a batch of Deals", + "operationId": "getContacts", + "summary": "List a batch of CRM Contacts", "parameters": [ { "name": "x-connection-token", @@ -1394,7 +1453,7 @@ "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 CRM software.", "schema": { "type": "boolean" } @@ -1413,7 +1472,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedDealOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } @@ -1424,13 +1483,13 @@ } }, "tags": [ - "crm/deals" + "crm/contacts" ] }, "post": { - "operationId": "addDeal", - "summary": "Create a Deal", - "description": "Create a deal in any supported Crm software", + "operationId": "addContact", + "summary": "Create CRM Contact", + "description": "Create a contact in any supported CRM", "parameters": [ { "name": "x-connection-token", @@ -1445,7 +1504,7 @@ "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 CRM software.", "schema": { "type": "boolean" } @@ -1456,7 +1515,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedDealInput" + "$ref": "#/components/schemas/UnifiedContactInput" } } } @@ -1474,7 +1533,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedDealOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } @@ -1488,39 +1547,26 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedDealOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } } }, "tags": [ - "crm/deals" + "crm/contacts" ] - } - }, - "/crm/deals/{id}": { - "get": { - "operationId": "getDeal", - "summary": "Retrieve a Deal", - "description": "Retrieve a deal from any connected Crm software", + }, + "patch": { + "operationId": "updateContact", + "summary": "Update a CRM Contact", "parameters": [ { "name": "id", "required": true, - "in": "path", - "description": "id of the deal you want to retrieve.", - "schema": { - "type": "string" - } - }, - { - "name": "remote_data", - "required": false, "in": "query", - "description": "Set to true to include data from the original Crm software.", "schema": { - "type": "boolean" + "type": "string" } } ], @@ -1530,38 +1576,40 @@ "content": { "application/json": { "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ApiResponse" - }, - { - "properties": { - "data": { - "$ref": "#/components/schemas/UnifiedDealOutput" - } - } - } - ] + "$ref": "#/components/schemas/UnifiedContactOutput" } } } } }, "tags": [ - "crm/deals" + "crm/contacts" ] - }, - "patch": { - "operationId": "updateDeal", - "summary": "Update a Deal", + } + }, + "/crm/contacts/{id}": { + "get": { + "operationId": "getContact", + "summary": "Retrieve a CRM Contact", + "description": "Retrieve a contact from any connected CRM", "parameters": [ { "name": "id", "required": true, "in": "path", + "description": "id of the `contact` you want to retrive.", "schema": { "type": "string" } + }, + { + "name": "remote_data", + "required": false, + "in": "query", + "description": "Set to true to include data from the original CRM software.", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -1577,7 +1625,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedDealOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } @@ -1588,14 +1636,14 @@ } }, "tags": [ - "crm/deals" + "crm/contacts" ] } }, - "/crm/deals/batch": { + "/crm/contacts/batch": { "post": { - "operationId": "addDeals", - "summary": "Add a batch of Deals", + "operationId": "addContacts", + "summary": "Add a batch of CRM Contacts", "parameters": [ { "name": "x-connection-token", @@ -1610,7 +1658,7 @@ "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 CRM software.", "schema": { "type": "boolean" } @@ -1623,7 +1671,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedDealInput" + "$ref": "#/components/schemas/UnifiedContactInput" } } } @@ -1642,7 +1690,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedDealOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } @@ -1658,7 +1706,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedDealOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } @@ -1666,14 +1714,14 @@ } }, "tags": [ - "crm/deals" + "crm/contacts" ] } }, - "/crm/notes": { + "/crm/deals": { "get": { - "operationId": "getNotes", - "summary": "List a batch of Notes", + "operationId": "getDeals", + "summary": "List a batch of Deals", "parameters": [ { "name": "x-connection-token", @@ -1707,7 +1755,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedNoteOutput" + "$ref": "#/components/schemas/UnifiedDealOutput" } } } @@ -1718,13 +1766,13 @@ } }, "tags": [ - "crm/notes" + "crm/deals" ] }, "post": { - "operationId": "addNote", - "summary": "Create a Note", - "description": "Create a note in any supported Crm software", + "operationId": "addDeal", + "summary": "Create a Deal", + "description": "Create a deal in any supported Crm software", "parameters": [ { "name": "x-connection-token", @@ -1750,7 +1798,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedNoteInput" + "$ref": "#/components/schemas/UnifiedDealInput" } } } @@ -1768,7 +1816,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedNoteOutput" + "$ref": "#/components/schemas/UnifiedDealOutput" } } } @@ -1782,28 +1830,28 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedNoteOutput" + "$ref": "#/components/schemas/UnifiedDealOutput" } } } } }, "tags": [ - "crm/notes" + "crm/deals" ] } }, - "/crm/notes/{id}": { + "/crm/deals/{id}": { "get": { - "operationId": "getNote", - "summary": "Retrieve a Note", - "description": "Retrieve a note from any connected Crm software", - "parameters": [ + "operationId": "getDeal", + "summary": "Retrieve a Deal", + "description": "Retrieve a deal from any connected Crm software", + "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the note you want to retrieve.", + "description": "id of the deal you want to retrieve.", "schema": { "type": "string" } @@ -1831,7 +1879,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedNoteOutput" + "$ref": "#/components/schemas/UnifiedDealOutput" } } } @@ -1842,14 +1890,54 @@ } }, "tags": [ - "crm/notes" + "crm/deals" + ] + }, + "patch": { + "operationId": "updateDeal", + "summary": "Update a Deal", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ApiResponse" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/UnifiedDealOutput" + } + } + } + ] + } + } + } + } + }, + "tags": [ + "crm/deals" ] } }, - "/crm/notes/batch": { + "/crm/deals/batch": { "post": { - "operationId": "addNotes", - "summary": "Add a batch of Notes", + "operationId": "addDeals", + "summary": "Add a batch of Deals", "parameters": [ { "name": "x-connection-token", @@ -1877,7 +1965,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedNoteInput" + "$ref": "#/components/schemas/UnifiedDealInput" } } } @@ -1896,7 +1984,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedNoteOutput" + "$ref": "#/components/schemas/UnifiedDealOutput" } } } @@ -1912,7 +2000,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedNoteOutput" + "$ref": "#/components/schemas/UnifiedDealOutput" } } } @@ -1920,14 +2008,14 @@ } }, "tags": [ - "crm/notes" + "crm/deals" ] } }, - "/crm/companies": { + "/crm/engagements": { "get": { - "operationId": "getCompanies", - "summary": "List a batch of Companies", + "operationId": "getEngagements", + "summary": "List a batch of Engagements", "parameters": [ { "name": "x-connection-token", @@ -1961,7 +2049,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedCompanyOutput" + "$ref": "#/components/schemas/UnifiedEngagementOutput" } } } @@ -1972,13 +2060,13 @@ } }, "tags": [ - "crm/companies" + "crm/engagements" ] }, "post": { - "operationId": "addCompany", - "summary": "Create a Company", - "description": "Create a company in any supported Crm software", + "operationId": "addEngagement", + "summary": "Create a Engagement", + "description": "Create a engagement in any supported Crm software", "parameters": [ { "name": "x-connection-token", @@ -2004,7 +2092,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedCompanyInput" + "$ref": "#/components/schemas/UnifiedEngagementInput" } } } @@ -2022,7 +2110,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedCompanyOutput" + "$ref": "#/components/schemas/UnifiedEngagementOutput" } } } @@ -2036,19 +2124,19 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedCompanyOutput" + "$ref": "#/components/schemas/UnifiedEngagementOutput" } } } } }, "tags": [ - "crm/companies" + "crm/engagements" ] }, "patch": { - "operationId": "updateCompany", - "summary": "Update a Company", + "operationId": "updateEngagement", + "summary": "Update a Engagement", "parameters": [ { "name": "id", @@ -2072,7 +2160,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedCompanyOutput" + "$ref": "#/components/schemas/UnifiedEngagementOutput" } } } @@ -2083,21 +2171,21 @@ } }, "tags": [ - "crm/companies" + "crm/engagements" ] } }, - "/crm/companies/{id}": { + "/crm/engagements/{id}": { "get": { - "operationId": "getCompany", - "summary": "Retrieve a Company", - "description": "Retrieve a company from any connected Crm software", + "operationId": "getEngagement", + "summary": "Retrieve a Engagement", + "description": "Retrieve a engagement from any connected Crm software", "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the company you want to retrieve.", + "description": "id of the engagement you want to retrieve.", "schema": { "type": "string" } @@ -2125,7 +2213,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedCompanyOutput" + "$ref": "#/components/schemas/UnifiedEngagementOutput" } } } @@ -2136,14 +2224,14 @@ } }, "tags": [ - "crm/companies" + "crm/engagements" ] } }, - "/crm/companies/batch": { + "/crm/engagements/batch": { "post": { - "operationId": "addCompanies", - "summary": "Add a batch of Companies", + "operationId": "addEngagements", + "summary": "Add a batch of Engagements", "parameters": [ { "name": "x-connection-token", @@ -2171,7 +2259,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedCompanyInput" + "$ref": "#/components/schemas/UnifiedEngagementInput" } } } @@ -2190,7 +2278,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedCompanyOutput" + "$ref": "#/components/schemas/UnifiedEngagementOutput" } } } @@ -2206,7 +2294,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedCompanyOutput" + "$ref": "#/components/schemas/UnifiedEngagementOutput" } } } @@ -2214,14 +2302,14 @@ } }, "tags": [ - "crm/companies" + "crm/engagements" ] } }, - "/crm/engagements": { + "/crm/notes": { "get": { - "operationId": "getEngagements", - "summary": "List a batch of Engagements", + "operationId": "getNotes", + "summary": "List a batch of Notes", "parameters": [ { "name": "x-connection-token", @@ -2255,7 +2343,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedEngagementOutput" + "$ref": "#/components/schemas/UnifiedNoteOutput" } } } @@ -2266,13 +2354,13 @@ } }, "tags": [ - "crm/engagements" + "crm/notes" ] }, "post": { - "operationId": "addEngagement", - "summary": "Create a Engagement", - "description": "Create a engagement in any supported Crm software", + "operationId": "addNote", + "summary": "Create a Note", + "description": "Create a note in any supported Crm software", "parameters": [ { "name": "x-connection-token", @@ -2298,7 +2386,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedEngagementInput" + "$ref": "#/components/schemas/UnifiedNoteInput" } } } @@ -2316,7 +2404,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedEngagementOutput" + "$ref": "#/components/schemas/UnifiedNoteOutput" } } } @@ -2330,68 +2418,28 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnifiedEngagementOutput" - } - } - } - } - }, - "tags": [ - "crm/engagements" - ] - }, - "patch": { - "operationId": "updateEngagement", - "summary": "Update a Engagement", - "parameters": [ - { - "name": "id", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ApiResponse" - }, - { - "properties": { - "data": { - "$ref": "#/components/schemas/UnifiedEngagementOutput" - } - } - } - ] + "$ref": "#/components/schemas/UnifiedNoteOutput" } } } } }, "tags": [ - "crm/engagements" + "crm/notes" ] } }, - "/crm/engagements/{id}": { + "/crm/notes/{id}": { "get": { - "operationId": "getEngagement", - "summary": "Retrieve a Engagement", - "description": "Retrieve a engagement from any connected Crm software", + "operationId": "getNote", + "summary": "Retrieve a Note", + "description": "Retrieve a note from any connected Crm software", "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the engagement you want to retrieve.", + "description": "id of the note you want to retrieve.", "schema": { "type": "string" } @@ -2419,7 +2467,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedEngagementOutput" + "$ref": "#/components/schemas/UnifiedNoteOutput" } } } @@ -2430,14 +2478,14 @@ } }, "tags": [ - "crm/engagements" + "crm/notes" ] } }, - "/crm/engagements/batch": { + "/crm/notes/batch": { "post": { - "operationId": "addEngagements", - "summary": "Add a batch of Engagements", + "operationId": "addNotes", + "summary": "Add a batch of Notes", "parameters": [ { "name": "x-connection-token", @@ -2465,7 +2513,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedEngagementInput" + "$ref": "#/components/schemas/UnifiedNoteInput" } } } @@ -2484,7 +2532,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedEngagementOutput" + "$ref": "#/components/schemas/UnifiedNoteOutput" } } } @@ -2500,7 +2548,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedEngagementOutput" + "$ref": "#/components/schemas/UnifiedNoteOutput" } } } @@ -2508,7 +2556,7 @@ } }, "tags": [ - "crm/engagements" + "crm/notes" ] } }, @@ -3016,10 +3064,10 @@ ] } }, - "/ticketing/tickets": { + "/ticketing/accounts": { "get": { - "operationId": "getTickets", - "summary": "List a batch of Tickets", + "operationId": "getAccounts", + "summary": "List a batch of Accounts", "parameters": [ { "name": "x-connection-token", @@ -3053,7 +3101,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTicketOutput" + "$ref": "#/components/schemas/UnifiedAccountOutput" } } } @@ -3064,19 +3112,21 @@ } }, "tags": [ - "ticketing/tickets" + "ticketing/accounts" ] - }, - "post": { - "operationId": "addTicket", - "summary": "Create a Ticket", - "description": "Create a ticket in any supported Ticketing software", + } + }, + "/ticketing/accounts/{id}": { + "get": { + "operationId": "getAccount", + "summary": "Retrieve an Account", + "description": "Retrieve an account from any connected Ticketing software", "parameters": [ { - "name": "x-connection-token", + "name": "id", "required": true, - "in": "header", - "description": "The connection token", + "in": "path", + "description": "id of the account you want to retrieve.", "schema": { "type": "string" } @@ -3091,16 +3141,6 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnifiedTicketInput" - } - } - } - }, "responses": { "200": { "description": "", @@ -3114,7 +3154,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTicketOutput" + "$ref": "#/components/schemas/UnifiedAccountOutput" } } } @@ -3122,63 +3162,23 @@ } } } - }, - "201": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnifiedTicketOutput" - } - } - } - } - }, - "tags": [ - "ticketing/tickets" - ] - }, - "patch": { - "operationId": "updateTicket", - "summary": "Update a Ticket", - "parameters": [ - { - "name": "id", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnifiedTicketOutput" - } - } - } } }, "tags": [ - "ticketing/tickets" + "ticketing/accounts" ] } }, - "/ticketing/tickets/{id}": { + "/ticketing/collection": { "get": { - "operationId": "getTicket", - "summary": "Retrieve a Ticket", - "description": "Retrieve a ticket from any connected Ticketing software", + "operationId": "getCollections", + "summary": "List a batch of Collections", "parameters": [ { - "name": "id", + "name": "x-connection-token", "required": true, - "in": "path", - "description": "id of the `ticket` you want to retrive.", + "in": "header", + "description": "The connection token", "schema": { "type": "string" } @@ -3206,7 +3206,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTicketOutput" + "$ref": "#/components/schemas/UnifiedCollectionOutput" } } } @@ -3217,20 +3217,21 @@ } }, "tags": [ - "ticketing/tickets" + "ticketing/collection" ] } }, - "/ticketing/tickets/batch": { - "post": { - "operationId": "addTickets", - "summary": "Add a batch of Tickets", + "/ticketing/collection/{id}": { + "get": { + "operationId": "getCollection", + "summary": "Retrieve a Collection", + "description": "Retrieve a collection from any connected Ticketing software", "parameters": [ { - "name": "x-connection-token", + "name": "id", "required": true, - "in": "header", - "description": "The connection token", + "in": "path", + "description": "id of the collection you want to retrieve.", "schema": { "type": "string" } @@ -3245,19 +3246,6 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UnifiedTicketInput" - } - } - } - } - }, "responses": { "200": { "description": "", @@ -3271,7 +3259,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTicketOutput" + "$ref": "#/components/schemas/UnifiedCollectionOutput" } } } @@ -3279,23 +3267,10 @@ } } } - }, - "201": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UnifiedTicketOutput" - } - } - } - } } }, "tags": [ - "ticketing/tickets" + "ticketing/collection" ] } }, @@ -3553,10 +3528,10 @@ ] } }, - "/ticketing/users": { + "/ticketing/contacts": { "get": { - "operationId": "getUsers", - "summary": "List a batch of Users", + "operationId": "getContacts", + "summary": "List a batch of Contacts", "parameters": [ { "name": "x-connection-token", @@ -3590,7 +3565,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedUserOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } @@ -3601,21 +3576,21 @@ } }, "tags": [ - "ticketing/users" + "ticketing/contacts" ] } }, - "/ticketing/users/{id}": { + "/ticketing/contacts/{id}": { "get": { - "operationId": "getUser", - "summary": "Retrieve a User", - "description": "Retrieve a user from any connected Ticketing software", + "operationId": "getContact", + "summary": "Retrieve a Contact", + "description": "Retrieve a contact from any connected Ticketing software", "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the user you want to retrieve.", + "description": "id of the contact you want to retrieve.", "schema": { "type": "string" } @@ -3643,7 +3618,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedUserOutput" + "$ref": "#/components/schemas/UnifiedContactOutput" } } } @@ -3654,14 +3629,14 @@ } }, "tags": [ - "ticketing/users" + "ticketing/contacts" ] } }, - "/ticketing/attachments": { + "/ticketing/tags": { "get": { - "operationId": "getAttachments", - "summary": "List a batch of Attachments", + "operationId": "getTags", + "summary": "List a batch of Tags", "parameters": [ { "name": "x-connection-token", @@ -3695,7 +3670,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedAttachmentOutput" + "$ref": "#/components/schemas/UnifiedTagOutput" } } } @@ -3706,19 +3681,21 @@ } }, "tags": [ - "ticketing/attachments" + "ticketing/tags" ] - }, - "post": { - "operationId": "addAttachment", - "summary": "Create a Attachment", - "description": "Create a attachment in any supported Ticketing software", + } + }, + "/ticketing/tags/{id}": { + "get": { + "operationId": "getTag", + "summary": "Retrieve a Tag", + "description": "Retrieve a tag from any connected Ticketing software", "parameters": [ { - "name": "x-connection-token", + "name": "id", "required": true, - "in": "header", - "description": "The connection token", + "in": "path", + "description": "id of the tag you want to retrieve.", "schema": { "type": "string" } @@ -3733,16 +3710,6 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnifiedAttachmentInput" - } - } - } - }, "responses": { "200": { "description": "", @@ -3756,7 +3723,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedAttachmentOutput" + "$ref": "#/components/schemas/UnifiedTagOutput" } } } @@ -3764,34 +3731,23 @@ } } } - }, - "201": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnifiedAttachmentOutput" - } - } - } } }, "tags": [ - "ticketing/attachments" + "ticketing/tags" ] } }, - "/ticketing/attachments/{id}": { + "/ticketing/teams": { "get": { - "operationId": "getAttachment", - "summary": "Retrieve a Attachment", - "description": "Retrieve a attachment from any connected Ticketing software", + "operationId": "getTeams", + "summary": "List a batch of Teams", "parameters": [ { - "name": "id", + "name": "x-connection-token", "required": true, - "in": "path", - "description": "id of the attachment you want to retrive.", + "in": "header", + "description": "The connection token", "schema": { "type": "string" } @@ -3819,7 +3775,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedAttachmentOutput" + "$ref": "#/components/schemas/UnifiedTeamOutput" } } } @@ -3830,21 +3786,21 @@ } }, "tags": [ - "ticketing/attachments" + "ticketing/teams" ] } }, - "/ticketing/attachments/{id}/download": { + "/ticketing/teams/{id}": { "get": { - "operationId": "downloadAttachment", - "summary": "Download a Attachment", - "description": "Download a attachment from any connected Ticketing software", + "operationId": "getTeam", + "summary": "Retrieve a Team", + "description": "Retrieve a team from any connected Ticketing software", "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the attachment you want to retrive.", + "description": "id of the team you want to retrieve.", "schema": { "type": "string" } @@ -3872,7 +3828,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedAttachmentOutput" + "$ref": "#/components/schemas/UnifiedTeamOutput" } } } @@ -3883,14 +3839,14 @@ } }, "tags": [ - "ticketing/attachments" + "ticketing/teams" ] } }, - "/ticketing/attachments/batch": { - "post": { - "operationId": "addAttachments", - "summary": "Add a batch of Attachments", + "/ticketing/tickets": { + "get": { + "operationId": "getTickets", + "summary": "List a batch of Tickets", "parameters": [ { "name": "x-connection-token", @@ -3911,19 +3867,6 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UnifiedAttachmentInput" - } - } - } - } - }, "responses": { "200": { "description": "", @@ -3937,7 +3880,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedAttachmentOutput" + "$ref": "#/components/schemas/UnifiedTicketOutput" } } } @@ -3945,32 +3888,18 @@ } } } - }, - "201": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UnifiedAttachmentOutput" - } - } - } - } } }, "tags": [ - "ticketing/attachments" + "ticketing/tickets" ] - } - }, - "/ticketing/contacts": { - "get": { - "operationId": "getContacts", - "summary": "List a batch of Contacts", - "parameters": [ - { + }, + "post": { + "operationId": "addTicket", + "summary": "Create a Ticket", + "description": "Create a ticket in any supported Ticketing software", + "parameters": [ + { "name": "x-connection-token", "required": true, "in": "header", @@ -3989,6 +3918,16 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnifiedTicketInput" + } + } + } + }, "responses": { "200": { "description": "", @@ -4002,7 +3941,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedContactOutput" + "$ref": "#/components/schemas/UnifiedTicketOutput" } } } @@ -4010,35 +3949,32 @@ } } } + }, + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnifiedTicketOutput" + } + } + } } }, "tags": [ - "ticketing/contacts" + "ticketing/tickets" ] - } - }, - "/ticketing/contacts/{id}": { - "get": { - "operationId": "getContact", - "summary": "Retrieve a Contact", - "description": "Retrieve a contact from any connected Ticketing software", + }, + "patch": { + "operationId": "updateTicket", + "summary": "Update a Ticket", "parameters": [ { "name": "id", "required": true, - "in": "path", - "description": "id of the contact you want to retrieve.", - "schema": { - "type": "string" - } - }, - { - "name": "remote_data", - "required": false, "in": "query", - "description": "Set to true to include data from the original Ticketing software.", "schema": { - "type": "boolean" + "type": "string" } } ], @@ -4048,38 +3984,28 @@ "content": { "application/json": { "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ApiResponse" - }, - { - "properties": { - "data": { - "$ref": "#/components/schemas/UnifiedContactOutput" - } - } - } - ] + "$ref": "#/components/schemas/UnifiedTicketOutput" } } } } }, "tags": [ - "ticketing/contacts" + "ticketing/tickets" ] } }, - "/ticketing/accounts": { + "/ticketing/tickets/{id}": { "get": { - "operationId": "getAccounts", - "summary": "List a batch of Accounts", + "operationId": "getTicket", + "summary": "Retrieve a Ticket", + "description": "Retrieve a ticket from any connected Ticketing software", "parameters": [ { - "name": "x-connection-token", + "name": "id", "required": true, - "in": "header", - "description": "The connection token", + "in": "path", + "description": "id of the `ticket` you want to retrive.", "schema": { "type": "string" } @@ -4107,7 +4033,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedAccountOutput" + "$ref": "#/components/schemas/UnifiedTicketOutput" } } } @@ -4118,21 +4044,20 @@ } }, "tags": [ - "ticketing/accounts" + "ticketing/tickets" ] } }, - "/ticketing/accounts/{id}": { - "get": { - "operationId": "getAccount", - "summary": "Retrieve an Account", - "description": "Retrieve an account from any connected Ticketing software", + "/ticketing/tickets/batch": { + "post": { + "operationId": "addTickets", + "summary": "Add a batch of Tickets", "parameters": [ { - "name": "id", + "name": "x-connection-token", "required": true, - "in": "path", - "description": "id of the account you want to retrieve.", + "in": "header", + "description": "The connection token", "schema": { "type": "string" } @@ -4147,6 +4072,19 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnifiedTicketInput" + } + } + } + } + }, "responses": { "200": { "description": "", @@ -4160,7 +4098,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedAccountOutput" + "$ref": "#/components/schemas/UnifiedTicketOutput" } } } @@ -4168,17 +4106,30 @@ } } } + }, + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnifiedTicketOutput" + } + } + } + } } }, "tags": [ - "ticketing/accounts" + "ticketing/tickets" ] } }, - "/ticketing/tags": { + "/ticketing/users": { "get": { - "operationId": "getTags", - "summary": "List a batch of Tags", + "operationId": "getUsers", + "summary": "List a batch of Users", "parameters": [ { "name": "x-connection-token", @@ -4212,7 +4163,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTagOutput" + "$ref": "#/components/schemas/UnifiedUserOutput" } } } @@ -4223,21 +4174,21 @@ } }, "tags": [ - "ticketing/tags" + "ticketing/users" ] } }, - "/ticketing/tags/{id}": { + "/ticketing/users/{id}": { "get": { - "operationId": "getTag", - "summary": "Retrieve a Tag", - "description": "Retrieve a tag from any connected Ticketing software", + "operationId": "getUser", + "summary": "Retrieve a User", + "description": "Retrieve a user from any connected Ticketing software", "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the tag you want to retrieve.", + "description": "id of the user you want to retrieve.", "schema": { "type": "string" } @@ -4265,7 +4216,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTagOutput" + "$ref": "#/components/schemas/UnifiedUserOutput" } } } @@ -4276,14 +4227,14 @@ } }, "tags": [ - "ticketing/tags" + "ticketing/users" ] } }, - "/ticketing/teams": { + "/ticketing/attachments": { "get": { - "operationId": "getTeams", - "summary": "List a batch of Teams", + "operationId": "getAttachments", + "summary": "List a batch of Attachments", "parameters": [ { "name": "x-connection-token", @@ -4317,7 +4268,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTeamOutput" + "$ref": "#/components/schemas/UnifiedAttachmentOutput" } } } @@ -4328,21 +4279,19 @@ } }, "tags": [ - "ticketing/teams" + "ticketing/attachments" ] - } - }, - "/ticketing/teams/{id}": { - "get": { - "operationId": "getTeam", - "summary": "Retrieve a Team", - "description": "Retrieve a team from any connected Ticketing software", + }, + "post": { + "operationId": "addAttachment", + "summary": "Create a Attachment", + "description": "Create a attachment in any supported Ticketing software", "parameters": [ { - "name": "id", + "name": "x-connection-token", "required": true, - "in": "path", - "description": "id of the team you want to retrieve.", + "in": "header", + "description": "The connection token", "schema": { "type": "string" } @@ -4357,6 +4306,16 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnifiedAttachmentInput" + } + } + } + }, "responses": { "200": { "description": "", @@ -4370,7 +4329,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedTeamOutput" + "$ref": "#/components/schemas/UnifiedAttachmentOutput" } } } @@ -4378,23 +4337,34 @@ } } } + }, + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnifiedAttachmentOutput" + } + } + } } }, "tags": [ - "ticketing/teams" + "ticketing/attachments" ] } }, - "/ticketing/collection": { + "/ticketing/attachments/{id}": { "get": { - "operationId": "getCollections", - "summary": "List a batch of Collections", + "operationId": "getAttachment", + "summary": "Retrieve a Attachment", + "description": "Retrieve a attachment from any connected Ticketing software", "parameters": [ { - "name": "x-connection-token", + "name": "id", "required": true, - "in": "header", - "description": "The connection token", + "in": "path", + "description": "id of the attachment you want to retrive.", "schema": { "type": "string" } @@ -4422,7 +4392,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedCollectionOutput" + "$ref": "#/components/schemas/UnifiedAttachmentOutput" } } } @@ -4433,21 +4403,21 @@ } }, "tags": [ - "ticketing/collection" + "ticketing/attachments" ] } }, - "/ticketing/collection/{id}": { + "/ticketing/attachments/{id}/download": { "get": { - "operationId": "getCollection", - "summary": "Retrieve a Collection", - "description": "Retrieve a collection from any connected Ticketing software", + "operationId": "downloadAttachment", + "summary": "Download a Attachment", + "description": "Download a attachment from any connected Ticketing software", "parameters": [ { "name": "id", "required": true, "in": "path", - "description": "id of the collection you want to retrieve.", + "description": "id of the attachment you want to retrive.", "schema": { "type": "string" } @@ -4475,7 +4445,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/UnifiedCollectionOutput" + "$ref": "#/components/schemas/UnifiedAttachmentOutput" } } } @@ -4486,27 +4456,105 @@ } }, "tags": [ - "ticketing/collection" + "ticketing/attachments" ] } - } - }, - "info": { - "title": "Unified Panora API", - "description": "The Panora API description", - "version": "1.0", - "contact": {} - }, - "tags": [], - "servers": [], - "components": { - "schemas": { - "CreateUserDto": { - "type": "object", - "properties": { - "first_name": { - "type": "string" - }, + }, + "/ticketing/attachments/batch": { + "post": { + "operationId": "addAttachments", + "summary": "Add a batch of Attachments", + "parameters": [ + { + "name": "x-connection-token", + "required": true, + "in": "header", + "description": "The connection token", + "schema": { + "type": "string" + } + }, + { + "name": "remote_data", + "required": false, + "in": "query", + "description": "Set to true to include data from the original Ticketing software.", + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnifiedAttachmentInput" + } + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ApiResponse" + }, + { + "properties": { + "data": { + "$ref": "#/components/schemas/UnifiedAttachmentOutput" + } + } + } + ] + } + } + } + }, + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnifiedAttachmentOutput" + } + } + } + } + } + }, + "tags": [ + "ticketing/attachments" + ] + } + } + }, + "info": { + "title": "Unified Panora API", + "description": "The Panora API description", + "version": "1.0", + "contact": {} + }, + "tags": [], + "servers": [], + "components": { + "schemas": { + "CreateUserDto": { + "type": "object", + "properties": { + "first_name": { + "type": "string" + }, "last_name": { "type": "string" }, @@ -4911,7 +4959,7 @@ }, "email_address_type": { "type": "string", - "description": "The email address type" + "description": "The email address type. Authorized values are either PERSONAL or WORK." }, "owner_type": { "type": "string", @@ -4923,27 +4971,6 @@ "email_address_type" ] }, - "Phone": { - "type": "object", - "properties": { - "phone_number": { - "type": "string", - "description": "The phone number" - }, - "phone_type": { - "type": "string", - "description": "The phone type" - }, - "owner_type": { - "type": "string", - "description": "The owner type of a phone number" - } - }, - "required": [ - "phone_number", - "phone_type" - ] - }, "Address": { "type": "object", "properties": { @@ -4969,11 +4996,11 @@ }, "country": { "type": "string", - "description": "The country" + "description": "The country." }, "address_type": { "type": "string", - "description": "The address type" + "description": "The address type. Authorized values are either PERSONAL or WORK." }, "owner_type": { "type": "string", @@ -4991,6 +5018,140 @@ "owner_type" ] }, + "Phone": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "description": "The phone number starting with a plus (+) followed by the country code (e.g +336676778890 for France)" + }, + "phone_type": { + "type": "string", + "description": "The phone type. Authorized values are either MOBILE or WORK" + }, + "owner_type": { + "type": "string", + "description": "The owner type of a phone number" + } + }, + "required": [ + "phone_number", + "phone_type" + ] + }, + "UnifiedCompanyOutput": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the company" + }, + "industry": { + "type": "string", + "description": "The industry of the company. Authorized values can be found in the Industry enum." + }, + "number_of_employees": { + "type": "number", + "description": "The number of employees of the company" + }, + "user_id": { + "type": "string", + "description": "The uuid of the user who owns the company" + }, + "email_addresses": { + "description": "The email addresses of the company", + "type": "array", + "items": { + "$ref": "#/components/schemas/Email" + } + }, + "addresses": { + "description": "The addresses of the company", + "type": "array", + "items": { + "$ref": "#/components/schemas/Address" + } + }, + "phone_numbers": { + "description": "The phone numbers of the company", + "type": "array", + "items": { + "$ref": "#/components/schemas/Phone" + } + }, + "field_mappings": { + "type": "object", + "properties": {} + }, + "id": { + "type": "string", + "description": "The uuid of the company" + }, + "remote_id": { + "type": "string", + "description": "The id of the company in the context of the Crm 3rd Party" + }, + "remote_data": { + "type": "object", + "properties": {} + } + }, + "required": [ + "name", + "field_mappings", + "remote_data" + ] + }, + "UnifiedCompanyInput": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the company" + }, + "industry": { + "type": "string", + "description": "The industry of the company. Authorized values can be found in the Industry enum." + }, + "number_of_employees": { + "type": "number", + "description": "The number of employees of the company" + }, + "user_id": { + "type": "string", + "description": "The uuid of the user who owns the company" + }, + "email_addresses": { + "description": "The email addresses of the company", + "type": "array", + "items": { + "$ref": "#/components/schemas/Email" + } + }, + "addresses": { + "description": "The addresses of the company", + "type": "array", + "items": { + "$ref": "#/components/schemas/Address" + } + }, + "phone_numbers": { + "description": "The phone numbers of the company", + "type": "array", + "items": { + "$ref": "#/components/schemas/Phone" + } + }, + "field_mappings": { + "type": "object", + "properties": {} + } + }, + "required": [ + "name", + "field_mappings" + ] + }, "UnifiedContactOutput": { "type": "object", "properties": { @@ -5172,123 +5333,48 @@ "field_mappings" ] }, - "UnifiedNoteOutput": { + "UnifiedEngagementOutput": { "type": "object", "properties": { "content": { "type": "string", - "description": "The content of the note" + "description": "The content of the engagement" }, - "user_id": { + "direction": { "type": "string", - "description": "The uuid of the user tied the note" + "description": "The direction of the engagement. Authorized values are INBOUND or OUTBOUND" }, - "company_id": { + "subject": { "type": "string", - "description": "The uuid of the company tied to the note" + "description": "The subject of the engagement" }, - "contact_id": { - "type": "string", - "description": "The uuid fo the contact tied to the note" - }, - "deal_id": { - "type": "string", - "description": "The uuid of the deal tied to the note" - }, - "field_mappings": { - "type": "object", - "properties": {} - }, - "id": { + "start_at": { + "format": "date-time", "type": "string", - "description": "The uuid of the note" + "description": "The start time of the engagement" }, - "remote_id": { + "end_time": { + "format": "date-time", "type": "string", - "description": "The id of the note in the context of the Crm 3rd Party" + "description": "The end time of the engagement" }, - "remote_data": { - "type": "object", - "properties": {} - } - }, - "required": [ - "content", - "field_mappings", - "remote_data" - ] - }, - "UnifiedNoteInput": { - "type": "object", - "properties": { - "content": { + "type": { "type": "string", - "description": "The content of the note" + "description": "The type of the engagement. Authorized values are EMAIL, CALL or MEETING" }, "user_id": { "type": "string", - "description": "The uuid of the user tied the note" + "description": "The uuid of the user tied to the engagement" }, "company_id": { "type": "string", - "description": "The uuid of the company tied to the note" - }, - "contact_id": { - "type": "string", - "description": "The uuid fo the contact tied to the note" - }, - "deal_id": { - "type": "string", - "description": "The uuid of the deal tied to the note" - }, - "field_mappings": { - "type": "object", - "properties": {} - } - }, - "required": [ - "content", - "field_mappings" - ] - }, - "UnifiedCompanyOutput": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the company" - }, - "industry": { - "type": "string", - "description": "The industry of the company" - }, - "number_of_employees": { - "type": "number", - "description": "The number of employees of the company" - }, - "user_id": { - "type": "string", - "description": "The uuid of the user who owns the company" - }, - "email_addresses": { - "description": "The email addresses of the company", - "type": "array", - "items": { - "$ref": "#/components/schemas/Email" - } - }, - "addresses": { - "description": "The addresses of the company", - "type": "array", - "items": { - "$ref": "#/components/schemas/Address" - } + "description": "The uuid of the company tied to the engagement" }, - "phone_numbers": { - "description": "The phone numbers of the company", + "contacts": { + "description": "The uuids of contacts tied to the engagement object", "type": "array", "items": { - "$ref": "#/components/schemas/Phone" + "type": "string" } }, "field_mappings": { @@ -5297,11 +5383,11 @@ }, "id": { "type": "string", - "description": "The uuid of the company" + "description": "The uuid of the engagement" }, "remote_id": { "type": "string", - "description": "The id of the company in the context of the Crm 3rd Party" + "description": "The id of the engagement in the context of the Crm 3rd Party" }, "remote_data": { "type": "object", @@ -5309,62 +5395,12 @@ } }, "required": [ - "name", + "type", "field_mappings", "remote_data" ] }, - "UnifiedCompanyInput": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the company" - }, - "industry": { - "type": "string", - "description": "The industry of the company" - }, - "number_of_employees": { - "type": "number", - "description": "The number of employees of the company" - }, - "user_id": { - "type": "string", - "description": "The uuid of the user who owns the company" - }, - "email_addresses": { - "description": "The email addresses of the company", - "type": "array", - "items": { - "$ref": "#/components/schemas/Email" - } - }, - "addresses": { - "description": "The addresses of the company", - "type": "array", - "items": { - "$ref": "#/components/schemas/Address" - } - }, - "phone_numbers": { - "description": "The phone numbers of the company", - "type": "array", - "items": { - "$ref": "#/components/schemas/Phone" - } - }, - "field_mappings": { - "type": "object", - "properties": {} - } - }, - "required": [ - "name", - "field_mappings" - ] - }, - "UnifiedEngagementOutput": { + "UnifiedEngagementInput": { "type": "object", "properties": { "content": { @@ -5373,7 +5409,7 @@ }, "direction": { "type": "string", - "description": "The direction of the engagement" + "description": "The direction of the engagement. Authorized values are INBOUND or OUTBOUND" }, "subject": { "type": "string", @@ -5408,17 +5444,50 @@ "type": "string" } }, + "field_mappings": { + "type": "object", + "properties": {} + } + }, + "required": [ + "type", + "field_mappings" + ] + }, + "UnifiedNoteOutput": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The content of the note" + }, + "user_id": { + "type": "string", + "description": "The uuid of the user tied the note" + }, + "company_id": { + "type": "string", + "description": "The uuid of the company tied to the note" + }, + "contact_id": { + "type": "string", + "description": "The uuid fo the contact tied to the note" + }, + "deal_id": { + "type": "string", + "description": "The uuid of the deal tied to the note" + }, "field_mappings": { "type": "object", "properties": {} }, "id": { "type": "string", - "description": "The uuid of the engagement" + "description": "The uuid of the note" }, "remote_id": { "type": "string", - "description": "The id of the engagement in the context of the Crm 3rd Party" + "description": "The id of the note in the context of the Crm 3rd Party" }, "remote_data": { "type": "object", @@ -5426,54 +5495,33 @@ } }, "required": [ - "type", + "content", "field_mappings", "remote_data" ] }, - "UnifiedEngagementInput": { + "UnifiedNoteInput": { "type": "object", "properties": { "content": { "type": "string", - "description": "The content of the engagement" - }, - "direction": { - "type": "string", - "description": "The direction of the engagement" - }, - "subject": { - "type": "string", - "description": "The subject of the engagement" - }, - "start_at": { - "format": "date-time", - "type": "string", - "description": "The start time of the engagement" + "description": "The content of the note" }, - "end_time": { - "format": "date-time", + "user_id": { "type": "string", - "description": "The end time of the engagement" + "description": "The uuid of the user tied the note" }, - "type": { + "company_id": { "type": "string", - "description": "The type of the engagement. Authorized values are EMAIL, CALL or MEETING" + "description": "The uuid of the company tied to the note" }, - "user_id": { + "contact_id": { "type": "string", - "description": "The uuid of the user tied to the engagement" + "description": "The uuid fo the contact tied to the note" }, - "company_id": { + "deal_id": { "type": "string", - "description": "The uuid of the company tied to the engagement" - }, - "contacts": { - "description": "The uuids of contacts tied to the engagement object", - "type": "array", - "items": { - "type": "string" - } + "description": "The uuid of the deal tied to the note" }, "field_mappings": { "type": "object", @@ -5481,7 +5529,7 @@ } }, "required": [ - "type", + "content", "field_mappings" ] }, @@ -5528,7 +5576,7 @@ }, "status": { "type": "string", - "description": "The status of the task. Authorized values are \"Completed\" and \"Not Completed\" " + "description": "The status of the task. Authorized values are PENDING, COMPLETED." }, "due_date": { "format": "date-time", @@ -5590,7 +5638,7 @@ }, "status": { "type": "string", - "description": "The status of the task. Authorized values are \"Completed\" and \"Not Completed\" " + "description": "The status of the task. Authorized values are PENDING, COMPLETED." }, "due_date": { "format": "date-time", @@ -5645,11 +5693,8 @@ } }, "account_id": { - "description": "The account or organization the user is part of", - "type": "array", - "items": { - "type": "string" - } + "type": "string", + "description": "The account or organization the user is part of" }, "field_mappings": { "type": "object", @@ -5675,134 +5720,31 @@ "remote_data" ] }, - "UnifiedCommentInput": { + "UnifiedAccountOutput": { "type": "object", "properties": { - "body": { - "type": "string", - "description": "The body of the comment" - }, - "html_body": { + "name": { "type": "string", - "description": "The html body of the comment" + "description": "The name of the account" }, - "is_private": { - "type": "boolean", - "description": "The public status of the comment" - }, - "creator_type": { - "type": "string", - "description": "The creator type of the comment (either user or contact)" - }, - "ticket_id": { - "type": "string", - "description": "The uuid of the ticket the comment is tied to" - }, - "contact_id": { - "type": "string", - "description": "The uuid of the contact which the comment belongs to (if no user_id specified)" - }, - "user_id": { - "type": "string", - "description": "The uuid of the user which the comment belongs to (if no contact_id specified)" - }, - "attachments": { - "description": "The attachements uuids tied to the comment", - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "body" - ] - }, - "UnifiedTicketOutput": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the ticket" - }, - "status": { - "type": "string", - "description": "The status of the ticket" - }, - "description": { - "description": "The description of the ticket", - "type": "array", - "items": { - "type": "string" - } - }, - "due_date": { - "format": "date-time", - "type": "string", - "description": "The date the ticket is due" - }, - "type": { - "type": "string", - "description": "The type of the ticket" - }, - "parent_ticket": { - "type": "string", - "description": "The uuid of the parent ticket" - }, - "project_id": { - "type": "string", - "description": "The uuid of the project the ticket belongs to" - }, - "tags": { - "description": "The tags names of the ticket", - "type": "array", - "items": { - "type": "string" - } - }, - "completed_at": { - "format": "date-time", - "type": "string", - "description": "The date the ticket has been completed" - }, - "priority": { - "type": "string", - "description": "The priority of the ticket" - }, - "assigned_to": { - "description": "The users uuids the ticket is assigned to", + "domains": { + "description": "The domains of the account", "type": "array", "items": { "type": "string" } }, - "comment": { - "description": "The comment of the ticket", - "allOf": [ - { - "$ref": "#/components/schemas/UnifiedCommentInput" - } - ] - }, - "account_id": { - "type": "string", - "description": "The uuid of the account which the ticket belongs to" - }, - "contact_id": { - "type": "string", - "description": "The uuid of the contact which the ticket belongs to" - }, "field_mappings": { "type": "object", "properties": {} }, "id": { "type": "string", - "description": "The uuid of the ticket" + "description": "The uuid of the account" }, "remote_id": { "type": "string", - "description": "The id of the ticket in the context of the 3rd Party" + "description": "The id of the account in the context of the 3rd Party" }, "remote_data": { "type": "object", @@ -5811,94 +5753,41 @@ }, "required": [ "name", - "description", "field_mappings", "remote_data" ] }, - "UnifiedTicketInput": { + "UnifiedCollectionOutput": { "type": "object", "properties": { "name": { "type": "string", - "description": "The name of the ticket" - }, - "status": { - "type": "string", - "description": "The status of the ticket" + "description": "The name of the collection" }, "description": { - "description": "The description of the ticket", - "type": "array", - "items": { - "type": "string" - } - }, - "due_date": { - "format": "date-time", - "type": "string", - "description": "The date the ticket is due" - }, - "type": { - "type": "string", - "description": "The type of the ticket" - }, - "parent_ticket": { - "type": "string", - "description": "The uuid of the parent ticket" - }, - "project_id": { "type": "string", - "description": "The uuid of the project the ticket belongs to" - }, - "tags": { - "description": "The tags names of the ticket", - "type": "array", - "items": { - "type": "string" - } - }, - "completed_at": { - "format": "date-time", - "type": "string", - "description": "The date the ticket has been completed" + "description": "The description of the collection" }, - "priority": { + "collection_type": { "type": "string", - "description": "The priority of the ticket" - }, - "assigned_to": { - "description": "The users uuids the ticket is assigned to", - "type": "array", - "items": { - "type": "string" - } + "description": "The type of the collection. Authorized values are either PROJECT or LIST " }, - "comment": { - "description": "The comment of the ticket", - "allOf": [ - { - "$ref": "#/components/schemas/UnifiedCommentInput" - } - ] - }, - "account_id": { + "id": { "type": "string", - "description": "The uuid of the account which the ticket belongs to" + "description": "The uuid of the collection" }, - "contact_id": { + "remote_id": { "type": "string", - "description": "The uuid of the contact which the ticket belongs to" + "description": "The id of the collection in the context of the 3rd Party" }, - "field_mappings": { + "remote_data": { "type": "object", "properties": {} } }, "required": [ "name", - "description", - "field_mappings" + "remote_data" ] }, "UnifiedAttachmentOutput": { @@ -5958,7 +5847,7 @@ }, "creator_type": { "type": "string", - "description": "The creator type of the comment (either user or contact)" + "description": "The creator type of the comment. Authorized values are either USER or CONTACT" }, "ticket_id": { "type": "string", @@ -5997,46 +5886,55 @@ "remote_data" ] }, - "UnifiedAttachmentInput": { + "UnifiedCommentInput": { "type": "object", "properties": { - "file_name": { + "body": { "type": "string", - "description": "The file name of the attachment" + "description": "The body of the comment" }, - "file_url": { + "html_body": { "type": "string", - "description": "The file url of the attachment" + "description": "The html body of the comment" }, - "uploader": { + "is_private": { + "type": "boolean", + "description": "The public status of the comment" + }, + "creator_type": { "type": "string", - "description": "The uploader's uuid of the attachment" + "description": "The creator type of the comment. Authorized values are either USER or CONTACT" }, - "field_mappings": { - "type": "object", - "properties": {} + "ticket_id": { + "type": "string", + "description": "The uuid of the ticket the comment is tied to" + }, + "contact_id": { + "type": "string", + "description": "The uuid of the contact which the comment belongs to (if no user_id specified)" + }, + "user_id": { + "type": "string", + "description": "The uuid of the user which the comment belongs to (if no contact_id specified)" + }, + "attachments": { + "description": "The attachements uuids tied to the comment", + "type": "array", + "items": { + "type": "string" + } } }, "required": [ - "file_name", - "file_url", - "uploader", - "field_mappings" + "body" ] }, - "UnifiedAccountOutput": { + "UnifiedTagOutput": { "type": "object", "properties": { "name": { "type": "string", - "description": "The name of the account" - }, - "domains": { - "description": "The domains of the account", - "type": "array", - "items": { - "type": "string" - } + "description": "The name of the tag" }, "field_mappings": { "type": "object", @@ -6044,11 +5942,11 @@ }, "id": { "type": "string", - "description": "The uuid of the account" + "description": "The uuid of the tag" }, "remote_id": { "type": "string", - "description": "The id of the account in the context of the 3rd Party" + "description": "The id of the tag in the context of the 3rd Party" }, "remote_data": { "type": "object", @@ -6061,12 +5959,16 @@ "remote_data" ] }, - "UnifiedTagOutput": { + "UnifiedTeamOutput": { "type": "object", "properties": { "name": { "type": "string", - "description": "The name of the tag" + "description": "The name of the team" + }, + "description": { + "type": "string", + "description": "The description of the team" }, "field_mappings": { "type": "object", @@ -6074,11 +5976,11 @@ }, "id": { "type": "string", - "description": "The uuid of the tag" + "description": "The uuid of the team" }, "remote_id": { "type": "string", - "description": "The id of the tag in the context of the 3rd Party" + "description": "The id of the team in the context of the 3rd Party" }, "remote_data": { "type": "object", @@ -6091,16 +5993,76 @@ "remote_data" ] }, - "UnifiedTeamOutput": { + "UnifiedTicketOutput": { "type": "object", "properties": { "name": { "type": "string", - "description": "The name of the team" + "description": "The name of the ticket" + }, + "status": { + "type": "string", + "description": "The status of the ticket. Authorized values are OPEN or CLOSED." }, "description": { "type": "string", - "description": "The description of the team" + "description": "The description of the ticket" + }, + "due_date": { + "format": "date-time", + "type": "string", + "description": "The date the ticket is due" + }, + "type": { + "type": "string", + "description": "The type of the ticket. Authorized values are PROBLEM, QUESTION, or TASK" + }, + "parent_ticket": { + "type": "string", + "description": "The uuid of the parent ticket" + }, + "project_id": { + "type": "string", + "description": "The uuid of the collection (project) the ticket belongs to" + }, + "tags": { + "description": "The tags names of the ticket", + "type": "array", + "items": { + "type": "string" + } + }, + "completed_at": { + "format": "date-time", + "type": "string", + "description": "The date the ticket has been completed" + }, + "priority": { + "type": "string", + "description": "The priority of the ticket. Authorized values are HIGH, MEDIUM or LOW." + }, + "assigned_to": { + "description": "The users uuids the ticket is assigned to", + "type": "array", + "items": { + "type": "string" + } + }, + "comment": { + "description": "The comment of the ticket", + "allOf": [ + { + "$ref": "#/components/schemas/UnifiedCommentInput" + } + ] + }, + "account_id": { + "type": "string", + "description": "The uuid of the account which the ticket belongs to" + }, + "contact_id": { + "type": "string", + "description": "The uuid of the contact which the ticket belongs to" }, "field_mappings": { "type": "object", @@ -6108,11 +6070,11 @@ }, "id": { "type": "string", - "description": "The uuid of the team" + "description": "The uuid of the ticket" }, "remote_id": { "type": "string", - "description": "The id of the team in the context of the 3rd Party" + "description": "The id of the ticket in the context of the 3rd Party" }, "remote_data": { "type": "object", @@ -6121,41 +6083,118 @@ }, "required": [ "name", + "description", "field_mappings", "remote_data" ] }, - "UnifiedCollectionOutput": { + "UnifiedTicketInput": { "type": "object", "properties": { "name": { "type": "string", - "description": "The name of the collection" + "description": "The name of the ticket" + }, + "status": { + "type": "string", + "description": "The status of the ticket. Authorized values are OPEN or CLOSED." }, "description": { "type": "string", - "description": "The description of the collection" + "description": "The description of the ticket" }, - "collection_type": { + "due_date": { + "format": "date-time", "type": "string", - "description": "The type of the collection, either PROJECT or LIST " + "description": "The date the ticket is due" }, - "id": { + "type": { "type": "string", - "description": "The uuid of the collection" + "description": "The type of the ticket. Authorized values are PROBLEM, QUESTION, or TASK" }, - "remote_id": { + "parent_ticket": { "type": "string", - "description": "The id of the collection in the context of the 3rd Party" + "description": "The uuid of the parent ticket" }, - "remote_data": { + "project_id": { + "type": "string", + "description": "The uuid of the collection (project) the ticket belongs to" + }, + "tags": { + "description": "The tags names of the ticket", + "type": "array", + "items": { + "type": "string" + } + }, + "completed_at": { + "format": "date-time", + "type": "string", + "description": "The date the ticket has been completed" + }, + "priority": { + "type": "string", + "description": "The priority of the ticket. Authorized values are HIGH, MEDIUM or LOW." + }, + "assigned_to": { + "description": "The users uuids the ticket is assigned to", + "type": "array", + "items": { + "type": "string" + } + }, + "comment": { + "description": "The comment of the ticket", + "allOf": [ + { + "$ref": "#/components/schemas/UnifiedCommentInput" + } + ] + }, + "account_id": { + "type": "string", + "description": "The uuid of the account which the ticket belongs to" + }, + "contact_id": { + "type": "string", + "description": "The uuid of the contact which the ticket belongs to" + }, + "field_mappings": { "type": "object", "properties": {} } }, "required": [ "name", - "remote_data" + "description", + "field_mappings" + ] + }, + "UnifiedAttachmentInput": { + "type": "object", + "properties": { + "file_name": { + "type": "string", + "description": "The file name of the attachment" + }, + "file_url": { + "type": "string", + "description": "The file url of the attachment" + }, + "uploader": { + "type": "string", + "description": "The uploader's uuid of the attachment" + }, + "field_mappings": { + "type": "object", + "properties": {} + } + }, + "required": [ + "file_name", + "file_url", + "uploader", + "field_mappings" ] } } From b50f22d7736fe3068dcf2880c78acdc9fc7000f0 Mon Sep 17 00:00:00 2001 From: nael Date: Thu, 2 May 2024 01:54:51 +0200 Subject: [PATCH 11/11] :bug: Fix project id routes --- .../src/app/(Dashboard)/api-keys/page.tsx | 2 +- .../src/app/(Dashboard)/b2c/profile/page.tsx | 4 +- .../app/(Dashboard)/configuration/page.tsx | 6 +- apps/client-ts/src/app/(Dashboard)/layout.tsx | 2 - .../Configuration/FieldMappingModal.tsx | 2 +- .../components/Connection/ConnectionTable.tsx | 2 +- .../src/components/Events/EventsTable.tsx | 2 +- .../src/components/RootLayout/index.tsx | 12 +-- .../src/components/shared/team-switcher.tsx | 12 +-- .../src/hooks/mutations/useLoginMutation.tsx | 15 +-- .../useRefreshAccessTokenMutation.tsx | 58 +++++++++++ apps/client-ts/src/hooks/useApiKeys.tsx | 4 +- .../src/hooks/useConnectionStrategies.tsx | 4 +- apps/client-ts/src/hooks/useConnections.tsx | 4 +- apps/client-ts/src/hooks/useEvents.tsx | 8 +- apps/client-ts/src/hooks/useLinkedUsers.tsx | 4 +- apps/client-ts/src/hooks/useWebhooks.tsx | 4 +- .../api/src/@core/auth/auth.controller.ts | 33 +++++-- packages/api/src/@core/auth/auth.service.ts | 35 ++++++- .../api/src/@core/auth/dto/refresh.dto.ts | 6 ++ .../src/@core/auth/strategies/jwt.strategy.ts | 1 + .../connections-strategies.controller.ts | 23 +++-- .../connections/connections.controller.ts | 12 +-- .../api/src/@core/events/events.controller.ts | 12 +-- .../linked-users/linked-users.controller.ts | 18 +++- .../src/@core/webhook/webhook.controller.ts | 12 +-- packages/api/swagger/swagger-spec.json | 99 ++++++++----------- 27 files changed, 235 insertions(+), 161 deletions(-) create mode 100644 apps/client-ts/src/hooks/mutations/useRefreshAccessTokenMutation.tsx create mode 100644 packages/api/src/@core/auth/dto/refresh.dto.ts diff --git a/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx b/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx index 12fd14c88..370605b82 100644 --- a/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx @@ -57,7 +57,7 @@ export default function Page() { const {idProject} = useProjectStore(); const {profile} = useProfileStore(); - const { data: apiKeys, isLoading, error } = useApiKeys(idProject); + const { data: apiKeys, isLoading, error } = useApiKeys(); const { mutate } = useApiKeyMutation(); useEffect(() => { diff --git a/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx b/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx index a3c9f2f6b..1d73e54ad 100644 --- a/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx @@ -19,8 +19,8 @@ import { useQueryClient } from '@tanstack/react-query'; const Profile = () => { - const {profile,setProfile} = useProfileStore(); - const { idProject, setIdProject } = useProjectStore(); + const { profile, setProfile } = useProfileStore(); + const { setIdProject } = useProjectStore(); const queryClient = useQueryClient(); diff --git a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx index 77afcc08d..d365165cf 100644 --- a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx @@ -46,9 +46,9 @@ import { Heading } from "@/components/ui/heading"; export default function Page() { const {idProject} = useProjectStore(); - const { data: linkedUsers, isLoading, error } = useLinkedUsers(idProject); - const { data: webhooks, isLoading: isWebhooksLoading, error: isWebhooksError } = useWebhooks(idProject); - const {data: ConnectionStrategies, isLoading: isConnectionStrategiesLoading,error: isConnectionStategiesError} = useConnectionStrategies(idProject) + const { data: linkedUsers, isLoading, error } = useLinkedUsers(); + const { data: webhooks, isLoading: isWebhooksLoading, error: isWebhooksError } = useWebhooks(); + const {data: ConnectionStrategies, isLoading: isConnectionStrategiesLoading,error: isConnectionStategiesError} = useConnectionStrategies() const { data: mappings, isLoading: isFieldMappingsLoading, error: isFieldMappingsError } = useFieldMappings(); const [open, setOpen] = useState(false); diff --git a/apps/client-ts/src/app/(Dashboard)/layout.tsx b/apps/client-ts/src/app/(Dashboard)/layout.tsx index cf01b3cf5..195d4fc98 100644 --- a/apps/client-ts/src/app/(Dashboard)/layout.tsx +++ b/apps/client-ts/src/app/(Dashboard)/layout.tsx @@ -7,8 +7,6 @@ import { useEffect, useState } from "react"; import Cookies from 'js-cookie'; import useFetchUserMutation from "@/hooks/mutations/useFetchUserMutation"; - - const inter = Inter({ subsets: ["latin"] }); export default function Layout({ diff --git a/apps/client-ts/src/components/Configuration/FieldMappingModal.tsx b/apps/client-ts/src/components/Configuration/FieldMappingModal.tsx index 59b1fbeec..a13c0090b 100644 --- a/apps/client-ts/src/components/Configuration/FieldMappingModal.tsx +++ b/apps/client-ts/src/components/Configuration/FieldMappingModal.tsx @@ -109,7 +109,7 @@ export function FModal({ onClose }: {onClose: () => void}) { const { data: mappings } = useFieldMappings(); const { mutate: mutateDefineField } = useDefineFieldMutation(); const { mutate: mutateMapField } = useMapFieldMutation(); - const { data: linkedUsers } = useLinkedUsers(idProject); + const { data: linkedUsers } = useLinkedUsers(); const { data: sourceCustomFields, error, isLoading } = useProviderProperties(linkedUserId,sourceProvider); const posthog = usePostHog() diff --git a/apps/client-ts/src/components/Connection/ConnectionTable.tsx b/apps/client-ts/src/components/Connection/ConnectionTable.tsx index bd75c50ec..a271e762e 100644 --- a/apps/client-ts/src/components/Connection/ConnectionTable.tsx +++ b/apps/client-ts/src/components/Connection/ConnectionTable.tsx @@ -26,7 +26,7 @@ import Cookies from 'js-cookie'; export default function ConnectionTable() { const {idProject} = useProjectStore(); - const { data: connections, isLoading, error } = useConnections(idProject); + const { data: connections, isLoading, error } = useConnections(); const [isGenerated, setIsGenerated] = useState(false); const posthog = usePostHog() diff --git a/apps/client-ts/src/components/Events/EventsTable.tsx b/apps/client-ts/src/components/Events/EventsTable.tsx index 5fc3285ff..b60993520 100644 --- a/apps/client-ts/src/components/Events/EventsTable.tsx +++ b/apps/client-ts/src/components/Events/EventsTable.tsx @@ -21,7 +21,7 @@ export default function EventsTable() { } = useEvents({ page: pagination.page, pageSize: pagination.pageSize, - }, idProject); + }); //TODO const transformedEvents = events?.map((event: Event) => ({ diff --git a/apps/client-ts/src/components/RootLayout/index.tsx b/apps/client-ts/src/components/RootLayout/index.tsx index f2c254d34..f5cacc40e 100644 --- a/apps/client-ts/src/components/RootLayout/index.tsx +++ b/apps/client-ts/src/components/RootLayout/index.tsx @@ -12,19 +12,14 @@ import useProfileStore from '@/state/profileStore'; import useProjectStore from '@/state/projectStore'; import { ThemeToggle } from '@/components/Nav/theme-toggle'; import useProjects from '@/hooks/useProjects'; +import useRefreshAccessTokenMutation from '@/hooks/mutations/useRefreshAccessTokenMutation'; export const RootLayout = ({children}:{children:React.ReactNode}) => { const router = useRouter() const base = process.env.NEXT_PUBLIC_WEBAPP_DOMAIN; - - - const {profile} = useProfileStore() const {data : projectsData} = useProjects(); const { idProject, setIdProject } = useProjectStore(); - - - // const { setIdProject } = useProjectStore(); - + const {mutate : refreshAccessToken} = useRefreshAccessTokenMutation() useEffect(() => { if(projectsData) @@ -34,10 +29,9 @@ export const RootLayout = ({children}:{children:React.ReactNode}) => { { console.log("Project Id setting : ",projectsData[0]?.id_project) setIdProject(projectsData[0]?.id_project); - } } - },[projectsData]) + },[idProject, projectsData, refreshAccessToken, setIdProject]) const handlePageChange = (page: string) => { if (page) { diff --git a/apps/client-ts/src/components/shared/team-switcher.tsx b/apps/client-ts/src/components/shared/team-switcher.tsx index b4e7d5362..6203a62ff 100644 --- a/apps/client-ts/src/components/shared/team-switcher.tsx +++ b/apps/client-ts/src/components/shared/team-switcher.tsx @@ -56,6 +56,7 @@ import config from "@/lib/config" import { Skeleton } from "@/components/ui/skeleton"; import useProfileStore from "@/state/profileStore" import { projects as Project } from 'api'; +import useRefreshAccessTokenMutation from "@/hooks/mutations/useRefreshAccessTokenMutation" const projectFormSchema = z.object({ @@ -85,15 +86,7 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) const { profile } = useProfileStore(); const { idProject, setIdProject } = useProjectStore(); - - // const projects = profile?.projects; - - // useEffect(() => { - // if(idProject==="" && projects) - // { - // setIdProject(projects[0]?.id_project) - // } - // },[projects]) + const {mutate : refreshAccessToken} = useRefreshAccessTokenMutation() const handleOpenChange = (open: boolean) => { setShowNewDialog(prevState => ({ ...prevState, open })); @@ -149,6 +142,7 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) key={project.id_project} onSelect={() => { setIdProject(project.id_project) + refreshAccessToken(project.id_project) setOpen(false) }} className="text-sm" diff --git a/apps/client-ts/src/hooks/mutations/useLoginMutation.tsx b/apps/client-ts/src/hooks/mutations/useLoginMutation.tsx index 5939dd8dc..39897ca01 100644 --- a/apps/client-ts/src/hooks/mutations/useLoginMutation.tsx +++ b/apps/client-ts/src/hooks/mutations/useLoginMutation.tsx @@ -1,8 +1,7 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { toast } from "sonner" import useProfileStore from '@/state/profileStore'; -import { projects as Project } from 'api'; import Cookies from 'js-cookie'; type IUserDto = { @@ -18,15 +17,11 @@ interface ILoginInputDto { password_hash:string } - - interface ILoginOutputDto { user: IUserDto, access_token: string - } - const useLoginMutation = () => { const {setProfile} = useProfileStore() @@ -41,15 +36,10 @@ const useLoginMutation = () => { }, }); - - if (!response.ok) { throw new Error("Login Failed!!") } - - - return response.json(); }; return useMutation({ @@ -73,10 +63,9 @@ const useLoginMutation = () => { }) }, onSuccess: (data : ILoginOutputDto) => { - setProfile(data.user); Cookies.set('access_token',data.access_token,{expires:1}); - console.log("Bearer Token in client Side : ",data.access_token); + //console.log("Bearer Token in client Side : ",data.access_token); toast.success("User has been generated !", { description: "", diff --git a/apps/client-ts/src/hooks/mutations/useRefreshAccessTokenMutation.tsx b/apps/client-ts/src/hooks/mutations/useRefreshAccessTokenMutation.tsx new file mode 100644 index 000000000..63e87fce7 --- /dev/null +++ b/apps/client-ts/src/hooks/mutations/useRefreshAccessTokenMutation.tsx @@ -0,0 +1,58 @@ +import config from '@/lib/config'; +import { useMutation } from '@tanstack/react-query'; +import { toast } from "sonner" +import Cookies from 'js-cookie'; + +interface IRefreshOutputDto { + access_token: string +} + +const useRefreshAccessTokenMutation = () => { + const refreshAccessToken = async (projectId: string) => { + const response = await fetch(`${config.API_URL}/auth/refresh-token`, { + method: 'POST', + body: JSON.stringify({ + projectId: projectId + }), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${Cookies.get('access_token')}`, + }, + }); + + if (!response.ok) { + throw new Error("Login Failed!!") + } + + return response.json(); + }; + return useMutation({ + mutationFn: refreshAccessToken, + onMutate: () => { + }, + onError: (error) => { + toast.error("Refreshing token generation failed !", { + description: error as any, + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) + }, + onSuccess: (data : IRefreshOutputDto) => { + Cookies.remove('access_token'); + Cookies.set('access_token', data.access_token, {expires:1}); + /*toast.success("Refresh has been generated !", { + description: "", + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + })*/ + }, + onSettled: () => { + }, + }); +}; + +export default useRefreshAccessTokenMutation; diff --git a/apps/client-ts/src/hooks/useApiKeys.tsx b/apps/client-ts/src/hooks/useApiKeys.tsx index e814d2ef2..6bd57e3af 100644 --- a/apps/client-ts/src/hooks/useApiKeys.tsx +++ b/apps/client-ts/src/hooks/useApiKeys.tsx @@ -3,11 +3,11 @@ import { useQuery } from '@tanstack/react-query'; import { api_keys as ApiKey } from 'api'; import Cookies from 'js-cookie'; -const useApiKeys = (project_id: string) => { +const useApiKeys = () => { return useQuery({ queryKey: ['api-keys'], queryFn: async (): Promise => { - const response = await fetch(`${config.API_URL}/auth/api-keys?project_id=${project_id}`, + const response = await fetch(`${config.API_URL}/auth/api-keys`, { method: 'GET', headers: { diff --git a/apps/client-ts/src/hooks/useConnectionStrategies.tsx b/apps/client-ts/src/hooks/useConnectionStrategies.tsx index 3adfc0899..8adca921f 100644 --- a/apps/client-ts/src/hooks/useConnectionStrategies.tsx +++ b/apps/client-ts/src/hooks/useConnectionStrategies.tsx @@ -3,11 +3,11 @@ import { useQuery } from '@tanstack/react-query'; import { connection_strategies as ConnectionStrategies } from 'api'; import Cookies from 'js-cookie'; -const useConnectionStrategies = (projectId : string) => { +const useConnectionStrategies = () => { return useQuery({ queryKey: ['connection-strategies'], queryFn: async (): Promise => { - const response = await fetch(`${config.API_URL}/connections-strategies/getConnectionStrategiesForProject?projectId=${projectId}`, + const response = await fetch(`${config.API_URL}/connections-strategies/getConnectionStrategiesForProject`, { method: 'GET', headers: { diff --git a/apps/client-ts/src/hooks/useConnections.tsx b/apps/client-ts/src/hooks/useConnections.tsx index 15e6f607a..166936958 100644 --- a/apps/client-ts/src/hooks/useConnections.tsx +++ b/apps/client-ts/src/hooks/useConnections.tsx @@ -4,11 +4,11 @@ import { connections as Connection } from 'api'; import Cookies from 'js-cookie'; -const useConnections = (project_id: string) => { +const useConnections = () => { return useQuery({ queryKey: ['connections'], queryFn: async (): Promise => { - const response = await fetch(`${config.API_URL}/connections?project_id=${project_id}`,{ + const response = await fetch(`${config.API_URL}/connections`,{ method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/apps/client-ts/src/hooks/useEvents.tsx b/apps/client-ts/src/hooks/useEvents.tsx index 84f02c130..3f726d02b 100644 --- a/apps/client-ts/src/hooks/useEvents.tsx +++ b/apps/client-ts/src/hooks/useEvents.tsx @@ -4,13 +4,13 @@ import { useQuery } from '@tanstack/react-query'; import { events as Event } from 'api'; import Cookies from 'js-cookie'; -const fetchEvents = async (params: PaginationParams, project_id: string): Promise => { +const fetchEvents = async (params: PaginationParams): Promise => { const searchParams = new URLSearchParams({ page: params.page.toString(), pageSize: params.pageSize.toString(), }); - const response = await fetch(`${config.API_URL}/events?project_id=${project_id}&${searchParams.toString()}`, + const response = await fetch(`${config.API_URL}/events?${searchParams.toString()}`, { method: 'GET', headers: { @@ -25,10 +25,10 @@ const fetchEvents = async (params: PaginationParams, project_id: string): Promis return response.json(); }; -const useEvents = (params: PaginationParams, project_id: string) => { +const useEvents = (params: PaginationParams) => { return useQuery({ queryKey: ['events', { page: params.page, pageSize: params.pageSize }], - queryFn: () => fetchEvents(params, project_id), + queryFn: () => fetchEvents(params), }); }; diff --git a/apps/client-ts/src/hooks/useLinkedUsers.tsx b/apps/client-ts/src/hooks/useLinkedUsers.tsx index 54a062044..484b9575b 100644 --- a/apps/client-ts/src/hooks/useLinkedUsers.tsx +++ b/apps/client-ts/src/hooks/useLinkedUsers.tsx @@ -3,11 +3,11 @@ import { useQuery } from '@tanstack/react-query'; import { linked_users as LinkedUser } from 'api'; import Cookies from 'js-cookie'; -const useLinkedUsers = (project_id: string) => { +const useLinkedUsers = () => { return useQuery({ queryKey: ['linked-users'], queryFn: async (): Promise => { - const response = await fetch(`${config.API_URL}/linked-users?project_id=${project_id}`, + const response = await fetch(`${config.API_URL}/linked-users`, { method: 'GET', headers: { diff --git a/apps/client-ts/src/hooks/useWebhooks.tsx b/apps/client-ts/src/hooks/useWebhooks.tsx index 636c7172f..482580a96 100644 --- a/apps/client-ts/src/hooks/useWebhooks.tsx +++ b/apps/client-ts/src/hooks/useWebhooks.tsx @@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query'; import { webhook_endpoints as Webhook } from 'api'; import Cookies from 'js-cookie'; -const useWebhooks = (project_id: string) => { +const useWebhooks = () => { return useQuery({ queryKey: ['webhooks'], queryFn: async (): Promise => { console.log("Webhook mutation called") - const response = await fetch(`${config.API_URL}/webhook?project_id=${project_id}`, + const response = await fetch(`${config.API_URL}/webhook`, { method: 'GET', headers: { diff --git a/packages/api/src/@core/auth/auth.controller.ts b/packages/api/src/@core/auth/auth.controller.ts index 6f34af10f..acfda5f99 100644 --- a/packages/api/src/@core/auth/auth.controller.ts +++ b/packages/api/src/@core/auth/auth.controller.ts @@ -14,7 +14,7 @@ import { LoggerService } from '@@core/logger/logger.service'; import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiKeyDto } from './dto/api-key.dto'; import { LoginDto } from './dto/login.dto'; -import { ValidateUserGuard } from '@@core/utils/guards/validate-user.guard'; +import { RefreshDto } from './dto/refresh.dto'; @ApiTags('auth') @Controller('auth') @@ -67,14 +67,11 @@ export class AuthController { @ApiOperation({ operationId: 'getApiKeys', summary: 'Retrieve API Keys' }) @ApiResponse({ status: 200 }) - @UseGuards(JwtAuthGuard, ValidateUserGuard) + @UseGuards(JwtAuthGuard) @Get('api-keys') - async getApiKeys( - @Request() req: any, - @Query('project_id') project_id: string, - ) { - const id_user = req.user.id_user; // Extracted from JWT payload - return this.authService.getApiKeys(id_user, project_id); + async getApiKeys(@Request() req: any) { + const { id_project } = req.user; + return this.authService.getApiKeys(id_project); } @ApiOperation({ operationId: 'generateApiKey', summary: 'Create API Key' }) @@ -89,4 +86,24 @@ export class AuthController { data.keyName, ); } + + @ApiOperation({ + operationId: 'refreshAccessToken', + summary: 'Refresh Access Token', + }) + @ApiBody({ type: RefreshDto }) + @ApiResponse({ status: 201 }) + @UseGuards(JwtAuthGuard) + @Post('refresh-token') + refreshAccessToken(@Request() req: any, @Body() body: RefreshDto) { + const { projectId } = body; + const { id_user, email, first_name, last_name } = req.user; + return this.authService.refreshAccessToken( + projectId, + id_user, + email, + first_name, + last_name, + ); + } } diff --git a/packages/api/src/@core/auth/auth.service.ts b/packages/api/src/@core/auth/auth.service.ts index 30a89aa9e..c495cf585 100644 --- a/packages/api/src/@core/auth/auth.service.ts +++ b/packages/api/src/@core/auth/auth.service.ts @@ -60,11 +60,10 @@ export class AuthService { } } - async getApiKeys(user_id: string, project_id: string) { + async getApiKeys(project_id: string) { try { return await this.prisma.api_keys.findMany({ where: { - id_user: user_id, id_project: project_id, }, }); @@ -141,6 +140,12 @@ export class AuthService { }, }); + const project = await this.prisma.projects.findFirst({ + where: { + id_user: foundUser.id_user, + }, + }); + if (!foundUser) { throw new UnauthorizedException('user does not exist!'); } @@ -159,6 +164,7 @@ export class AuthService { sub: userData.id_user, first_name: userData.first_name, last_name: userData.last_name, + id_project: project.id_project, }; return { @@ -177,6 +183,31 @@ export class AuthService { } } + async refreshAccessToken( + projectId: string, + id_user: string, + email: string, + first_name: string, + last_name: string, + ) { + try { + const payload = { + email: email, + sub: id_user, + first_name: first_name, + last_name: last_name, + id_project: projectId, + }; + return { + access_token: this.jwtService.sign(payload, { + secret: process.env.JWT_SECRET, + }), + }; + } catch (error) { + handleServiceError(error, this.logger); + } + } + hashApiKey(apiKey: string): string { return crypto.createHash('sha256').update(apiKey).digest('hex'); } diff --git a/packages/api/src/@core/auth/dto/refresh.dto.ts b/packages/api/src/@core/auth/dto/refresh.dto.ts new file mode 100644 index 000000000..6492650dd --- /dev/null +++ b/packages/api/src/@core/auth/dto/refresh.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RefreshDto { + @ApiProperty() + projectId: string; +} diff --git a/packages/api/src/@core/auth/strategies/jwt.strategy.ts b/packages/api/src/@core/auth/strategies/jwt.strategy.ts index c75ff6529..6c3ea8926 100644 --- a/packages/api/src/@core/auth/strategies/jwt.strategy.ts +++ b/packages/api/src/@core/auth/strategies/jwt.strategy.ts @@ -20,6 +20,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { email: payload.email, first_name: payload.first_name, last_name: payload.last_name, + id_project: payload.id_project, }; } } diff --git a/packages/api/src/@core/connections-strategies/connections-strategies.controller.ts b/packages/api/src/@core/connections-strategies/connections-strategies.controller.ts index 68eb39b6b..e3ed06180 100644 --- a/packages/api/src/@core/connections-strategies/connections-strategies.controller.ts +++ b/packages/api/src/@core/connections-strategies/connections-strategies.controller.ts @@ -1,4 +1,12 @@ -import { Body, Controller, Get, Post, Query, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Post, + Query, + UseGuards, + Request, +} from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ConnectionsStrategiesService } from './connections-strategies.service'; @@ -8,7 +16,6 @@ import { DeleteCSDto } from './dto/delete-cs.dto'; import { UpdateCSDto } from './dto/update-cs.dto'; import { ConnectionStrategyCredentials } from './dto/get-connection-cs-credentials.dto'; import { JwtAuthGuard } from '@@core/auth/guards/jwt-auth.guard'; -import { ValidateUserGuard } from '@@core/utils/guards/validate-user.guard'; @ApiTags('connections-strategies') @Controller('connections-strategies') @@ -32,7 +39,6 @@ export class ConnectionsStrategiesController { @Body() connectionStrategyCreateDto: CreateConnectionStrategyDto, ) { const { projectId, type, attributes, values } = connectionStrategyCreateDto; - // validate user against project_id return await this.connectionsStrategiesService.createConnectionStrategy( projectId, type, @@ -50,7 +56,6 @@ export class ConnectionsStrategiesController { @UseGuards(JwtAuthGuard) @Post('toggle') async toggleConnectionStrategy(@Body() data: ToggleStrategyDto) { - // validate user against project_id return await this.connectionsStrategiesService.toggle(data.id_cs); } @@ -63,7 +68,6 @@ export class ConnectionsStrategiesController { @UseGuards(JwtAuthGuard) @Post('delete') async deleteConnectionStrategy(@Body() data: DeleteCSDto) { - // validate user against project_id return await this.connectionsStrategiesService.deleteConnectionStrategy( data.id, ); @@ -131,13 +135,12 @@ export class ConnectionsStrategiesController { summary: 'Fetch All Connection Strategies for Project', }) @ApiResponse({ status: 200 }) - @UseGuards(JwtAuthGuard, ValidateUserGuard) + @UseGuards(JwtAuthGuard) @Get('getConnectionStrategiesForProject') - async getConnectionStrategiesForProject( - @Query('projectId') projectId: string, - ) { + async getConnectionStrategiesForProject(@Request() req: any) { + const { id_project } = req.user; return await this.connectionsStrategiesService.getConnectionStrategiesForProject( - projectId, + id_project, ); } diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index 4596e5c9a..410908a50 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -18,7 +18,6 @@ import { ProviderVertical } from '@panora/shared'; import { AccountingConnectionsService } from './accounting/services/accounting.connection.service'; import { MarketingAutomationConnectionsService } from './marketingautomation/services/marketingautomation.connection.service'; import { JwtAuthGuard } from '@@core/auth/guards/jwt-auth.guard'; -import { ValidateUserGuard } from '@@core/utils/guards/validate-user.guard'; export type StateDataType = { projectId: string; @@ -143,15 +142,14 @@ export class ConnectionsController { summary: 'List Connections', }) @ApiResponse({ status: 200 }) - @UseGuards(JwtAuthGuard, ValidateUserGuard) + @UseGuards(JwtAuthGuard) @Get() - async getConnections( - @Request() req: any, - @Query('projectId') projectId: string, - ) { + async getConnections(@Request() req: any) { + const { id_project } = req.user; + console.log('Req data is:', req.user); return await this.prisma.connections.findMany({ where: { - id_project: projectId, + id_project: id_project, }, }); } diff --git a/packages/api/src/@core/events/events.controller.ts b/packages/api/src/@core/events/events.controller.ts index 758410fa9..c8704c6bb 100644 --- a/packages/api/src/@core/events/events.controller.ts +++ b/packages/api/src/@core/events/events.controller.ts @@ -5,13 +5,13 @@ import { UseGuards, UsePipes, ValidationPipe, + Request, } from '@nestjs/common'; import { EventsService } from './events.service'; import { LoggerService } from '@@core/logger/logger.service'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { PaginationDto } from '@@core/utils/dtos/pagination.dto'; import { JwtAuthGuard } from '@@core/auth/guards/jwt-auth.guard'; -import { ValidateUserGuard } from '@@core/utils/guards/validate-user.guard'; @ApiTags('events') @Controller('events') @@ -34,13 +34,11 @@ export class EventsController { }, }), ) - @UseGuards(JwtAuthGuard, ValidateUserGuard) + @UseGuards(JwtAuthGuard) @Get() - async getEvents( - @Query() dto: PaginationDto, - @Query('project_id') project_id: string, - ) { - return await this.eventsService.findEvents(dto, project_id); + async getEvents(@Query() dto: PaginationDto, @Request() req: any) { + const { id_project } = req.user; + return await this.eventsService.findEvents(dto, id_project); } // todo diff --git a/packages/api/src/@core/linked-users/linked-users.controller.ts b/packages/api/src/@core/linked-users/linked-users.controller.ts index acd605dc3..94a585f66 100644 --- a/packages/api/src/@core/linked-users/linked-users.controller.ts +++ b/packages/api/src/@core/linked-users/linked-users.controller.ts @@ -1,4 +1,12 @@ -import { Body, Controller, Get, Post, Query, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Post, + Query, + UseGuards, + Request, +} from '@nestjs/common'; import { LinkedUsersService } from './linked-users.service'; import { LoggerService } from '../logger/logger.service'; import { CreateLinkedUserDto } from './dto/create-linked-user.dto'; @@ -10,7 +18,6 @@ import { ApiTags, } from '@nestjs/swagger'; import { JwtAuthGuard } from '@@core/auth/guards/jwt-auth.guard'; -import { ValidateUserGuard } from '@@core/utils/guards/validate-user.guard'; @ApiTags('linked-users') @Controller('linked-users') @@ -37,10 +44,11 @@ export class LinkedUsersController { summary: 'Retrieve Linked Users', }) @ApiResponse({ status: 200 }) - @UseGuards(JwtAuthGuard, ValidateUserGuard) + @UseGuards(JwtAuthGuard) @Get() - getLinkedUsers(@Query('project_id') project_id: string) { - return this.linkedUsersService.getLinkedUsers(project_id); + getLinkedUsers(@Request() req: any) { + const { id_project } = req.user; + return this.linkedUsersService.getLinkedUsers(id_project); } @ApiOperation({ diff --git a/packages/api/src/@core/webhook/webhook.controller.ts b/packages/api/src/@core/webhook/webhook.controller.ts index 6a208e04f..499f88ff2 100644 --- a/packages/api/src/@core/webhook/webhook.controller.ts +++ b/packages/api/src/@core/webhook/webhook.controller.ts @@ -7,14 +7,12 @@ import { Param, UseGuards, Request, - Query, } from '@nestjs/common'; import { LoggerService } from '@@core/logger/logger.service'; import { ApiBody, ApiResponse, ApiTags, ApiOperation } from '@nestjs/swagger'; import { WebhookService } from './webhook.service'; import { WebhookDto } from './dto/webhook.dto'; import { JwtAuthGuard } from '@@core/auth/guards/jwt-auth.guard'; -import { ValidateUserGuard } from '@@core/utils/guards/validate-user.guard'; @ApiTags('webhook') @Controller('webhook') @@ -31,10 +29,11 @@ export class WebhookController { summary: 'Retrieve webhooks metadata ', }) @ApiResponse({ status: 200 }) - @UseGuards(JwtAuthGuard, ValidateUserGuard) + @UseGuards(JwtAuthGuard) @Get() - getWebhooks(@Query('project_id') project_id: string) { - return this.webhookService.getWebhookEndpoints(project_id); + getWebhooks(@Request() req: any) { + const { id_project } = req.user; + return this.webhookService.getWebhookEndpoints(id_project); } @ApiOperation({ @@ -46,9 +45,7 @@ export class WebhookController { async updateWebhookStatus( @Param('id') id: string, @Body('active') active: boolean, - @Request() req: any, ) { - // verify id of webhook belongs to user from req return this.webhookService.updateStatusWebhookEndpoint(id, active); } @@ -61,7 +58,6 @@ export class WebhookController { @UseGuards(JwtAuthGuard) @Post() async addWebhook(@Body() data: WebhookDto) { - // verify project id of user is same from data return this.webhookService.createWebhookEndpoint(data); } } diff --git a/packages/api/swagger/swagger-spec.json b/packages/api/swagger/swagger-spec.json index e8948840e..c00c2d20a 100644 --- a/packages/api/swagger/swagger-spec.json +++ b/packages/api/swagger/swagger-spec.json @@ -176,16 +176,7 @@ "get": { "operationId": "getApiKeys", "summary": "Retrieve API Keys", - "parameters": [ - { - "name": "project_id", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "responses": { "200": { "description": "" @@ -221,6 +212,31 @@ ] } }, + "/auth/refresh-token": { + "post": { + "operationId": "refreshAccessToken", + "summary": "Refresh Access Token", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "auth" + ] + } + }, "/connections/oauth/callback": { "get": { "operationId": "handleOAuthCallback", @@ -336,16 +352,7 @@ "get": { "operationId": "getConnections", "summary": "List Connections", - "parameters": [ - { - "name": "projectId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "responses": { "200": { "description": "" @@ -360,16 +367,7 @@ "get": { "operationId": "getWebhooksMetadata", "summary": "Retrieve webhooks metadata ", - "parameters": [ - { - "name": "project_id", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "responses": { "200": { "description": "" @@ -456,16 +454,7 @@ "get": { "operationId": "getLinkedUsers", "summary": "Retrieve Linked Users", - "parameters": [ - { - "name": "project_id", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "responses": { "200": { "description": "" @@ -739,14 +728,6 @@ "default": 10, "type": "number" } - }, - { - "name": "project_id", - "required": true, - "in": "query", - "schema": { - "type": "string" - } } ], "responses": { @@ -1073,16 +1054,7 @@ "get": { "operationId": "getConnectionStrategiesForProject", "summary": "Fetch All Connection Strategies for Project", - "parameters": [ - { - "name": "projectId", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], + "parameters": [], "responses": { "200": { "description": "" @@ -4638,6 +4610,17 @@ "keyName" ] }, + "RefreshDto": { + "type": "object", + "properties": { + "projectId": { + "type": "string" + } + }, + "required": [ + "projectId" + ] + }, "WebhookDto": { "type": "object", "properties": {