diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 6db11f1b8..1a7861b70 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -66,8 +66,6 @@ services: JIRA_SERVICE_MGMT_TICKETING_CLIENT_SECRET: ${JIRA_SERVICE_MGMT_TICKETING_CLIENT_SECRET} LINEAR_TICKETING_CLIENT_ID: ${LINEAR_TICKETING_CLIENT_ID} LINEAR_TICKETING_CLIENT_SECRET: ${LINEAR_TICKETING_CLIENT_SECRET} - AFFINITY_CRM_CLIENT_ID: ${AFFINITY_CRM_CLIENT_ID} - AFFINITY_CRM_CLIENT_SECRET: ${AFFINITY_CRM_CLIENT_SECRET} ACCELO_CRM_CLIENT_ID: ${ACCELO_CRM_CLIENT_ID} ACCELO_CRM_CLIENT_SECRET: ${ACCELO_CRM_CLIENT_SECRET} ACCELO_CRM_SUBDOMAIN: ${ACCELO_CRM_SUBDOMAIN} @@ -77,13 +75,8 @@ services: CLOSE_CRM_CLIENT_SECRET: ${CLOSE_CRM_CLIENT_SECRET} COPPER_CRM_CLIENT_ID: ${COPPER_CRM_CLIENT_ID} COPPER_CRM_CLIENT_SECRET: ${COPPER_CRM_CLIENT_SECRET} - INSIGHTLY_CRM_CLIENT_ID: ${INSIGHTLY_CRM_CLIENT_ID} - INSIGHTLY_CRM_CLIENT_SECRET: ${INSIGHTLY_CRM_CLIENT_SECRET} KEAP_CRM_CLIENT_ID: ${KEAP_CRM_CLIENT_ID} KEAP_CRM_CLIENT_SECRET: ${KEAP_CRM_CLIENT_SECRET} - SUGARCRM_CRM_CLIENT_ID: ${SUGARCRM_CRM_CLIENT_ID} - SUGARCRM_CRM_CLIENT_SECRET: ${SUGARCRM_CRM_CLIENT_SECRET} - SUGARCRM_CRM_SUBDOMAIN: ${SUGARCRM_CRM_SUBDOMAIN} TEAMLEADER_CRM_CLIENT_ID: ${TEAMLEADER_CRM_CLIENT_ID} TEAMLEADER_CRM_CLIENT_SECRET: ${TEAMLEADER_CRM_CLIENT_SECRET} TEAMWORK_CRM_CLIENT_ID: ${TEAMWORK_CRM_CLIENT_ID} @@ -91,6 +84,33 @@ services: AHA_TICKETING_CLIENT_ID: ${AHA_TICKETING_CLIENT_ID} AHA_TICKETING_CLIENT_SECRET: ${AHA_TICKETING_CLIENT_SECRET} AHA_TICKETING_SUBDOMAIN: ${AHA_TICKETING_SUBDOMAIN} + WRIKE_TICKETING_CLOUD_CLIENT_ID: ${WRIKE_TICKETING_CLOUD_CLIENT_ID} + WRIKE_TICKETING_CLOUD_CLIENT_SECRET: ${WRIKE_TICKETING_CLOUD_CLIENT_SECRET} + ASANA_TICKETING_CLOUD_CLIENT_ID: ${ASANA_TICKETING_CLOUD_CLIENT_ID} + ASANA_TICKETING_CLOUD_CLIENT_SECRET: ${ASANA_TICKETING_CLOUD_CLIENT_SECRET} + PENNYLANE_ACCOUNTING_CLOUD_CLIENT_ID: ${PENNYLANE_ACCOUNTING_CLOUD_CLIENT_ID} + PENNYLANE_ACCOUNTING_CLOUD_CLIENT_SECRET: ${PENNYLANE_ACCOUNTING_CLOUD_CLIENT_SECRET} + FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_ID: ${FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_ID} + FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET} + FREEAGENT_ACCOUNTING_CLOUD_CLIENT_ID: ${FREEAGENT_ACCOUNTING_CLOUD_CLIENT_ID} + FREEAGENT_ACCOUNTING_CLOUD_CLIENT_SECRET: ${FREEAGENT_ACCOUNTING_CLOUD_CLIENT_SECRET} + SAGE_ACCOUNTING_CLOUD_CLIENT_ID: ${SAGE_ACCOUNTING_CLOUD_CLIENT_ID} + SAGE_ACCOUNTING_CLOUD_CLIENT_SECRET: ${SAGE_ACCOUNTING_CLOUD_CLIENT_SECRET} + MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_ID: ${MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_ID} + MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_SECRET: ${MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_SECRET} + QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_ID: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_ID} + QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET} + WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID} + WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET} + GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + + + restart: unless-stopped ports: diff --git a/docker-compose.source.yml b/docker-compose.source.yml index 988e49967..1d1c3741b 100644 --- a/docker-compose.source.yml +++ b/docker-compose.source.yml @@ -67,8 +67,6 @@ services: JIRA_SERVICE_MGMT_TICKETING_CLIENT_SECRET: ${JIRA_SERVICE_MGMT_TICKETING_CLIENT_SECRET} LINEAR_TICKETING_CLIENT_ID: ${LINEAR_TICKETING_CLIENT_ID} LINEAR_TICKETING_CLIENT_SECRET: ${LINEAR_TICKETING_CLIENT_SECRET} - AFFINITY_CRM_CLIENT_ID: ${AFFINITY_CRM_CLIENT_ID} - AFFINITY_CRM_CLIENT_SECRET: ${AFFINITY_CRM_CLIENT_SECRET} ACCELO_CRM_CLIENT_ID: ${ACCELO_CRM_CLIENT_ID} ACCELO_CRM_CLIENT_SECRET: ${ACCELO_CRM_CLIENT_SECRET} ACCELO_CRM_SUBDOMAIN: ${ACCELO_CRM_SUBDOMAIN} @@ -78,20 +76,43 @@ services: CLOSE_CRM_CLIENT_SECRET: ${CLOSE_CRM_CLIENT_SECRET} COPPER_CRM_CLIENT_ID: ${COPPER_CRM_CLIENT_ID} COPPER_CRM_CLIENT_SECRET: ${COPPER_CRM_CLIENT_SECRET} - INSIGHTLY_CRM_CLIENT_ID: ${INSIGHTLY_CRM_CLIENT_ID} - INSIGHTLY_CRM_CLIENT_SECRET: ${INSIGHTLY_CRM_CLIENT_SECRET} KEAP_CRM_CLIENT_ID: ${KEAP_CRM_CLIENT_ID} KEAP_CRM_CLIENT_SECRET: ${KEAP_CRM_CLIENT_SECRET} - SUGARCRM_CRM_CLIENT_ID: ${SUGARCRM_CRM_CLIENT_ID} - SUGARCRM_CRM_CLIENT_SECRET: ${SUGARCRM_CRM_CLIENT_SECRET} - SUGARCRM_CRM_SUBDOMAIN: ${SUGARCRM_CRM_SUBDOMAIN} TEAMLEADER_CRM_CLIENT_ID: ${TEAMLEADER_CRM_CLIENT_ID} TEAMLEADER_CRM_CLIENT_SECRET: ${TEAMLEADER_CRM_CLIENT_SECRET} TEAMWORK_CRM_CLIENT_ID: ${TEAMWORK_CRM_CLIENT_ID} TEAMWORK_CRM_CLIENT_SECRET: ${TEAMWORK_CRM_CLIENT_SECRET} AHA_TICKETING_CLIENT_ID: ${AHA_TICKETING_CLIENT_ID} AHA_TICKETING_CLIENT_SECRET: ${AHA_TICKETING_CLIENT_SECRET} - AHA_TICKETING_SUBDOMAIN: ${AHA_TICKETING_SUBDOMAIN} + AHA_TICKETING_SUBDOMAIN: ${AHA_TICKETING_SUBDOMAIN} + WRIKE_TICKETING_CLOUD_CLIENT_ID: ${WRIKE_TICKETING_CLOUD_CLIENT_ID} + WRIKE_TICKETING_CLOUD_CLIENT_SECRET: ${WRIKE_TICKETING_CLOUD_CLIENT_SECRET} + ASANA_TICKETING_CLOUD_CLIENT_ID: ${ASANA_TICKETING_CLOUD_CLIENT_ID} + ASANA_TICKETING_CLOUD_CLIENT_SECRET: ${ASANA_TICKETING_CLOUD_CLIENT_SECRET} + PENNYLANE_ACCOUNTING_CLOUD_CLIENT_ID: ${PENNYLANE_ACCOUNTING_CLOUD_CLIENT_ID} + PENNYLANE_ACCOUNTING_CLOUD_CLIENT_SECRET: ${PENNYLANE_ACCOUNTING_CLOUD_CLIENT_SECRET} + FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_ID: ${FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_ID} + FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET} + FREEAGENT_ACCOUNTING_CLOUD_CLIENT_ID: ${FREEAGENT_ACCOUNTING_CLOUD_CLIENT_ID} + FREEAGENT_ACCOUNTING_CLOUD_CLIENT_SECRET: ${FREEAGENT_ACCOUNTING_CLOUD_CLIENT_SECRET} + SAGE_ACCOUNTING_CLOUD_CLIENT_ID: ${SAGE_ACCOUNTING_CLOUD_CLIENT_ID} + SAGE_ACCOUNTING_CLOUD_CLIENT_SECRET: ${SAGE_ACCOUNTING_CLOUD_CLIENT_SECRET} + MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_ID: ${MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_ID} + MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_SECRET: ${MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_SECRET} + QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_ID: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_ID} + QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET} + WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID} + WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET} + GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + + + + restart: unless-stopped ports: - 3000:3000 diff --git a/docker-compose.yml b/docker-compose.yml index e02a34ca7..4e500a7fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,8 +61,6 @@ services: JIRA_SERVICE_MGMT_TICKETING_CLIENT_SECRET: ${JIRA_SERVICE_MGMT_TICKETING_CLIENT_SECRET} LINEAR_TICKETING_CLIENT_ID: ${LINEAR_TICKETING_CLIENT_ID} LINEAR_TICKETING_CLIENT_SECRET: ${LINEAR_TICKETING_CLIENT_SECRET} - AFFINITY_CRM_CLIENT_ID: ${AFFINITY_CRM_CLIENT_ID} - AFFINITY_CRM_CLIENT_SECRET: ${AFFINITY_CRM_CLIENT_SECRET} ACCELO_CRM_CLIENT_ID: ${ACCELO_CRM_CLIENT_ID} ACCELO_CRM_CLIENT_SECRET: ${ACCELO_CRM_CLIENT_SECRET} ACCELO_CRM_SUBDOMAIN: ${ACCELO_CRM_SUBDOMAIN} @@ -72,13 +70,8 @@ services: CLOSE_CRM_CLIENT_SECRET: ${CLOSE_CRM_CLIENT_SECRET} COPPER_CRM_CLIENT_ID: ${COPPER_CRM_CLIENT_ID} COPPER_CRM_CLIENT_SECRET: ${COPPER_CRM_CLIENT_SECRET} - INSIGHTLY_CRM_CLIENT_ID: ${INSIGHTLY_CRM_CLIENT_ID} - INSIGHTLY_CRM_CLIENT_SECRET: ${INSIGHTLY_CRM_CLIENT_SECRET} KEAP_CRM_CLIENT_ID: ${KEAP_CRM_CLIENT_ID} KEAP_CRM_CLIENT_SECRET: ${KEAP_CRM_CLIENT_SECRET} - SUGARCRM_CRM_CLIENT_ID: ${SUGARCRM_CRM_CLIENT_ID} - SUGARCRM_CRM_CLIENT_SECRET: ${SUGARCRM_CRM_CLIENT_SECRET} - SUGARCRM_CRM_SUBDOMAIN: ${SUGARCRM_CRM_SUBDOMAIN} TEAMLEADER_CRM_CLIENT_ID: ${TEAMLEADER_CRM_CLIENT_ID} TEAMLEADER_CRM_CLIENT_SECRET: ${TEAMLEADER_CRM_CLIENT_SECRET} TEAMWORK_CRM_CLIENT_ID: ${TEAMWORK_CRM_CLIENT_ID} @@ -86,6 +79,34 @@ services: AHA_TICKETING_CLIENT_ID: ${AHA_TICKETING_CLIENT_ID} AHA_TICKETING_CLIENT_SECRET: ${AHA_TICKETING_CLIENT_SECRET} AHA_TICKETING_SUBDOMAIN: ${AHA_TICKETING_SUBDOMAIN} + WRIKE_TICKETING_CLOUD_CLIENT_ID: ${WRIKE_TICKETING_CLOUD_CLIENT_ID} + WRIKE_TICKETING_CLOUD_CLIENT_SECRET: ${WRIKE_TICKETING_CLOUD_CLIENT_SECRET} + ASANA_TICKETING_CLOUD_CLIENT_ID: ${ASANA_TICKETING_CLOUD_CLIENT_ID} + ASANA_TICKETING_CLOUD_CLIENT_SECRET: ${ASANA_TICKETING_CLOUD_CLIENT_SECRET} + PENNYLANE_ACCOUNTING_CLOUD_CLIENT_ID: ${PENNYLANE_ACCOUNTING_CLOUD_CLIENT_ID} + PENNYLANE_ACCOUNTING_CLOUD_CLIENT_SECRET: ${PENNYLANE_ACCOUNTING_CLOUD_CLIENT_SECRET} + FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_ID: ${FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_ID} + FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${FRESHBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET} + FREEAGENT_ACCOUNTING_CLOUD_CLIENT_ID: ${FREEAGENT_ACCOUNTING_CLOUD_CLIENT_ID} + FREEAGENT_ACCOUNTING_CLOUD_CLIENT_SECRET: ${FREEAGENT_ACCOUNTING_CLOUD_CLIENT_SECRET} + SAGE_ACCOUNTING_CLOUD_CLIENT_ID: ${SAGE_ACCOUNTING_CLOUD_CLIENT_ID} + SAGE_ACCOUNTING_CLOUD_CLIENT_SECRET: ${SAGE_ACCOUNTING_CLOUD_CLIENT_SECRET} + MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_ID: ${MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_ID} + MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_SECRET: ${MONEYBIRD_ACCOUNTING_CLOUD_CLIENT_SECRET} + QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_ID: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_ID} + QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET} + WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID} + WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET} + GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID} + PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET} + + + + restart: unless-stopped ports: diff --git a/packages/api/package.json b/packages/api/package.json index 96d8eba08..1eac78130 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -111,3 +111,4 @@ "testEnvironment": "node" } } + \ No newline at end of file diff --git a/packages/api/scripts/oauthConnector.js b/packages/api/scripts/oauthConnector.js index 6e6e1b939..6eb16a701 100755 --- a/packages/api/scripts/oauthConnector.js +++ b/packages/api/scripts/oauthConnector.js @@ -56,7 +56,8 @@ import { ConnectionsStrategiesService } from '@@core/connections-strategies/conn export type ${providerUpper}OAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string; + token_type: string; }; @Injectable() @@ -126,7 +127,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: "", expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + Number(data.expires_in) * 1000, ), status: 'valid', created_at: new Date(), @@ -144,7 +145,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + Number(data.expires_in) * 1000, ), status: 'valid', created_at: new Date(), @@ -165,16 +166,15 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne async handleTokenRefresh(opts: RefreshParams) { try { - const { connectionId, refreshToken } = opts; - const formData = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: this.cryptoService.decrypt(refreshToken), - }); + const { connectionId, refreshToken, projectId } = opts; const CREDENTIALS = (await this.cService.getCredentials( projectId, this.type, )) as OAuth2AuthData; - + const formData = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }); const res = await axios.post( "", formData.toString(), @@ -198,7 +198,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + Number(data.expires_in) * 1000, ), }, }); @@ -286,7 +286,7 @@ function addProviderToDockerCompose(provider, vertical, dockerComposePath) { function handleUpdate(vertical, provider) { createServiceFile(vertical, provider); - addProviderToEnvironmentService(provider, envServiceFilePath); + //addProviderToEnvironmentService(provider, envServiceFilePath); for (const path of paths) { addProviderToDockerCompose(provider, vertical, path); } @@ -298,7 +298,7 @@ if (import.meta.url === process.argv[1]) { const vertical = args[0]; const provider = args[1]; createServiceFile(vertical, provider); - addProviderToEnvironmentService(provider, envServiceFilePath); + //addProviderToEnvironmentService(provider, envServiceFilePath); for (const path of paths) { addProviderToDockerCompose(argv.provider, path); } diff --git a/packages/api/src/@core/connections/accounting/accounting.connection.module.ts b/packages/api/src/@core/connections/accounting/accounting.connection.module.ts new file mode 100644 index 000000000..f705017a9 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/accounting.connection.module.ts @@ -0,0 +1,41 @@ +import { Module } from '@nestjs/common'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { LoggerService } from '@@core/logger/logger.service'; +import { WebhookService } from '@@core/webhook/webhook.service'; +import { WebhookModule } from '@@core/webhook/webhook.module'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ServiceRegistry } from './services/registry.service'; +import { AccountingConnectionsService } from './services/accounting.connection.service'; +import { PennylaneConnectionService } from './services/pennylane/pennylane.service'; +import { FreeagentConnectionService } from './services/freeagent/freeagent.service'; +import { FreshbooksConnectionService } from './services/freshbooks/freshbooks.service'; +import { MoneybirdConnectionService } from './services/moneybird/moneybird.service'; +import { QuickbooksConnectionService } from './services/quickbooks/quickbooks.service'; +import { SageConnectionService } from './services/sage/sage.service'; +import { WaveFinancialConnectionService } from './services/wave_financial/wave_financial.service'; + +@Module({ + imports: [WebhookModule], + providers: [ + AccountingConnectionsService, + PrismaService, + LoggerService, + WebhookService, + EnvironmentService, + EncryptionService, + ServiceRegistry, + ConnectionsStrategiesService, + //PROVIDERS SERVICES, + PennylaneConnectionService, + FreeagentConnectionService, + FreshbooksConnectionService, + MoneybirdConnectionService, + QuickbooksConnectionService, + SageConnectionService, + WaveFinancialConnectionService, + ], + exports: [AccountingConnectionsService], +}) +export class AccountingConnectionModule {} diff --git a/packages/api/src/@core/connections/accounting/services/accounting.connection.service.ts b/packages/api/src/@core/connections/accounting/services/accounting.connection.service.ts new file mode 100644 index 000000000..82fb92811 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/accounting.connection.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { WebhookService } from '@@core/webhook/webhook.service'; +import { connections as Connection } from '@prisma/client'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { v4 as uuidv4 } from 'uuid'; +import { CallbackParams, RefreshParams } from '../types'; +import { ServiceRegistry } from './registry.service'; + +@Injectable() +export class AccountingConnectionsService { + constructor( + private serviceRegistry: ServiceRegistry, + private webhook: WebhookService, + private logger: LoggerService, + private prisma: PrismaService, + ) { + this.logger.setContext(AccountingConnectionsService.name); + } + //STEP 1:[FRONTEND STEP] + //create a frontend SDK snippet in which an authorization embedded link is set up so when users click + // on it to grant access => they grant US the access and then when confirmed + /*const authUrl = + 'https://app.hubspot.com/oauth/authorize' + + `?client_id=${encodeURIComponent(CLIENT_ID)}` + + `&scope=${encodeURIComponent(SCOPES)}` + + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;*/ //oauth/callback + + // oauth server calls this redirect callback + // WE WOULD HAVE CREATED A DEV ACCOUNT IN THE 5 CRMs (Panora dev account) + // we catch the tmp token and swap it against oauth2 server for access/refresh tokens + // to perform actions on his behalf + // this call pass 1. integrationID 2. CustomerId 3. Panora Api Key + async handleAccountingCallBack( + projectId: string, + linkedUserId: string, + providerName: string, + code: string, + ) { + try { + const serviceName = providerName.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + + if (!service) { + throw new NotFoundError(`Unknown provider, found ${providerName}`); + } + const callbackOpts: CallbackParams = { + linkedUserId: linkedUserId, + projectId: projectId, + code: code, + }; + const data: Connection = await service.handleCallback(callbackOpts); + + const event = await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'connection.created', + method: 'GET', + url: '/oauth/callback', + provider: providerName.toLowerCase(), + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + }, + }); + //directly send the webhook + await this.webhook.handlePriorityWebhook( + data, + 'connection.created', + projectId, + event.id_event, + ); + } catch (error) { + handleServiceError(error, this.logger); + } + } + + async handleAccountingTokensRefresh( + connectionId: string, + providerName: string, + refresh_token: string, + id_project: string, + account_url?: string, + ) { + try { + const serviceName = providerName.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + if (!service) { + throw new NotFoundError(`Unknown provider, found ${providerName}`); + } + const refreshOpts: RefreshParams = { + connectionId: connectionId, + refreshToken: refresh_token, + account_url: account_url, + projectId: id_project, + }; + const data = await service.handleTokenRefresh(refreshOpts); + } catch (error) { + handleServiceError(error, this.logger); + } + } +} diff --git a/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts new file mode 100644 index 000000000..fc411d48a --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts @@ -0,0 +1,180 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { + CallbackParams, + RefreshParams, + IAccountingConnectionService, +} from '../../types'; +import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type FreeagentOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: string | number; + refresh_token_expires_in: number; + token_type: string; +}; + +@Injectable() +export class FreeagentConnectionService + implements IAccountingConnectionService +{ + private readonly type: string; + + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private cService: ConnectionsStrategiesService, + ) { + this.logger.setContext(FreeagentConnectionService.name); + this.registry.registerService('freeagent', this); + this.type = providerToType('freeagent', 'accounting', AuthStrategy.oauth2); + } + + async handleCallback(opts: CallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'freeagent', + vertical: 'accounting', + }, + }); + + //reconstruct the redirect URI that was passed in the githubend it must be the same + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + redirect_uri: REDIRECT_URI, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://api.freeagent.com/v2/token_endpoint', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic ${Buffer.from( + `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, + ).toString('base64')}`, + }, + }, + ); + const data: FreeagentOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : freeagent ticketing ' + JSON.stringify(data), + ); + + let db_res; + const connection_token = uuidv4(); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + account_url: + providersConfig['accounting']['freshagent'].urls.apiUrl, + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'freeagent', + vertical: 'accounting', + token_type: 'oauth', + account_url: + providersConfig['accounting']['freshagent'].urls.apiUrl, + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { id_linked_user: linkedUserId }, + }, + }, + }); + } + return db_res; + } catch (error) { + handleServiceError(error, this.logger, 'freeagent', Action.oauthCallback); + } + } + + async handleTokenRefresh(opts: RefreshParams) { + try { + const { connectionId, refreshToken, projectId } = opts; + const formData = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }); + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const res = await axios.post( + 'https://api.freeagent.com/v2/token_endpoint', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic ${Buffer.from( + `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, + ).toString('base64')}`, + }, + }, + ); + const data: FreeagentOAuthResponse = res.data; + await this.prisma.connections.update({ + where: { + id_connection: connectionId, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : freeagent '); + } catch (error) { + handleServiceError(error, this.logger, 'freeagent', Action.oauthRefresh); + } + } +} diff --git a/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts b/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts new file mode 100644 index 000000000..23f0f5e22 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts @@ -0,0 +1,183 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { + CallbackParams, + RefreshParams, + IAccountingConnectionService, +} from '../../types'; +import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type FreshbooksOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: string; + created_at: number; + token_type: string; +}; + +@Injectable() +export class FreshbooksConnectionService + implements IAccountingConnectionService +{ + private readonly type: string; + + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private cService: ConnectionsStrategiesService, + ) { + this.logger.setContext(FreshbooksConnectionService.name); + this.registry.registerService('freshbooks', this); + this.type = providerToType('freshbooks', 'accounting', AuthStrategy.oauth2); + } + + async handleCallback(opts: CallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'freshbooks', + vertical: 'accounting', + }, + }); + + //reconstruct the redirect URI that was passed in the githubend it must be the same + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + redirect_uri: REDIRECT_URI, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://api.freshbooks.com/auth/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: FreshbooksOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : freshbooks ticketing ' + JSON.stringify(data), + ); + + let db_res; + const connection_token = uuidv4(); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + account_url: providersConfig['accounting']['freshbooks'].urls.apiUrl, + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'freshbooks', + vertical: 'accounting', + token_type: 'oauth', + account_url: providersConfig['accounting']['freshbooks'].urls.apiUrl, + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { id_linked_user: linkedUserId }, + }, + }, + }); + } + return db_res; + } catch (error) { + handleServiceError( + error, + this.logger, + 'freshbooks', + Action.oauthCallback, + ); + } + } + + async handleTokenRefresh(opts: RefreshParams) { + try { + const { connectionId, refreshToken, projectId } = opts; + + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }); + + const res = await axios.post( + 'https://api.freshbooks.com/auth/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: FreshbooksOAuthResponse = res.data; + await this.prisma.connections.update({ + where: { + id_connection: connectionId, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : freshbooks '); + } catch (error) { + handleServiceError(error, this.logger, 'freshbooks', Action.oauthRefresh); + } + } +} diff --git a/packages/api/src/@core/connections/crm/services/insightly/insightly.service.ts b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts similarity index 68% rename from packages/api/src/@core/connections/crm/services/insightly/insightly.service.ts rename to packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts index 223b95cbc..4d2085d3a 100644 --- a/packages/api/src/@core/connections/crm/services/insightly/insightly.service.ts +++ b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts @@ -9,21 +9,26 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { CallbackParams, RefreshParams, - ICrmConnectionService, + IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; -import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; -export type InsightlyOAuthResponse = { +export type MoneybirdOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string; + token_type: string; + scope: string; + created_at: number; }; @Injectable() -export class InsightlyConnectionService implements ICrmConnectionService { +export class MoneybirdConnectionService + implements IAccountingConnectionService +{ private readonly type: string; constructor( @@ -34,9 +39,9 @@ export class InsightlyConnectionService implements ICrmConnectionService { private registry: ServiceRegistry, private cService: ConnectionsStrategiesService, ) { - this.logger.setContext(InsightlyConnectionService.name); - this.registry.registerService('insightly', this); - this.type = providerToType('insightly', 'crm', AuthStrategy.oauth2); + this.logger.setContext(MoneybirdConnectionService.name); + this.registry.registerService('moneybird', this); + this.type = providerToType('moneybird', 'accounting', AuthStrategy.oauth2); } async handleCallback(opts: CallbackParams) { @@ -45,8 +50,8 @@ export class InsightlyConnectionService implements ICrmConnectionService { const isNotUnique = await this.prisma.connections.findFirst({ where: { id_linked_user: linkedUserId, - provider_slug: `insightly`, - vertical: 'crm', + provider_slug: 'moneybird', + vertical: 'accounting', }, }); @@ -64,15 +69,18 @@ export class InsightlyConnectionService implements ICrmConnectionService { code: code, grant_type: 'authorization_code', }); - //const subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + const res = await axios.post( + ' https://moneybird.com/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, }, - }); - const data: InsightlyOAuthResponse = res.data; + ); + const data: MoneybirdOAuthResponse = res.data; this.logger.log( - 'OAuth credentials : insightly ticketing ' + JSON.stringify(data), + 'OAuth credentials : moneybird ticketing ' + JSON.stringify(data), ); let db_res; @@ -86,9 +94,9 @@ export class InsightlyConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['accounting']['moneybird'].urls.apiUrl, expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + Number(data.expires_in) * 1000, ), status: 'valid', created_at: new Date(), @@ -99,14 +107,14 @@ export class InsightlyConnectionService implements ICrmConnectionService { data: { id_connection: uuidv4(), connection_token: connection_token, - provider_slug: 'insightly', - vertical: 'crm', + provider_slug: 'moneybird', + vertical: 'accounting', token_type: 'oauth', - account_url: '', + account_url: providersConfig['accounting']['moneybird'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + Number(data.expires_in) * 1000, ), status: 'valid', created_at: new Date(), @@ -121,30 +129,33 @@ export class InsightlyConnectionService implements ICrmConnectionService { } return db_res; } catch (error) { - handleServiceError(error, this.logger, 'insightly', Action.oauthCallback); + handleServiceError(error, this.logger, 'moneybird', Action.oauthCallback); } } async handleTokenRefresh(opts: RefreshParams) { try { const { connectionId, refreshToken, projectId } = opts; - const formData = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: this.cryptoService.decrypt(refreshToken), - }); const CREDENTIALS = (await this.cService.getCredentials( projectId, this.type, )) as OAuth2AuthData; - - const subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - Authorization: `Basic JHt0aGlzLmVudi5nZXRgSW5zaWdodGx5U2VjcmV0YCgpLkNMSUVOVF9JRH06JHsKICAgICAgICAgICAgICAgIHRoaXMuZW52LmdldGBJbnNpZ2h0bHlTZWNyZXRgKCkuQ0xJRU5UX1NFQ1JFVAogICAgICAgICAgICAgIH0=`, - }, + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_SECRET: CREDENTIALS.CLIENT_SECRET, + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), }); - const data: InsightlyOAuthResponse = res.data; + const res = await axios.post( + 'https://moneybird.com/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: MoneybirdOAuthResponse = res.data; await this.prisma.connections.update({ where: { id_connection: connectionId, @@ -153,13 +164,13 @@ export class InsightlyConnectionService implements ICrmConnectionService { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + Number(data.expires_in) * 1000, ), }, }); - this.logger.log('OAuth credentials updated : insightly '); + this.logger.log('OAuth credentials updated : moneybird '); } catch (error) { - handleServiceError(error, this.logger, 'insightly', Action.oauthRefresh); + handleServiceError(error, this.logger, 'moneybird', Action.oauthRefresh); } } } diff --git a/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts b/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts new file mode 100644 index 000000000..af1827251 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts @@ -0,0 +1,177 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { + CallbackParams, + RefreshParams, + IAccountingConnectionService, +} from '../../types'; +import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type PennylaneOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: number; + token_type: string; +}; + +@Injectable() +export class PennylaneConnectionService + implements IAccountingConnectionService +{ + private readonly type: string; + + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private cService: ConnectionsStrategiesService, + ) { + this.logger.setContext(PennylaneConnectionService.name); + this.registry.registerService('pennylane', this); + this.type = providerToType('pennylane', 'accounting', AuthStrategy.oauth2); + } + + async handleCallback(opts: CallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'pennylane', + vertical: 'accounting', + }, + }); + + //reconstruct the redirect URI that was passed in the githubend it must be the same + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + redirect_uri: REDIRECT_URI, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://app.pennylane.com/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: PennylaneOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : pennylane ticketing ' + JSON.stringify(data), + ); + + let db_res; + const connection_token = uuidv4(); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + account_url: providersConfig['accounting']['pennylane'].urls.apiUrl, + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'pennylane', + vertical: 'accounting', + token_type: 'oauth', + account_url: providersConfig['accounting']['pennylane'].urls.apiUrl, + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { id_linked_user: linkedUserId }, + }, + }, + }); + } + return db_res; + } catch (error) { + handleServiceError(error, this.logger, 'pennylane', Action.oauthCallback); + } + } + + async handleTokenRefresh(opts: RefreshParams) { + try { + const { connectionId, refreshToken, projectId } = opts; + + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }); + const res = await axios.post( + 'https://app.pennylane.com/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, + }, + }, + ); + const data: PennylaneOAuthResponse = res.data; + await this.prisma.connections.update({ + where: { + id_connection: connectionId, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : pennylane '); + } catch (error) { + handleServiceError(error, this.logger, 'pennylane', Action.oauthRefresh); + } + } +} diff --git a/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts b/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts new file mode 100644 index 000000000..76b24e868 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts @@ -0,0 +1,179 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { + CallbackParams, + RefreshParams, + IAccountingConnectionService, +} from '../../types'; +import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type QuickbooksOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: string; + token_type: string; + x_refresh_token_expires_in: number; +}; + +@Injectable() +export class QuickbooksConnectionService + implements IAccountingConnectionService +{ + private readonly type: string; + + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private cService: ConnectionsStrategiesService, + ) { + this.logger.setContext(QuickbooksConnectionService.name); + this.registry.registerService('quickbooks', this); + this.type = providerToType('quickbooks', 'accounting', AuthStrategy.oauth2); + } + + async handleCallback(opts: CallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'quickbooks', + vertical: 'accounting', + }, + }); + + //reconstruct the redirect URI that was passed in the githubend it must be the same + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + redirect_uri: REDIRECT_URI, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic ${Buffer.from( + `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, + ).toString('base64')}`, + }, + }, + ); + const data: QuickbooksOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : quickbooks ticketing ' + JSON.stringify(data), + ); + + let db_res; + const connection_token = uuidv4(); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + account_url: providersConfig['accounting']['quickbooks'].urls.apiUrl, + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'quickbooks', + vertical: 'accounting', + token_type: 'oauth', + account_url: providersConfig['accounting']['quickbooks'].urls.apiUrl, + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { id_linked_user: linkedUserId }, + }, + }, + }); + } + return db_res; + } catch (error) { + handleServiceError( + error, + this.logger, + 'quickbooks', + Action.oauthCallback, + ); + } + } + + async handleTokenRefresh(opts: RefreshParams) { + try { + const { connectionId, refreshToken, projectId } = opts; + const formData = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }); + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const res = await axios.post('', formData.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic ${Buffer.from( + `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, + ).toString('base64')}`, + }, + }); + const data: QuickbooksOAuthResponse = res.data; + await this.prisma.connections.update({ + where: { + id_connection: connectionId, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : quickbooks '); + } catch (error) { + handleServiceError(error, this.logger, 'quickbooks', Action.oauthRefresh); + } + } +} diff --git a/packages/api/src/@core/connections/accounting/services/registry.service.ts b/packages/api/src/@core/connections/accounting/services/registry.service.ts new file mode 100644 index 000000000..3125b3a83 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/registry.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { IAccountingConnectionService } from '../types'; + +@Injectable() +export class ServiceRegistry { + private serviceMap: Map; + + constructor() { + this.serviceMap = new Map(); + } + + registerService(serviceKey: string, service: IAccountingConnectionService) { + this.serviceMap.set(serviceKey, service); + } + + getService(integrationId: string): IAccountingConnectionService { + const service = this.serviceMap.get(integrationId); + if (!service) { + throw new Error(`Service not found for integration ID: ${integrationId}`); + } + return service; + } +} diff --git a/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts b/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts similarity index 77% rename from packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts rename to packages/api/src/@core/connections/accounting/services/sage/sage.service.ts index 9cf4ce982..f29cb1b03 100644 --- a/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts +++ b/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts @@ -9,25 +9,25 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { CallbackParams, RefreshParams, - ICrmConnectionService, + IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; -import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; -export type SugarcrmOAuthResponse = { +export type SageOAuthResponse = { access_token: string; + refresh_token: string; + refresh_token_expires_in: number; expires_in: number; - token_type: string; scope: string; - refresh_token: string; - refresh_expires_in: number; - download_token: string; + token_type: string; + requested_by_id: string; }; @Injectable() -export class SugarcrmConnectionService implements ICrmConnectionService { +export class SageConnectionService implements IAccountingConnectionService { private readonly type: string; constructor( @@ -38,9 +38,9 @@ export class SugarcrmConnectionService implements ICrmConnectionService { private registry: ServiceRegistry, private cService: ConnectionsStrategiesService, ) { - this.logger.setContext(SugarcrmConnectionService.name); - this.registry.registerService('sugarcrm', this); - this.type = providerToType('sugarcrm', 'crm', AuthStrategy.oauth2); + this.logger.setContext(SageConnectionService.name); + this.registry.registerService('sage', this); + this.type = providerToType('sage', 'accounting', AuthStrategy.oauth2); } async handleCallback(opts: CallbackParams) { @@ -49,8 +49,8 @@ export class SugarcrmConnectionService implements ICrmConnectionService { const isNotUnique = await this.prisma.connections.findFirst({ where: { id_linked_user: linkedUserId, - provider_slug: `sugarcrm`, - vertical: 'crm', + provider_slug: 'sage', + vertical: 'accounting', }, }); @@ -64,14 +64,12 @@ export class SugarcrmConnectionService implements ICrmConnectionService { const formData = new URLSearchParams({ client_id: CREDENTIALS.CLIENT_ID, client_secret: CREDENTIALS.CLIENT_SECRET, - grant_type: 'password', - username: '', - password: '', - platform: 'custom', + redirect_uri: REDIRECT_URI, + code: code, + grant_type: 'authorization_code', }); - //const subdomain = 'panora'; const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/rest/v11/oauth2/token`, + 'https://oauth.accounting.sage.com/token', formData.toString(), { headers: { @@ -79,9 +77,9 @@ export class SugarcrmConnectionService implements ICrmConnectionService { }, }, ); - const data: SugarcrmOAuthResponse = res.data; + const data: SageOAuthResponse = res.data; this.logger.log( - 'OAuth credentials : sugarcrm ticketing ' + JSON.stringify(data), + 'OAuth credentials : sage ticketing ' + JSON.stringify(data), ); let db_res; @@ -95,7 +93,7 @@ export class SugarcrmConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['accounting']['sage'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -108,10 +106,10 @@ export class SugarcrmConnectionService implements ICrmConnectionService { data: { id_connection: uuidv4(), connection_token: connection_token, - provider_slug: 'sugarcrm', - vertical: 'crm', + provider_slug: 'sage', + vertical: 'accounting', token_type: 'oauth', - account_url: '', + account_url: providersConfig['accounting']['sage'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( @@ -130,7 +128,7 @@ export class SugarcrmConnectionService implements ICrmConnectionService { } return db_res; } catch (error) { - handleServiceError(error, this.logger, 'sugarcrm', Action.oauthCallback); + handleServiceError(error, this.logger, 'sage', Action.oauthCallback); } } @@ -146,12 +144,11 @@ export class SugarcrmConnectionService implements ICrmConnectionService { client_id: CREDENTIALS.CLIENT_ID, client_secret: CREDENTIALS.CLIENT_SECRET, grant_type: 'refresh_token', - platform: 'custom', refresh_token: this.cryptoService.decrypt(refreshToken), }); - const subdomain = 'panora'; + const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/rest/v11/oauth2/token`, + 'https://oauth.accounting.sage.com/toke', formData.toString(), { headers: { @@ -159,7 +156,7 @@ export class SugarcrmConnectionService implements ICrmConnectionService { }, }, ); - const data: SugarcrmOAuthResponse = res.data; + const data: SageOAuthResponse = res.data; await this.prisma.connections.update({ where: { id_connection: connectionId, @@ -172,9 +169,9 @@ export class SugarcrmConnectionService implements ICrmConnectionService { ), }, }); - this.logger.log('OAuth credentials updated : sugarcrm '); + this.logger.log('OAuth credentials updated : sage '); } catch (error) { - handleServiceError(error, this.logger, 'sugarcrm', Action.oauthRefresh); + handleServiceError(error, this.logger, 'sage', Action.oauthRefresh); } } } diff --git a/packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts b/packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts new file mode 100644 index 000000000..80194129b --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts @@ -0,0 +1,196 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { + CallbackParams, + RefreshParams, + IAccountingConnectionService, +} from '../../types'; +import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type WaveFinancialOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: number | string; + token_type: string; + scope: string; + userId: string; + businessId: string; +}; + +@Injectable() +export class WaveFinancialConnectionService + implements IAccountingConnectionService +{ + private readonly type: string; + + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private cService: ConnectionsStrategiesService, + ) { + this.logger.setContext(WaveFinancialConnectionService.name); + this.registry.registerService('wave_financial', this); + this.type = providerToType( + 'wave_financial', + 'accounting', + AuthStrategy.oauth2, + ); + } + + async handleCallback(opts: CallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'wave_financial', + vertical: 'accounting', + }, + }); + + //reconstruct the redirect URI that was passed in the githubend it must be the same + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + redirect_uri: REDIRECT_URI, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://api.waveapps.com/oauth2/token/', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: WaveFinancialOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : wave_financial ticketing ' + JSON.stringify(data), + ); + + let db_res; + const connection_token = uuidv4(); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + account_url: providersConfig['accounting']['wave_financial'].urls.apiUrl, + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'wave_financial', + vertical: 'accounting', + token_type: 'oauth', + account_url: providersConfig['accounting']['wave_financial'].urls.apiUrl, + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { id_linked_user: linkedUserId }, + }, + }, + }); + } + return db_res; + } catch (error) { + handleServiceError( + error, + this.logger, + 'wave_financial', + Action.oauthCallback, + ); + } + } + + async handleTokenRefresh(opts: RefreshParams) { + try { + const { connectionId, refreshToken, projectId } = opts; + + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + + const formData = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + redirect_uri: REDIRECT_URI, + }); + const res = await axios.post( + 'https://api.waveapps.com/oauth2/token/', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: WaveFinancialOAuthResponse = res.data; + await this.prisma.connections.update({ + where: { + id_connection: connectionId, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : wave_financial '); + } catch (error) { + handleServiceError( + error, + this.logger, + 'wave_financial', + Action.oauthRefresh, + ); + } + } +} diff --git a/packages/api/src/@core/connections/accounting/types/index.ts b/packages/api/src/@core/connections/accounting/types/index.ts new file mode 100644 index 000000000..d16f0330e --- /dev/null +++ b/packages/api/src/@core/connections/accounting/types/index.ts @@ -0,0 +1,19 @@ +import { connections as Connection } from '@prisma/client'; + +export type CallbackParams = { + linkedUserId: string; + projectId: string; + code: string; +}; + +export type RefreshParams = { + connectionId: string; + refreshToken: string; + account_url?: string; + projectId: string; +}; + +export interface IAccountingConnectionService { + handleCallback(opts: CallbackParams): Promise; + handleTokenRefresh(opts: RefreshParams): Promise; +} diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index d271abcbd..5fbacab2a 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -7,6 +7,8 @@ import { PrismaService } from '@@core/prisma/prisma.service'; import { ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; import { TicketingConnectionsService } from './ticketing/services/ticketing.connection.service'; import { ProviderVertical } from '@panora/shared'; +import { AccountingConnectionsService } from './accounting/services/accounting.connection.service'; +import { MarketingAutomationConnectionsService } from './marketing_automation/services/marketing_automation.connection.service'; export type StateDataType = { projectId: string; @@ -22,6 +24,8 @@ export class ConnectionsController { constructor( private readonly crmConnectionsService: CrmConnectionsService, private readonly ticketingConnectionsService: TicketingConnectionsService, + private readonly accountingConnectionsService: AccountingConnectionsService, + private readonly marketingAutomationConnectionsService: MarketingAutomationConnectionsService, private logger: LoggerService, private prisma: PrismaService, ) { @@ -70,12 +74,24 @@ export class ConnectionsController { case ProviderVertical.ATS: break; case ProviderVertical.Accounting: + this.accountingConnectionsService.handleAccountingCallBack( + projectId, + linkedUserId, + providerName, + code, + ); break; case ProviderVertical.FileStorage: break; case ProviderVertical.HRIS: break; case ProviderVertical.MarketingAutomation: + this.marketingAutomationConnectionsService.handleMarketingAutomationCallBack( + projectId, + linkedUserId, + providerName, + code, + ); break; case ProviderVertical.Ticketing: this.ticketingConnectionsService.handleTicketingCallBack( diff --git a/packages/api/src/@core/connections/connections.module.ts b/packages/api/src/@core/connections/connections.module.ts index 308c8abc8..3372594d3 100644 --- a/packages/api/src/@core/connections/connections.module.ts +++ b/packages/api/src/@core/connections/connections.module.ts @@ -4,11 +4,23 @@ import { ConnectionsController } from './connections.controller'; import { LoggerService } from '@@core/logger/logger.service'; import { PrismaService } from '@@core/prisma/prisma.service'; import { TicketingConnectionModule } from './ticketing/ticketing.connection.module'; +import { AccountingConnectionModule } from './accounting/accounting.connection.module'; +import { MarketingAutomationConnectionsModule } from './marketing_automation/marketing_automation.connection.module'; @Module({ controllers: [ConnectionsController], - imports: [CrmConnectionModule, TicketingConnectionModule], + imports: [ + CrmConnectionModule, + TicketingConnectionModule, + AccountingConnectionModule, + MarketingAutomationConnectionsModule, + ], providers: [LoggerService, PrismaService], - exports: [CrmConnectionModule, TicketingConnectionModule], + exports: [ + CrmConnectionModule, + TicketingConnectionModule, + AccountingConnectionModule, + MarketingAutomationConnectionsModule, + ], }) export class ConnectionsModule {} diff --git a/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts b/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts index 956ec833f..d4c6a33ae 100644 --- a/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts +++ b/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts @@ -12,7 +12,11 @@ import { EnvironmentService } from '@@core/environment/environment.service'; import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; import { LoggerService } from '@@core/logger/logger.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -68,7 +72,6 @@ export class AcceloConnectionService implements ICrmConnectionService { redirect_uri: REDIRECT_URI, code: code, }); - //const subdomain = 'panora'; //TODO: if custom oauth then get the actual domain from customer const res = await axios.post( `${CREDENTIALS.SUBDOMAIN}/oauth2/v0/token`, formData.toString(), @@ -87,6 +90,9 @@ export class AcceloConnectionService implements ICrmConnectionService { // Saving the token of customer inside db let db_res; const connection_token = uuidv4(); + //get the right BASE URL API + const BASE_API_URL = + CREDENTIALS.SUBDOMAIN + providersConfig['crm']['accelo'].urls.apiUrl; if (isNotUnique) { // Update existing connection @@ -96,7 +102,7 @@ export class AcceloConnectionService implements ICrmConnectionService { }, data: { access_token: this.cryptoService.encrypt(data.access_token), - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, expiration_timestamp: new Date( new Date().getTime() + data.expires_in * 1000, ), @@ -113,7 +119,7 @@ export class AcceloConnectionService implements ICrmConnectionService { provider_slug: 'accelo', vertical: 'crm', token_type: 'oauth', - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, access_token: this.cryptoService.encrypt(data.access_token), expiration_timestamp: new Date( new Date().getTime() + data.expires_in * 1000, @@ -151,7 +157,7 @@ export class AcceloConnectionService implements ICrmConnectionService { )) as OAuth2AuthData; const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/oauth2/v0/token`, + `${CREDENTIALS.SUBDOMAIN}/oauth2/v0/token`, formData.toString(), { headers: { 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 index b87665972..565d209e6 100644 --- a/packages/api/src/@core/connections/crm/services/attio/attio.service.ts +++ b/packages/api/src/@core/connections/crm/services/attio/attio.service.ts @@ -12,7 +12,11 @@ import { EnvironmentService } from '@@core/environment/environment.service'; import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; import { LoggerService } from '@@core/logger/logger.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -104,6 +108,7 @@ export class AttioConnectionService implements ICrmConnectionService { provider_slug: 'attio', vertical: 'crm', token_type: 'oauth', + account_url: providersConfig['crm']['attio'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), status: 'valid', created_at: new Date(), diff --git a/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts b/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts index f1a0973cd..d49062919 100644 --- a/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts +++ b/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts @@ -12,7 +12,11 @@ import { ICrmConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -90,7 +94,7 @@ export class CapsuleConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['crm']['capsule'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -106,7 +110,7 @@ export class CapsuleConnectionService implements ICrmConnectionService { provider_slug: 'capsule', vertical: 'crm', token_type: 'oauth', - account_url: '', + account_url: providersConfig['crm']['capsule'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/close/close.service.ts b/packages/api/src/@core/connections/crm/services/close/close.service.ts index 173266254..57da7967c 100644 --- a/packages/api/src/@core/connections/crm/services/close/close.service.ts +++ b/packages/api/src/@core/connections/crm/services/close/close.service.ts @@ -12,7 +12,11 @@ import { ICrmConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -94,7 +98,7 @@ export class CloseConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['crm']['close'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -110,7 +114,7 @@ export class CloseConnectionService implements ICrmConnectionService { provider_slug: 'close', vertical: 'crm', token_type: 'oauth', - account_url: '', + account_url: providersConfig['crm']['close'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/copper/copper.service.ts b/packages/api/src/@core/connections/crm/services/copper/copper.service.ts index 260eabca1..42dfa4202 100644 --- a/packages/api/src/@core/connections/crm/services/copper/copper.service.ts +++ b/packages/api/src/@core/connections/crm/services/copper/copper.service.ts @@ -12,7 +12,11 @@ import { ICrmConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -100,7 +104,7 @@ export class CopperConnectionService implements ICrmConnectionService { provider_slug: 'copper', vertical: 'crm', token_type: 'oauth', - account_url: '', + account_url: providersConfig['crm']['copper'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), status: 'valid', created_at: new Date(), diff --git a/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts b/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts index 657183b37..576a8c519 100644 --- a/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts +++ b/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts @@ -12,7 +12,11 @@ import { v4 as uuidv4 } from 'uuid'; import { EnvironmentService } from '@@core/environment/environment.service'; import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -107,6 +111,7 @@ export class HubspotConnectionService implements ICrmConnectionService { provider_slug: 'hubspot', vertical: 'crm', token_type: 'oauth', + account_url: providersConfig['crm']['hubspot'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/keap/keap.service.ts b/packages/api/src/@core/connections/crm/services/keap/keap.service.ts index 4c23b26e8..66772aa8a 100644 --- a/packages/api/src/@core/connections/crm/services/keap/keap.service.ts +++ b/packages/api/src/@core/connections/crm/services/keap/keap.service.ts @@ -12,7 +12,11 @@ import { ICrmConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -104,6 +108,7 @@ export class KeapConnectionService implements ICrmConnectionService { provider_slug: 'keap', vertical: 'crm', token_type: 'oauth', + account_url: providersConfig['crm']['keap'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( 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 cf50636dc..e8eb337ec 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 @@ -12,7 +12,11 @@ import { v4 as uuidv4 } from 'uuid'; import { EnvironmentService } from '@@core/environment/environment.service'; import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -104,6 +108,7 @@ export class PipedriveConnectionService implements ICrmConnectionService { provider_slug: 'pipedrive', vertical: 'crm', token_type: 'oauth', + account_url: providersConfig['crm']['pipedrive'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts b/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts index 74c5f20f7..48f09a133 100644 --- a/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts +++ b/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts @@ -12,7 +12,11 @@ import { ICrmConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -90,7 +94,7 @@ export class TeamleaderConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['crm']['teamleader'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), diff --git a/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts b/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts index ad099bf44..efab60ab2 100644 --- a/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts +++ b/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts @@ -12,7 +12,11 @@ import { ICrmConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -77,6 +81,9 @@ export class TeamworkConnectionService implements ICrmConnectionService { let db_res; const connection_token = uuidv4(); + //get the right BASE URL API + const BASE_API_URL = + CREDENTIALS.SUBDOMAIN + providersConfig['crm']['teamwork'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ @@ -97,6 +104,7 @@ export class TeamworkConnectionService implements ICrmConnectionService { provider_slug: 'teamwork', vertical: 'crm', token_type: 'oauth', + account_url: BASE_API_URL, access_token: this.cryptoService.encrypt(data.access_token), status: 'valid', created_at: new Date(), 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 378884e87..a57205c19 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 @@ -12,7 +12,11 @@ import { v4 as uuidv4 } from 'uuid'; import { EnvironmentService } from '@@core/environment/environment.service'; import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -108,6 +112,7 @@ export class ZendeskConnectionService implements ICrmConnectionService { provider_slug: 'zendesk', vertical: 'crm', token_type: 'oauth', + account_url: providersConfig['crm']['zendesk'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: data.refresh_token ? this.cryptoService.encrypt(data.refresh_token) diff --git a/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts b/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts index c40508032..57eb20088 100644 --- a/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts +++ b/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts @@ -12,16 +12,41 @@ import { v4 as uuidv4 } from 'uuid'; import { EnvironmentService } from '@@core/environment/environment.service'; import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { + OAuth2AuthData, + providersConfig, + providerToType, +} from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; -const ZOHOLocations = { - us: 'https://accounts.zoho.com', - eu: 'https://accounts.zoho.eu', - in: 'https://accounts.zoho.in', - au: 'https://accounts.zoho.com.au', - jp: 'https://accounts.zoho.jp', +type ZohoUrlType = { + [key: string]: { + authBase: string; + apiBase: string; + }; +}; +export const ZOHOLocations: ZohoUrlType = { + us: { + authBase: 'https://accounts.zoho.com', + apiBase: 'https://www.zohoapis.com', + }, + eu: { + authBase: 'https://accounts.zoho.eu', + apiBase: 'https://www.zohoapis.eu', + }, + in: { + authBase: 'https://accounts.zoho.in', + apiBase: 'https://www.zohoapis.in', + }, + au: { + authBase: 'https://accounts.zoho.com.au', + apiBase: 'https://www.zohoapis.com.au', + }, + jp: { + authBase: 'https://accounts.zoho.jp', + apiBase: 'https://www.zohoapis.jp', + }, }; export interface ZohoOAuthResponse { @@ -32,6 +57,7 @@ export interface ZohoOAuthResponse { expires_in: number; } +//TODO: manage domains @Injectable() export class ZohoConnectionService implements ICrmConnectionService { private readonly type: string; @@ -77,9 +103,9 @@ export class ZohoConnectionService implements ICrmConnectionService { code: code, }); //no refresh token - const domain = ZOHOLocations[location]; + const authDomain = ZOHOLocations[location].authBase; const res = await axios.post( - `${domain}/oauth/v2/token`, + `${authDomain}/oauth/v2/token`, formData.toString(), { headers: { @@ -91,6 +117,7 @@ export class ZohoConnectionService implements ICrmConnectionService { this.logger.log('OAuth credentials : zoho ' + JSON.stringify(data)); let db_res; const connection_token = uuidv4(); + const apiDomain = ZOHOLocations[location].apiBase; if (isNotUnique) { db_res = await this.prisma.connections.update({ @@ -107,7 +134,7 @@ export class ZohoConnectionService implements ICrmConnectionService { ), status: 'valid', created_at: new Date(), - account_url: domain, + account_url: apiDomain + providersConfig['crm']['zoho'].urls.apiUrl, }, }); } else { @@ -133,7 +160,7 @@ export class ZohoConnectionService implements ICrmConnectionService { linked_users: { connect: { id_linked_user: linkedUserId }, }, - account_url: domain, + account_url: apiDomain + providersConfig['crm']['zoho'].urls.apiUrl, }, }); } diff --git a/packages/api/src/@core/connections/marketing_automation/marketing_automation.connection.module.ts b/packages/api/src/@core/connections/marketing_automation/marketing_automation.connection.module.ts new file mode 100644 index 000000000..7195614a3 --- /dev/null +++ b/packages/api/src/@core/connections/marketing_automation/marketing_automation.connection.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { LoggerService } from '@@core/logger/logger.service'; +import { WebhookService } from '@@core/webhook/webhook.service'; +import { WebhookModule } from '@@core/webhook/webhook.module'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { MarketingAutomationConnectionsService } from './services/marketing_automation.connection.service'; +import { ServiceRegistry } from './services/registry.service'; + +@Module({ + imports: [WebhookModule], + providers: [ + MarketingAutomationConnectionsService, + PrismaService, + LoggerService, + WebhookService, + EnvironmentService, + EncryptionService, + ServiceRegistry, + ConnectionsStrategiesService, + //PROVIDERS SERVICES + ], + exports: [MarketingAutomationConnectionsService], +}) +export class MarketingAutomationConnectionsModule {} diff --git a/packages/api/src/@core/connections/marketing_automation/services/getresponse/getresponse.service.ts b/packages/api/src/@core/connections/marketing_automation/services/getresponse/getresponse.service.ts new file mode 100644 index 000000000..66ad6861f --- /dev/null +++ b/packages/api/src/@core/connections/marketing_automation/services/getresponse/getresponse.service.ts @@ -0,0 +1,194 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { + CallbackParams, + RefreshParams, + IMarketingAutomationConnectionService, +} from '../../types'; +import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type GetresponseOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: string; + token_type: string; + scope: any; +}; + +@Injectable() +export class GetresponseConnectionService + implements IMarketingAutomationConnectionService +{ + private readonly type: string; + + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private cService: ConnectionsStrategiesService, + ) { + this.logger.setContext(GetresponseConnectionService.name); + this.registry.registerService('getresponse', this); + this.type = providerToType( + 'getresponse', + 'marketing_automation', + AuthStrategy.oauth2, + ); + } + + async handleCallback(opts: CallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'getresponse', + vertical: 'marketing_automation', + }, + }); + + //reconstruct the redirect URI that was passed in the githubend it must be the same + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://api.getresponse.com/v3/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic ${Buffer.from( + `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, + ).toString('base64')}`, + }, + }, + ); + const data: GetresponseOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : getresponse ticketing ' + JSON.stringify(data), + ); + + let db_res; + const connection_token = uuidv4(); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + account_url: + providersConfig['marketing_automation']['getresponse'].urls + .apiUrl, + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'getresponse', + vertical: 'marketing_automation', + token_type: 'oauth', + account_url: + providersConfig['marketing_automation']['getresponse'].urls + .apiUrl, + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { id_linked_user: linkedUserId }, + }, + }, + }); + } + return db_res; + } catch (error) { + handleServiceError( + error, + this.logger, + 'getresponse', + Action.oauthCallback, + ); + } + } + + async handleTokenRefresh(opts: RefreshParams) { + try { + const { connectionId, refreshToken, projectId } = opts; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + const formData = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }); + const res = await axios.post( + 'https://api.getresponse.com/v3/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic ${Buffer.from( + `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, + ).toString('base64')}`, + }, + }, + ); + const data: GetresponseOAuthResponse = res.data; + await this.prisma.connections.update({ + where: { + id_connection: connectionId, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : getresponse '); + } catch (error) { + handleServiceError( + error, + this.logger, + 'getresponse', + Action.oauthRefresh, + ); + } + } +} diff --git a/packages/api/src/@core/connections/marketing_automation/services/mailchimp/mailchimp.service.ts b/packages/api/src/@core/connections/marketing_automation/services/mailchimp/mailchimp.service.ts new file mode 100644 index 000000000..b4d7945e0 --- /dev/null +++ b/packages/api/src/@core/connections/marketing_automation/services/mailchimp/mailchimp.service.ts @@ -0,0 +1,152 @@ +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { Action, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { + CallbackParams, + RefreshParams, + IMarketingAutomationConnectionService, +} from '../../types'; +import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type MailchimpOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: string; + token_type: string; +}; + +@Injectable() +export class MailchimpConnectionService + implements IMarketingAutomationConnectionService +{ + private readonly type: string; + + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private env: EnvironmentService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private cService: ConnectionsStrategiesService, + ) { + this.logger.setContext(MailchimpConnectionService.name); + this.registry.registerService('mailchimp', this); + this.type = providerToType( + 'mailchimp', + 'marketing_automation', + AuthStrategy.oauth2, + ); + } + + async handleCallback(opts: CallbackParams) { + try { + const { linkedUserId, projectId, code } = opts; + const isNotUnique = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'mailchimp', + vertical: 'marketing_automation', + }, + }); + + //reconstruct the redirect URI that was passed in the githubend it must be the same + const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`; + const CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; + + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + redirect_uri: REDIRECT_URI, + code: code, + grant_type: 'authorization_code', + }); + const res = await axios.post( + 'https://login.mailchimp.com/oauth2/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: MailchimpOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : mailchimp ticketing ' + JSON.stringify(data), + ); + + //get right server to make right api calls + const res_ = await axios.post( + 'https://login.mailchimp.com/oauth2/metadata', + formData.toString(), + { + headers: { + Authorization: `OAuth ${data.access_token}`, + }, + }, + ); + const server_url = res_.data; + let db_res; + const connection_token = uuidv4(); + + if (isNotUnique) { + db_res = await this.prisma.connections.update({ + where: { + id_connection: isNotUnique.id_connection, + }, + data: { + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + account_url: server_url, + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + }, + }); + } else { + db_res = await this.prisma.connections.create({ + data: { + id_connection: uuidv4(), + connection_token: connection_token, + provider_slug: 'mailchimp', + vertical: 'marketing_automation', + token_type: 'oauth', + account_url: server_url, + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_in) * 1000, + ), + status: 'valid', + created_at: new Date(), + projects: { + connect: { id_project: projectId }, + }, + linked_users: { + connect: { id_linked_user: linkedUserId }, + }, + }, + }); + } + return db_res; + } catch (error) { + handleServiceError(error, this.logger, 'mailchimp', Action.oauthCallback); + } + } + + async handleTokenRefresh(opts: RefreshParams) { + return; + } +} diff --git a/packages/api/src/@core/connections/marketing_automation/services/marketing_automation.connection.service.ts b/packages/api/src/@core/connections/marketing_automation/services/marketing_automation.connection.service.ts new file mode 100644 index 000000000..03b9cf934 --- /dev/null +++ b/packages/api/src/@core/connections/marketing_automation/services/marketing_automation.connection.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { WebhookService } from '@@core/webhook/webhook.service'; +import { connections as Connection } from '@prisma/client'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { v4 as uuidv4 } from 'uuid'; +import { CallbackParams, RefreshParams } from '../types'; +import { ServiceRegistry } from './registry.service'; + +@Injectable() +export class MarketingAutomationConnectionsService { + constructor( + private serviceRegistry: ServiceRegistry, + private webhook: WebhookService, + private logger: LoggerService, + private prisma: PrismaService, + ) { + this.logger.setContext(MarketingAutomationConnectionsService.name); + } + //STEP 1:[FRONTEND STEP] + //create a frontend SDK snippet in which an authorization embedded link is set up so when users click + // on it to grant access => they grant US the access and then when confirmed + /*const authUrl = + 'https://app.hubspot.com/oauth/authorize' + + `?client_id=${encodeURIComponent(CLIENT_ID)}` + + `&scope=${encodeURIComponent(SCOPES)}` + + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;*/ //oauth/callback + + // oauth server calls this redirect callback + // WE WOULD HAVE CREATED A DEV ACCOUNT IN THE 5 CRMs (Panora dev account) + // we catch the tmp token and swap it against oauth2 server for access/refresh tokens + // to perform actions on his behalf + // this call pass 1. integrationID 2. CustomerId 3. Panora Api Key + async handleMarketingAutomationCallBack( + projectId: string, + linkedUserId: string, + providerName: string, + code: string, + ) { + try { + const serviceName = providerName.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + + if (!service) { + throw new NotFoundError(`Unknown provider, found ${providerName}`); + } + const callbackOpts: CallbackParams = { + linkedUserId: linkedUserId, + projectId: projectId, + code: code, + }; + const data: Connection = await service.handleCallback(callbackOpts); + + const event = await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'connection.created', + method: 'GET', + url: '/oauth/callback', + provider: providerName.toLowerCase(), + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + }, + }); + //directly send the webhook + await this.webhook.handlePriorityWebhook( + data, + 'connection.created', + projectId, + event.id_event, + ); + } catch (error) { + handleServiceError(error, this.logger); + } + } + + async handleMarketingAutomationTokensRefresh( + connectionId: string, + providerName: string, + refresh_token: string, + id_project: string, + account_url?: string, + ) { + try { + const serviceName = providerName.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + if (!service) { + throw new NotFoundError(`Unknown provider, found ${providerName}`); + } + const refreshOpts: RefreshParams = { + connectionId: connectionId, + refreshToken: refresh_token, + account_url: account_url, + projectId: id_project, + }; + const data = await service.handleTokenRefresh(refreshOpts); + } catch (error) { + handleServiceError(error, this.logger); + } + } +} diff --git a/packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts b/packages/api/src/@core/connections/marketing_automation/services/podium/podium.service.ts similarity index 67% rename from packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts rename to packages/api/src/@core/connections/marketing_automation/services/podium/podium.service.ts index 226b8be58..ec948abde 100644 --- a/packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts +++ b/packages/api/src/@core/connections/marketing_automation/services/podium/podium.service.ts @@ -9,21 +9,22 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { CallbackParams, RefreshParams, - ICrmConnectionService, + IMarketingAutomationConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; +import { AuthStrategy, providersConfig } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; -import { AuthStrategy } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; -export type AffinityOAuthResponse = { +export type PodiumOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; }; @Injectable() -export class AffinityConnectionService implements ICrmConnectionService { +export class PodiumConnectionService + implements IMarketingAutomationConnectionService +{ private readonly type: string; constructor( @@ -34,19 +35,22 @@ export class AffinityConnectionService implements ICrmConnectionService { private registry: ServiceRegistry, private cService: ConnectionsStrategiesService, ) { - this.logger.setContext(AffinityConnectionService.name); - this.registry.registerService('affinity', this); - this.type = providerToType('affinity', 'crm', AuthStrategy.oauth2); + this.logger.setContext(PodiumConnectionService.name); + this.registry.registerService('podium', this); + this.type = providerToType( + 'podium', + 'marketing_automation', + AuthStrategy.oauth2, + ); } - async handleCallback(opts: CallbackParams) { try { const { linkedUserId, projectId, code } = opts; const isNotUnique = await this.prisma.connections.findFirst({ where: { id_linked_user: linkedUserId, - provider_slug: `affinity`, - vertical: 'crm', + provider_slug: 'podium', + vertical: 'marketing_automation', }, }); @@ -64,15 +68,18 @@ export class AffinityConnectionService implements ICrmConnectionService { code: code, grant_type: 'authorization_code', }); - const subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + const res = await axios.post( + 'https://api.podium.com/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, }, - }); - const data: AffinityOAuthResponse = res.data; + ); + const data: PodiumOAuthResponse = res.data; this.logger.log( - 'OAuth credentials : affinity ticketing ' + JSON.stringify(data), + 'OAuth credentials : podium ticketing ' + JSON.stringify(data), ); let db_res; @@ -86,9 +93,10 @@ export class AffinityConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: + providersConfig['marketing_automation']['podium'].urls.apiUrl, expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + 10 * 60 * 60 * 1000, ), status: 'valid', created_at: new Date(), @@ -99,14 +107,15 @@ export class AffinityConnectionService implements ICrmConnectionService { data: { id_connection: uuidv4(), connection_token: connection_token, - provider_slug: 'affinity', - vertical: 'crm', + provider_slug: 'podium', + vertical: 'marketing_automation', token_type: 'oauth', - account_url: '', + account_url: + providersConfig['marketing_automation']['pdoum'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + 10 * 60 * 60 * 1000, ), status: 'valid', created_at: new Date(), @@ -121,30 +130,33 @@ export class AffinityConnectionService implements ICrmConnectionService { } return db_res; } catch (error) { - handleServiceError(error, this.logger, 'affinity', Action.oauthCallback); + handleServiceError(error, this.logger, 'podium', Action.oauthCallback); } } async handleTokenRefresh(opts: RefreshParams) { try { const { connectionId, refreshToken, projectId } = opts; - const formData = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: this.cryptoService.decrypt(refreshToken), - }); const CREDENTIALS = (await this.cService.getCredentials( projectId, this.type, )) as OAuth2AuthData; - - //const subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - Authorization: `Basic JHt0aGlzLmVudi5nZXRgQWZmaW5pdHlTZWNyZXRgKCkuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgdGhpcy5lbnYuZ2V0YEFmZmluaXR5U2VjcmV0YCgpLkNMSUVOVF9TRUNSRVQKICAgICAgICAgICAgICB9`, - }, + const formData = new URLSearchParams({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), }); - const data: AffinityOAuthResponse = res.data; + const res = await axios.post( + 'https://api.podium.com/oauth/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: PodiumOAuthResponse = res.data; await this.prisma.connections.update({ where: { id_connection: connectionId, @@ -153,13 +165,13 @@ export class AffinityConnectionService implements ICrmConnectionService { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + 10 * 60 * 60 * 1000, ), }, }); - this.logger.log('OAuth credentials updated : affinity '); + this.logger.log('OAuth credentials updated : podium '); } catch (error) { - handleServiceError(error, this.logger, 'affinity', Action.oauthRefresh); + handleServiceError(error, this.logger, 'podium', Action.oauthRefresh); } } } diff --git a/packages/api/src/@core/connections/marketing_automation/services/registry.service.ts b/packages/api/src/@core/connections/marketing_automation/services/registry.service.ts new file mode 100644 index 000000000..b907b5f03 --- /dev/null +++ b/packages/api/src/@core/connections/marketing_automation/services/registry.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { IMarketingAutomationConnectionService } from '../types'; + +@Injectable() +export class ServiceRegistry { + private serviceMap: Map; + + constructor() { + this.serviceMap = new Map(); + } + + registerService( + serviceKey: string, + service: IMarketingAutomationConnectionService, + ) { + this.serviceMap.set(serviceKey, service); + } + + getService(integrationId: string): IMarketingAutomationConnectionService { + const service = this.serviceMap.get(integrationId); + if (!service) { + throw new Error(`Service not found for integration ID: ${integrationId}`); + } + return service; + } +} diff --git a/packages/api/src/@core/connections/marketing_automation/types/index.ts b/packages/api/src/@core/connections/marketing_automation/types/index.ts new file mode 100644 index 000000000..01185a641 --- /dev/null +++ b/packages/api/src/@core/connections/marketing_automation/types/index.ts @@ -0,0 +1,19 @@ +import { connections as Connection } from '@prisma/client'; + +export type CallbackParams = { + linkedUserId: string; + projectId: string; + code: string; +}; + +export type RefreshParams = { + connectionId: string; + refreshToken: string; + account_url?: string; + projectId: string; +}; + +export interface IMarketingAutomationConnectionService { + handleCallback(opts: CallbackParams): Promise; + handleTokenRefresh(opts: RefreshParams): Promise; +} diff --git a/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts b/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts index 71af88eeb..3a0f241c8 100644 --- a/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts @@ -12,7 +12,7 @@ import { ITicketingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy } from '@panora/shared'; +import { AuthStrategy, providersConfig } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -65,7 +65,7 @@ export class AhaConnectionService implements ITicketingConnectionService { grant_type: 'authorization_code', }); const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/oauth/token`, + `${CREDENTIALS.SUBDOMAIN}/oauth/token`, formData.toString(), { headers: { @@ -80,6 +80,9 @@ export class AhaConnectionService implements ITicketingConnectionService { let db_res; const connection_token = uuidv4(); + //get the right BASE URL API + const BASE_API_URL = + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['aha'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ @@ -88,7 +91,7 @@ export class AhaConnectionService implements ITicketingConnectionService { }, data: { access_token: this.cryptoService.encrypt(data.access_token), - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, status: 'valid', created_at: new Date(), }, @@ -101,7 +104,7 @@ export class AhaConnectionService implements ITicketingConnectionService { provider_slug: 'aha', vertical: 'ticketing', token_type: 'oauth', - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, access_token: this.cryptoService.encrypt(data.access_token), status: 'valid', created_at: new Date(), 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 d31f403d7..900ca77cb 100644 --- a/packages/api/src/@core/field-mapping/field-mapping.service.ts +++ b/packages/api/src/@core/field-mapping/field-mapping.service.ts @@ -130,8 +130,12 @@ export class FieldMappingService { }, }); const provider = providersConfig[vertical][providerId.toLowerCase()]; + //TODO: handle case where apiUrl is == "" or starts with "/" + if (!provider.urls.apiUrl || !provider.urls.customPropertiesUrl) + throw new Error('proivder urls are invalid'); + const resp = await axios.get( - provider.apiUrl + provider.customPropertiesUrl, + provider.urls.apiUrl + provider.urls.customPropertiesUrl, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/@core/passthrough/passthrough.service.ts b/packages/api/src/@core/passthrough/passthrough.service.ts index 5f5e9c81a..c489be02d 100644 --- a/packages/api/src/@core/passthrough/passthrough.service.ts +++ b/packages/api/src/@core/passthrough/passthrough.service.ts @@ -42,7 +42,8 @@ export class PassthroughService { }, }); const intId = integrationId.toLowerCase(); - const providerUrl = providersConfig[vertical.toLowerCase()][intId].apiUrl; + const providerUrl = + providersConfig[vertical.toLowerCase()][intId].urls.apiUrl; const BASE_URL = `${providerUrl}${path}`; const connection = await this.prisma.connections.findFirst({ where: { diff --git a/packages/api/src/@core/utils/types/original/original.ticketing.ts b/packages/api/src/@core/utils/types/original/original.ticketing.ts index bbcc19bfc..bb360ef86 100644 --- a/packages/api/src/@core/utils/types/original/original.ticketing.ts +++ b/packages/api/src/@core/utils/types/original/original.ticketing.ts @@ -1,7 +1,3 @@ -import { - ClickupTeamInput, - ClickupTeamOutput, -} from '@ticketing/team/services/clickup/types'; import { FrontAccountInput, FrontAccountOutput, @@ -196,7 +192,6 @@ export type OriginalTagInput = | JiraTagInput; /* team */ export type OriginalTeamInput = - | ClickupTeamInput | ZendeskTeamInput | GithubTeamInput | FrontTeamInput @@ -266,7 +261,6 @@ export type OriginalTagOutput = /* team */ export type OriginalTeamOutput = - | ClickupTeamOutput | ZendeskTeamOutput | GithubTeamOutput | FrontTeamOutput diff --git a/packages/api/src/crm/company/services/attio/index.ts b/packages/api/src/crm/company/services/attio/index.ts index 11244940a..2e3cd3ec3 100644 --- a/packages/api/src/crm/company/services/attio/index.ts +++ b/packages/api/src/crm/company/services/attio/index.ts @@ -39,7 +39,7 @@ export class AttioService implements ICompanyService { }); const resp = await axios.post( - 'https://api.attio.com/v2/objects/companies/records', + `${connection.account_url}/objects/companies/records`, JSON.stringify({ data: companyData, }), @@ -80,7 +80,7 @@ export class AttioService implements ICompanyService { }, }); const resp = await axios.post( - `https://api.attio.com/v2/objects/companies/records/query`, + `${connection.account_url}/objects/companies/records/query`, {}, { headers: { diff --git a/packages/api/src/crm/company/services/hubspot/index.ts b/packages/api/src/crm/company/services/hubspot/index.ts index ce8ebb3c7..c9b14fefe 100644 --- a/packages/api/src/crm/company/services/hubspot/index.ts +++ b/packages/api/src/crm/company/services/hubspot/index.ts @@ -43,7 +43,7 @@ export class HubspotService implements ICompanyService { properties: companyData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/companies`, + `${connection.account_url}/objects/companies`, JSON.stringify(dataBody), { headers: { @@ -85,8 +85,7 @@ export class HubspotService implements ICompanyService { const commonPropertyNames = Object.keys(commonCompanyHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/companies'; - + const baseURL = `${connection.account_url}/objects/companies`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) .join('&'); diff --git a/packages/api/src/crm/company/services/pipedrive/index.ts b/packages/api/src/crm/company/services/pipedrive/index.ts index 3f4f65987..a012edc01 100644 --- a/packages/api/src/crm/company/services/pipedrive/index.ts +++ b/packages/api/src/crm/company/services/pipedrive/index.ts @@ -37,7 +37,7 @@ export class PipedriveService implements ICompanyService { }, }); const resp = await axios.post( - `https://api.pipedrive.com/v1/organizations`, + `${connection.account_url}/organizations`, JSON.stringify(companyData), { headers: { @@ -76,17 +76,14 @@ export class PipedriveService implements ICompanyService { vertical: 'crm', }, }); - const resp = await axios.get( - `https://api.pipedrive.com/v1/organizations`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, + const resp = await axios.get(`${connection.account_url}/organizations`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, }, - ); + }); return { data: resp.data.data, diff --git a/packages/api/src/crm/company/services/zendesk/index.ts b/packages/api/src/crm/company/services/zendesk/index.ts index 7f93e2a43..fa597c351 100644 --- a/packages/api/src/crm/company/services/zendesk/index.ts +++ b/packages/api/src/crm/company/services/zendesk/index.ts @@ -36,7 +36,7 @@ export class ZendeskService implements ICompanyService { }, }); const resp = await axios.post( - `https://api.getbase.com/v2/contacts`, + `${connection.account_url}/contacts`, { data: companyData, }, @@ -78,7 +78,7 @@ export class ZendeskService implements ICompanyService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.getbase.com/v2/contacts`, { + const resp = await axios.get(`${connection.account_url}/contacts`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/company/services/zoho/index.ts b/packages/api/src/crm/company/services/zoho/index.ts index 52e61eec2..f9e4f9016 100644 --- a/packages/api/src/crm/company/services/zoho/index.ts +++ b/packages/api/src/crm/company/services/zoho/index.ts @@ -37,7 +37,7 @@ export class ZohoService implements ICompanyService { }, }); const resp = await axios.post( - `https://www.zohoapis.eu/crm/v3/Companys`, + `${connection.account_url}/Companys`, { data: [companyData] }, { headers: { @@ -80,7 +80,7 @@ export class ZohoService implements ICompanyService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Companys?fields=${fields}`, + `${connection.account_url}/Companys?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/contact/services/attio/index.ts b/packages/api/src/crm/contact/services/attio/index.ts index b843ac210..1b2e966d7 100644 --- a/packages/api/src/crm/contact/services/attio/index.ts +++ b/packages/api/src/crm/contact/services/attio/index.ts @@ -38,7 +38,7 @@ export class AttioService implements IContactService { }); const resp = await axios.post( - `https://api.attio.com/v2/objects/people/records`, + `${connection.account_url}/objects/people/records`, JSON.stringify({ data: contactData, }), @@ -83,7 +83,7 @@ export class AttioService implements IContactService { // console.log(this.cryptoService.decrypt(connection.access_token)); const resp = await axios.post( - `https://api.attio.com/v2/objects/people/records/query`, + `${connection.account_url}/objects/people/records/query`, {}, { headers: { diff --git a/packages/api/src/crm/contact/services/hubspot/index.ts b/packages/api/src/crm/contact/services/hubspot/index.ts index 37da950d7..33913cf51 100644 --- a/packages/api/src/crm/contact/services/hubspot/index.ts +++ b/packages/api/src/crm/contact/services/hubspot/index.ts @@ -44,7 +44,7 @@ export class HubspotService implements IContactService { properties: contactData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/contacts/`, + `${connection.account_url}/objects/contacts`, JSON.stringify(dataBody), { headers: { @@ -86,7 +86,7 @@ export class HubspotService implements IContactService { const commonPropertyNames = Object.keys(commonHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/contacts/'; + const baseURL = `${connection.account_url}/objects/contacts`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/crm/contact/services/pipedrive/index.ts b/packages/api/src/crm/contact/services/pipedrive/index.ts index 2f7d763b2..7b5350a16 100644 --- a/packages/api/src/crm/contact/services/pipedrive/index.ts +++ b/packages/api/src/crm/contact/services/pipedrive/index.ts @@ -38,7 +38,7 @@ export class PipedriveService implements IContactService { }); const resp = await axios.post( - `https://api.pipedrive.com/v1/persons`, + `${connection.account_url}/persons`, JSON.stringify(contactData), { headers: { @@ -77,7 +77,7 @@ export class PipedriveService implements IContactService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.pipedrive.com/v1/persons`, { + const resp = await axios.get(`${connection.account_url}/persons`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/contact/services/zendesk/index.ts b/packages/api/src/crm/contact/services/zendesk/index.ts index b6bace3d0..4d96e4396 100644 --- a/packages/api/src/crm/contact/services/zendesk/index.ts +++ b/packages/api/src/crm/contact/services/zendesk/index.ts @@ -36,7 +36,7 @@ export class ZendeskService implements IContactService { }, }); const resp = await axios.post( - `https://api.getbase.com/v2/contacts`, + `${connection.account_url}/contacts`, { data: contactData, }, @@ -78,7 +78,7 @@ export class ZendeskService implements IContactService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.getbase.com/v2/contacts`, { + const resp = await axios.get(`${connection.account_url}/contacts`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/contact/services/zoho/index.ts b/packages/api/src/crm/contact/services/zoho/index.ts index 6396fa87b..3c6022027 100644 --- a/packages/api/src/crm/contact/services/zoho/index.ts +++ b/packages/api/src/crm/contact/services/zoho/index.ts @@ -37,7 +37,7 @@ export class ZohoService implements IContactService { }, }); const resp = await axios.post( - `https://www.zohoapis.eu/crm/v3/Contacts`, + `${connection.account_url}/Contacts`, { data: [contactData] }, { headers: { @@ -80,7 +80,7 @@ export class ZohoService implements IContactService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Contacts?fields=${fields}`, + `${connection.account_url}/Contacts?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/deal/services/hubspot/index.ts b/packages/api/src/crm/deal/services/hubspot/index.ts index 72452cce9..8e6bf0054 100644 --- a/packages/api/src/crm/deal/services/hubspot/index.ts +++ b/packages/api/src/crm/deal/services/hubspot/index.ts @@ -42,7 +42,7 @@ export class HubspotService implements IDealService { properties: dealData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/deals`, + `${connection.account_url}/objects/deals`, JSON.stringify(dataBody), { headers: { @@ -85,7 +85,7 @@ export class HubspotService implements IDealService { const commonPropertyNames = Object.keys(commonDealHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/deals'; + const baseURL = `${connection.account_url}/objects/deals`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/crm/deal/services/pipedrive/index.ts b/packages/api/src/crm/deal/services/pipedrive/index.ts index c53362b91..bed9673c2 100644 --- a/packages/api/src/crm/deal/services/pipedrive/index.ts +++ b/packages/api/src/crm/deal/services/pipedrive/index.ts @@ -37,7 +37,7 @@ export class PipedriveService implements IDealService { }, }); const resp = await axios.post( - `https://api.pipedrive.com/v1/deals`, + `${connection.account_url}/deals`, JSON.stringify(dealData), { headers: { @@ -76,7 +76,7 @@ export class PipedriveService implements IDealService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.pipedrive.com/v1/deals`, { + const resp = await axios.get(`${connection.account_url}/deals`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/deal/services/zendesk/index.ts b/packages/api/src/crm/deal/services/zendesk/index.ts index 06b388cd2..f8a994afe 100644 --- a/packages/api/src/crm/deal/services/zendesk/index.ts +++ b/packages/api/src/crm/deal/services/zendesk/index.ts @@ -36,7 +36,7 @@ export class ZendeskService implements IDealService { }, }); const resp = await axios.post( - `https://api.getbase.com/v2/deals`, + `${connection.account_url}/deals`, { data: dealData, }, @@ -78,7 +78,7 @@ export class ZendeskService implements IDealService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.getbase.com/v2/deals`, { + const resp = await axios.get(`${connection.account_url}/deals`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/deal/services/zoho/index.ts b/packages/api/src/crm/deal/services/zoho/index.ts index a7e004a94..391cc61db 100644 --- a/packages/api/src/crm/deal/services/zoho/index.ts +++ b/packages/api/src/crm/deal/services/zoho/index.ts @@ -36,7 +36,7 @@ export class ZohoService implements IDealService { }, }); const resp = await axios.post( - `https://www.zohoapis.eu/crm/v3/Deals`, + `${connection.account_url}/Deals`, { data: [dealData] }, { headers: { @@ -79,7 +79,7 @@ export class ZohoService implements IDealService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Deals?fields=${fields}`, + `${connection.account_url}/Deals?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/engagement/services/hubspot/index.ts b/packages/api/src/crm/engagement/services/hubspot/index.ts index bad92c754..f65ea7e00 100644 --- a/packages/api/src/crm/engagement/services/hubspot/index.ts +++ b/packages/api/src/crm/engagement/services/hubspot/index.ts @@ -86,7 +86,7 @@ export class HubspotService implements IEngagementService { properties: engagementData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/calls`, + `${connection.account_url}/objects/calls`, JSON.stringify(dataBody), { headers: { @@ -129,7 +129,7 @@ export class HubspotService implements IEngagementService { properties: engagementData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/calls`, + `${connection.account_url}/objects/calls`, JSON.stringify(dataBody), { headers: { diff --git a/packages/api/src/crm/engagement/services/pipedrive/index.ts b/packages/api/src/crm/engagement/services/pipedrive/index.ts index f3b4562dd..0501d6651 100644 --- a/packages/api/src/crm/engagement/services/pipedrive/index.ts +++ b/packages/api/src/crm/engagement/services/pipedrive/index.ts @@ -90,7 +90,7 @@ export class PipedriveService implements IEngagementService { }, }); - const resp = await axios.get(`https://api.pipedrive.com/v1/activities`, { + const resp = await axios.get(`${connection.account_url}/activities`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( @@ -129,7 +129,7 @@ export class PipedriveService implements IEngagementService { }, }); - const resp = await axios.get(`https://api.pipedrive.com/v1/activities`, { + const resp = await axios.get(`${connection.account_url}/activities`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/engagement/services/zendesk/index.ts b/packages/api/src/crm/engagement/services/zendesk/index.ts index 4162fcef3..a1caf1e53 100644 --- a/packages/api/src/crm/engagement/services/zendesk/index.ts +++ b/packages/api/src/crm/engagement/services/zendesk/index.ts @@ -63,7 +63,7 @@ export class ZendeskService implements IEngagementService { }, }); const resp = await axios.post( - `https://api.getbase.com/v2/engagements`, + `${connection.account_url}/engagements`, { data: engagementData, }, @@ -129,7 +129,7 @@ export class ZendeskService implements IEngagementService { }, }); - const resp = await axios.get(`https://api.getbase.com/v2/calls`, { + const resp = await axios.get(`${connection.account_url}/calls`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/engagement/services/zoho/index.ts b/packages/api/src/crm/engagement/services/zoho/index.ts index 86c321c1b..62d851f7d 100644 --- a/packages/api/src/crm/engagement/services/zoho/index.ts +++ b/packages/api/src/crm/engagement/services/zoho/index.ts @@ -37,7 +37,7 @@ export class ZohoService implements IEngagementService { }, }); const resp = await axios.post( - `https://www.zohoapis.eu/crm/v3/Engagements`, + `${connection.account_url}/Engagements`, { data: [engagementData] }, { headers: { @@ -80,7 +80,7 @@ export class ZohoService implements IEngagementService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Engagements?fields=${fields}`, + `${connection.account_url}/Engagements?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/note/services/hubspot/index.ts b/packages/api/src/crm/note/services/hubspot/index.ts index 5b34c885e..27b380966 100644 --- a/packages/api/src/crm/note/services/hubspot/index.ts +++ b/packages/api/src/crm/note/services/hubspot/index.ts @@ -43,7 +43,7 @@ export class HubspotService implements INoteService { properties: noteData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/notes`, + `${connection.account_url}/objects/notes`, JSON.stringify(dataBody), { headers: { @@ -85,7 +85,7 @@ export class HubspotService implements INoteService { const commonPropertyNames = Object.keys(commonNoteHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/notes'; + const baseURL = `${connection.account_url}/objects/notes`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/crm/note/services/pipedrive/index.ts b/packages/api/src/crm/note/services/pipedrive/index.ts index 7aadde72b..f6af7903d 100644 --- a/packages/api/src/crm/note/services/pipedrive/index.ts +++ b/packages/api/src/crm/note/services/pipedrive/index.ts @@ -37,7 +37,7 @@ export class PipedriveService implements INoteService { }, }); const resp = await axios.post( - `https://api.pipedrive.com/v1/notes`, + `${connection.account_url}/notes`, JSON.stringify(noteData), { headers: { @@ -76,7 +76,7 @@ export class PipedriveService implements INoteService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.pipedrive.com/v1/notes`, { + const resp = await axios.get(`${connection.account_url}/notes`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/note/services/zendesk/index.ts b/packages/api/src/crm/note/services/zendesk/index.ts index 2da196330..bd3408ec1 100644 --- a/packages/api/src/crm/note/services/zendesk/index.ts +++ b/packages/api/src/crm/note/services/zendesk/index.ts @@ -36,7 +36,7 @@ export class ZendeskService implements INoteService { }, }); const resp = await axios.post( - `https://api.getbase.com/v2/notes`, + `${connection.account_url}/notes`, { data: noteData, }, @@ -78,7 +78,7 @@ export class ZendeskService implements INoteService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.getbase.com/v2/notes`, { + const resp = await axios.get(`${connection.account_url}/notes`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/note/services/zoho/index.ts b/packages/api/src/crm/note/services/zoho/index.ts index 02b5a4919..cc84f8d13 100644 --- a/packages/api/src/crm/note/services/zoho/index.ts +++ b/packages/api/src/crm/note/services/zoho/index.ts @@ -37,7 +37,7 @@ export class ZohoService implements INoteService { }, }); const resp = await axios.post( - `https://www.zohoapis.eu/crm/v3/Notes`, + `${connection.account_url}/Notes`, { data: [noteData] }, { headers: { @@ -80,7 +80,7 @@ export class ZohoService implements INoteService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Notes?fields=${fields}`, + `${connection.account_url}/Notes?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/stage/services/hubspot/index.ts b/packages/api/src/crm/stage/services/hubspot/index.ts index a07d9c3b1..046947b7b 100644 --- a/packages/api/src/crm/stage/services/hubspot/index.ts +++ b/packages/api/src/crm/stage/services/hubspot/index.ts @@ -44,7 +44,7 @@ export class HubspotService implements IStageService { const commonPropertyNames = Object.keys(commonStageHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = `https://api.hubapi.com/crm/v3/objects/deals/${res.remote_id}`; + const baseURL = `${connection.account_url}/objects/deals/${res.remote_id}`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/crm/stage/services/pipedrive/index.ts b/packages/api/src/crm/stage/services/pipedrive/index.ts index ab7d6397c..704882971 100644 --- a/packages/api/src/crm/stage/services/pipedrive/index.ts +++ b/packages/api/src/crm/stage/services/pipedrive/index.ts @@ -40,7 +40,7 @@ export class PipedriveService implements IStageService { where: { id_crm_deal: deal_id }, }); - const deals = await axios.get(`https://api.pipedrive.com/v1/deals`, { + const deals = await axios.get(`${connection.account_url}/deals`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( @@ -52,7 +52,7 @@ export class PipedriveService implements IStageService { const deal = deals.data.data.find( (item) => String(item.id) === res.remote_id, ); - const resp = await axios.get(`https://api.pipedrive.com/v1/stages`, { + const resp = await axios.get(`${connection.account_url}/stages`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/stage/services/zendesk/index.ts b/packages/api/src/crm/stage/services/zendesk/index.ts index 121c5e36a..70db655dc 100644 --- a/packages/api/src/crm/stage/services/zendesk/index.ts +++ b/packages/api/src/crm/stage/services/zendesk/index.ts @@ -39,7 +39,7 @@ export class ZendeskService implements IStageService { where: { id_crm_deal: deal_id }, }); const deal = await axios.get( - `https://api.getbase.com/v2/deals/${res.remote_id}`, + `${connection.account_url}/deals/${res.remote_id}`, { headers: { 'Content-Type': 'application/json', @@ -50,7 +50,7 @@ export class ZendeskService implements IStageService { }, ); const stage_remote_id: number = deal.data.data.stage_id; - const resp = await axios.get(`https://api.getbase.com/v2/stages`, { + const resp = await axios.get(`${connection.account_url}/stages`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/stage/services/zoho/index.ts b/packages/api/src/crm/stage/services/zoho/index.ts index fd012aff2..f67aa8959 100644 --- a/packages/api/src/crm/stage/services/zoho/index.ts +++ b/packages/api/src/crm/stage/services/zoho/index.ts @@ -38,7 +38,7 @@ export class ZohoService implements IStageService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Stages?fields=${fields}`, + `${connection.account_url}Stages?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/task/services/hubspot/index.ts b/packages/api/src/crm/task/services/hubspot/index.ts index 26acb1dac..4f39ddbdc 100644 --- a/packages/api/src/crm/task/services/hubspot/index.ts +++ b/packages/api/src/crm/task/services/hubspot/index.ts @@ -43,7 +43,7 @@ export class HubspotService implements ITaskService { properties: taskData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/tasks`, + `${connection.account_url}/objects/tasks`, JSON.stringify(dataBody), { headers: { @@ -85,7 +85,7 @@ export class HubspotService implements ITaskService { const commonPropertyNames = Object.keys(commonTaskHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/tasks'; + const baseURL = `${connection.account_url}/objects/tasks`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/crm/task/services/pipedrive/index.ts b/packages/api/src/crm/task/services/pipedrive/index.ts index 9371370f1..aca95d494 100644 --- a/packages/api/src/crm/task/services/pipedrive/index.ts +++ b/packages/api/src/crm/task/services/pipedrive/index.ts @@ -37,7 +37,7 @@ export class PipedriveService implements ITaskService { }, }); const resp = await axios.post( - `https://api.pipedrive.com/v1/activities`, + `${connection.account_url}/activities`, JSON.stringify(taskData), { headers: { @@ -77,7 +77,7 @@ export class PipedriveService implements ITaskService { }, }); const resp = await axios.get( - `https://api.pipedrive.com/v1/activities?type=task&user_id=${19156166}`, + `${connection.account_url}/activities?type=task&user_id=${19156166}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/task/services/zendesk/index.ts b/packages/api/src/crm/task/services/zendesk/index.ts index 3df61d9cd..9cdecad10 100644 --- a/packages/api/src/crm/task/services/zendesk/index.ts +++ b/packages/api/src/crm/task/services/zendesk/index.ts @@ -38,7 +38,7 @@ export class ZendeskService implements ITaskService { }); const resp = await axios.post( - `https://api.getbase.com/v2/tasks`, + `${connection.account_url}/tasks`, { data: taskData, meta: { @@ -83,7 +83,7 @@ export class ZendeskService implements ITaskService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.getbase.com/v2/tasks`, { + const resp = await axios.get(`${connection.account_url}/tasks`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/task/services/zoho/index.ts b/packages/api/src/crm/task/services/zoho/index.ts index 612a32cd3..dd6b4d0e7 100644 --- a/packages/api/src/crm/task/services/zoho/index.ts +++ b/packages/api/src/crm/task/services/zoho/index.ts @@ -37,7 +37,7 @@ export class ZohoService implements ITaskService { }, }); const resp = await axios.post( - `https://www.zohoapis.eu/crm/v3/Tasks`, + `${connection.account_url}/Tasks`, { data: [taskData] }, { headers: { @@ -80,7 +80,7 @@ export class ZohoService implements ITaskService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Tasks?fields=${fields}`, + `${connection.account_url}/Tasks?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/crm/user/services/hubspot/index.ts b/packages/api/src/crm/user/services/hubspot/index.ts index dc2ab1f0c..c9e0756ec 100644 --- a/packages/api/src/crm/user/services/hubspot/index.ts +++ b/packages/api/src/crm/user/services/hubspot/index.ts @@ -39,7 +39,7 @@ export class HubspotService implements IUserService { const commonPropertyNames = Object.keys(commonUserHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/owners'; + const baseURL = `${connection.account_url}/owners`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/crm/user/services/pipedrive/index.ts b/packages/api/src/crm/user/services/pipedrive/index.ts index 77052b6a5..453823167 100644 --- a/packages/api/src/crm/user/services/pipedrive/index.ts +++ b/packages/api/src/crm/user/services/pipedrive/index.ts @@ -35,7 +35,7 @@ export class PipedriveService implements IUserService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.pipedrive.com/v1/users`, { + const resp = await axios.get(`${connection.account_url}/users`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/user/services/zendesk/index.ts b/packages/api/src/crm/user/services/zendesk/index.ts index 067702a4d..7fbaf70dc 100644 --- a/packages/api/src/crm/user/services/zendesk/index.ts +++ b/packages/api/src/crm/user/services/zendesk/index.ts @@ -34,7 +34,7 @@ export class ZendeskService implements IUserService { vertical: 'crm', }, }); - const resp = await axios.get(`https://api.getbase.com/v2/users`, { + const resp = await axios.get(`${connection.account_url}/users`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/crm/user/services/zoho/index.ts b/packages/api/src/crm/user/services/zoho/index.ts index 8238a6eeb..dbea715eb 100644 --- a/packages/api/src/crm/user/services/zoho/index.ts +++ b/packages/api/src/crm/user/services/zoho/index.ts @@ -38,7 +38,7 @@ export class ZohoService implements IUserService { //TODO: handle fields const fields = 'First_Name,Last_Name,Full_Name,Email,Phone'; const resp = await axios.get( - `https://www.zohoapis.eu/crm/v3/Users?fields=${fields}`, + `${connection.account_url}/Users?fields=${fields}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/account/services/front/index.ts b/packages/api/src/ticketing/account/services/front/index.ts index e0d531096..63ae02aa5 100644 --- a/packages/api/src/ticketing/account/services/front/index.ts +++ b/packages/api/src/ticketing/account/services/front/index.ts @@ -36,7 +36,7 @@ export class FrontService implements IAccountService { }, }); - const resp = await axios.get('https://api2.frontapp.com/accounts', { + const resp = await axios.get(`${connection.account_url}/accounts`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/account/services/github/index.ts b/packages/api/src/ticketing/account/services/github/index.ts index a07e5552a..ab398e186 100644 --- a/packages/api/src/ticketing/account/services/github/index.ts +++ b/packages/api/src/ticketing/account/services/github/index.ts @@ -36,7 +36,7 @@ export class GithubService implements IAccountService { vertical: 'ticketing', }, }); - const resp = await axios.get(`https://api.github.com/user/orgs`, { + const resp = await axios.get(`${connection.account_url}/user/orgs`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/account/services/zendesk/index.ts b/packages/api/src/ticketing/account/services/zendesk/index.ts index 714e45cdc..61645fd09 100644 --- a/packages/api/src/ticketing/account/services/zendesk/index.ts +++ b/packages/api/src/ticketing/account/services/zendesk/index.ts @@ -40,7 +40,7 @@ export class ZendeskService implements IAccountService { }); const resp = await axios.get( - `${connection.account_url}/api/v2/organizations.json`, + `${connection.account_url}/organizations.json`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/collection/services/jira/index.ts b/packages/api/src/ticketing/collection/services/jira/index.ts index 64b101658..2ec6d7046 100644 --- a/packages/api/src/ticketing/collection/services/jira/index.ts +++ b/packages/api/src/ticketing/collection/services/jira/index.ts @@ -36,17 +36,14 @@ export class JiraService implements ICollectionService { }, }); - const resp = await axios.get( - `${connection.account_url}/rest/api/3/project/search`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, + const resp = await axios.get(`${connection.account_url}/project/search`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, }, - ); + }); this.logger.log(`Synced jira collections !`); return { diff --git a/packages/api/src/ticketing/comment/services/front/index.ts b/packages/api/src/ticketing/comment/services/front/index.ts index f7e205898..0aa52cf24 100644 --- a/packages/api/src/ticketing/comment/services/front/index.ts +++ b/packages/api/src/ticketing/comment/services/front/index.ts @@ -86,7 +86,7 @@ export class FrontService implements ICommentService { // Send request with attachments resp = await axios.post( - `https://api2.frontapp.com/conversations/${remoteIdTicket}/comments`, + `${connection.account_url}/conversations/${remoteIdTicket}/comments`, formData, { headers: { @@ -100,7 +100,7 @@ export class FrontService implements ICommentService { } else { // Send request without attachments resp = await axios.post( - `https://api2.frontapp.com/conversations/${remoteIdTicket}/comments`, + `${connection.account_url}/conversations/${remoteIdTicket}/comments`, JSON.stringify(dataBody), { headers: { @@ -152,7 +152,7 @@ export class FrontService implements ICommentService { }); const resp = await axios.get( - `https://api2.frontapp.com/conversations/${ticket.remote_id}/comments`, + `${connection.account_url}conversations/${ticket.remote_id}/comments`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/comment/services/github/index.ts b/packages/api/src/ticketing/comment/services/github/index.ts index 78aea85ce..27aec9186 100644 --- a/packages/api/src/ticketing/comment/services/github/index.ts +++ b/packages/api/src/ticketing/comment/services/github/index.ts @@ -42,7 +42,7 @@ export class GithubService implements ICommentService { comment: commentData, }; const resp = await axios.post( - `https://api2.frontapp.com/conversations/${remoteIdTicket}/comments`, + `${connection.account_url}conversations/${remoteIdTicket}/comments`, JSON.stringify(dataBody), { headers: { @@ -91,7 +91,7 @@ export class GithubService implements ICommentService { }); const resp = await axios.get( - `https://api2.frontapp.com/conversations/${ticket.remote_id}/comments`, + `${connection.account_url}/conversations/${ticket.remote_id}/comments`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/comment/services/gorgias/index.ts b/packages/api/src/ticketing/comment/services/gorgias/index.ts index 06c8f4344..f6f8c3bdf 100644 --- a/packages/api/src/ticketing/comment/services/gorgias/index.ts +++ b/packages/api/src/ticketing/comment/services/gorgias/index.ts @@ -76,7 +76,7 @@ export class GorgiasService implements ICommentService { }; const resp = await axios.post( - `${connection.account_url}/api/tickets/${remoteIdTicket}/messages`, + `${connection.account_url}/tickets/${remoteIdTicket}/messages`, JSON.stringify(data), { headers: { @@ -126,7 +126,7 @@ export class GorgiasService implements ICommentService { }); const resp = await axios.get( - `https://api2.gorgiasapp.com/conversations/${ticket.remote_id}/comments`, + `${connection.account_url}/conversations/${ticket.remote_id}/comments`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/comment/services/jira/index.ts b/packages/api/src/ticketing/comment/services/jira/index.ts index 4e50d3343..b3478b2e9 100644 --- a/packages/api/src/ticketing/comment/services/jira/index.ts +++ b/packages/api/src/ticketing/comment/services/jira/index.ts @@ -45,7 +45,7 @@ export class JiraService implements ICommentService { // Send request without attachments const resp = await axios.post( - `${connection.account_url}/rest/api/3/issue/${remoteIdTicket}/comment`, + `${connection.account_url}/issue/${remoteIdTicket}/comment`, JSON.stringify(commentData), { headers: { @@ -91,7 +91,7 @@ export class JiraService implements ICommentService { // Send request with attachments const resp_ = await axios.post( - `${connection.account_url}/rest/api/3/issue/${remoteIdTicket}/attachments`, + `${connection.account_url}/issue/${remoteIdTicket}/attachments`, formData, { headers: { @@ -143,7 +143,7 @@ export class JiraService implements ICommentService { }, }); const resp = await axios.get( - `${connection.account_url}/rest/api/3/issue/${ticket.remote_id}/comment`, + `${connection.account_url}/issue/${ticket.remote_id}/comment`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/comment/services/zendesk/index.ts b/packages/api/src/ticketing/comment/services/zendesk/index.ts index 3fbdeceb9..7ba8769be 100644 --- a/packages/api/src/ticketing/comment/services/zendesk/index.ts +++ b/packages/api/src/ticketing/comment/services/zendesk/index.ts @@ -66,7 +66,7 @@ export class ZendeskService implements ICommentService { //TODO:; fetch the right file from AWS s3 const s3File = ''; - const url = `${connection.account_url}/api/v2/uploads.json?filename=${res.file_name}`; + const url = `${connection.account_url}/uploads.json?filename=${res.file_name}`; const resp = await axios.get(url, { headers: { @@ -92,7 +92,7 @@ export class ZendeskService implements ICommentService { //to add a comment on Zendesk you must update a ticket using the Ticket API const resp = await axios.put( - `${connection.account_url}/api/v2/tickets/${remoteIdTicket}.json`, + `${connection.account_url}/tickets/${remoteIdTicket}.json`, JSON.stringify(dataBody), { headers: { @@ -144,7 +144,7 @@ export class ZendeskService implements ICommentService { }); const resp = await axios.get( - `${connection.account_url}/api/v2/tickets/${ticket.remote_id}/comments.json`, + `${connection.account_url}/tickets/${ticket.remote_id}/comments.json`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/contact/services/front/index.ts b/packages/api/src/ticketing/contact/services/front/index.ts index 81dd621e7..e3ea2c1ab 100644 --- a/packages/api/src/ticketing/contact/services/front/index.ts +++ b/packages/api/src/ticketing/contact/services/front/index.ts @@ -41,7 +41,7 @@ export class FrontService implements IContactService { }); const resp = await axios.get( - `https://api2.frontapp.com/accounts/${remote_account_id}/contacts`, + `${connection.account_url}/accounts/${remote_account_id}/contacts`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/contact/services/github/index.ts b/packages/api/src/ticketing/contact/services/github/index.ts index 111464e05..6c27e0e2a 100644 --- a/packages/api/src/ticketing/contact/services/github/index.ts +++ b/packages/api/src/ticketing/contact/services/github/index.ts @@ -37,7 +37,7 @@ export class GithubService implements IContactService { vertical: 'ticketing', }, }); - const resp = await axios.get(`https://api.github.com/contacts`, { + const resp = await axios.get(`${connection.account_url}/contacts`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/contact/services/gorgias/index.ts b/packages/api/src/ticketing/contact/services/gorgias/index.ts index b4e1fdf19..25198681f 100644 --- a/packages/api/src/ticketing/contact/services/gorgias/index.ts +++ b/packages/api/src/ticketing/contact/services/gorgias/index.ts @@ -40,7 +40,7 @@ export class GorgiasService implements IContactService { }, }); - const resp = await axios.get(`${connection.account_url}/api/customers`, { + const resp = await axios.get(`${connection.account_url}/customers`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/contact/services/zendesk/index.ts b/packages/api/src/ticketing/contact/services/zendesk/index.ts index cc13fb2db..7af57f9e6 100644 --- a/packages/api/src/ticketing/contact/services/zendesk/index.ts +++ b/packages/api/src/ticketing/contact/services/zendesk/index.ts @@ -39,7 +39,7 @@ export class ZendeskService implements IContactService { }, }); - const resp = await axios.get(`${connection.account_url}/api/v2/users`, { + const resp = await axios.get(`${connection.account_url}/users`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/tag/services/front/index.ts b/packages/api/src/ticketing/tag/services/front/index.ts index c077454bf..15b5e4bb9 100644 --- a/packages/api/src/ticketing/tag/services/front/index.ts +++ b/packages/api/src/ticketing/tag/services/front/index.ts @@ -46,7 +46,7 @@ export class FrontService implements ITagService { }, }); - const resp = await axios.get('https://api2.frontapp.com/conversations', { + const resp = await axios.get(`${connection.account_url}/conversations`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/tag/services/github/index.ts b/packages/api/src/ticketing/tag/services/github/index.ts index 9215cbfe7..7df11274d 100644 --- a/packages/api/src/ticketing/tag/services/github/index.ts +++ b/packages/api/src/ticketing/tag/services/github/index.ts @@ -37,7 +37,7 @@ export class GithubService implements ITagService { vertical: 'ticketing', }, }); - const resp = await axios.get(`https://api.github.com/tags`, { + const resp = await axios.get(`${connection.account_url}/tags`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/tag/services/gorgias/index.ts b/packages/api/src/ticketing/tag/services/gorgias/index.ts index 06abbed27..30155db5f 100644 --- a/packages/api/src/ticketing/tag/services/gorgias/index.ts +++ b/packages/api/src/ticketing/tag/services/gorgias/index.ts @@ -46,7 +46,7 @@ export class GorgiasService implements ITagService { }, }); - const resp = await axios.get(`${connection.account_url}/api/tags`, { + const resp = await axios.get(`${connection.account_url}/tags`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/tag/services/jira/index.ts b/packages/api/src/ticketing/tag/services/jira/index.ts index af0347c89..d205eace6 100644 --- a/packages/api/src/ticketing/tag/services/jira/index.ts +++ b/packages/api/src/ticketing/tag/services/jira/index.ts @@ -47,7 +47,7 @@ export class JiraService implements ITagService { }); //todo: TAGS - const resp = await axios.get('https://api2.jiraapp.com/conversations', { + const resp = await axios.get(`${connection.account_url}/conversations`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/tag/services/zendesk/index.ts b/packages/api/src/ticketing/tag/services/zendesk/index.ts index 4f4782518..b50ad900e 100644 --- a/packages/api/src/ticketing/tag/services/zendesk/index.ts +++ b/packages/api/src/ticketing/tag/services/zendesk/index.ts @@ -49,7 +49,7 @@ export class ZendeskService implements ITagService { }); const resp = await axios.get( - `${connection.account_url}/api/v2/tickets/${ticket.remote_id}/tags`, + `${connection.account_url}/tickets/${ticket.remote_id}/tags`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/team/services/clickup/index.ts b/packages/api/src/ticketing/team/services/clickup/index.ts deleted file mode 100644 index a7dcb45a5..000000000 --- a/packages/api/src/ticketing/team/services/clickup/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { LoggerService } from '@@core/logger/logger.service'; -import { PrismaService } from '@@core/prisma/prisma.service'; -import { EncryptionService } from '@@core/encryption/encryption.service'; -import { TicketingObject } from '@ticketing/@utils/@types'; -import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handleServiceError } from '@@core/utils/errors'; -import { ServiceRegistry } from '../registry.service'; -import { ITeamService } from '@ticketing/team/types'; -import { ClickupTeamOutput } from './types'; - -@Injectable() -export class ClickupService implements ITeamService { - constructor( - private prisma: PrismaService, - private logger: LoggerService, - private cryptoService: EncryptionService, - private registry: ServiceRegistry, - ) { - this.logger.setContext( - TicketingObject.team.toUpperCase() + ':' + ClickupService.name, - ); - this.registry.registerService('front', this); - } - - async syncTeams( - linkedUserId: string, - ): Promise> { - try { - const connection = await this.prisma.connections.findFirst({ - where: { - id_linked_user: linkedUserId, - provider_slug: 'front', - vertical: 'ticketing', - }, - }); - - const resp = await axios.get('https://api2.frontapp.com/teams', { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, - }); - this.logger.log(`Synced front teams !`); - - return { - data: resp.data._results, - message: 'Clickup teams retrieved', - statusCode: 200, - }; - } catch (error) { - handleServiceError( - error, - this.logger, - 'Clickup', - TicketingObject.team, - ActionType.GET, - ); - } - } -} diff --git a/packages/api/src/ticketing/team/services/clickup/mappers.ts b/packages/api/src/ticketing/team/services/clickup/mappers.ts deleted file mode 100644 index afe83bb61..000000000 --- a/packages/api/src/ticketing/team/services/clickup/mappers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ITeamMapper } from '@ticketing/team/types'; -import { ClickupTeamInput, ClickupTeamOutput } from './types'; -import { - UnifiedTeamInput, - UnifiedTeamOutput, -} from '@ticketing/team/types/model.unified'; - -export class ClickupTeamMapper implements ITeamMapper { - desunify( - source: UnifiedTeamInput, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): ClickupTeamInput { - return; - } - - unify( - source: ClickupTeamOutput | ClickupTeamOutput[], - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): UnifiedTeamOutput | UnifiedTeamOutput[] { - return; - } - - private mapSingleTeamToUnified( - team: ClickupTeamOutput, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): UnifiedTeamOutput { - return; - } -} diff --git a/packages/api/src/ticketing/team/services/clickup/types.ts b/packages/api/src/ticketing/team/services/clickup/types.ts deleted file mode 100644 index 8274999b7..000000000 --- a/packages/api/src/ticketing/team/services/clickup/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type ClickupTeamInput = { - id: string; -}; - -export type ClickupTeamOutput = { - id: string; -}; diff --git a/packages/api/src/ticketing/team/services/front/index.ts b/packages/api/src/ticketing/team/services/front/index.ts index c98501f38..d10d0dd72 100644 --- a/packages/api/src/ticketing/team/services/front/index.ts +++ b/packages/api/src/ticketing/team/services/front/index.ts @@ -36,7 +36,7 @@ export class FrontService implements ITeamService { }, }); - const resp = await axios.get('https://api2.frontapp.com/teams', { + const resp = await axios.get(`${connection.account_url}/teams`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/team/services/github/index.ts b/packages/api/src/ticketing/team/services/github/index.ts index 35253ecda..e9ba67034 100644 --- a/packages/api/src/ticketing/team/services/github/index.ts +++ b/packages/api/src/ticketing/team/services/github/index.ts @@ -39,7 +39,7 @@ export class GithubService implements ITeamService { }); const org = ''; - const resp = await axios.get(`https://api.github.com/${org}/teams`, { + const resp = await axios.get(`${connection.account_url}/${org}/teams`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/team/services/gorgias/index.ts b/packages/api/src/ticketing/team/services/gorgias/index.ts index 1a2d70759..69da4c197 100644 --- a/packages/api/src/ticketing/team/services/gorgias/index.ts +++ b/packages/api/src/ticketing/team/services/gorgias/index.ts @@ -36,7 +36,7 @@ export class GorgiasService implements ITeamService { }, }); - const resp = await axios.get(`${connection.account_url}/api/teams`, { + const resp = await axios.get(`${connection.account_url}/teams`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/team/services/jira/index.ts b/packages/api/src/ticketing/team/services/jira/index.ts index b8ede2a3a..a407ee330 100644 --- a/packages/api/src/ticketing/team/services/jira/index.ts +++ b/packages/api/src/ticketing/team/services/jira/index.ts @@ -36,17 +36,14 @@ export class JiraService implements ITeamService { }, }); - const resp = await axios.get( - `${connection.account_url}/rest/api/3/user/groups`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, + const resp = await axios.get(`${connection.account_url}/user/groups`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, }, - ); + }); this.logger.log(`Synced jira teams !`); return { diff --git a/packages/api/src/ticketing/team/services/zendesk/index.ts b/packages/api/src/ticketing/team/services/zendesk/index.ts index d5fc1e40c..eed48f4f0 100644 --- a/packages/api/src/ticketing/team/services/zendesk/index.ts +++ b/packages/api/src/ticketing/team/services/zendesk/index.ts @@ -39,17 +39,14 @@ export class ZendeskService implements ITeamService { }, }); - const resp = await axios.get( - `${connection.account_url}/api/v2/groups.json`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, + const resp = await axios.get(`${connection.account_url}/groups.json`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, }, - ); + }); this.logger.log(`Synced zendesk teams !`); return { diff --git a/packages/api/src/ticketing/team/types/mappingsTypes.ts b/packages/api/src/ticketing/team/types/mappingsTypes.ts index 0955fc8a1..7d1e7f5af 100644 --- a/packages/api/src/ticketing/team/types/mappingsTypes.ts +++ b/packages/api/src/ticketing/team/types/mappingsTypes.ts @@ -1,4 +1,3 @@ -import { ClickupTeamMapper } from '../services/clickup/mappers'; import { JiraTeamMapper } from '../services/jira/mappers'; import { FrontTeamMapper } from '../services/front/mappers'; import { GithubTeamMapper } from '../services/github/mappers'; @@ -11,8 +10,6 @@ const githubTeamMapper = new GithubTeamMapper(); const gorgiasTeamMapper = new GorgiasTeamMapper(); const jiraTeamMapper = new JiraTeamMapper(); -const clickupTeamMapper = new ClickupTeamMapper(); - export const teamUnificationMapping = { zendesk: { unify: zendeskTeamMapper.unify.bind(zendeskTeamMapper), @@ -34,8 +31,4 @@ export const teamUnificationMapping = { unify: jiraTeamMapper.unify.bind(jiraTeamMapper), desunify: jiraTeamMapper.desunify, }, - clickup: { - unify: clickupTeamMapper.unify.bind(clickupTeamMapper), - desunify: clickupTeamMapper.desunify, - }, }; diff --git a/packages/api/src/ticketing/ticket/services/front/index.ts b/packages/api/src/ticketing/ticket/services/front/index.ts index 6a4b6d6da..0b5fc45f9 100644 --- a/packages/api/src/ticketing/ticket/services/front/index.ts +++ b/packages/api/src/ticketing/ticket/services/front/index.ts @@ -96,7 +96,7 @@ export class FrontService implements ITicketService { } resp = await axios.post( - `https://api2.frontapp.com/conversations`, + `${connection.account_url}/conversations`, formData, { headers: { @@ -109,7 +109,7 @@ export class FrontService implements ITicketService { ); } else { resp = await axios.post( - `https://api2.frontapp.com/conversations`, + `${connection.account_url}/conversations`, JSON.stringify(restOfTicketData), { headers: { @@ -131,7 +131,7 @@ export class FrontService implements ITicketService { final = { ...final, custom_fields: custom_fields }; } const tag_resp = await axios.patch( - `https://api2.frontapp.com/conversations/${resp.data.id}`, + `${connection.account_url}/conversations/${resp.data.id}`, JSON.stringify(final), { headers: { @@ -173,7 +173,7 @@ export class FrontService implements ITicketService { }, }); - const resp = await axios.get('https://api2.frontapp.com/conversations', { + const resp = await axios.get(`${connection.account_url}/conversations`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/ticket/services/github/index.ts b/packages/api/src/ticketing/ticket/services/github/index.ts index 83a58aaad..c1e450ad1 100644 --- a/packages/api/src/ticketing/ticket/services/github/index.ts +++ b/packages/api/src/ticketing/ticket/services/github/index.ts @@ -40,7 +40,7 @@ export class GithubService implements ITicketService { const org = ''; const repo = ''; const resp = await axios.post( - `https://api.github.com/repos/${org}/${repo}/issues`, + `${connection.account_url}/repos/${org}/${repo}/issues`, JSON.stringify(dataBody), { headers: { @@ -80,7 +80,7 @@ export class GithubService implements ITicketService { }); const owner = ''; const repo = ''; - const resp = await axios.get(`https://api.github.com/repos/issues`, { + const resp = await axios.get(`${connection.account_url}/repos/issues`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/ticket/services/gorgias/index.ts b/packages/api/src/ticketing/ticket/services/gorgias/index.ts index e1751cc3f..eefc450cc 100644 --- a/packages/api/src/ticketing/ticket/services/gorgias/index.ts +++ b/packages/api/src/ticketing/ticket/services/gorgias/index.ts @@ -76,7 +76,7 @@ export class GorgiasService implements ITicketService { ); const resp = await axios.post( - `${connection.account_url}/api/tickets`, + `${connection.account_url}/tickets`, JSON.stringify({ ...ticketData, messages: modifiedComments }), { headers: { @@ -116,7 +116,7 @@ export class GorgiasService implements ITicketService { }, }); - const resp = await axios.get(`${connection.account_url}/api/tickets`, { + const resp = await axios.get(`${connection.account_url}s/tickets`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/ticket/services/hubspot/index.ts b/packages/api/src/ticketing/ticket/services/hubspot/index.ts index 2ce23bc04..1f5cba44c 100644 --- a/packages/api/src/ticketing/ticket/services/hubspot/index.ts +++ b/packages/api/src/ticketing/ticket/services/hubspot/index.ts @@ -41,7 +41,7 @@ export class HubspotService implements ITicketService { }); const dataBody = { properties: ticketData }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/tickets`, + `${connection.account_url}/objects/tickets`, JSON.stringify(dataBody), { headers: { @@ -82,7 +82,7 @@ export class HubspotService implements ITicketService { const commonPropertyNames = Object.keys(commonHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/tickets/'; + const baseURL = `${connection.account_url}/objects/tickets/`; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/ticketing/ticket/services/jira/index.ts b/packages/api/src/ticketing/ticket/services/jira/index.ts index e9e55ff5f..413229aa7 100644 --- a/packages/api/src/ticketing/ticket/services/jira/index.ts +++ b/packages/api/src/ticketing/ticket/services/jira/index.ts @@ -44,7 +44,7 @@ export class JiraService implements ITicketService { //Add comment by calling the unified comment function but first insert the ticket base data const resp = await axios.post( - `${connection.account_url}/rest/api/3/issue`, + `${connection.account_url}/issue`, JSON.stringify(ticketData), { headers: { @@ -84,17 +84,14 @@ export class JiraService implements ITicketService { vertical: 'ticketing', }, }); - const resp = await axios.get( - `${connection.account_url}/rest/api/3/search`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, + const resp = await axios.get(`${connection.account_url}/search`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, }, - ); + }); this.logger.log(`Synced jira tickets !`); return { diff --git a/packages/api/src/ticketing/ticket/services/zendesk/index.ts b/packages/api/src/ticketing/ticket/services/zendesk/index.ts index 3124fcd31..e075e5a6b 100644 --- a/packages/api/src/ticketing/ticket/services/zendesk/index.ts +++ b/packages/api/src/ticketing/ticket/services/zendesk/index.ts @@ -56,7 +56,7 @@ export class ZendeskService implements ITicketService { //TODO:; fetch the right file from AWS s3 const s3File = ''; - const url = `${connection.account_url}/api/v2/uploads.json?filename=${res.file_name}`; + const url = `${connection.account_url}/uploads.json?filename=${res.file_name}`; const resp = await axios.get(url, { headers: { @@ -82,7 +82,7 @@ export class ZendeskService implements ITicketService { } const resp = await axios.post( - `${connection.account_url}/api/v2/tickets.json`, + `${connection.account_url}/tickets.json`, JSON.stringify(dataBody), { headers: { @@ -121,17 +121,14 @@ export class ZendeskService implements ITicketService { }, }); - const resp = await axios.get( - `${connection.account_url}/api/v2/tickets.json`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, + const resp = await axios.get(`${connection.account_url}/tickets.json`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, }, - ); + }); this.logger.log(`Synced zendesk tickets !`); return { diff --git a/packages/api/src/ticketing/user/services/front/index.ts b/packages/api/src/ticketing/user/services/front/index.ts index a88185bda..2d22630b4 100644 --- a/packages/api/src/ticketing/user/services/front/index.ts +++ b/packages/api/src/ticketing/user/services/front/index.ts @@ -36,7 +36,7 @@ export class FrontService implements IUserService { }, }); - const resp = await axios.get('https://api2.frontapp.com/teammates', { + const resp = await axios.get(`${connection.account_url}/teammates`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/user/services/github/index.ts b/packages/api/src/ticketing/user/services/github/index.ts index 64fb95784..a92b04abc 100644 --- a/packages/api/src/ticketing/user/services/github/index.ts +++ b/packages/api/src/ticketing/user/services/github/index.ts @@ -37,7 +37,7 @@ export class GithubService implements IUserService { vertical: 'ticketing', }, }); - const resp = await axios.get(`https://api.github.com/user`, { + const resp = await axios.get(`${connection.account_url}/user`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/user/services/gorgias/index.ts b/packages/api/src/ticketing/user/services/gorgias/index.ts index a07b78a85..6245ea0ce 100644 --- a/packages/api/src/ticketing/user/services/gorgias/index.ts +++ b/packages/api/src/ticketing/user/services/gorgias/index.ts @@ -36,7 +36,7 @@ export class GorgiasService implements IUserService { }, }); - const resp = await axios.get(`${connection.account_url}/api/users`, { + const resp = await axios.get(`${connection.account_url}/users`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/api/src/ticketing/user/services/jira/index.ts b/packages/api/src/ticketing/user/services/jira/index.ts index 7a09d9ef3..32c3d5e5e 100644 --- a/packages/api/src/ticketing/user/services/jira/index.ts +++ b/packages/api/src/ticketing/user/services/jira/index.ts @@ -35,17 +35,14 @@ export class JiraService implements IUserService { vertical: 'ticketing', }, }); - const resp = await axios.get( - `${connection.account_url}/rest/api/3/users/search`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, + const resp = await axios.get(`${connection.account_url}/users/search`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, }, - ); + }); this.logger.log(`Synced jira users !`); return { diff --git a/packages/api/src/ticketing/user/services/zendesk/index.ts b/packages/api/src/ticketing/user/services/zendesk/index.ts index d3b8030e8..277bc7677 100644 --- a/packages/api/src/ticketing/user/services/zendesk/index.ts +++ b/packages/api/src/ticketing/user/services/zendesk/index.ts @@ -39,7 +39,7 @@ export class ZendeskService implements IUserService { }, }); - const resp = await axios.get(`${connection.account_url}/api/v2/users`, { + const resp = await axios.get(`${connection.account_url}/users`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.cryptoService.decrypt( diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index bb6ebed40..23ad15a50 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -10,6 +10,16 @@ interface AuthParams { vertical: string; } +const randomString = () => { + const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * charSet.length); + result += charSet[randomIndex]; + } + return result; +} + // make sure to check wether its api_key or oauth2 to build the right auth // make sure to check if client has own credentials to connect or panora managed ones export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, returnUrl, apiUrl, vertical }: AuthParams) => { @@ -83,17 +93,25 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { const clientId = data.CLIENT_ID; if (!clientId) throw new Error(`No client id for type ${type}`) - const { scopes, authBaseUrl: baseUrl } = config; + const { scopes, urls: urls } = config; + const { authBaseUrl: baseUrl } = urls; + + if(!baseUrl) throw new Error(`No authBaseUrl found for type ${type}`) - if (!baseUrl) { + //construct the baseAuthUrl based on the fact that client may use custom subdomain + const BASE_URL: string = data.SUBDOMAIN ? data.SUBDOMAIN + baseUrl : baseUrl; + + if (!baseUrl || !BASE_URL) { throw new Error(`Unsupported provider: ${providerName}`); } // Default URL structure let params = `client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&state=${state}`; + const providersWithoutScopes = ["pipedrive", "clickup", "aha", "freeagent", "teamwork", "attio", "close", "teamleader", 'getresponse'] + // Adding scope for providers that require it, except for 'pipedrive' - if (providerName !== "pipedrive") { + if (!providersWithoutScopes.includes(providerName) ) { params += `&scope=${encodeURIComponent(scopes)}`; } @@ -109,12 +127,15 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { case "gitlab": params += "&code_challenge=&code_challenge_method="; break; + case "gorgias": + params = `&response_type=code&nonce=${randomString()}`; + break; default: // For most providers, response_type=code is common params += "&response_type=code"; } - const finalAuthUrl = `${baseUrl}?${params}`; + const finalAuthUrl = `${BASE_URL}?${params}`; console.log("Final Authentication : ", finalAuthUrl); return finalAuthUrl; } diff --git a/packages/shared/src/enum.ts b/packages/shared/src/enum.ts index f0972cf6e..9ce5de31a 100644 --- a/packages/shared/src/enum.ts +++ b/packages/shared/src/enum.ts @@ -24,7 +24,6 @@ export enum TicketingProviders { GITHUB = 'github', JIRA = 'jira', GORGIAS = 'gorgias', - CLICKUP = 'clickup', } export enum AccountingProviders { diff --git a/packages/shared/src/envConfig.ts b/packages/shared/src/envConfig.ts index ddfc07d3f..1f8acfd4a 100644 --- a/packages/shared/src/envConfig.ts +++ b/packages/shared/src/envConfig.ts @@ -91,10 +91,9 @@ export function needsSubdomain(provider: string, vertical: string): boolean { // Extract the provider's config const providerConfig = providersConfig[vertical][provider]; - // Check if authBaseUrl and apiUrl start with a '/' - const authBaseUrlStartsWithSlash = providerConfig.authBaseUrl.startsWith('/'); - const apiUrlStartsWithSlash = providerConfig.apiUrl!.startsWith('/'); - - // Return true if both URLs start with a '/', otherwise false - return authBaseUrlStartsWithSlash && apiUrlStartsWithSlash; + const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.startsWith('/'); + const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.startsWith('/'); + const apiUrlIsBlank = providerConfig.urls.apiUrl! == ""; + + return authBaseUrlStartsWithSlash || apiUrlStartsWithSlash || apiUrlIsBlank; } diff --git a/packages/shared/src/providers.ts b/packages/shared/src/providers.ts index 89895a992..f2c3bc4f7 100644 --- a/packages/shared/src/providers.ts +++ b/packages/shared/src/providers.ts @@ -7,7 +7,7 @@ export const CRM_PROVIDERS = ['zoho', 'zendesk', 'hubspot', 'pipedrive', 'attio' export const HRIS_PROVIDERS = ['']; export const ATS_PROVIDERS = ['']; export const ACCOUNTING_PROVIDERS = ['']; -export const TICKETING_PROVIDERS = ['zendesk', 'front', 'github', 'jira', 'gorgias', 'clickup']; +export const TICKETING_PROVIDERS = ['zendesk', 'front', 'github', 'jira', 'gorgias']; export const MARKETING_AUTOMATION_PROVIDERS = ['']; export const FILE_STORAGE_PROVIDERS = ['']; diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 87f6e0052..2613f2a7f 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -4,16 +4,19 @@ export enum AuthStrategy { basic } -// TODO : remove clientId export type ProviderConfig = { scopes: string; - authBaseUrl: string; logoPath: string; description: string; active?: boolean; - apiUrl: string; customPropertiesUrl?: string; authStrategy?: AuthStrategy; + urls: { + docsUrl: string; + apiUrl: string; + authBaseUrl?: string; //url used to authorize an application on behalf of the user (only when authStrategy is oauth2) + customPropertiesUrl?: string; + } }; type VerticalConfig = { @@ -22,53 +25,69 @@ type VerticalConfig = { export type ProvidersConfig = { [vertical: string]: VerticalConfig; -}; - +} -//If authBaseUrl or apiUrl both start with / it means a subdomain is likely needed +// If authBaseUrl or apiUrl both start with / it means a subdomain is likely needed +// If authBaseUrl is blank then it must be manually built in the client given the provider (meaning its not deterministic) export const providersConfig: ProvidersConfig = { 'crm': { 'hubspot': { 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', + urls: { + docsUrl: "https://developers.hubspot.com/docs/api/crm/understanding-the-crm", + authBaseUrl: 'https://app-eu1.hubspot.com/oauth/authorize', + apiUrl: 'https://api.hubapi.com/crm/v3', + customPropertiesUrl: '/properties/v1/contacts/properties', + }, logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png", description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - apiUrl: 'https://api.hubapi.com', - customPropertiesUrl: '/properties/v1/contacts/properties', authStrategy: AuthStrategy.oauth2 }, 'attio': { scopes: 'record_permission:read', - authBaseUrl: 'https://app.attio.com/authorize', + urls: { + docsUrl: "https://developers.attio.com/reference", + authBaseUrl: 'https://app.attio.com/authorize', + apiUrl: 'https://api.attio.com/v2', + customPropertiesUrl: '/docs/standard-objects-people', + }, logoPath: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSJWZsShi0G6mZ451MngEvQrmJ2JIGH-AF8JyFU-q-n3w&s", description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - apiUrl: 'https://developers.attio.com', - customPropertiesUrl: '/docs/standard-objects-people', authStrategy: AuthStrategy.oauth2 }, 'zoho': { scopes: 'ZohoCRM.modules.ALL', - authBaseUrl: 'https://accounts.zoho.eu/oauth/v2/auth', + urls: { + docsUrl: "https://www.zoho.com/crm/developer/docs/api/v5/", + authBaseUrl: '/oauth/v2/auth', + apiUrl: '/crm/v3', + customPropertiesUrl: '/settings/fields?module=Contact', + }, logoPath: 'https://assets-global.website-files.com/64f68d43d25e5962af5f82dd/64f68d43d25e5962af5f9812_64ad8bbe47c78358489b29fc_645e3ccf636a8d659f320e25_Group%25252012.png', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - apiUrl: 'https://www.zohoapis.eu/crm/v3', - customPropertiesUrl: '/settings/fields?module=Contact', authStrategy: AuthStrategy.oauth2 }, 'pipedrive': { scopes: 'Pipedrive_Scope', - authBaseUrl: 'https://oauth.pipedrive.com/oauth/authorize', + urls: { + docsUrl: "https://developers.pipedrive.com/docs/api/v1", + authBaseUrl: 'https://oauth.pipedrive.com/oauth/authorize', + apiUrl: 'https://api.pipedrive.com/v1', + customPropertiesUrl: '/v1/personFields', + }, logoPath: 'https://asset.brandfetch.io/idZG_U1qqs/ideqSFbb2E.jpeg', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - apiUrl: 'https://api.pipedrive.com', - customPropertiesUrl: '/v1/personFields', authStrategy: AuthStrategy.oauth2 }, + //todo 'freshsales': { scopes: '', - authBaseUrl: '', - apiUrl: '', + urls: { + docsUrl: "", + authBaseUrl: '', + apiUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/Mwgb5c2sVHGHoDlthAYPnMGekEOzsvMR5zotxskrl0erKTW-xpZbuIXn7AEIqvrRHQ', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -76,17 +95,23 @@ export const providersConfig: ProvidersConfig = { }, 'zendesk': { scopes: 'read write', - authBaseUrl: 'https://api.getbase.com/oauth2/authorize', + urls: { + docsUrl: "https://developer.zendesk.com/api-reference/sales-crm/introduction/", + authBaseUrl: 'https://api.getbase.com/oauth2/authorize', + apiUrl: 'https://api.getbase.com/v2', + customPropertiesUrl: '/contact/custom_fields', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRNKVceZGVM7PbARp_2bjdOICUxlpS5B29UYlurvh6Z2Q&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - apiUrl: 'https://api.getbase.com/v2', - customPropertiesUrl: '/contact/custom_fields', authStrategy: AuthStrategy.oauth2 }, 'accelo': { scopes: '', - authBaseUrl: 'https://{deployment}.api.accelo.com/oauth2/v0/authorize', - apiUrl: '', + urls: { + docsUrl: "https://api.accelo.com/docs/#introduction", + authBaseUrl: '/oauth2/v0/authorize', + apiUrl: '/api/v0', + }, logoPath: 'https://play-lh.googleusercontent.com/j63K2u8ZXukgPs8QPgyXfyoxuNBl_ST7gLx5DEFeczCTtM9e5JNpDjjBy32qLxFS7p0', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -94,8 +119,10 @@ export const providersConfig: ProvidersConfig = { }, 'active_campaign': { scopes: '', - authBaseUrl: '', - apiUrl: '', + urls: { + docsUrl: "https://developers.activecampaign.com/reference/overview", + apiUrl: '/api/3', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSymrBOaXpQab_5RPRZfiOXU7h9dfsduGZeCaZZw59xJA&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -103,8 +130,10 @@ export const providersConfig: ProvidersConfig = { }, 'affinity': { scopes: '', - authBaseUrl: '', - apiUrl: 'https://api.affinity.co', + urls: { + docsUrl: "https://api-docs.affinity.co/#getting-started", + apiUrl: 'https://api.affinity.co', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTMRfcwBA9Jn9z9dJQgY3f_H-bBeUzl-jRHNOm8xrmwtA&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -112,8 +141,11 @@ export const providersConfig: ProvidersConfig = { }, 'capsule': { scopes: '', - authBaseUrl: 'https://api.capsulecrm.com/oauth/authorise', - apiUrl: 'https://api.capsulecrm.com/api/v2', + urls: { + docsUrl: "https://developer.capsulecrm.com/", + authBaseUrl: 'https://api.capsulecrm.com/oauth/authorise', + apiUrl: 'https://api.capsulecrm.com/api/v2', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSjS3qFlJJbQ802nGEV9w2GEgmnAIgJj6JJxe14cH6Wuw&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -121,17 +153,23 @@ export const providersConfig: ProvidersConfig = { }, 'close': { scopes: '', - authBaseUrl: 'https://app.close.com/oauth2/authorize', - apiUrl: 'https://api.close.com/api/v1', + urls: { + docsUrl: "https://developer.close.com/", + authBaseUrl: 'https://app.close.com/oauth2/authorize', + apiUrl: 'https://api.close.com/api/v1', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEH77yPBUkStmoc1ZtgJS4XeBmQiaq_Q1vgF5oerOGbg&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, - authStrategy: AuthStrategy.api_key + authStrategy: AuthStrategy.oauth2 }, 'copper': { scopes: '', - authBaseUrl: 'https://app.copper.com/oauth/authorize', - apiUrl: 'https://api.copper.com/developer_api/v1', + urls: { + docsUrl: "https://developer.copper.com/index.html", + authBaseUrl: 'https://app.copper.com/oauth/authorize', + apiUrl: 'https://api.copper.com/developer_api/v1', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRVa1YDciibzviRJxGovqH4gNgPxpZUAHEz36Bwnj54uQ&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -139,70 +177,95 @@ export const providersConfig: ProvidersConfig = { }, 'insightly': { scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://api.insightly.com/v3.1/Help#!/Overview/Introduction", + apiUrl: '/v3.1', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, authStrategy: AuthStrategy.api_key, - apiUrl: '' }, 'keap': { scopes: '', - authBaseUrl: 'https://accounts.infusionsoft.com/app/oauth/authorize', - apiUrl: 'https://api.infusionsoft.com/crm/rest/v2', + urls: { + docsUrl: "https://developer.infusionsoft.com/docs/restv2/", + authBaseUrl: 'https://accounts.infusionsoft.com/app/oauth/authorize', + apiUrl: 'https://api.infusionsoft.com/crm/rest/v2', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRPYsWSMe9KVWgCIQ8fw-vBOnfTlZaSS6p_43ZhEIx51A&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, authStrategy: AuthStrategy.oauth2 }, + //todo 'microsoft_dynamics_sales': { scopes: '', - authBaseUrl: '', - apiUrl: '', + urls: { + docsUrl: "", + authBaseUrl: '', + apiUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, + //todo 'nutshell': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developers.nutshell.com/", + apiUrl: '/api/v1/json', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRbCONyN9DCKfd4E8pzIdItl5VqPTEErpoEn9vHCgblRg&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, - authStrategy: AuthStrategy.api_key + authStrategy: AuthStrategy.basic }, + //todo 'pipeliner': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://pipeliner.stoplight.io/docs/api-docs", + apiUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/rK9Qv_w9C8Py_aLZdQQDobNdHWSG8KL4dj3cBBQLcimVu-ctxwujA4VE442lIpZ65AE', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, authStrategy: AuthStrategy.api_key }, 'salesflare': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://api.salesflare.com/docs#section/Introduction/Getting-Started", + apiUrl: 'https://api.salesflare.com', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTesqSVCSaCDrjedsKbepr14iJPySzUwrh7Fg9MhgKh9w&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, authStrategy: AuthStrategy.api_key }, + //todo 'salesforce': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + authBaseUrl: '', + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTgL4FJb-GptGfxDDkWbIX2CjIM77t5q-d7eCFY6sGsHA&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, authStrategy: AuthStrategy.oauth2 }, + //todo 'sugarcrm': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + authBaseUrl: '', + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQftNERc1ImBHm8MXXuWdhQiFYwW-dXNcogRL1UV8JyHFQGY2BbsbpwKvERwKRB39RH6zw&usqp=CAU', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -210,8 +273,11 @@ export const providersConfig: ProvidersConfig = { }, 'teamleader': { scopes: '', - authBaseUrl: 'https://focus.teamleader.eu/oauth2/authorize', - apiUrl: 'https://api.focus.teamleader.eu', + urls: { + docsUrl: "https://developer.teamleader.eu/#/introduction/ap-what?", + authBaseUrl: 'https://focus.teamleader.eu/oauth2/authorize', + apiUrl: 'https://api.focus.teamleader.eu', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTE99rDOwXdRYGET0oeSCqK2kB02slJxZtTeBC79pb8IQ&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, @@ -219,26 +285,36 @@ export const providersConfig: ProvidersConfig = { }, 'teamwork': { scopes: '', - authBaseUrl: 'https://www.teamwork.com/launchpad/login', - apiUrl: '', + urls: { + docsUrl: "https://apidocs.teamwork.com/guides/teamwork/getting-started-with-the-teamwork-com-api", + authBaseUrl: 'https://www.teamwork.com/launchpad/login', + apiUrl: '', //on purpose blank => everything is contained inside the accountUrl(subdomain) + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQr6gYDMNagMEicBb4dhKz4BC1fQs72In45QF7Ls6-moA&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, authStrategy: AuthStrategy.oauth2 }, + //todo 'vtiger': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRcUYrYD8lnaFaDN93vwjHhksKJUG3rqlb1TCFC__oPBw&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false, authStrategy: AuthStrategy.basic }, + //todo 'twenty': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + authBaseUrl: '', + apiUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false @@ -247,294 +323,408 @@ export const providersConfig: ProvidersConfig = { 'ticketing': { 'front': { scopes: '', - authBaseUrl: 'https://app.frontapp.com/oauth/authorize', + urls: { + docsUrl: "", + authBaseUrl: 'https://app.frontapp.com/oauth/authorize', + apiUrl: 'https://api2.frontapp.com', + }, logoPath: 'https://i.pinimg.com/originals/43/a2/43/43a24316bd773798c7638ad98521eb81.png', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - apiUrl: 'https://api2.frontapp.com', authStrategy: AuthStrategy.oauth2 }, 'zendesk': { scopes: 'read write', - authBaseUrl: 'https://panora7548.zendesk.com/oauth/authorizations/new', + urls: { + docsUrl: "https://developer.zendesk.com/api-reference/sales-crm/introduction/", + authBaseUrl: '/oauth/authorizations/new', + apiUrl: '/api/v2', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRNKVceZGVM7PbARp_2bjdOICUxlpS5B29UYlurvh6Z2Q&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - apiUrl: '/api/v2', authStrategy: AuthStrategy.oauth2 }, 'gorgias': { scopes: 'write:all openid email profile offline', - authBaseUrl: '/oauth/authorize', + urls: { + docsUrl: "https://developers.gorgias.com/reference/introduction", + apiUrl: '/api', + authBaseUrl: '/oauth/authorize', + }, logoPath: 'https://x5h8w2v3.rocketcdn.me/wp-content/uploads/2020/09/FS-AFFI-00660Gorgias.png', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - apiUrl: '/api', authStrategy: AuthStrategy.oauth2 }, 'jira': { scopes: 'read:jira-work manage:jira-project manage:jira-data-provider manage:jira-webhook write:jira-work manage:jira-configuration read:jira-user offline_access', - authBaseUrl: 'https://auth.atlassian.com/authorize', + urls: { + docsUrl: "", + apiUrl: '/rest/api/3', + authBaseUrl: 'https://auth.atlassian.com/authorize', + }, logoPath: 'https://logowik.com/content/uploads/images/jira3124.jpg', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - apiUrl: '/rest/api/3', authStrategy: AuthStrategy.oauth2 }, + //todo 'jira_service_mgmt': { - apiUrl: '', scopes: 'read:servicedesk-request manage:servicedesk-customer read:servicemanagement-insight-objects write:servicedesk-request offline_access', - authBaseUrl: 'https://auth.atlassian.com/authorize', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: 'https://auth.atlassian.com/authorize' + }, logoPath: '', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false, authStrategy: AuthStrategy.oauth2 }, 'linear': { - apiUrl: '', scopes: 'read,write', - authBaseUrl: 'https://linear.app/oauth/authorize', + urls: { + docsUrl: "https://developers.linear.app/docs", + apiUrl: 'https://api.linear.app/graphql', + authBaseUrl: 'https://linear.app/oauth/authorize', + }, logoPath: '', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false, authStrategy: AuthStrategy.oauth2 }, 'gitlab': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://gitlab.example.com/oauth/authorize', + urls: { + docsUrl: "https://docs.gitlab.com/ee/api/rest/#", + apiUrl: '/api/v4', + authBaseUrl: '/oauth/authorize', + }, logoPath: '', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false, authStrategy: AuthStrategy.oauth2 }, 'clickup': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://app.clickup.com/api', + urls: { + docsUrl: "https://clickup.com/api/", + apiUrl: 'https://api.clickup.com/v2', + authBaseUrl: 'https://app.clickup.com/api', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRewJj9y5yKzSCf-qGgjmdLagEhxfnlZ7TUsvukbfZaIg&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false, authStrategy: AuthStrategy.oauth2 }, 'github': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://docs.github.com/fr/rest", + apiUrl: 'https://api.github.com', + authBaseUrl: 'https://github.com/login/oauth/authorize', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false, authStrategy: AuthStrategy.oauth2 }, 'aha': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://.aha.io/oauth/authorize', + urls: { + docsUrl: "https://www.aha.io/api", + apiUrl: '/api/v1', + authBaseUrl: '/oauth/authorize', + }, logoPath: 'https://www.aha.io/aha-logo-2x.png', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false, authStrategy: AuthStrategy.oauth2 }, 'asana': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://developers.asana.com/docs/overview", + apiUrl: 'https://app.asana.com/api/1.0', + authBaseUrl: 'https://app.asana.com/-/oauth_authorize', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, + //todo 'azure': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'basecamp': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'bitbucket': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, 'dixa': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://docs.dixa.io/docs/", + apiUrl: 'https://dev.dixa.io', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'freshdesk': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'freshservice': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, 'gladly': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://developer.gladly.com/rest/", + apiUrl: '/api/v1', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.basic }, + //todo 'height': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, 'help_scout': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://developer.helpscout.com/docs-api/", + apiUrl: 'https://docsapi.helpscout.net/v1', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, 'hive': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://developers.hive.com/reference/introduction", + apiUrl: 'https://app.hive.com/api/v1', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'intercom': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'ironclad': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, 'kustomer': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://developer.kustomer.com/kustomer-api-docs/reference/introduction", + apiUrl: 'https://api.kustomerapp.com', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'pivotal_tracker': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'rally': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, 'reamaze': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://www.reamaze.com/api", + apiUrl: '/api/v1', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'salesforce': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'servicenow': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, 'shortcut': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://developer.shortcut.com/api/rest/v3", + apiUrl: 'https://api.app.shortcut.com', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'spotdraft': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'teamwork': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'trello': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: 'https://api.app.shortcut.com', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, 'wrike': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "https://developers.wrike.com/overview/", + apiUrl: '/api/v4', + authBaseUrl: 'https://login.wrike.com/oauth2/authorize/v4', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, 'zoho_bugtracker': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, 'zoho_desk': { - apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + urls: { + docsUrl: "", + apiUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false @@ -542,1071 +732,1390 @@ export const providersConfig: ProvidersConfig = { }, 'accounting': { 'pennylane': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://pennylane.readme.io/docs/getting-started", + apiUrl: 'https://app.pennylane.com/api/external/v1', + authBaseUrl: 'https://app.pennylane.com/oauth/authorize', + }, logoPath: 'https://cdn-images-1.medium.com/max/1200/1*wk7CNGik_1Szbt7s1fNZxA.png', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, 'freshbooks': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://www.freshbooks.com/api/start", + apiUrl: 'https://api.freshbooks.com', + authBaseUrl: 'https://auth.freshbooks.com/oauth/authorize', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, + //todo 'clearbooks': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "", + authBaseUrl: '', + }, logoPath: 'https://s3-eu-west-1.amazonaws.com/clearbooks-marketing/media-centre/MediaCentre/clear-books/CMYK/icon/clear-books-icon-cmyk.png', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'freeagent': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://dev.freeagent.com/docs/quick_start", + apiUrl: 'https://api.freeagent.com/v2', + authBaseUrl: 'https://api.freeagent.com/v2/approve_app', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQU-fob0b9pBNQdm80usnYa2yWdagm3eeBDH-870vSmfg&s', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, 'sage': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developer.sage.com/accounting/reference/", + apiUrl: 'https://api.accounting.sage.com/v3.1', + authBaseUrl: 'https://www.sageone.com/oauth2/auth/central?filter=apiv3.1', + }, logoPath: 'https://upload.wikimedia.org/wikipedia/en/thumb/b/b7/Sage_Group_logo_2022.svg/2560px-Sage_Group_logo_2022.svg.png', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, + //todo 'sage_intacct': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, + //todo 'microsoft_dynamics': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'moneybird': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developer.moneybird.com/", + apiUrl: 'https://moneybird.com/api/v2', + authBaseUrl: 'https://moneybird.com/oauth/authorize', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, + //todo 'netsuite': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'quickbooks': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developer.intuit.com/app/developer/qbo/docs/develop", + apiUrl: 'https://quickbooks.api.intuit.com/v3', + authBaseUrl: 'https://appcenter.intuit.com/connect/oauth2', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, + //todo 'workday': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'wave_financial': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developer.waveapps.com/hc/en-us/articles/360019968212-API-Reference", + apiUrl: 'https://gql.waveapps.com/graphql/public', + authBaseUrl: 'https://api.waveapps.com/oauth2/authorize/', + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, }, 'marketing_automation': { 'active_campaign': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developers.activecampaign.com/reference/overview", + apiUrl: "/api/3" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'customerio': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://customer.io/docs/api/track/", + apiUrl: "https://track.customer.io/api/" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'getresponse': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + authBaseUrl: 'https://app.getresponse.com/oauth2_authorize.html', + docsUrl: "https://apidocs.getresponse.com/v3", + apiUrl: "https://api.getresponse.com/v3" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, + //todo 'hubspot_marketing_hub': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, + //todo 'keap': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + authBaseUrl: 'https://accounts.infusionsoft.com/app/oauth/authorize', + docsUrl: "https://developer.infusionsoft.com/docs/rest/", + apiUrl: "https://api.infusionsoft.com/crm/rest/v1/account/profile" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, - 'klaviyo': { - apiUrl: '', + 'klaviyo': { scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developers.klaviyo.com/en/reference/api_overview", + apiUrl: "https://a.klaviyo.com/api" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, 'mailchimp': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + authBaseUrl: "https://login.mailchimp.com/oauth2/authorize", + docsUrl: "https://mailchimp.com/developer/marketing/api/", + apiUrl: "" //todo + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, 'messagebird': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developers.messagebird.com/api/", + apiUrl: "https://rest.messagebird.com" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, 'podium': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + authBaseUrl: "https://api.podium.com/oauth/authorize", + docsUrl: "https://docs.podium.com/reference/introduction", + apiUrl: "https://api.podium.com/v4" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.oauth2 }, 'sendgrid': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://docs.sendgrid.com/for-developers/sending-email/api-getting-started", + apiUrl: "https://api.sendgrid.com/v3" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, - 'sendinblue': { - apiUrl: '', + 'brevo': { scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developers.brevo.com/docs/getting-started", + apiUrl: "https://api.brevo.com/v3" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, }, + //TODO 'ats': { + //todo 'applicantstack': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'ashby': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developers.ashbyhq.com", + apiUrl: "https://api.ashbyhq.com" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'bamboohr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://documentation.bamboohr.com/docs/getting-started", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, 'breezy': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developer.breezy.hr/reference/overview", + apiUrl: "https://api.breezy.hr/v3" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'bullhorn': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://bullhorn.github.io/rest-api-docs/", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'cats': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://docs.catsone.com/api/v3/", + apiUrl: "https://api.catsone.com/v3" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, 'clayhr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://clayhr.readme.io/", + apiUrl: "/rm/api/v3" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, + //todo 'clockwork': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, + //todo 'comeet': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'cornerstone_talentlink': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "https://developer.lumesse-talenthub.com/rest-api-developers-guide/1.21.33/index.html?page=rest-api&subpage=introduction", + apiUrl: "https://apiproxy.shared.lumessetalentlink.com/tlk/rest" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - active: false + active: false, + authStrategy: AuthStrategy.api_key }, 'engage_ats': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'eploy': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'fountain': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'freshteam': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'greenhouse': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'greenhouse_job_boards': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'harbour_ats': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'homerun': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'hrcloud': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'icims': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'infinite_brassring': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'jazzhr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'jobadder': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'jobscore': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'jobvite': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'lano': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'lever': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'occupop': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'oracle_fusion': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'oracle_taleo': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'personio_recruiting': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'pinpoint': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'polymer': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'recruiterflow': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'recruitive': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'sage_hr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'sap_successfactors': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'smartrecruiters': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'talentlyft': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'talentreef': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'teamtailor': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'tellent': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'tribepad': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'ukg_pro_recruiting': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'workable': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'workday': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'zoho_recruit': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, }, + //TODO 'hris': { '7shifts': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'adp_workforce_now': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'alexishr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'alliancehcm': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'altera_payroll': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'bamboohr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'breathe': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'ceridian_dayforce': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'charlie': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'charthop': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'clayhr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'cyberark': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'deel': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'employment_hero': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'factorial': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'freshteam': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'google_workspace': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'gusto': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'hibob': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'hrcloud': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'hrpartner': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'humaans': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'humi': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'insperity_premier': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'active_campaign': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'intellli_hr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'iris_cascade': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'jumpcloud': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'justworks': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'kallidus': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'keka': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'kenjo': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'lano': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'lucca': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'microsoft_entra_id': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'namely': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'nmbrs': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'officient': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'okta': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'onelogin': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'oracle_hcm': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'oyster_hr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'paycaptain': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'paychex': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'paycor': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'payfit': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'paylocity': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'people_hr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'personio': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'pingone': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'proliant': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'remote': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'sage_hr': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'sap_successfactors': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'sesame': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'square_payroll': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'trinet': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'trinet_hr_platform': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'ukg_pro': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'ukg_pro_workforce': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'ukg_ready': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'workday': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, 'zoho_people': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false @@ -1642,9 +2151,12 @@ export const getDescription = (name: string): string | null => { type Provider = { name: string; - apiUrl: string; + urls: { + docsUrl: string; + apiUrl: string; + authBaseUrl?: string | null; + }; scopes: string; - authBaseUrl: string; logoPath: string; description?: string; }; @@ -1653,9 +2165,12 @@ export function providersArray(vertical: string): Provider[] { const activeProviders = getActiveProvidersForVertical(vertical); return Object.entries(activeProviders).map(([providerName, config]) => ({ name: providerName, - apiUrl: config.apiUrl, + urls: { + docsUrl: config.urls.docsUrl, + apiUrl: config.urls.apiUrl, + authBaseUrl: config.urls.authBaseUrl, + }, scopes: config.scopes, - authBaseUrl: config.authBaseUrl, logoPath: config.logoPath, description: config.description, }));