From 0b4fa2704074cf6b0db6bbe24ee7f720d6e79098 Mon Sep 17 00:00:00 2001 From: Mit Suthar Date: Fri, 15 Mar 2024 06:39:02 +0000 Subject: [PATCH 1/6] fix docker-compose.dev to successfully create connection via magiclink --- .env.example | 3 ++- docker-compose.dev.yml | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 34a868d5b..cc62aea6f 100644 --- a/.env.example +++ b/.env.example @@ -55,7 +55,8 @@ ZENDESK_TICKETING_CLIENT_SECRET= # Must be set in the perspective of the end user browser NEXT_PUBLIC_BACKEND_DOMAIN=http://localhost:3000 # https://api.panora.dev/ -NEXT_PUBLIC_FRONTEND_DOMAIN=http://127.0.0.1:5173 +NEXT_PUBLIC_FRONTEND_DOMAIN=http://localhost:81 +NEXT_PUBLIC_ML_FRONTED_DOMAIN=http://localhost:81 NEXT_PUBLIC_POSTHOG_KEY= NEXT_PUBLIC_POSTHOG_HOST= NEXT_PUBLIC_DISTRIBUTION="managed" #managed or self-host diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index da0e1727d..5eed3d974 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -82,8 +82,8 @@ services: dockerfile: ./apps/client-ts/Dockerfile.dev context: ./ args: - VITE_BACKEND_DOMAIN: ${VITE_BACKEND_DOMAIN} - VITE_FRONTEND_DOMAIN: ${VITE_FRONTEND_DOMAIN} + VITE_BACKEND_DOMAIN: ${NEXT_PUBLIC_BACKEND_DOMAIN} + VITE_FRONTEND_DOMAIN: ${NEXT_PUBLIC_FRONTEND_DOMAIN} environment: NEXT_PUBLIC_STYTCH_SECRET: ${NEXT_PUBLIC_STYTCH_SECRET} NEXT_PUBLIC_STYTCH_PROJECT_ID: ${NEXT_PUBLIC_STYTCH_PROJECT_ID} @@ -111,6 +111,9 @@ services: build: dockerfile: ./apps/magic-link/Dockerfile.dev context: ./ + args: + VITE_BACKEND_DOMAIN: ${NEXT_PUBLIC_BACKEND_DOMAIN} + VITE_ML_FRONTEND_URL: ${NEXT_PUBLIC_ML_FRONTED_DOMAIN} restart: always ports: From 9f11d3221672e0daa9dd071ba452721c50328644 Mon Sep 17 00:00:00 2001 From: Mit Suthar Date: Sat, 16 Mar 2024 06:23:49 +0000 Subject: [PATCH 2/6] Added attio integration --- .env.example | 3 + docker-compose.dev.yml | 2 + docker-compose.source.yml | 2 + docker-compose.yml | 2 + .../connections/connections.controller.ts | 3 + .../connections/crm/crm.connection.module.ts | 4 +- .../crm/services/attio/attio.service.ts | 117 ++++++++++++++++ .../crm/services/crm.connection.service.ts | 5 + .../src/@core/connections/crm/types/index.ts | 5 + .../@core/environment/environment.service.ts | 10 +- packages/api/src/@core/utils/types/index.ts | 4 + packages/api/src/crm/@utils/@types/index.ts | 1 + .../api/src/crm/contact/contact.module.ts | 4 +- .../src/crm/contact/services/attio/index.ts | 110 +++++++++++++++ .../src/crm/contact/services/attio/mappers.ts | 132 ++++++++++++++++++ .../src/crm/contact/services/attio/types.ts | 127 +++++++++++++++++ .../src/crm/contact/types/mappingsTypes.ts | 6 + packages/shared/src/authUrl.ts | 26 ++-- packages/shared/src/enum.ts | 4 +- packages/shared/src/utils.ts | 20 ++- 20 files changed, 567 insertions(+), 20 deletions(-) create mode 100644 packages/api/src/@core/connections/crm/services/attio/attio.service.ts create mode 100644 packages/api/src/crm/contact/services/attio/index.ts create mode 100644 packages/api/src/crm/contact/services/attio/mappers.ts create mode 100644 packages/api/src/crm/contact/services/attio/types.ts diff --git a/.env.example b/.env.example index cc62aea6f..ffb2feea5 100644 --- a/.env.example +++ b/.env.example @@ -42,6 +42,9 @@ ZENDESK_SELL_CLIENT_SECRET= # Freshsales FRESHSALES_CLIENT_ID= FRESHSALES_CLIENT_SECRET= +# Attio +ATTIO_CLIENT_ID= +ATTIO_CLIENT_SECRET= # ================================================ # Ticketing # ================================================ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 5eed3d974..08fcb34ea 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -37,6 +37,8 @@ services: ENCRYPT_CRYPTO_SECRET_KEY: ${ENCRYPT_CRYPTO_SECRET_KEY} HUBSPOT_CLIENT_ID: ${HUBSPOT_CLIENT_ID} HUBSPOT_CLIENT_SECRET: ${HUBSPOT_CLIENT_SECRET} + ATTIO_CLIENT_ID: ${ATTIO_CLIENT_ID} + ATTIO_CLIENT_SECRET: ${ATTIO_CLIENT_SECRET} ZOHOCRM_CLIENT_ID: ${ZOHOCRM_CLIENT_ID} ZOHOCRM_CLIENT_SECRET: ${ZOHOCRM_CLIENT_SECRET} PIPEDRIVE_CLIENT_ID: ${PIPEDRIVE_CLIENT_ID} diff --git a/docker-compose.source.yml b/docker-compose.source.yml index 25c1d1b80..9a43bc969 100644 --- a/docker-compose.source.yml +++ b/docker-compose.source.yml @@ -37,6 +37,8 @@ services: ENCRYPT_CRYPTO_SECRET_KEY: ${ENCRYPT_CRYPTO_SECRET_KEY} HUBSPOT_CLIENT_ID: ${HUBSPOT_CLIENT_ID} HUBSPOT_CLIENT_SECRET: ${HUBSPOT_CLIENT_SECRET} + ATTIO_CLIENT_ID: ${ATTIO_CLIENT_ID} + ATTIO_CLIENT_SECRET: ${ATTIO_CLIENT_SECRET} ZOHOCRM_CLIENT_ID: ${ZOHOCRM_CLIENT_ID} ZOHOCRM_CLIENT_SECRET: ${ZOHOCRM_CLIENT_SECRET} PIPEDRIVE_CLIENT_ID: ${PIPEDRIVE_CLIENT_ID} diff --git a/docker-compose.yml b/docker-compose.yml index e8a140af2..37f3fcd03 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,6 +32,8 @@ services: ENCRYPT_CRYPTO_SECRET_KEY: ${ENCRYPT_CRYPTO_SECRET_KEY} HUBSPOT_CLIENT_ID: ${HUBSPOT_CLIENT_ID} HUBSPOT_CLIENT_SECRET: ${HUBSPOT_CLIENT_SECRET} + ATTIO_CLIENT_ID: ${ATTIO_CLIENT_ID} + ATTIO_CLIENT_SECRET: ${ATTIO_CLIENT_SECRET} ZOHOCRM_CLIENT_ID: ${ZOHOCRM_CLIENT_ID} ZOHOCRM_CLIENT_SECRET: ${ZOHOCRM_CLIENT_SECRET} PIPEDRIVE_CLIENT_ID: ${PIPEDRIVE_CLIENT_ID} diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index a2595c148..40e4d2a9e 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -45,6 +45,9 @@ export class ConnectionsController { `No Callback Params found for code, found ${code}`, ); + console.log("In connection controller - Mit") + this.logger.log("in connection controller - Mit") + const stateData = JSON.parse(decodeURIComponent(state)); const { projectId, linkedUserId, providerName, returnUrl } = stateData; switch (getProviderVertical(providerName.toLowerCase())) { diff --git a/packages/api/src/@core/connections/crm/crm.connection.module.ts b/packages/api/src/@core/connections/crm/crm.connection.module.ts index 8794afd28..45e82fb8f 100644 --- a/packages/api/src/@core/connections/crm/crm.connection.module.ts +++ b/packages/api/src/@core/connections/crm/crm.connection.module.ts @@ -12,6 +12,7 @@ import { HubspotConnectionService } from './services/hubspot/hubspot.service'; import { ZohoConnectionService } from './services/zoho/zoho.service'; import { ZendeskConnectionService } from './services/zendesk/zendesk.service'; import { PipedriveConnectionService } from './services/pipedrive/pipedrive.service'; +import { AttioConnectionService } from './services/attio/attio.service'; @Module({ imports: [WebhookModule], @@ -26,10 +27,11 @@ import { PipedriveConnectionService } from './services/pipedrive/pipedrive.servi // PROVIDERS SERVICES FreshsalesConnectionService, HubspotConnectionService, + AttioConnectionService, ZohoConnectionService, ZendeskConnectionService, PipedriveConnectionService, ], exports: [CrmConnectionsService], }) -export class CrmConnectionModule {} +export class CrmConnectionModule { } diff --git a/packages/api/src/@core/connections/crm/services/attio/attio.service.ts b/packages/api/src/@core/connections/crm/services/attio/attio.service.ts new file mode 100644 index 000000000..8d110fbb3 --- /dev/null +++ b/packages/api/src/@core/connections/crm/services/attio/attio.service.ts @@ -0,0 +1,117 @@ +import { Injectable } from "@nestjs/common"; +import { + AttioOAuthResponse, + CallbackParams, + ICrmConnectionService, + RefreshParams, +} from "../../types"; +import { PrismaService } from '@@core/prisma/prisma.service'; +import axios from 'axios'; +import { v4 as uuidv4 } from 'uuid'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { ServiceConnectionRegistry } from '../registry.service'; +import { LoggerService } from '@@core/logger/logger.service'; + + +@Injectable() +export class AttioConnectionService implements ICrmConnectionService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceConnectionRegistry + ) { + this.logger.setContext(AttioConnectionService.name); + this.registry.registerService("attio", this); + } + + async handleCallback(opts: CallbackParams) { + try { + console.log("Linked User iD : ('ENV'); @@ -46,6 +46,14 @@ export class EnvironmentService { CLIENT_SECRET: this.configService.get('HUBSPOT_CLIENT_SECRET'), }; } + + getAttioAuth(): OAuth { + return { + CLIENT_ID: this.configService.get('ATTIO_CLIENT_ID'), + CLIENT_SECRET: this.configService.get('ATTIO_CLIENT_SECRET'), + } + } + getZohoSecret(): OAuth { return { CLIENT_ID: this.configService.get('ZOHO_CLIENT_ID'), diff --git a/packages/api/src/@core/utils/types/index.ts b/packages/api/src/@core/utils/types/index.ts index 7cf470ff2..191bc38e8 100644 --- a/packages/api/src/@core/utils/types/index.ts +++ b/packages/api/src/@core/utils/types/index.ts @@ -31,6 +31,7 @@ export type StandardObject = TargetObject; export const domains = { CRM: { hubspot: 'https://api.hubapi.com', + attio: 'https://developers.attio.com', zoho: 'https://www.zohoapis.eu/crm/v3', zendesk: 'https://api.getbase.com/v2', freshsales: '', @@ -43,6 +44,7 @@ export const customPropertiesUrls = { hubspot: `${domains['CRM']['hubspot']}/properties/v1/contacts/properties`, zoho: `${domains['CRM']['zoho']}/settings/fields?module=Contact`, zendesk: `${domains['CRM']['zendesk']}/contact/custom_fields`, + attio: `${domains['CRM']['attio']}/docs/standard-objects-people`, freshsales: `${domains['CRM']['freshsales']}`, //TODO pipedrive: `${domains['CRM']['pipedrive']}/v1/personFields`, }, @@ -67,6 +69,7 @@ export enum CrmProviders { HUBSPOT = 'hubspot', PIPEDRIVE = 'pipedrive', FRESHSALES = 'freshsales', + ATTIO = 'attio', } export enum AccountingProviders { @@ -85,6 +88,7 @@ export const CRM_PROVIDERS = [ 'hubspot', 'pipedrive', 'freshsales', + 'attio', ]; export const HRIS_PROVIDERS = ['']; diff --git a/packages/api/src/crm/@utils/@types/index.ts b/packages/api/src/crm/@utils/@types/index.ts index 17b5d9d13..ce2507a85 100644 --- a/packages/api/src/crm/@utils/@types/index.ts +++ b/packages/api/src/crm/@utils/@types/index.ts @@ -109,6 +109,7 @@ 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'; diff --git a/packages/api/src/crm/contact/contact.module.ts b/packages/api/src/crm/contact/contact.module.ts index 40ddaa9fb..41e2b28f3 100644 --- a/packages/api/src/crm/contact/contact.module.ts +++ b/packages/api/src/crm/contact/contact.module.ts @@ -4,6 +4,7 @@ import { ContactController } from './contact.controller'; import { PrismaService } from '@@core/prisma/prisma.service'; import { FreshsalesService } from './services/freshsales'; import { ZendeskService } from './services/zendesk'; +import { AttioService } from './services/attio' import { ZohoService } from './services/zoho'; import { PipedriveService } from './services/pipedrive'; import { HubspotService } from './services/hubspot'; @@ -33,6 +34,7 @@ import { ServiceRegistry } from './services/registry.service'; ServiceRegistry, /* PROVIDERS SERVICES */ FreshsalesService, + AttioService, ZendeskService, ZohoService, PipedriveService, @@ -40,4 +42,4 @@ import { ServiceRegistry } from './services/registry.service'; ], exports: [SyncContactsService], }) -export class ContactModule {} +export class ContactModule { } diff --git a/packages/api/src/crm/contact/services/attio/index.ts b/packages/api/src/crm/contact/services/attio/index.ts new file mode 100644 index 000000000..c7e397e5a --- /dev/null +++ b/packages/api/src/crm/contact/services/attio/index.ts @@ -0,0 +1,110 @@ +import { Injectable } from '@nestjs/common'; +import { IContactService } from '@crm/contact/types'; +import { + CrmObject, + AttioContactInput, + AttioContactOutput, + commonHubspotProperties, +} from '@crm/@utils/@types'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { LoggerService } from '@@core/logger/logger.service'; +import { ActionType, handleServiceError } from '@@core/utils/errors'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { ApiResponse } from '@@core/utils/types'; +import { ServiceRegistry } from '../registry.service'; + +@Injectable() +export class AttioService implements IContactService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + CrmObject.contact.toUpperCase() + ':' + AttioService.name, + ); + this.registry.registerService('attio', this); + + } + + async addContact( + contactData: AttioContactInput, + linkedUserId: string, + ): Promise> { + try { + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'attio', + }, + }); + + const resp = await axios.post( + `https://api.attio.com/v2/objects/people/records`, + JSON.stringify(contactData), + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + return { + data: resp.data.data, + message: 'attio contact created', + statusCode: 201, + }; + } catch (error) { + handleServiceError( + error, + this.logger, + 'Attio', + CrmObject.contact, + ActionType.POST, + ); + } + return; + } + + async syncContacts( + linkedUserId: string, + ): Promise> { + try { + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'attio', + }, + }); + const resp = await axios.get(`https://api.attio.com/v2/objects/people/records/query`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }); + + return { + data: resp.data.data, + message: 'Attio contacts retrieved', + statusCode: 200, + }; + } catch (error) { + handleServiceError( + error, + this.logger, + 'Attio', + CrmObject.contact, + ActionType.GET, + ); + } + } + + + +} diff --git a/packages/api/src/crm/contact/services/attio/mappers.ts b/packages/api/src/crm/contact/services/attio/mappers.ts new file mode 100644 index 000000000..e7483984f --- /dev/null +++ b/packages/api/src/crm/contact/services/attio/mappers.ts @@ -0,0 +1,132 @@ +import { + Address, + AttioContactInput, + AttioContactOutput, +} from '@crm/@utils/@types'; +import { + UnifiedContactInput, + UnifiedContactOutput, +} from '@crm/contact/types/model.unified'; +import { IContactMapper } from '@crm/contact/types'; +import { Utils } from '@crm/contact/utils'; + +export class AttioContactMapper implements IContactMapper { + private readonly utils: Utils; + + constructor() { + this.utils = new Utils(); + } + + async desunify( + source: UnifiedContactInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + // 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; + + // const emailObject = primaryEmail + // ? [{ value: primaryEmail, primary: true, label: '' }] + // : []; + // const phoneObject = primaryPhone + // ? [{ value: primaryPhone, primary: true, label: '' }] + // : []; + + const result: AttioContactInput = { + values: { + name: [{ first_name: source.first_name, last_name: source.last_name, full_name: `${source.first_name} ${source.last_name}` }], + email_addresses: [{ email_address: primaryEmail }], + phone_numbers: [{ phone_number: 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 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; + } + + async unify( + source: AttioContactOutput | AttioContactOutput[], + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + if (!Array.isArray(source)) { + return await this.mapSingleContactToUnified(source, customFieldMappings); + } + + // Handling array of HubspotContactOutput + return Promise.all( + source.map((contact) => + this.mapSingleContactToUnified(contact, customFieldMappings), + ), + ); + } + + private async mapSingleContactToUnified( + contact: AttioContactOutput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + const field_mappings = customFieldMappings.map((mapping) => ({ + [mapping.slug]: contact[mapping.remote_id], + })); + const address: Address = { + street_1: '', + city: '', + state: '', + postal_code: '', + country: '', + }; + let opts: any = {}; + + + return { + first_name: contact.values.name[0].first_name, + last_name: contact.values.name[0].last_name, + email_addresses: contact.values.email_addresses.map((e) => ({ + email_address: e.email_address, + email_address_type: e.attribute_type ? e.attribute_type : '', + })), // Map each email + phone_numbers: contact.values.phone_numbers.map((p) => ({ + phone_number: p.phone_number, + phone_type: p.attribute_type ? p.attribute_type : '', + })), // Map each phone number, + field_mappings, + addresses: [address], + ...opts, + }; + } +} diff --git a/packages/api/src/crm/contact/services/attio/types.ts b/packages/api/src/crm/contact/services/attio/types.ts new file mode 100644 index 000000000..1f2d88e94 --- /dev/null +++ b/packages/api/src/crm/contact/services/attio/types.ts @@ -0,0 +1,127 @@ +interface Actor { + type: string; + id: string | null; +} + +interface Option { + id: { + workspace_id: string; + object_id: string; + attribute_id: string; + option_id: string; + }; + title: string; + is_archived: boolean; +} + +interface ValueItemBase { + active_from?: string; + active_until?: string | null; + created_by_actor?: Actor; + attribute_type?: string; +} + +interface NumberValueItem extends ValueItemBase { + value: number; +} + +interface TextValueItem extends ValueItemBase { + value: string; +} + +interface InteractionValueItem extends ValueItemBase { + interaction_type: string; + interacted_at: string; + owner_actor: Actor; +} + +interface LocationValueItem extends ValueItemBase { + line_1: string | null; + line_2: string | null; + line_3: string | null; + line_4: string | null; + locality: string; + region: string; + postcode: string; + country_code: string; + latitude: string; + longitude: string; +} + +interface RecordReferenceValueItem extends ValueItemBase { + target_object: string; + target_record_id: string; +} + +interface ActorReferenceValueItem extends ValueItemBase { + referenced_actor_type: string; + referenced_actor_id: string; +} + +interface EmailAddressValueItem extends ValueItemBase { + original_email_address?: string; + email_address: string; + email_domain?: string; + email_root_domain?: string; + email_local_specifier?: string; +} + +interface PhoneValueItem extends ValueItemBase { + country_code?: string; + original_phone_numbers?: string; + phone_number: string; + +} + +interface PersonalNameValueItem extends ValueItemBase { + first_name: string; + last_name: string; + full_name: string; +} + +interface SelectValueItem extends ValueItemBase { + option: Option; +} + +export interface AttioContact { + id: { + workspace_id: string; + object_id: string; + record_id: string; + }; + created_at: string; + values: { + strongest_connection_strength_legacy?: NumberValueItem[]; + last_interaction?: InteractionValueItem[]; + twitter?: TextValueItem[]; + avatar_url?: TextValueItem[]; + job_title?: TextValueItem[]; + next_calendar_interaction?: InteractionValueItem[]; + company?: RecordReferenceValueItem[]; + primary_location?: LocationValueItem[]; + angellist?: TextValueItem[]; + description?: TextValueItem[]; + strongest_connection_user?: ActorReferenceValueItem[]; + strongest_connection_strength?: SelectValueItem[]; + last_email_interaction?: InteractionValueItem[]; + email_addresses?: EmailAddressValueItem[]; + first_interaction?: InteractionValueItem[]; + created_at?: ValueItemBase[]; + created_by?: ActorReferenceValueItem[]; + last_calendar_interaction?: InteractionValueItem[]; + linkedin?: TextValueItem[]; + facebook?: TextValueItem[]; + name?: PersonalNameValueItem[]; + first_calendar_interaction?: InteractionValueItem[]; + instagram?: TextValueItem[]; + first_email_interaction?: InteractionValueItem[]; + phone_numbers?: PhoneValueItem[]; + }; +} + + + + + +export type AttioContactInput = Partial; +export type AttioContactOutput = AttioContactInput; diff --git a/packages/api/src/crm/contact/types/mappingsTypes.ts b/packages/api/src/crm/contact/types/mappingsTypes.ts index d06cc528c..cf42a0bc1 100644 --- a/packages/api/src/crm/contact/types/mappingsTypes.ts +++ b/packages/api/src/crm/contact/types/mappingsTypes.ts @@ -3,12 +3,14 @@ import { HubspotContactMapper } from '@crm/contact/services/hubspot/mappers'; import { PipedriveContactMapper } from '@crm/contact/services/pipedrive/mappers'; import { ZendeskContactMapper } from '@crm/contact/services/zendesk/mappers'; import { ZohoContactMapper } from '@crm/contact/services/zoho/mappers'; +import { AttioContactMapper } from '@crm/contact/services/attio/mappers'; const hubspotContactMapper = new HubspotContactMapper(); const zendeskContactMapper = new ZendeskContactMapper(); const zohoContactMapper = new ZohoContactMapper(); const pipedriveContactMapper = new PipedriveContactMapper(); const freshSalesContactMapper = new FreshsalesContactMapper(); +const attioContactMapper = new AttioContactMapper(); export const contactUnificationMapping = { hubspot: { @@ -31,4 +33,8 @@ export const contactUnificationMapping = { unify: freshSalesContactMapper.unify.bind(freshSalesContactMapper), desunify: freshSalesContactMapper.desunify.bind(freshSalesContactMapper), }, + attio: { + unify: attioContactMapper.unify.bind(attioContactMapper), + desunify: attioContactMapper.desunify.bind(attioContactMapper) + } }; diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index 1f6008836..005e5d6f4 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -12,8 +12,11 @@ export const constructAuthUrl = ({ projectId, linkedUserId, providerName, return const encodedRedirectUrl = encodeURIComponent(`${apiUrl}/connections/oauth/callback`); const state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, returnUrl })); + console.log("State : ", JSON.stringify({ projectId, linkedUserId, providerName, returnUrl })) + console.log("encodedRedirect URL : ", encodedRedirectUrl) + const vertical = findProviderVertical(providerName); - if(vertical == null) { + if (vertical == null) { return null; } @@ -25,22 +28,29 @@ export const constructAuthUrl = ({ projectId, linkedUserId, providerName, return const { clientId, scopes } = config_; const baseUrl = config_.authBaseUrl; + + if (!baseUrl) { throw new Error(`Unsupported provider: ${providerName}`); } const addScope = providerName == "pipedrive" ? false : true; let finalAuth = ''; - if ( providerName == 'zoho' ) { + if (providerName == 'zoho') { finalAuth = `${baseUrl}?response_type=code&client_id=${encodeURIComponent(clientId)}&scope=${encodeURIComponent(scopes)}&redirect_uri=${encodedRedirectUrl}&access_type=offline&state=${state}` console.log(finalAuth); - } else if(providerName == "zendesk"){ + } else if (providerName == "zendesk") { finalAuth = `${baseUrl}?client_id=${encodeURIComponent(clientId)}&response_type=code&redirect_uri=${encodedRedirectUrl}&state=${state}` - } else if(providerName == "zendesk_tcg" || providerName=="front") { + } else if (providerName == "attio") { + finalAuth = `${baseUrl}?client_id=${encodeURIComponent(clientId)}&response_type=code&redirect_uri=${encodedRedirectUrl}&state=${state}` + + } else if (providerName == "zendesk_tcg" || providerName == "front") { finalAuth = `${baseUrl}?client_id=${encodeURIComponent(clientId)}&response_type=code&redirect_uri=${encodedRedirectUrl}&scope=${encodeURIComponent(scopes)}&state=${state}` - }else{ - finalAuth = addScope ? - `${baseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&scope=${encodeURIComponent(scopes)}&state=${state}` - : `${baseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&state=${state}`; + } else { + finalAuth = addScope ? + `${baseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&scope=${encodeURIComponent(scopes)}&state=${state}` + : `${baseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&state=${state}`; } + + console.log("Final Authentication : ", finalAuth) return finalAuth; }; \ No newline at end of file diff --git a/packages/shared/src/enum.ts b/packages/shared/src/enum.ts index 7765ecee0..f160d7802 100644 --- a/packages/shared/src/enum.ts +++ b/packages/shared/src/enum.ts @@ -15,8 +15,9 @@ export enum CrmProviders { HUBSPOT = 'hubspot', PIPEDRIVE = 'pipedrive', FRESHSALES = 'freshsales', + ATTIO = 'attio' } - + export enum TicketingProviders { ZENDESK = 'zendesk', FRONT = 'front', @@ -32,4 +33,3 @@ export enum AccountingProviders { FREEAGENT = 'freeagent', SAGE = 'sage', } - \ No newline at end of file diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index fffb52d2f..955d1a50d 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -5,7 +5,7 @@ type ProviderConfig = { logoPath: string; description: string; }; - + type VerticalConfig = { [key: string]: ProviderConfig; }; @@ -13,17 +13,24 @@ type VerticalConfig = { type ProvidersConfig = { [vertical: string]: VerticalConfig; }; - - + + export const providersConfig: ProvidersConfig = { 'crm': { 'hubspot': { - clientId: 'ba591170-a7c7-4fca-8086-1bd178c6b14d', + clientId: 'clientid', scopes: 'crm.objects.contacts.read crm.objects.contacts.write crm.schemas.deals.read crm.schemas.deals.write crm.objects.deals.read crm.objects.deals.write crm.objects.companies.read crm.objects.companies.write crm.objects.owners.read settings.users.read settings.users.write settings.users.teams.read settings.users.teams.write', authBaseUrl: 'https://app-eu1.hubspot.com/oauth/authorize', logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png", description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users" }, + 'attio': { + clientId: 'clientid', + scopes: 'record_permission:read', + authBaseUrl: 'https://app.attio.com/authorize', + logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png", + description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users" + }, 'zoho': { clientId: '1000.CWBWAO0XK6QNROXMA2Y0RUZYMGJIGT', scopes: 'ZohoCRM.modules.ALL', @@ -122,7 +129,7 @@ export const providersConfig: ProvidersConfig = { export const getDescription = (name: string): string | null => { const vertical = findProviderVertical(name); - if(vertical == null){ + if (vertical == null) { return null; } return providersConfig[vertical.toLowerCase()][name].description; @@ -138,7 +145,7 @@ type Provider = { }; export function providersArray(vertical: string): Provider[] { - if(!providersConfig[vertical.toLowerCase()]){ + if (!providersConfig[vertical.toLowerCase()]) { return []; } return Object.entries(providersConfig[vertical.toLowerCase()]).map(([providerName, config]) => { @@ -176,4 +183,3 @@ export function findProviderByName(providerName: string): Provider | null { } return null; } - \ No newline at end of file From 14b5f4f7ce1f7d6f264587a223cde13e1630bbfd Mon Sep 17 00:00:00 2001 From: Mit Suthar Date: Sat, 16 Mar 2024 07:27:52 +0000 Subject: [PATCH 3/6] Sync contacts --- .../src/crm/contact/services/attio/index.ts | 24 ++++++++++++------- packages/shared/src/utils.ts | 5 ++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/api/src/crm/contact/services/attio/index.ts b/packages/api/src/crm/contact/services/attio/index.ts index c7e397e5a..b8d1e7a10 100644 --- a/packages/api/src/crm/contact/services/attio/index.ts +++ b/packages/api/src/crm/contact/services/attio/index.ts @@ -80,14 +80,22 @@ export class AttioService implements IContactService { provider_slug: 'attio', }, }); - const resp = await axios.get(`https://api.attio.com/v2/objects/people/records/query`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, - }); + console.log("Before Axios") + console.log(this.cryptoService.decrypt(connection.access_token)) + + const resp = await axios.post(`https://api.attio.com/v2/objects/people/records/query`, {}, + { + headers: { + accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt(connection.access_token)}`, + }, + } + + + ); + + console.log("After Axios") return { data: resp.data.data, diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 955d1a50d..6ccff8454 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -17,15 +17,16 @@ type ProvidersConfig = { export const providersConfig: ProvidersConfig = { 'crm': { + // Add client id in their respective crm 'hubspot': { - clientId: 'clientid', + clientId: 'Add hubspot requested client id', scopes: 'crm.objects.contacts.read crm.objects.contacts.write crm.schemas.deals.read crm.schemas.deals.write crm.objects.deals.read crm.objects.deals.write crm.objects.companies.read crm.objects.companies.write crm.objects.owners.read settings.users.read settings.users.write settings.users.teams.read settings.users.teams.write', authBaseUrl: 'https://app-eu1.hubspot.com/oauth/authorize', logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png", description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users" }, 'attio': { - clientId: 'clientid', + clientId: '', scopes: 'record_permission:read', authBaseUrl: 'https://app.attio.com/authorize', logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png", From 66b6a3b13a2f90c2285d5657ac6b7951f5463cb4 Mon Sep 17 00:00:00 2001 From: Mit Suthar Date: Sat, 16 Mar 2024 07:35:53 +0000 Subject: [PATCH 4/6] Removed unnecessary comments --- packages/api/src/@core/connections/connections.controller.ts | 3 --- .../@core/connections/crm/services/crm.connection.service.ts | 2 -- 2 files changed, 5 deletions(-) diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index 40e4d2a9e..a2595c148 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -45,9 +45,6 @@ export class ConnectionsController { `No Callback Params found for code, found ${code}`, ); - console.log("In connection controller - Mit") - this.logger.log("in connection controller - Mit") - const stateData = JSON.parse(decodeURIComponent(state)); const { projectId, linkedUserId, providerName, returnUrl } = stateData; switch (getProviderVertical(providerName.toLowerCase())) { diff --git a/packages/api/src/@core/connections/crm/services/crm.connection.service.ts b/packages/api/src/@core/connections/crm/services/crm.connection.service.ts index f76132e29..7d8ae73fe 100644 --- a/packages/api/src/@core/connections/crm/services/crm.connection.service.ts +++ b/packages/api/src/@core/connections/crm/services/crm.connection.service.ts @@ -41,8 +41,6 @@ export class CrmConnectionsService { zohoLocation?: string, ) { try { - this.logger.log("callback called") - console.log("callback called") if (!code) { throw new NotFoundError(`no ${providerName} code found, found ${code}`); } From 08e7a0a64540870cd6753686685a134d3c4cedec Mon Sep 17 00:00:00 2001 From: mit-27 Date: Mon, 18 Mar 2024 20:24:16 -0400 Subject: [PATCH 5/6] Added attio icons --- .npmrc | 2 ++ apps/client-ts/public/providers/crm/attio.png | Bin 0 -> 2197 bytes .../crm/services/crm.connection.service.ts | 2 -- packages/shared/src/providers.ts | 1 + packages/shared/src/utils.ts | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .npmrc create mode 100644 apps/client-ts/public/providers/crm/attio.png diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..a914207cd --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +node-linker=hoisted +package-import-method=clone-or-copy diff --git a/apps/client-ts/public/providers/crm/attio.png b/apps/client-ts/public/providers/crm/attio.png new file mode 100644 index 0000000000000000000000000000000000000000..2d360fe65ff84b41d95c6f360296fc879cff6442 GIT binary patch literal 2197 zcmbtVSyWS57JV+m!T`1zPy(Vn$pc%O0wV~`WQ@d+Fo*?$NtH^G5D;erAqWaoj2Ni| z5 zMmhihY@|`iJ^-*zvUWGDhndR2))n|cgpj;Q0H8?HT@KWO^L1f9ZU=y}r^e%O0F!od zPEIW?sc)B*2C+k|tO?@8q~YP$PR>LQhZ`5ikB#F`PJV1^ZlSq*R#a4p#gf~%?_|kx zs5GyN%BqVTE-UC{cek>ssby&BwNCZj2>{S8r;$lVBD1FkCnEYCQTyKHZHUJ?JS9+6L z@&AhU+4>HqynP!GM{}QiQk<|Zib3bndGzQ3SxLY(lOnu4y|;3AUPEz=;kil6PYNRU zzV5Zv;TP7;zYycU(*2*!9M%vgqKtU)U;F-atM%^56H4_xk#j@Nv_~paicdMzKCtkh z&ObJ|m>ois+&qJ}4|Kv)x-6aKy;nyWM`B$?bOt6!{Vp@DXBdsQD?)s&77Pn;fyHd5 zH;N?cE2P3mH={|e7UL-*B_sqXXE0`K=(C2F$~9c_$N>4c@#&7;CT&Plp|oCUzQ)qB zL2YkO(dT4G5`v&?Yus%Tf+eHyR|?i9k8PB~J>5nStleVMBwseWfRylfcUU^9mj-IP z!X}OEYCrXx;kH!?tb_vKhJn2+m`raEN+Xe)Dls11{E%eq)1w!@oK zUAA1OpKbM&JQws2zUmU?g7UGut=_1Z&C1|q#G8lAJF{4FtxRTsfvh2FFjZ0~0W)`y z<__@RUqf{0A(Q_nh%&+X%HJ5>0}}Akw~C*7jGc2Mvq8P%W>|_rz+3L!ec7SsmV$`52cu8kVfM73)%3 zAGFGS%L8q$LDc!%7dm2w7Yuiqks9+G30b3Rk$wtT+vN7|eJZ*;-!@4neR5$dz7T3( z$p4_een~84V#p)=jBmpc2SXM(e>TKxnF(huua+lc$ctjB59WR3=-6J0j)x>=XIQ1m z;yaSynat229c*Na^fSOlmIz+NV}icIENDldVeCSnhnNK~5a@blP(OlEh`Y10T#}OG zUyU?%ZlV4MX=>a;B_Ne6q!UDfPXvF1wTa5YnIuTZ?Q$Url5ntdW_|O=9Ta0oID^7( z$B2cy~e-iG6jLNg2c{K&Jp**F48pEZ?=%`j|AF4M$4FeBOa zo-IA$qv-V_Snlu`q!!)FKfPsK{Q+HLVr8Nkb-e_!+)+Kr7kIXZ8G2Ywu~GXMZuk>{ zWkN3M2~mYFRnc4T<*)i}^84^rp0t9!)e-2+mvd7B&PKJw3;!xVI-B%78H~>AxL35~ zI~ymb#~5H@eR@*Cn~MjbNkxJSkHeyq-&*UQqe;Lep7DH#+^6^R$mf$pg;R10qPjO} zb8Uh^k+5-oI`(@_^Azq3QYjoT-gXS=yPx1pnWD;%HtW2#Ynz&-Yu#Utw-_-_(In7G zT_cgP&i*%|ruG{Rx)FCGmAsvX3Wq;gH)FWJYBveOO*GjMb$XLu`1!;WA4$xYq1>sm zDWqwQU0dxtg*$tx;8OeBHmhgz zu&&MRg_h;n&+8sKMA1;veU!%QGf&w+zgb8%s90x@H1(WVdTIZbvxVO3SnJ^Ej*>c3 z`pNpNALA~jfu@0? z@S$yk`gr1kSK@SYt`()`0eD=r=z)@2P#P@;ajtx#%@^7dFg#v( zC!M4BX8F)0jS211O>P#d5a?Y@NPd5iAu*-YnF|+Lk?D%2czm(cCAN+yA_bU?E)ithoJ6%r`q;I01 zl?RBJbwaD@=dk1qX3+V~4Oqi4Ifuc6vd%Ax9sH|@yr!jADo;0VS;~xrO?fyi{i}~> z^(nBbYR-R^@o2Q3b=Q6MRN~|4yvoq|{)wzDtVF-^mbp&>%N^pLhG*`vm l65VRVZrk7cGy&kkWb8U(gmf!QrC1w9J9vm(cHk%ee*xN{#LfT! literal 0 HcmV?d00001 diff --git a/packages/api/src/@core/connections/crm/services/crm.connection.service.ts b/packages/api/src/@core/connections/crm/services/crm.connection.service.ts index 7d8ae73fe..6a33d0ac0 100644 --- a/packages/api/src/@core/connections/crm/services/crm.connection.service.ts +++ b/packages/api/src/@core/connections/crm/services/crm.connection.service.ts @@ -46,7 +46,6 @@ export class CrmConnectionsService { } const serviceName = providerName.toLowerCase(); - this.logger.log("callback in connection service - Mit") const service = this.serviceRegistry.getService(serviceName); @@ -59,7 +58,6 @@ export class CrmConnectionsService { code: code, location: zohoLocation || null, }; - console.log("In callback2") const data: Connection = await service.handleCallback(callbackOpts); this.logger.log('data is ' + data); const event = await this.prisma.events.create({ diff --git a/packages/shared/src/providers.ts b/packages/shared/src/providers.ts index 4523210b0..1f8fac1e7 100644 --- a/packages/shared/src/providers.ts +++ b/packages/shared/src/providers.ts @@ -7,6 +7,7 @@ export const CRM_PROVIDERS = [ 'zendesk', 'hubspot', 'pipedrive', + 'attio', 'freshsales', ]; diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 6ccff8454..5bf241f96 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -29,7 +29,7 @@ export const providersConfig: ProvidersConfig = { clientId: '', scopes: 'record_permission:read', authBaseUrl: 'https://app.attio.com/authorize', - logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png", + logoPath: "https://asset.brandfetch.io/idZA7HYRWK/idYZS6Vp_r.png", description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users" }, 'zoho': { From 94598652de8322bd46f38380ae4182e0dfb64e0d Mon Sep 17 00:00:00 2001 From: Mit Suthar Date: Tue, 19 Mar 2024 00:22:34 -0400 Subject: [PATCH 6/6] Delete .npmrc --- .npmrc | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .npmrc diff --git a/.npmrc b/.npmrc deleted file mode 100644 index a914207cd..000000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -node-linker=hoisted -package-import-method=clone-or-copy