diff --git a/.env.example b/.env.example index 34a868d5b..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 # ================================================ @@ -55,7 +58,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/apps/client-ts/public/providers/crm/attio.png b/apps/client-ts/public/providers/crm/attio.png new file mode 100644 index 000000000..2d360fe65 Binary files /dev/null and b/apps/client-ts/public/providers/crm/attio.png differ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index da0e1727d..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} @@ -82,8 +84,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 +113,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: 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/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..b8d1e7a10 --- /dev/null +++ b/packages/api/src/crm/contact/services/attio/index.ts @@ -0,0 +1,118 @@ +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', + }, + }); + 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, + 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/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 fffb52d2f..5bf241f96 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,25 @@ type VerticalConfig = { type ProvidersConfig = { [vertical: string]: VerticalConfig; }; - - + + export const providersConfig: ProvidersConfig = { 'crm': { + // Add client id in their respective crm 'hubspot': { - clientId: 'ba591170-a7c7-4fca-8086-1bd178c6b14d', + 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: '', + scopes: 'record_permission:read', + authBaseUrl: 'https://app.attio.com/authorize', + logoPath: "https://asset.brandfetch.io/idZA7HYRWK/idYZS6Vp_r.png", + description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users" + }, 'zoho': { clientId: '1000.CWBWAO0XK6QNROXMA2Y0RUZYMGJIGT', scopes: 'ZohoCRM.modules.ALL', @@ -122,7 +130,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 +146,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 +184,3 @@ export function findProviderByName(providerName: string): Provider | null { } return null; } - \ No newline at end of file