diff --git a/.github/workflows/docker-compose.dev.healthcheck.yml b/.github/workflows/docker-compose.dev.healthcheck.yml deleted file mode 100644 index 91f17b09c..000000000 --- a/.github/workflows/docker-compose.dev.healthcheck.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: docker-compose.dev.yml - Healthcheck - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -jobs: - docker: - name: Run containers - runs-on: ubuntu-latest - env: - POSTGRES_USER: ${{ secrets.POSTGRES_USER }} - POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} - POSTGRES_DB: ${{ secrets.POSTGRES_DB }} - DISTRIBUTION: ${{ secrets.DISTRIBUTION }} - JWT_SECRET: ${{ secrets.JWT_SECRET }} - REDIS_HOST: ${{ secrets.REDIS_HOST }} - NEXT_PUBLIC_BACKEND_DOMAIN: ${{ secrets.NEXT_PUBLIC_BACKEND_DOMAIN }} - NEXT_PUBLIC_MAGIC_LINK_DOMAIN: ${{ secrets.NEXT_PUBLIC_MAGIC_LINK_DOMAIN }} - NEXT_PUBLIC_STYTCH_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_STYTCH_PROJECT_ID }} - NEXT_PUBLIC_STYTCH_SECRET: ${{ secrets.NEXT_PUBLIC_STYTCH_SECRET }} - NEXT_PUBLIC_STYTCH_PROJECT_ENV: ${{ secrets.NEXT_PUBLIC_STYTCH_PROJECT_ENV }} - NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN: ${{ secrets.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN }} - steps: - - uses: actions/checkout@v3 - - uses: isbang/compose-action@v1.5.1 - with: - compose-file: "./docker-compose.dev.yml" \ No newline at end of file diff --git a/apps/client-ts/src/components/Auth/b2c/LoginWithStytchSDKUI.tsx b/apps/client-ts/src/components/Auth/b2c/LoginWithStytchSDKUI.tsx index f961ef441..15674fe51 100644 --- a/apps/client-ts/src/components/Auth/b2c/LoginWithStytchSDKUI.tsx +++ b/apps/client-ts/src/components/Auth/b2c/LoginWithStytchSDKUI.tsx @@ -4,6 +4,7 @@ import { StytchLogin } from '@stytch/nextjs'; import { StytchLoginConfig, OAuthProviders, Products, StyleConfig, StytchEvent, StytchError } from '@stytch/vanilla-js'; import { getDomainFromWindow } from '@/lib/stytch/urlUtils'; import { Button } from '@/components/ui/button'; +import { Icons } from '@/components/shared/icons'; const sdkStyle: StyleConfig = { fontFamily: '"Helvetica New", Helvetica, sans-serif', @@ -31,18 +32,47 @@ const callbackConfig = { onError: (error: StytchError) => console.log(error), } +const getOauthUrl = (provider: string) => { + const isTest = process.env.NEXT_PUBLIC_STYTCH_PROJECT_ENV == 'test'; + const baseStytch = isTest ? "test" : "api" + return `https://${baseStytch}.stytch.com/v1/public/oauth/${provider.toLowerCase()}/start?public_token=${process.env.NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN}`; +} + const LoginWithStytchSDKUI = () => { return ( <> -
- - - - - - -
- +
+ +
+
+
+ +
+
+ + Or continue with + +
+
+ + ) } diff --git a/apps/client-ts/src/components/shared/icons.tsx b/apps/client-ts/src/components/shared/icons.tsx new file mode 100644 index 000000000..a0362adcf --- /dev/null +++ b/apps/client-ts/src/components/shared/icons.tsx @@ -0,0 +1,26 @@ +import { LucideProps } from "lucide-react"; + + + + export const Icons = { + gitHub: ({ ...props }: LucideProps) => ( + + ), + google: ({ ...props }: LucideProps) => ( + + ) + } \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 19280187f..4d3ebf949 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} + + + + + + + + + restart: unless-stopped ports: diff --git a/docker-compose.source.yml b/docker-compose.source.yml index 79c4bbef0..a28722d7a 100644 --- a/docker-compose.source.yml +++ b/docker-compose.source.yml @@ -91,6 +91,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} + + + + + + + + + restart: unless-stopped ports: - 3000:3000 diff --git a/docker-compose.yml b/docker-compose.yml index fb8314689..7501f73ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -86,6 +86,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} + + + + + + + + + restart: unless-stopped ports: diff --git a/docs/open-source/contributors.mdx b/docs/open-source/contributors.mdx index 27a73afb3..7940c1e8a 100644 --- a/docs/open-source/contributors.mdx +++ b/docs/open-source/contributors.mdx @@ -15,18 +15,23 @@ We made a docker file that builds Panora from sources, specifically to help you -```bash cp .env.example .env ``` + +```bash +cp .env.example .env +``` + - ```bash rm -rf node_modules .pnpm-store ./packages/api/dist - ./packages/api/node_modules ./apps/webapp/node_modules - ./apps/frontend_snippet/node_modules ``` + ```bash + rm -rf node_modules .pnpm-store ./packages/api/dist ./packages/api/node_modules ./apps/client-ts/node_modules ./apps/embedded-catalog/react/node_modules ./apps/magic-link/node_modules ./packages/shared/dist + ``` - ```bash echo -e "node-linker=hoisted\npackage-import-method=clone-or-copy" > - .npmrc ``` + ```bash + echo -e "node-linker=hoisted\npackage-import-method=clone-or-copy" > .npmrc + ``` diff --git a/packages/api/scripts/oauthConnector.js b/packages/api/scripts/oauthConnector.js index 6e6e1b939..33b572217 100755 --- a/packages/api/scripts/oauthConnector.js +++ b/packages/api/scripts/oauthConnector.js @@ -43,7 +43,7 @@ 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 { +import { CallbackParams, RefreshParams, I${verticalUpper}ConnectionService, @@ -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,15 +166,16 @@ 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( "", @@ -198,7 +200,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 +288,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 +300,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..d7562ba42 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -7,6 +7,7 @@ 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'; export type StateDataType = { projectId: string; @@ -22,6 +23,7 @@ export class ConnectionsController { constructor( private readonly crmConnectionsService: CrmConnectionsService, private readonly ticketingConnectionsService: TicketingConnectionsService, + private readonly accountingConnectionsService: AccountingConnectionsService, private logger: LoggerService, private prisma: PrismaService, ) { @@ -70,6 +72,12 @@ export class ConnectionsController { case ProviderVertical.ATS: break; case ProviderVertical.Accounting: + this.accountingConnectionsService.handleAccountingCallBack( + projectId, + linkedUserId, + providerName, + code, + ); break; case ProviderVertical.FileStorage: break; diff --git a/packages/api/src/@core/connections/connections.module.ts b/packages/api/src/@core/connections/connections.module.ts index 308c8abc8..85cd4b220 100644 --- a/packages/api/src/@core/connections/connections.module.ts +++ b/packages/api/src/@core/connections/connections.module.ts @@ -4,11 +4,20 @@ 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'; @Module({ controllers: [ConnectionsController], - imports: [CrmConnectionModule, TicketingConnectionModule], + imports: [ + CrmConnectionModule, + TicketingConnectionModule, + AccountingConnectionModule, + ], providers: [LoggerService, PrismaService], - exports: [CrmConnectionModule, TicketingConnectionModule], + exports: [ + CrmConnectionModule, + TicketingConnectionModule, + AccountingConnectionModule, + ], }) 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/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/connections/ticketing/services/asana/asana.service.ts b/packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts new file mode 100644 index 000000000..5d72478a2 --- /dev/null +++ b/packages/api/src/@core/connections/ticketing/services/asana/asana.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, + ITicketingConnectionService, +} 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 AsanaOAuthResponse = { + access_token: string; + refresh_token: string; + expires_in: number; + token_type: string; + data: { + id: number; + gid: string; + name: string; + email: string; + }; +}; + +@Injectable() +export class AsanaConnectionService implements ITicketingConnectionService { + 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(AsanaConnectionService.name); + this.registry.registerService('asana', this); + this.type = providerToType('asana', 'ticketing', 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: 'asana', + vertical: 'ticketing', + }, + }); + + //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.asana.com/-/oauth_token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: AsanaOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : asana 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['ticketing']['asana'].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: 'asana', + vertical: 'ticketing', + token_type: 'oauth', + account_url: providersConfig['ticketing']['asana'].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, 'asana', 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.asana.com/-/oauth_token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, + }, + }, + ); + const data: AsanaOAuthResponse = 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 : asana '); + } catch (error) { + handleServiceError(error, this.logger, 'asana', Action.oauthRefresh); + } + } +} diff --git a/packages/api/src/@core/connections/ticketing/services/clickup/clickup.service.ts b/packages/api/src/@core/connections/ticketing/services/clickup/clickup.service.ts index 83248f2c0..b36c8df34 100644 --- a/packages/api/src/@core/connections/ticketing/services/clickup/clickup.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/clickup/clickup.service.ts @@ -12,7 +12,11 @@ import { ITicketingConnectionService, } 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'; @@ -98,6 +102,7 @@ export class ClickupConnectionService implements ITicketingConnectionService { provider_slug: 'clickup', vertical: 'ticketing', token_type: 'oauth', + account_url: providersConfig['ticketing']['clickup'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: '', expiration_timestamp: '', diff --git a/packages/api/src/@core/connections/ticketing/services/front/front.service.ts b/packages/api/src/@core/connections/ticketing/services/front/front.service.ts index 9f0aed530..e1fb6381a 100644 --- a/packages/api/src/@core/connections/ticketing/services/front/front.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/front/front.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'; @@ -106,6 +106,7 @@ export class FrontConnectionService implements ITicketingConnectionService { provider_slug: 'front', vertical: 'ticketing', token_type: 'oauth', + account_url: providersConfig['ticketing']['front'].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/ticketing/services/github/github.service.ts b/packages/api/src/@core/connections/ticketing/services/github/github.service.ts index 918392e77..b668b1116 100644 --- a/packages/api/src/@core/connections/ticketing/services/github/github.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/github/github.service.ts @@ -12,7 +12,11 @@ import { ITicketingConnectionService, } 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'; @@ -106,6 +110,7 @@ export class GithubConnectionService implements ITicketingConnectionService { connection_token: connection_token, provider_slug: 'github', vertical: 'ticketing', + account_url: providersConfig['ticketing']['github'].urls.apiUrl, token_type: 'oauth', access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), diff --git a/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.service.ts b/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.service.ts index d9794346f..f00ea7ab4 100644 --- a/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.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'; @@ -82,6 +82,8 @@ export class GitlabConnectionService implements ITicketingConnectionService { let db_res; const connection_token = uuidv4(); + const BASE_API_URL = + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['gitlab'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ @@ -106,6 +108,7 @@ export class GitlabConnectionService implements ITicketingConnectionService { provider_slug: 'gitlab', vertical: 'ticketing', token_type: 'oauth', + account_url: BASE_API_URL, 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/ticketing/services/gorgias/gorgias.service.ts b/packages/api/src/@core/connections/ticketing/services/gorgias/gorgias.service.ts index f9f1eeadc..1d8d5bb8f 100644 --- a/packages/api/src/@core/connections/ticketing/services/gorgias/gorgias.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/gorgias/gorgias.service.ts @@ -12,7 +12,11 @@ import { ITicketingConnectionService, } 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'; @@ -68,8 +72,9 @@ export class GorgiasConnectionService implements ITicketingConnectionService { code: code, grant_type: 'authorization_code', }); + const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/oauth/token`, + `${CREDENTIALS.SUBDOMAIN}/oauth/token`, formData.toString(), { headers: { @@ -85,6 +90,10 @@ export class GorgiasConnectionService implements ITicketingConnectionService { let db_res; const connection_token = uuidv4(); + //get the right BASE URL API + const BASE_API_URL = + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['gorgias'].urls.apiUrl; + if (isNotUnique) { db_res = await this.prisma.connections.update({ where: { @@ -93,7 +102,7 @@ export class GorgiasConnectionService implements ITicketingConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -109,7 +118,7 @@ export class GorgiasConnectionService implements ITicketingConnectionService { provider_slug: 'gorgias', vertical: 'ticketing', token_type: 'oauth', - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( @@ -145,7 +154,7 @@ export class GorgiasConnectionService implements ITicketingConnectionService { )) as OAuth2AuthData; const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/oauth/token`, + `${CREDENTIALS.SUBDOMAIN}/oauth/token`, formData.toString(), { headers: { diff --git a/packages/api/src/@core/connections/ticketing/services/jira/jira.service.ts b/packages/api/src/@core/connections/ticketing/services/jira/jira.service.ts index 458ef701d..b29e1bcd2 100644 --- a/packages/api/src/@core/connections/ticketing/services/jira/jira.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/jira/jira.service.ts @@ -102,7 +102,7 @@ export class JiraConnectionService implements ITicketingConnectionService { const sites_scopes: JiraCloudIdInformation[] = res_.data; let cloud_id: string; for (const site of sites_scopes) { - if (site.url == 'https://panora.atlassian.net') { + if (site.url == CREDENTIALS.SUBDOMAIN) { cloud_id = site.id; break; } diff --git a/packages/api/src/@core/connections/ticketing/services/jira_service_mgmt/jira.service.ts b/packages/api/src/@core/connections/ticketing/services/jira_service_mgmt/jira.service.ts index 33820e25c..cba849b77 100644 --- a/packages/api/src/@core/connections/ticketing/services/jira_service_mgmt/jira.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/jira_service_mgmt/jira.service.ts @@ -113,7 +113,7 @@ export class JiraServiceMgmtConnectionService let cloud_id: string; for (const site of sites_scopes) { //todo - if (site.url == 'https://panora.atlassian.net') { + if (site.url == CREDENTIALS.SUBDOMAIN) { cloud_id = site.id; break; } diff --git a/packages/api/src/@core/connections/ticketing/services/linear/linear.service.ts b/packages/api/src/@core/connections/ticketing/services/linear/linear.service.ts index 2e4338e7c..7ced77587 100644 --- a/packages/api/src/@core/connections/ticketing/services/linear/linear.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/linear/linear.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'; @@ -105,6 +105,7 @@ export class LinearConnectionService implements ITicketingConnectionService { provider_slug: 'linear', vertical: 'ticketing', token_type: 'oauth', + account_url: providersConfig['ticketing']['linear'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: '', expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts similarity index 70% rename from packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts rename to packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts index 226b8be58..4bc527610 100644 --- a/packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts @@ -9,21 +9,23 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { CallbackParams, RefreshParams, - ICrmConnectionService, + ITicketingConnectionService, } 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 WrikeOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string; + host: string; + token_type: string; }; @Injectable() -export class AffinityConnectionService implements ICrmConnectionService { +export class WrikeConnectionService implements ITicketingConnectionService { private readonly type: string; constructor( @@ -34,9 +36,9 @@ 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(WrikeConnectionService.name); + this.registry.registerService('wrike', this); + this.type = providerToType('wrike', 'ticketing', AuthStrategy.oauth2); } async handleCallback(opts: CallbackParams) { @@ -45,8 +47,8 @@ export class AffinityConnectionService implements ICrmConnectionService { const isNotUnique = await this.prisma.connections.findFirst({ where: { id_linked_user: linkedUserId, - provider_slug: `affinity`, - vertical: 'crm', + provider_slug: 'wrike', + vertical: 'ticketing', }, }); @@ -64,15 +66,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://login.wrike.com/oauth2/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, }, - }); - const data: AffinityOAuthResponse = res.data; + ); + const data: WrikeOAuthResponse = res.data; this.logger.log( - 'OAuth credentials : affinity ticketing ' + JSON.stringify(data), + 'OAuth credentials : wrike ticketing ' + JSON.stringify(data), ); let db_res; @@ -86,9 +91,11 @@ 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: + `https://${data.host}` + + providersConfig['ticketing']['wriker'].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 +106,16 @@ export class AffinityConnectionService implements ICrmConnectionService { data: { id_connection: uuidv4(), connection_token: connection_token, - provider_slug: 'affinity', - vertical: 'crm', + provider_slug: 'wrike', + vertical: 'ticketing', token_type: 'oauth', - account_url: '', + account_url: + `https://${data.host}` + + providersConfig['ticketing']['wriker'].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 +130,29 @@ export class AffinityConnectionService implements ICrmConnectionService { } return db_res; } catch (error) { - handleServiceError(error, this.logger, 'affinity', Action.oauthCallback); + handleServiceError(error, this.logger, 'wrike', 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 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('', formData.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - Authorization: `Basic JHt0aGlzLmVudi5nZXRgQWZmaW5pdHlTZWNyZXRgKCkuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgdGhpcy5lbnYuZ2V0YEFmZmluaXR5U2VjcmV0YCgpLkNMSUVOVF9TRUNSRVQKICAgICAgICAgICAgICB9`, }, }); - const data: AffinityOAuthResponse = res.data; + const data: WrikeOAuthResponse = res.data; await this.prisma.connections.update({ where: { id_connection: connectionId, @@ -153,13 +161,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() + Number(data.expires_in) * 1000, ), }, }); - this.logger.log('OAuth credentials updated : affinity '); + this.logger.log('OAuth credentials updated : wrike '); } catch (error) { - handleServiceError(error, this.logger, 'affinity', Action.oauthRefresh); + handleServiceError(error, this.logger, 'wrike', Action.oauthRefresh); } } } diff --git a/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.service.ts b/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.service.ts index 8a095dfee..d77c6abde 100644 --- a/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.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'; @@ -67,7 +67,7 @@ export class ZendeskConnectionService implements ITicketingConnectionService { //const subdomain = 'panora7548'; const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/oauth/tokens`, + `${CREDENTIALS.SUBDOMAIN}/oauth/tokens`, formData.toString(), { headers: { @@ -82,6 +82,9 @@ export class ZendeskConnectionService implements ITicketingConnectionService { let db_res; const connection_token = uuidv4(); + //get the right BASE URL API + const BASE_API_URL = + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['zendesk'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ @@ -90,7 +93,7 @@ export class ZendeskConnectionService implements ITicketingConnectionService { }, data: { access_token: this.cryptoService.encrypt(data.access_token), - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, refresh_token: '', expiration_timestamp: new Date(), //TODO status: 'valid', @@ -105,7 +108,7 @@ export class ZendeskConnectionService implements ITicketingConnectionService { provider_slug: 'zendesk', vertical: 'ticketing', token_type: 'oauth', - account_url: CREDENTIALS.SUBDOMAIN!, + account_url: BASE_API_URL, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: '', expiration_timestamp: new Date(), //TODO diff --git a/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts b/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts index 70cb5822b..d5b6bdee0 100644 --- a/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts +++ b/packages/api/src/@core/connections/ticketing/ticketing.connection.module.ts @@ -17,6 +17,9 @@ import { GitlabConnectionService } from './services/gitlab/gitlab.service'; import { ClickupConnectionService } from './services/clickup/clickup.service'; import { GorgiasConnectionService } from './services/gorgias/gorgias.service'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { AhaConnectionService } from './services/aha/aha.service'; +import { AsanaConnectionService } from './services/asana/asana.service'; +import { WrikeConnectionService } from './services/wrike/wrike.service'; @Module({ imports: [WebhookModule], @@ -39,6 +42,9 @@ import { ConnectionsStrategiesService } from '@@core/connections-strategies/conn GitlabConnectionService, ClickupConnectionService, GorgiasConnectionService, + AhaConnectionService, + AsanaConnectionService, + WrikeConnectionService, ], exports: [TicketingConnectionsService], }) 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..0d4bc02b6 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,11 @@ 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..44b2e88bf 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; + //TODO: handle case where apiUrl is == "" or starts with "/" const BASE_URL = `${providerUrl}${path}`; const connection = await this.prisma.connections.findFirst({ where: { diff --git a/packages/api/src/crm/company/services/hubspot/index.ts b/packages/api/src/crm/company/services/hubspot/index.ts index ce8ebb3c7..7514dc756 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,7 +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)}`) 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..0e2145a08 100644 --- a/packages/api/src/crm/contact/services/attio/index.ts +++ b/packages/api/src/crm/contact/services/attio/index.ts @@ -9,6 +9,7 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { ApiResponse } from '@@core/utils/types'; import { ServiceRegistry } from '../registry.service'; import { AttioContactInput, AttioContactOutput } from './types'; +import { providersConfig } from '@panora/shared'; @Injectable() export class AttioService implements IContactService { @@ -36,9 +37,8 @@ export class AttioService implements IContactService { vertical: 'crm', }, }); - const resp = await axios.post( - `https://api.attio.com/v2/objects/people/records`, + `${connection.account_url}/objects/people/records`, JSON.stringify({ data: contactData, }), diff --git a/packages/api/src/crm/contact/services/hubspot/index.ts b/packages/api/src/crm/contact/services/hubspot/index.ts index 37da950d7..a7b39e873 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,13 +86,12 @@ 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 queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) .join('&'); - const url = `${baseURL}?${queryString}`; + const url = `${connection.account_url}/?${queryString}`; const resp = await axios.get(url, { headers: { 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..2b5ddc9c3 100644 --- a/packages/api/src/crm/contact/services/zoho/index.ts +++ b/packages/api/src/crm/contact/services/zoho/index.ts @@ -36,8 +36,9 @@ export class ZohoService implements IContactService { vertical: 'crm', }, }); + //todo const resp = await axios.post( - `https://www.zohoapis.eu/crm/v3/Contacts`, + `${connection.account_url}/Contacts`, { data: [contactData] }, { headers: { @@ -80,7 +81,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..37ac7f2e7 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/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..70664fadf 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: { @@ -172,7 +172,7 @@ export class HubspotService implements IEngagementService { properties: engagementData, }; const resp = await axios.post( - `https://api.hubapi.com/crm/v3/objects/emails`, + `${connection.account_url}/objects/emails`, JSON.stringify(dataBody), { headers: { @@ -238,7 +238,7 @@ export class HubspotService implements IEngagementService { const commonPropertyNames = Object.keys(commonCallHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/calls'; + const baseURL = '${connection.account_url}/objects/calls'; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) @@ -287,7 +287,7 @@ export class HubspotService implements IEngagementService { const commonPropertyNames = Object.keys(commonMeetingHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/meetings'; + const baseURL = '${connection.account_url}/objects/meetings'; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) @@ -333,7 +333,7 @@ export class HubspotService implements IEngagementService { const commonPropertyNames = Object.keys(commonCallHubspotProperties); const allProperties = [...commonPropertyNames, ...custom_properties]; - const baseURL = 'https://api.hubapi.com/crm/v3/objects/emails'; + const baseURL = '${connection.account_url}/objects/emails'; const queryString = allProperties .map((prop) => `properties=${encodeURIComponent(prop)}`) diff --git a/packages/api/src/crm/engagement/services/pipedrive/index.ts b/packages/api/src/crm/engagement/services/pipedrive/index.ts index f3b4562dd..4369b2cc0 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( @@ -168,7 +168,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..82d8a4193 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..c1affafb2 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..cc38a41ee 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..b485e63e6 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..4ccddf08e 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/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..fec2d5608 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..12c192c6d 100644 --- a/packages/api/src/ticketing/comment/services/github/index.ts +++ b/packages/api/src/ticketing/comment/services/github/index.ts @@ -12,6 +12,7 @@ import { OriginalCommentOutput } from '@@core/utils/types/original/original.tick import { ServiceRegistry } from '../registry.service'; import { GithubCommentInput, GithubCommentOutput } from './types'; +//TODO; @Injectable() export class GithubService implements ICommentService { constructor( @@ -42,7 +43,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 +92,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..6184df8ec 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: { diff --git a/packages/api/src/ticketing/comment/services/hubspot/index.ts b/packages/api/src/ticketing/comment/services/hubspot/index.ts index f844a1383..6dd37e96b 100644 --- a/packages/api/src/ticketing/comment/services/hubspot/index.ts +++ b/packages/api/src/ticketing/comment/services/hubspot/index.ts @@ -41,7 +41,7 @@ export class HubspotService 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: { @@ -90,7 +90,7 @@ export class HubspotService 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/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/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/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/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 index a7dcb45a5..cc65bffbd 100644 --- a/packages/api/src/ticketing/team/services/clickup/index.ts +++ b/packages/api/src/ticketing/team/services/clickup/index.ts @@ -36,7 +36,7 @@ export class ClickupService 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/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/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..0a0fb8a2b 100644 --- a/packages/api/src/ticketing/team/services/jira/index.ts +++ b/packages/api/src/ticketing/team/services/jira/index.ts @@ -37,7 +37,7 @@ export class JiraService implements ITeamService { }); const resp = await axios.get( - `${connection.account_url}/rest/api/3/user/groups`, + `${connection.account_url}/user/groups`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/team/services/zendesk/index.ts b/packages/api/src/ticketing/team/services/zendesk/index.ts index d5fc1e40c..ed3b7dd56 100644 --- a/packages/api/src/ticketing/team/services/zendesk/index.ts +++ b/packages/api/src/ticketing/team/services/zendesk/index.ts @@ -40,7 +40,7 @@ export class ZendeskService implements ITeamService { }); const resp = await axios.get( - `${connection.account_url}/api/v2/groups.json`, + `${connection.account_url}/groups.json`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/ticket/services/front/index.ts b/packages/api/src/ticketing/ticket/services/front/index.ts index 6a4b6d6da..32154441e 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/gorgias/index.ts b/packages/api/src/ticketing/ticket/services/gorgias/index.ts index e1751cc3f..8cb59e4bd 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}/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..ad9071172 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..93f47309d 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: { @@ -85,7 +85,7 @@ export class JiraService implements ITicketService { }, }); const resp = await axios.get( - `${connection.account_url}/rest/api/3/search`, + `${connection.account_url}/search`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/api/src/ticketing/ticket/services/zendesk/index.ts b/packages/api/src/ticketing/ticket/services/zendesk/index.ts index 3124fcd31..9232b7d48 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: { @@ -122,7 +122,7 @@ export class ZendeskService implements ITicketService { }); const resp = await axios.get( - `${connection.account_url}/api/v2/tickets.json`, + `${connection.account_url}/tickets.json`, { headers: { 'Content-Type': 'application/json', 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/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..5c860fa63 100644 --- a/packages/api/src/ticketing/user/services/jira/index.ts +++ b/packages/api/src/ticketing/user/services/jira/index.ts @@ -36,7 +36,7 @@ export class JiraService implements IUserService { }, }); const resp = await axios.get( - `${connection.account_url}/rest/api/3/users/search`, + `${connection.account_url}/users/search`, { headers: { 'Content-Type': 'application/json', 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/api/swagger/swagger-spec.json b/packages/api/swagger/swagger-spec.json index f46d24485..21b94df30 100644 --- a/packages/api/swagger/swagger-spec.json +++ b/packages/api/swagger/swagger-spec.json @@ -324,14 +324,7 @@ ], "responses": { "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } + "description": "" } }, "tags": [ @@ -665,7 +658,7 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "number" } } } diff --git a/packages/shared/package.json b/packages/shared/package.json index 233b2b6dc..3f6166d87 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -50,10 +50,10 @@ "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-node": "^10.9.1", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3", "tsc-watch": "2.2.1", - "tslint": "5.16.0" + "tsconfig-paths": "^4.2.0", + "tslint": "5.16.0", + "typescript": "^5.1.3" }, "jest": { "moduleFileExtensions": [ diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index bb6ebed40..b7d6c170f 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -1,6 +1,15 @@ import { OAuth2AuthData, providerToType } from "./envConfig"; import { AuthStrategy, providersConfig, ProviderConfig } from "./utils"; +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; +} interface AuthParams { projectId: string; linkedUserId: string; @@ -83,7 +92,13 @@ 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}`) + + //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) { throw new Error(`Unsupported provider: ${providerName}`); @@ -92,8 +107,9 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { // Default URL structure let params = `client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&state=${state}`; + const providersWithoutScopes = ["pipedrive", "clickup", "aha", "freeagent", "teamwork", "attio", "close", "teamleader"] // Adding scope for providers that require it, except for 'pipedrive' - if (providerName !== "pipedrive") { + if (!providersWithoutScopes.includes(providerName) ) { params += `&scope=${encodeURIComponent(scopes)}`; } @@ -106,15 +122,15 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { case "jira_service_mgmt": params = `audience=api.atlassian.com&${params}&prompt=consent`; break; - case "gitlab": - params += "&code_challenge=&code_challenge_method="; + 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/envConfig.ts b/packages/shared/src/envConfig.ts index ddfc07d3f..88e0e1e62 100644 --- a/packages/shared/src/envConfig.ts +++ b/packages/shared/src/envConfig.ts @@ -92,9 +92,9 @@ export function needsSubdomain(provider: string, vertical: string): boolean { 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/utils.ts b/packages/shared/src/utils.ts index 87f6e0052..bbf69829e 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,94 @@ 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: '', - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRbCONyN9DCKfd4E8pzIdItl5VqPTEErpoEn9vHCgblRg&s', + urls: { + docsUrl: "", + authBaseUrl: '', + apiUrl: '', + }, 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: '', - logoPath: 'https://play-lh.googleusercontent.com/rK9Qv_w9C8Py_aLZdQQDobNdHWSG8KL4dj3cBBQLcimVu-ctxwujA4VE442lIpZ65AE', + urls: { + docsUrl: "", + 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 +272,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 +284,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 +322,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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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 +731,1364 @@ export const providersConfig: ProvidersConfig = { }, 'accounting': { 'pennylane': { - apiUrl: '', scopes: '', - authBaseUrl: '', + urls: { + docsUrl: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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: "", + 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 }, }, + //TODO 'marketing_automation': { '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 }, 'customerio': { - 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 }, 'getresponse': { - 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 }, '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 }, 'keap': { - 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 }, 'klaviyo': { - 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 }, 'mailchimp': { - 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 }, 'messagebird': { - 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 }, 'podium': { - 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 }, 'sendgrid': { - 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 }, 'sendinblue': { - 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 'ats': { '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: "", + 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 }, 'breezy': { - 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 }, 'bullhorn': { - 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 }, 'cats': { - 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 }, '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 }, '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: "", + apiUrl: "" + }, logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, '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 +2124,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 +2138,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, })); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86b053edb..3937b5d3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -511,6 +511,9 @@ importers: dotenv: specifier: ^16.3.1 version: 16.4.5 + randomstring: + specifier: ^1.3.0 + version: 1.3.0 devDependencies: '@nestjs/common': specifier: ^10.0.0 @@ -10863,12 +10866,23 @@ packages: engines: {node: '>=10'} dev: false + /randombytes@2.0.3: + resolution: {integrity: sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==} + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 dev: true + /randomstring@1.3.0: + resolution: {integrity: sha512-gY7aQ4i1BgwZ8I1Op4YseITAyiDiajeZOPQUbIq9TPGPhUm5FX59izIaOpmKbME1nmnEiABf28d9K2VSii6BBg==} + hasBin: true + dependencies: + randombytes: 2.0.3 + dev: false + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'}