From 7a6a6a86a0b2077e7193178b73ab0857832885f0 Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 12 Dec 2023 23:26:28 +0100 Subject: [PATCH 1/4] :bug: Feat: dd --- .../services/pipedrive/pipedrive.service.ts | 1 - .../crm/services/zendesk/zendesk.service.ts | 4 +- .../field-mapping/field-mapping.service.ts | 1 + packages/api/src/@core/utils/types.ts | 16 ++++---- .../crm/contact/services/contact.service.ts | 3 ++ .../crm/contact/services/freshsales/index.ts | 2 +- .../src/crm/contact/services/hubspot/index.ts | 2 +- .../crm/contact/services/pipedrive/index.ts | 2 +- .../crm/contact/services/pipedrive/mappers.ts | 21 +++++++++- .../crm/contact/services/pipedrive/types.ts | 1 + .../src/crm/contact/services/zendesk/index.ts | 2 +- .../crm/contact/services/zendesk/mappers.ts | 21 +++++++++- .../src/crm/contact/services/zendesk/types.ts | 1 + .../src/crm/contact/services/zoho/index.ts | 2 +- .../src/crm/contact/services/zoho/mappers.ts | 21 +++++++++- .../src/crm/contact/services/zoho/types.ts | 1 + .../api/src/crm/contact/sync/sync.service.ts | 41 +++++++++++++------ packages/api/src/crm/contact/types/index.ts | 1 + 18 files changed, 109 insertions(+), 34 deletions(-) diff --git a/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts b/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts index 69e1afb6b..e8ed33ad2 100644 --- a/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts +++ b/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts @@ -51,7 +51,6 @@ export class PipedriveConnectionService { }, }, ); - //TODO: handle if res throws an error const data: PipeDriveOAuthResponse = res.data; this.logger.log('OAuth credentials : pipedrive '); const db_res = await this.prisma.connections.upsert({ diff --git a/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts b/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts index ca2491fcd..c5f8a7058 100644 --- a/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts +++ b/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts @@ -50,11 +50,9 @@ export class ZendeskConnectionService { }, }, ); - //TODO: handle if res throws an error const data: ZendeskOAuthResponse = res.data; this.logger.log('OAuth credentials : zendesk ' + JSON.stringify(data)); - //todo: refresh token + expiration timestamp - // save tokens for this customer inside our db + const db_res = await this.prisma.connections.upsert({ where: { id_connection: isNotUnique.id_connection, diff --git a/packages/api/src/@core/field-mapping/field-mapping.service.ts b/packages/api/src/@core/field-mapping/field-mapping.service.ts index c20c16cd6..f0579d509 100644 --- a/packages/api/src/@core/field-mapping/field-mapping.service.ts +++ b/packages/api/src/@core/field-mapping/field-mapping.service.ts @@ -117,6 +117,7 @@ export class FieldMappingService { const connection = await this.prisma.connections.findFirst({ where: { id_linked_user: linkedUserId, + provider_slug: providerId.toLowerCase(), }, }); diff --git a/packages/api/src/@core/utils/types.ts b/packages/api/src/@core/utils/types.ts index 45dae056a..931678e0f 100644 --- a/packages/api/src/@core/utils/types.ts +++ b/packages/api/src/@core/utils/types.ts @@ -134,21 +134,21 @@ export type UnifySourceType = export const domains = { CRM: { - hubspot: 'https://api.hubapi.com/', - zoho: 'https://www.zohoapis.com/crm/v3/', - zendesk: '', // TODO + hubspot: 'https://api.hubapi.com', + zoho: 'https://www.zohoapis.eu/crm/v3', + zendesk: 'https://api.getbase.com/v2', freshsales: '', - pipedrive: '', + pipedrive: 'https://api.pipedrive.com', }, }; export const customPropertiesUrls = { CRM: { hubspot: `${domains['CRM']['hubspot']}/properties/v1/contacts/properties`, - zoho: `${domains['CRM']['zoho']}`, // TODO - zendesk: `${domains['CRM']['zendesk']}`, - freshsales: `${domains['CRM']['freshsales']}`, - pipedrive: `${domains['CRM']['pipedrive']}`, + zoho: `${domains['CRM']['zoho']}/settings/fields?module=Contact`, + zendesk: `${domains['CRM']['zendesk']}/contact/custom_fields`, + freshsales: `${domains['CRM']['freshsales']}`, //TODO + pipedrive: `${domains['CRM']['pipedrive']}/v1/personFields`, }, }; diff --git a/packages/api/src/crm/contact/services/contact.service.ts b/packages/api/src/crm/contact/services/contact.service.ts index b1b599734..abca1e8ba 100644 --- a/packages/api/src/crm/contact/services/contact.service.ts +++ b/packages/api/src/crm/contact/services/contact.service.ts @@ -274,7 +274,10 @@ export class ContactService { contacts: unifiedContacts, }; + //TODO /*if (remote_data) { + const resp = await this.prisma.remote_data + res = { ...res, remote_data: [resp.data], diff --git a/packages/api/src/crm/contact/services/freshsales/index.ts b/packages/api/src/crm/contact/services/freshsales/index.ts index 927397d85..125fbb707 100644 --- a/packages/api/src/crm/contact/services/freshsales/index.ts +++ b/packages/api/src/crm/contact/services/freshsales/index.ts @@ -59,7 +59,7 @@ export class FreshSalesService { } } - async getContacts( + async syncContacts( linkedUserId: string, ): Promise> { try { diff --git a/packages/api/src/crm/contact/services/hubspot/index.ts b/packages/api/src/crm/contact/services/hubspot/index.ts index 3d03c2c70..2c0b981f7 100644 --- a/packages/api/src/crm/contact/services/hubspot/index.ts +++ b/packages/api/src/crm/contact/services/hubspot/index.ts @@ -61,7 +61,7 @@ export class HubspotService { return; } - async getContacts( + async syncContacts( linkedUserId: string, custom_properties?: string[], ): Promise> { diff --git a/packages/api/src/crm/contact/services/pipedrive/index.ts b/packages/api/src/crm/contact/services/pipedrive/index.ts index d1eaac949..9aed8545b 100644 --- a/packages/api/src/crm/contact/services/pipedrive/index.ts +++ b/packages/api/src/crm/contact/services/pipedrive/index.ts @@ -58,7 +58,7 @@ export class PipedriveService { return; } - async getContacts( + async syncContacts( linkedUserId: string, ): Promise> { try { diff --git a/packages/api/src/crm/contact/services/pipedrive/mappers.ts b/packages/api/src/crm/contact/services/pipedrive/mappers.ts index 7845f8fb5..cd7a375b6 100644 --- a/packages/api/src/crm/contact/services/pipedrive/mappers.ts +++ b/packages/api/src/crm/contact/services/pipedrive/mappers.ts @@ -6,12 +6,15 @@ import { export function mapToContact_Pipedrive( source: UnifiedContactInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], ): PipedriveContactInput { // Assuming 'email_addresses' and 'phone_numbers' arrays contain at least one entry const primaryEmail = source.email_addresses?.[0]?.email_address; const primaryPhone = source.phone_numbers?.[0]?.phone_number; - // Convert to Pipedrive format if needed const emailObject = primaryEmail ? [{ value: primaryEmail, primary: true, label: '' }] : []; @@ -19,11 +22,25 @@ export function mapToContact_Pipedrive( ? [{ value: primaryPhone, primary: true, label: '' }] : []; - return { + const result: PipedriveContactInput = { name: `${source.first_name} ${source.last_name}`, email: emailObject, phone: phoneObject, }; + + if (customFieldMappings && source.field_mappings) { + for (const fieldMapping of source.field_mappings) { + for (const key in fieldMapping) { + const mapping = customFieldMappings.find( + (mapping) => mapping.slug === key, + ); + if (mapping) { + result[mapping.remote_id] = fieldMapping[key]; + } + } + } + } + return result; } export function mapToUnifiedContact_Pipedrive( diff --git a/packages/api/src/crm/contact/services/pipedrive/types.ts b/packages/api/src/crm/contact/services/pipedrive/types.ts index 4560b62e3..cce5de44f 100644 --- a/packages/api/src/crm/contact/services/pipedrive/types.ts +++ b/packages/api/src/crm/contact/services/pipedrive/types.ts @@ -72,6 +72,7 @@ export interface PipedriveContact { org_name: string; owner_name: string; cc_email: string; + [key: string]: any; } export type PipedriveContactInput = Partial; diff --git a/packages/api/src/crm/contact/services/zendesk/index.ts b/packages/api/src/crm/contact/services/zendesk/index.ts index 4d7388472..b478d0e3a 100644 --- a/packages/api/src/crm/contact/services/zendesk/index.ts +++ b/packages/api/src/crm/contact/services/zendesk/index.ts @@ -60,7 +60,7 @@ export class ZendeskService { return; } - async getContacts( + async syncContacts( linkedUserId: string, ): Promise> { try { diff --git a/packages/api/src/crm/contact/services/zendesk/mappers.ts b/packages/api/src/crm/contact/services/zendesk/mappers.ts index 96060bd0a..010da6c2c 100644 --- a/packages/api/src/crm/contact/services/zendesk/mappers.ts +++ b/packages/api/src/crm/contact/services/zendesk/mappers.ts @@ -6,18 +6,37 @@ import { export function mapToContact_Zendesk( source: UnifiedContactInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], ): ZendeskContactInput { // Assuming 'email_addresses' array contains at least one email and 'phone_numbers' array contains at least one phone number const primaryEmail = source.email_addresses?.[0]?.email_address; const primaryPhone = source.phone_numbers?.[0]?.phone_number; - return { + const result: ZendeskContactInput = { name: `${source.first_name} ${source.last_name}`, first_name: source.first_name, last_name: source.last_name, email: primaryEmail, phone: primaryPhone, }; + + if (customFieldMappings && source.field_mappings) { + for (const fieldMapping of source.field_mappings) { + for (const key in fieldMapping) { + const mapping = customFieldMappings.find( + (mapping) => mapping.slug === key, + ); + if (mapping) { + result.custom_fields[mapping.remote_id] = fieldMapping[key]; + } + } + } + } + + return result; } export function mapToUnifiedContact_Zendesk( diff --git a/packages/api/src/crm/contact/services/zendesk/types.ts b/packages/api/src/crm/contact/services/zendesk/types.ts index a3d4468ae..35586facc 100644 --- a/packages/api/src/crm/contact/services/zendesk/types.ts +++ b/packages/api/src/crm/contact/services/zendesk/types.ts @@ -28,6 +28,7 @@ export interface ZendeskContact { name: string; creator_id: number; meta: Meta; + custom_fields: Record; } export type ZendeskContactInput = Partial; diff --git a/packages/api/src/crm/contact/services/zoho/index.ts b/packages/api/src/crm/contact/services/zoho/index.ts index f6e56f7e8..dfbf6a39d 100644 --- a/packages/api/src/crm/contact/services/zoho/index.ts +++ b/packages/api/src/crm/contact/services/zoho/index.ts @@ -57,7 +57,7 @@ export class ZohoService { return; } - async getContacts( + async syncContacts( linkedUserId: string, ): Promise> { try { diff --git a/packages/api/src/crm/contact/services/zoho/mappers.ts b/packages/api/src/crm/contact/services/zoho/mappers.ts index 823c1621f..0529ba622 100644 --- a/packages/api/src/crm/contact/services/zoho/mappers.ts +++ b/packages/api/src/crm/contact/services/zoho/mappers.ts @@ -6,17 +6,36 @@ import { export function mapToContact_Zoho( source: UnifiedContactInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], ): ZohoContactInput { // Assuming 'email_addresses' array contains at least one email and 'phone_numbers' array contains at least one phone number const primaryEmail = source.email_addresses?.[0]?.email_address; const primaryPhone = source.phone_numbers?.[0]?.phone_number; - return { + const result: ZohoContactInput = { First_Name: source.first_name, Last_Name: source.last_name, Email: primaryEmail, Phone: primaryPhone, }; + + if (customFieldMappings && source.field_mappings) { + for (const fieldMapping of source.field_mappings) { + for (const key in fieldMapping) { + const mapping = customFieldMappings.find( + (mapping) => mapping.slug === key, + ); + if (mapping) { + result[mapping.remote_id] = fieldMapping[key]; + } + } + } + } + + return result; } export function mapToUnifiedContact_Zoho( diff --git a/packages/api/src/crm/contact/services/zoho/types.ts b/packages/api/src/crm/contact/services/zoho/types.ts index 98b0b8279..e62c964c6 100644 --- a/packages/api/src/crm/contact/services/zoho/types.ts +++ b/packages/api/src/crm/contact/services/zoho/types.ts @@ -43,6 +43,7 @@ export interface ZohoContact { Other_Country: string; Description: string; Record_Image: string; + [key: string]: any; } export type ZohoContactInput = Partial; diff --git a/packages/api/src/crm/contact/sync/sync.service.ts b/packages/api/src/crm/contact/sync/sync.service.ts index 005084551..b37b85cea 100644 --- a/packages/api/src/crm/contact/sync/sync.service.ts +++ b/packages/api/src/crm/contact/sync/sync.service.ts @@ -46,6 +46,7 @@ export class SyncContactsService implements OnModuleInit { originIds: string[], originSource: string, jobId: string, + remote_data: Record[], ) { try { for (let i = 0; i < contacts.length; i++) { @@ -178,6 +179,21 @@ export class SyncContactsService implements OnModuleInit { } } } + + //TODO: insert remote_data in db + /*await this.prisma.remote_data.upsert({ + where: { + id_crm_contact: unique_crm_contact_id, + }, + create: { + id_remote_data: uuidv4(), + id_crm_contact: unique_crm_contact_id, + data: remote_data, + }, + update: { + data: remote_data, + }, + });*/ } } catch (error) { handleServiceError(error, this.logger); @@ -191,7 +207,6 @@ export class SyncContactsService implements OnModuleInit { async syncContacts() { try { this.logger.log(`Syncing contacts....`); - //TODO: by default only sync linked_users for the default org for Project 1 for hubspot ! const defaultOrg = await this.prisma.organizations.findFirst({ where: { name: 'Acme Inc', @@ -211,7 +226,7 @@ export class SyncContactsService implements OnModuleInit { }); linkedUsers.map(async (linkedUser) => { try { - /*await this.syncContactsForLinkedUser( + await this.syncContactsForLinkedUser( 'hubspot', linkedUser.id_linked_user, ); @@ -222,7 +237,7 @@ export class SyncContactsService implements OnModuleInit { await this.syncContactsForLinkedUser( 'zendesk', linkedUser.id_linked_user, - );*/ + ); await this.syncContactsForLinkedUser( 'zoho', linkedUser.id_linked_user, @@ -237,11 +252,7 @@ export class SyncContactsService implements OnModuleInit { } //todo: HANDLE DATA REMOVED FROM PROVIDER - async syncContactsForLinkedUser( - integrationId: string, - linkedUserId: string, - remote_data?: boolean, - ) { + async syncContactsForLinkedUser(integrationId: string, linkedUserId: string) { try { this.logger.log( `Syncing ${integrationId} contacts for linkedUser ${linkedUserId}`, @@ -283,23 +294,26 @@ export class SyncContactsService implements OnModuleInit { let resp: ApiResponse; switch (integrationId) { case 'freshsales': - resp = await this.freshsales.getContacts(linkedUserId); + resp = await this.freshsales.syncContacts(linkedUserId); break; case 'zoho': - resp = await this.zoho.getContacts(linkedUserId); + resp = await this.zoho.syncContacts(linkedUserId); break; case 'zendesk': - resp = await this.zendesk.getContacts(linkedUserId); + resp = await this.zendesk.syncContacts(linkedUserId); break; case 'hubspot': - resp = await this.hubspot.getContacts(linkedUserId, remoteProperties); + resp = await this.hubspot.syncContacts( + linkedUserId, + remoteProperties, + ); break; case 'pipedrive': - resp = await this.pipedrive.getContacts(linkedUserId); + resp = await this.pipedrive.syncContacts(linkedUserId); break; default: @@ -331,6 +345,7 @@ export class SyncContactsService implements OnModuleInit { contactIds, integrationId, job_id, + sourceObject, ); await this.prisma.events.update({ where: { diff --git a/packages/api/src/crm/contact/types/index.ts b/packages/api/src/crm/contact/types/index.ts index bd25482f3..6ffa5aa5e 100644 --- a/packages/api/src/crm/contact/types/index.ts +++ b/packages/api/src/crm/contact/types/index.ts @@ -2,6 +2,7 @@ import { UnifiedContactOutput } from './model.unified'; export class ApiResponse { data: T; + remote_data?: Record; message?: string; error?: string; statusCode: number; From e180b9a1ccb5f8a7afd3a5f1a837a2b602e51baa Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 12 Dec 2023 23:29:16 +0100 Subject: [PATCH 2/4] :sparkles: Field mapping for other crm providers --- packages/api/src/crm/contact/services/pipedrive/mappers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/api/src/crm/contact/services/pipedrive/mappers.ts b/packages/api/src/crm/contact/services/pipedrive/mappers.ts index cd7a375b6..bba63a877 100644 --- a/packages/api/src/crm/contact/services/pipedrive/mappers.ts +++ b/packages/api/src/crm/contact/services/pipedrive/mappers.ts @@ -21,7 +21,6 @@ export function mapToContact_Pipedrive( const phoneObject = primaryPhone ? [{ value: primaryPhone, primary: true, label: '' }] : []; - const result: PipedriveContactInput = { name: `${source.first_name} ${source.last_name}`, email: emailObject, From 9535764d708c1f8de2adcf4ccf3ea038aaaa6c3b Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 12 Dec 2023 23:33:30 +0100 Subject: [PATCH 3/4] :bug: Fixed utils snippet --- apps/frontend-snippet/src/helpers/utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/frontend-snippet/src/helpers/utils.ts b/apps/frontend-snippet/src/helpers/utils.ts index 601f35042..bd32a1e64 100644 --- a/apps/frontend-snippet/src/helpers/utils.ts +++ b/apps/frontend-snippet/src/helpers/utils.ts @@ -41,11 +41,10 @@ export const providersConfig: ProvidersConfig = { authBaseUrl: '', logoPath: 'assets/crm/freshsales_logo.webp', }, - //TODO 'zendesk': { clientId: '26cd23a81a8fefc134edec2c533a1cb1761d359cfcf438fc159543931e92fc93', scopes: 'read write', - authBaseUrl: 'https://api.getbase.com/oauth2/authorize',//'https://api.getbase.com/oauth2/authorize', + authBaseUrl: 'https://api.getbase.com/oauth2/authorize', logoPath: 'assets/crm/zendesk_logo.png', }, From d2a73a2f692fe281b5825d76d20cf5b24fdd4316 Mon Sep 17 00:00:00 2001 From: nael Date: Tue, 12 Dec 2023 23:36:22 +0100 Subject: [PATCH 4/4] :art: Added logs to syncs --- packages/api/src/crm/contact/services/hubspot/index.ts | 2 ++ packages/api/src/crm/contact/services/zendesk/index.ts | 2 ++ packages/api/src/crm/contact/services/zoho/index.ts | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/api/src/crm/contact/services/hubspot/index.ts b/packages/api/src/crm/contact/services/hubspot/index.ts index 2c0b981f7..6aec39a27 100644 --- a/packages/api/src/crm/contact/services/hubspot/index.ts +++ b/packages/api/src/crm/contact/services/hubspot/index.ts @@ -90,6 +90,8 @@ export class HubspotService { Authorization: `Bearer ${decrypt(connection.access_token)}`, }, }); + this.logger.log(`Synced hubspot contacts !`); + return { data: resp.data.results, message: 'Hubspot contacts retrieved', diff --git a/packages/api/src/crm/contact/services/zendesk/index.ts b/packages/api/src/crm/contact/services/zendesk/index.ts index b478d0e3a..4ae755ce8 100644 --- a/packages/api/src/crm/contact/services/zendesk/index.ts +++ b/packages/api/src/crm/contact/services/zendesk/index.ts @@ -80,6 +80,8 @@ export class ZendeskService { const finalData = resp.data.items.map((item) => { return item.data; }); + this.logger.log(`Synced zendesk contacts !`); + return { data: finalData, message: 'Zendesk contacts retrieved', diff --git a/packages/api/src/crm/contact/services/zoho/index.ts b/packages/api/src/crm/contact/services/zoho/index.ts index dfbf6a39d..381096896 100644 --- a/packages/api/src/crm/contact/services/zoho/index.ts +++ b/packages/api/src/crm/contact/services/zoho/index.ts @@ -81,7 +81,8 @@ export class ZohoService { }, }, ); - this.logger.log('CONTACTS ZOHO ' + JSON.stringify(resp.data.data)); + //this.logger.log('CONTACTS ZOHO ' + JSON.stringify(resp.data.data)); + this.logger.log(`Synced zoho contacts !`); return { data: resp.data.data, message: 'Zoho contacts retrieved',