From 5626a7ab16aefb5b85ef68f18b4793b2fd7d44ce Mon Sep 17 00:00:00 2001 From: mit-27 Date: Thu, 11 Apr 2024 01:54:22 -0400 Subject: [PATCH 01/15] :page_facing_up: update --- docs/open-source/contributors.mdx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 + ``` From c04d74a948b0b8cf660799018dd63ecdfcfe2f2c Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 02:57:38 +0200 Subject: [PATCH 02/15] :bug: Fix some bugs related to stytch for live / local test --- .../Auth/b2c/LoginWithStytchSDKUI.tsx | 48 +++++++++++++++---- .../client-ts/src/components/shared/icons.tsx | 26 ++++++++++ packages/api/prisma/schema.prisma | 2 +- packages/api/swagger/swagger-spec.json | 11 +---- 4 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 apps/client-ts/src/components/shared/icons.tsx 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/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index df5e2acff..0ab263682 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -51,9 +51,9 @@ model connections { expiration_timestamp DateTime? @db.Timestamp(6) created_at DateTime @db.Timestamp(6) connection_token String? + vertical String id_project String @db.Uuid id_linked_user String @db.Uuid - vertical String? linked_users linked_users @relation(fields: [id_linked_user], references: [id_linked_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_11") projects projects @relation(fields: [id_project], references: [id_project], onDelete: NoAction, onUpdate: NoAction, map: "fk_9") 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" } } } From b4259fb8d131990d0b6e821d33f5e7ec4542cb1f Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 12:41:37 +0200 Subject: [PATCH 03/15] :sparkles: Feat --- packages/api/prisma/schema.prisma | 2 +- .../crm/services/accelo/accelo.service.ts | 15 +- .../crm/services/attio/attio.service.ts | 7 +- .../crm/services/capsule/capsule.service.ts | 10 +- .../crm/services/close/close.service.ts | 10 +- .../crm/services/copper/copper.service.ts | 8 +- .../crm/services/hubspot/hubspot.service.ts | 7 +- .../crm/services/keap/keap.service.ts | 7 +- .../services/pipedrive/pipedrive.service.ts | 7 +- .../crm/services/sugarcrm/sugarcrm.service.ts | 19 ++- .../services/teamleader/teamleader.service.ts | 8 +- .../crm/services/teamwork/teamwork.service.ts | 10 +- .../crm/services/zendesk/zendesk.service.ts | 7 +- .../crm/services/zoho/zoho.service.ts | 49 +++++-- .../ticketing/services/aha/aha.service.ts | 11 +- .../services/clickup/clickup.service.ts | 7 +- .../ticketing/services/front/front.service.ts | 3 +- .../services/github/github.service.ts | 7 +- .../services/gitlab/gitlab.service.ts | 5 +- .../services/gorgias/gorgias.service.ts | 19 ++- .../ticketing/services/jira/jira.service.ts | 2 +- .../jira_service_mgmt/jira.service.ts | 2 +- .../services/linear/linear.service.ts | 3 +- .../services/zendesk/zendesk.service.ts | 11 +- .../field-mapping/field-mapping.service.ts | 1 + .../@core/passthrough/passthrough.service.ts | 1 + .../src/crm/company/services/hubspot/index.ts | 4 +- .../crm/company/services/pipedrive/index.ts | 19 ++- .../src/crm/company/services/zendesk/index.ts | 4 +- .../src/crm/company/services/zoho/index.ts | 4 +- .../src/crm/contact/services/attio/index.ts | 4 +- .../src/crm/contact/services/hubspot/index.ts | 5 +- .../crm/contact/services/pipedrive/index.ts | 4 +- .../src/crm/contact/services/zendesk/index.ts | 4 +- .../src/crm/contact/services/zoho/index.ts | 5 +- .../src/crm/deal/services/hubspot/index.ts | 4 +- .../src/crm/deal/services/zendesk/index.ts | 4 +- .../api/src/crm/deal/services/zoho/index.ts | 4 +- .../crm/engagement/services/hubspot/index.ts | 12 +- .../engagement/services/pipedrive/index.ts | 6 +- .../crm/engagement/services/zendesk/index.ts | 4 +- .../src/crm/engagement/services/zoho/index.ts | 4 +- .../src/crm/note/services/hubspot/index.ts | 4 +- .../src/crm/note/services/pipedrive/index.ts | 4 +- .../src/crm/note/services/zendesk/index.ts | 4 +- .../api/src/crm/note/services/zoho/index.ts | 4 +- .../src/crm/stage/services/hubspot/index.ts | 2 +- .../src/crm/stage/services/pipedrive/index.ts | 4 +- .../src/crm/stage/services/zendesk/index.ts | 4 +- .../api/src/crm/stage/services/zoho/index.ts | 2 +- .../src/crm/task/services/hubspot/index.ts | 4 +- .../src/crm/task/services/pipedrive/index.ts | 4 +- .../src/crm/task/services/zendesk/index.ts | 4 +- .../api/src/crm/task/services/zoho/index.ts | 4 +- .../src/crm/user/services/hubspot/index.ts | 2 +- .../src/crm/user/services/pipedrive/index.ts | 2 +- .../src/crm/user/services/zendesk/index.ts | 2 +- .../api/src/crm/user/services/zoho/index.ts | 2 +- .../ticketing/account/services/front/index.ts | 2 +- .../account/services/zendesk/index.ts | 2 +- .../collection/services/jira/index.ts | 17 +-- .../ticketing/comment/services/front/index.ts | 6 +- .../comment/services/github/index.ts | 5 +- .../comment/services/gorgias/index.ts | 2 +- .../comment/services/hubspot/index.ts | 4 +- .../ticketing/comment/services/jira/index.ts | 6 +- .../comment/services/zendesk/index.ts | 6 +- .../ticketing/contact/services/front/index.ts | 2 +- .../contact/services/gorgias/index.ts | 2 +- .../contact/services/zendesk/index.ts | 2 +- .../src/ticketing/tag/services/front/index.ts | 2 +- .../ticketing/tag/services/gorgias/index.ts | 2 +- .../ticketing/tag/services/zendesk/index.ts | 2 +- .../ticketing/team/services/clickup/index.ts | 2 +- .../ticketing/team/services/front/index.ts | 2 +- .../ticketing/team/services/gorgias/index.ts | 2 +- .../src/ticketing/team/services/jira/index.ts | 2 +- .../ticketing/team/services/zendesk/index.ts | 2 +- .../ticketing/ticket/services/front/index.ts | 8 +- .../ticket/services/gorgias/index.ts | 4 +- .../ticket/services/hubspot/index.ts | 4 +- .../ticketing/ticket/services/jira/index.ts | 4 +- .../ticket/services/zendesk/index.ts | 6 +- .../ticketing/user/services/front/index.ts | 2 +- .../ticketing/user/services/gorgias/index.ts | 2 +- .../src/ticketing/user/services/jira/index.ts | 2 +- .../ticketing/user/services/zendesk/index.ts | 2 +- packages/shared/src/authUrl.ts | 7 +- packages/shared/src/envConfig.ts | 6 +- packages/shared/src/utils.ts | 132 +++++++++++------- 90 files changed, 388 insertions(+), 244 deletions(-) diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index 0ab263682..df5e2acff 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -51,9 +51,9 @@ model connections { expiration_timestamp DateTime? @db.Timestamp(6) created_at DateTime @db.Timestamp(6) connection_token String? - vertical String id_project String @db.Uuid id_linked_user String @db.Uuid + vertical String? linked_users linked_users @relation(fields: [id_linked_user], references: [id_linked_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_11") projects projects @relation(fields: [id_project], references: [id_project], onDelete: NoAction, onUpdate: NoAction, map: "fk_9") 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..39573aec2 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'; @@ -87,6 +91,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'].apiUrl; if (isNotUnique) { // Update existing connection @@ -96,7 +103,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 +120,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 +158,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..9d010e6cc 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'].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..b1e78c49a 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'].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'].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..27d9ff709 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'].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'].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..c2cbf3eb9 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'].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..41b04fa66 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'].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..4634b92b7 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'].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..6449aed72 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'].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/sugarcrm/sugarcrm.service.ts b/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts index 9cf4ce982..37dc5c191 100644 --- a/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts +++ b/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.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'; @@ -71,7 +75,7 @@ export class SugarcrmConnectionService implements ICrmConnectionService { }); //const subdomain = 'panora'; const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/rest/v11/oauth2/token`, + `${CREDENTIALS.SUBDOMAIN}/rest/v11/oauth2/token`, formData.toString(), { headers: { @@ -86,6 +90,9 @@ export class SugarcrmConnectionService implements ICrmConnectionService { let db_res; const connection_token = uuidv4(); + //get the right BASE URL API + const BASE_API_URL = + CREDENTIALS.SUBDOMAIN + providersConfig['crm']['sugarcrm'].apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ @@ -95,7 +102,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: BASE_API_URL, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -111,7 +118,7 @@ export class SugarcrmConnectionService implements ICrmConnectionService { provider_slug: 'sugarcrm', vertical: 'crm', token_type: 'oauth', - account_url: '', + 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( @@ -149,9 +156,9 @@ export class SugarcrmConnectionService implements ICrmConnectionService { platform: 'custom', refresh_token: this.cryptoService.decrypt(refreshToken), }); - const subdomain = 'panora'; + //const subdomain = 'panora'; const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN!}/rest/v11/oauth2/token`, + `${CREDENTIALS.SUBDOMAIN}/rest/v11/oauth2/token`, formData.toString(), { headers: { 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..da52623a7 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'].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..dcc3060d5 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'].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..422b55873 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'].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..0e122d873 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'].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'].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..2297d4ec9 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'].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/clickup/clickup.service.ts b/packages/api/src/@core/connections/ticketing/services/clickup/clickup.service.ts index 83248f2c0..666894fa5 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'].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..89373c0f7 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'].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..59e2be830 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'].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..9e450c99e 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'].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..bc6c89b6a 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'].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..a16afa741 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'].apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: '', expiration_timestamp: new Date( 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..80a03389f 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'].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/field-mapping/field-mapping.service.ts b/packages/api/src/@core/field-mapping/field-mapping.service.ts index d31f403d7..84c0eb709 100644 --- a/packages/api/src/@core/field-mapping/field-mapping.service.ts +++ b/packages/api/src/@core/field-mapping/field-mapping.service.ts @@ -130,6 +130,7 @@ export class FieldMappingService { }, }); const provider = providersConfig[vertical][providerId.toLowerCase()]; + //TODO: handle case where apiUrl is == "" or starts with "/" const resp = await axios.get( provider.apiUrl + provider.customPropertiesUrl, { diff --git a/packages/api/src/@core/passthrough/passthrough.service.ts b/packages/api/src/@core/passthrough/passthrough.service.ts index 5f5e9c81a..248f8e55d 100644 --- a/packages/api/src/@core/passthrough/passthrough.service.ts +++ b/packages/api/src/@core/passthrough/passthrough.service.ts @@ -43,6 +43,7 @@ export class PassthroughService { }); const intId = integrationId.toLowerCase(); const providerUrl = providersConfig[vertical.toLowerCase()][intId].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/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index bb6ebed40..2501a29dd 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -1,4 +1,4 @@ -import { OAuth2AuthData, providerToType } from "./envConfig"; +import { needsSubdomain, OAuth2AuthData, providerToType } from "./envConfig"; import { AuthStrategy, providersConfig, ProviderConfig } from "./utils"; interface AuthParams { @@ -84,6 +84,9 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { if (!clientId) throw new Error(`No client id for type ${type}`) const { scopes, authBaseUrl: baseUrl } = config; + + //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}`); @@ -114,7 +117,7 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { 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..174d890d8 100644 --- a/packages/shared/src/envConfig.ts +++ b/packages/shared/src/envConfig.ts @@ -94,7 +94,7 @@ export function needsSubdomain(provider: string, vertical: string): boolean { // 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 apiUrlIsBlank = providerConfig.apiUrl! == ""; + + return authBaseUrlStartsWithSlash || apiUrlStartsWithSlash || apiUrlIsBlank; } diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 87f6e0052..a9f143d7c 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -7,7 +7,7 @@ export enum AuthStrategy { // TODO : remove clientId export type ProviderConfig = { scopes: string; - authBaseUrl: string; + authBaseUrl: string; //url used to authorize an application on behalf of the user (only when authStrategy is oauth2) logoPath: string; description: string; active?: boolean; @@ -22,10 +22,10 @@ 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': { @@ -34,7 +34,7 @@ export const providersConfig: ProvidersConfig = { authBaseUrl: 'https://app-eu1.hubspot.com/oauth/authorize', logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png", description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", - apiUrl: 'https://api.hubapi.com', + apiUrl: 'https://api.hubapi.com/crm/v3', customPropertiesUrl: '/properties/v1/contacts/properties', authStrategy: AuthStrategy.oauth2 }, @@ -43,16 +43,16 @@ export const providersConfig: ProvidersConfig = { authBaseUrl: 'https://app.attio.com/authorize', 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', + apiUrl: 'https://api.attio.com/v2', customPropertiesUrl: '/docs/standard-objects-people', authStrategy: AuthStrategy.oauth2 }, 'zoho': { scopes: 'ZohoCRM.modules.ALL', - authBaseUrl: 'https://accounts.zoho.eu/oauth/v2/auth', + authBaseUrl: '/oauth/v2/auth', 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', + apiUrl: '/crm/v3', customPropertiesUrl: '/settings/fields?module=Contact', authStrategy: AuthStrategy.oauth2 }, @@ -61,7 +61,7 @@ export const providersConfig: ProvidersConfig = { authBaseUrl: 'https://oauth.pipedrive.com/oauth/authorize', 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', + apiUrl: 'https://api.pipedrive.com/v1', customPropertiesUrl: '/v1/personFields', authStrategy: AuthStrategy.oauth2 }, @@ -85,8 +85,8 @@ export const providersConfig: ProvidersConfig = { }, 'accelo': { scopes: '', - authBaseUrl: 'https://{deployment}.api.accelo.com/oauth2/v0/authorize', - apiUrl: '', + 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 +94,8 @@ export const providersConfig: ProvidersConfig = { }, 'active_campaign': { scopes: '', - authBaseUrl: '', - apiUrl: '', + authBaseUrl: null, + 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,7 +103,7 @@ export const providersConfig: ProvidersConfig = { }, 'affinity': { scopes: '', - authBaseUrl: '', + authBaseUrl: null, 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", @@ -126,7 +126,7 @@ export const providersConfig: ProvidersConfig = { 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: '', @@ -139,12 +139,12 @@ export const providersConfig: ProvidersConfig = { }, 'insightly': { scopes: '', - authBaseUrl: '', + authBaseUrl: null, + 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: '', @@ -155,6 +155,7 @@ export const providersConfig: ProvidersConfig = { active: false, authStrategy: AuthStrategy.oauth2 }, + //todo 'microsoft_dynamics_sales': { scopes: '', authBaseUrl: '', @@ -163,15 +164,17 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, + //todo 'nutshell': { apiUrl: '', scopes: '', - authBaseUrl: '', + authBaseUrl: null, 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: '', @@ -182,14 +185,15 @@ export const providersConfig: ProvidersConfig = { authStrategy: AuthStrategy.api_key }, 'salesflare': { - apiUrl: '', + apiUrl: 'https://api.salesflare.com', scopes: '', - authBaseUrl: '', + authBaseUrl: null, 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: '', @@ -199,6 +203,7 @@ export const providersConfig: ProvidersConfig = { active: false, authStrategy: AuthStrategy.oauth2 }, + //todo 'sugarcrm': { apiUrl: '', scopes: '', @@ -220,21 +225,23 @@ export const providersConfig: ProvidersConfig = { 'teamwork': { scopes: '', authBaseUrl: 'https://www.teamwork.com/launchpad/login', - apiUrl: '', + apiUrl: '', //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: '', + authBaseUrl: null, 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: '', @@ -248,17 +255,17 @@ export const providersConfig: ProvidersConfig = { 'front': { scopes: '', 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', + 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': { @@ -277,6 +284,7 @@ export const providersConfig: ProvidersConfig = { 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', @@ -287,7 +295,7 @@ export const providersConfig: ProvidersConfig = { authStrategy: AuthStrategy.oauth2 }, 'linear': { - apiUrl: '', + apiUrl: 'https://api.linear.app/graphql', scopes: 'read,write', authBaseUrl: 'https://linear.app/oauth/authorize', logoPath: '', @@ -296,16 +304,16 @@ export const providersConfig: ProvidersConfig = { authStrategy: AuthStrategy.oauth2 }, 'gitlab': { - apiUrl: '', + apiUrl: '/api/v4', scopes: '', - authBaseUrl: 'https://gitlab.example.com/oauth/authorize', + authBaseUrl: '/oauth/authorize', logoPath: '', description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false, authStrategy: AuthStrategy.oauth2 }, 'clickup': { - apiUrl: '', + apiUrl: 'https://api.clickup.com/v2', scopes: '', authBaseUrl: 'https://app.clickup.com/api', logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRewJj9y5yKzSCf-qGgjmdLagEhxfnlZ7TUsvukbfZaIg&s', @@ -314,31 +322,33 @@ export const providersConfig: ProvidersConfig = { authStrategy: AuthStrategy.oauth2 }, 'github': { - apiUrl: '', + apiUrl: 'https://api.github.com', scopes: '', - authBaseUrl: '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: '', + apiUrl: '/api/v1', scopes: '', - authBaseUrl: 'https://.aha.io/oauth/authorize', + 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: '', + apiUrl: 'https://app.asana.com/api/1.0', scopes: '', - authBaseUrl: 'https://api.github.com', + 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: '', @@ -347,6 +357,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'basecamp': { apiUrl: '', scopes: '', @@ -355,6 +366,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'bitbucket': { apiUrl: '', scopes: '', @@ -364,13 +376,15 @@ export const providersConfig: ProvidersConfig = { active: false }, 'dixa': { - apiUrl: '', + apiUrl: 'https://dev.dixa.io', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', @@ -379,6 +393,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'freshservice': { apiUrl: '', scopes: '', @@ -388,13 +403,15 @@ export const providersConfig: ProvidersConfig = { active: false }, 'gladly': { - apiUrl: '', + apiUrl: '/api/v1', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', @@ -404,21 +421,24 @@ export const providersConfig: ProvidersConfig = { active: false }, 'help_scout': { - apiUrl: '', + apiUrl: 'https://docsapi.helpscout.net/v1', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', + apiUrl: 'https://app.hive.com/api/v1', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', @@ -427,22 +447,26 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'ironclad': { apiUrl: '', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', + apiUrl: 'https://api.kustomerapp.com', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', From 44b265ffcf414deab66800cc1fc914671f9a8899 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 13:15:46 +0200 Subject: [PATCH 04/15] :sparkles: Features --- packages/shared/src/utils.ts | 84 +++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index a9f143d7c..8306b28df 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -475,6 +475,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'rally': { apiUrl: '', scopes: '', @@ -484,13 +485,15 @@ export const providersConfig: ProvidersConfig = { active: false }, 'reamaze': { - apiUrl: '', + apiUrl: '/api/v1', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', @@ -499,6 +502,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'servicenow': { apiUrl: '', scopes: '', @@ -508,13 +512,15 @@ export const providersConfig: ProvidersConfig = { active: false }, 'shortcut': { - apiUrl: '', + apiUrl: 'https://api.app.shortcut.com', scopes: '', - authBaseUrl: 'https://api.github.com', + authBaseUrl: null, 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: '', @@ -523,6 +529,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'teamwork': { apiUrl: '', scopes: '', @@ -531,6 +538,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users", active: false }, + //todo 'trello': { apiUrl: '', scopes: '', @@ -540,12 +548,13 @@ export const providersConfig: ProvidersConfig = { active: false }, 'wrike': { - apiUrl: '', + apiUrl: '/api/v4', scopes: '', - authBaseUrl: 'https://api.github.com', + 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: '', @@ -566,21 +575,24 @@ export const providersConfig: ProvidersConfig = { }, 'accounting': { 'pennylane': { - apiUrl: '', + apiUrl: 'https://app.pennylane.com/api/external/v1', scopes: '', - authBaseUrl: '', + 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: '', + apiUrl: 'https://api.freshbooks.com', scopes: '', - authBaseUrl: '', + 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: '', @@ -590,21 +602,24 @@ export const providersConfig: ProvidersConfig = { active: false }, 'freeagent': { - apiUrl: '', + apiUrl: 'https://api.freeagent.com/v2', scopes: '', - authBaseUrl: '', + 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: '', + apiUrl: 'https://api.accounting.sage.com/v3.1', scopes: '', - authBaseUrl: '', + 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: '', @@ -613,6 +628,7 @@ export const providersConfig: ProvidersConfig = { description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users", active: false }, + //todo 'microsoft_dynamics': { apiUrl: '', scopes: '', @@ -622,13 +638,15 @@ export const providersConfig: ProvidersConfig = { active: false }, 'moneybird': { - apiUrl: '', + apiUrl: 'https://moneybird.com/api/v2', scopes: '', - authBaseUrl: '', + 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: '', @@ -638,13 +656,15 @@ export const providersConfig: ProvidersConfig = { active: false }, 'quickbooks': { - apiUrl: '', + apiUrl: 'https://quickbooks.api.intuit.com/v3', scopes: '', - authBaseUrl: '', + 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: '', @@ -654,14 +674,16 @@ export const providersConfig: ProvidersConfig = { active: false }, 'wave_financial': { - apiUrl: '', + apiUrl: 'https://gql.waveapps.com/graphql/public', scopes: '', - authBaseUrl: '', + 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: '', @@ -752,6 +774,7 @@ export const providersConfig: ProvidersConfig = { active: false }, }, + //TODO 'ats': { 'applicantstack': { apiUrl: '', @@ -1130,6 +1153,7 @@ export const providersConfig: ProvidersConfig = { active: false }, }, + //TODO 'hris': { '7shifts': { apiUrl: '', From b7ca3e27497b755099163b613b68ffb9b72d7232 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 13:59:46 +0200 Subject: [PATCH 05/15] :sparkles: New oauth code --- docker-compose.dev.yml | 27 +++ docker-compose.source.yml | 27 +++ docker-compose.yml | 27 +++ packages/api/package.json | 3 +- packages/api/scripts/oauthConnector.js | 8 +- .../accounting.connection.module.ts | 41 ++++ .../services/accounting.connection.service.ts | 104 ++++++++++ .../services/freeagent/freeagent.service.ts | 166 +++++++++++++++ .../services/freshbooks/freshbooks.service.ts | 171 +++++++++++++++ .../services/moneybird/moneybird.service.ts | 166 +++++++++++++++ .../services/pennylane/pennylane.service.ts | 166 +++++++++++++++ .../services/quickbooks/quickbooks.service.ts | 179 ++++++++++++++++ .../accounting/services/registry.service.ts | 23 ++ .../accounting/services/sage/sage.service.ts | 177 ++++++++++++++++ .../wave_financial/wave_financial.service.ts | 196 ++++++++++++++++++ .../connections/accounting/types/index.ts | 19 ++ .../connections/connections.controller.ts | 8 + .../@core/connections/connections.module.ts | 13 +- .../ticketing/services/asana/asana.service.ts | 170 +++++++++++++++ .../ticketing/services/wrike/wrike.service.ts | 164 +++++++++++++++ .../ticketing/ticketing.connection.module.ts | 6 + 21 files changed, 1854 insertions(+), 7 deletions(-) create mode 100644 packages/api/src/@core/connections/accounting/accounting.connection.module.ts create mode 100644 packages/api/src/@core/connections/accounting/services/accounting.connection.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/registry.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/sage/sage.service.ts create mode 100644 packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts create mode 100644 packages/api/src/@core/connections/accounting/types/index.ts create mode 100644 packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts create mode 100644 packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 19280187f..e19561635 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.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: 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/packages/api/package.json b/packages/api/package.json index 96d8eba08..966e28c91 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -109,5 +109,6 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" - } + }, + "type": "module" } diff --git a/packages/api/scripts/oauthConnector.js b/packages/api/scripts/oauthConnector.js index 6e6e1b939..6577f6e6d 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, @@ -165,7 +165,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne async handleTokenRefresh(opts: RefreshParams) { try { - const { connectionId, refreshToken } = opts; + const { connectionId, refreshToken, projectId } = opts; const formData = new URLSearchParams({ grant_type: 'refresh_token', refresh_token: this.cryptoService.decrypt(refreshToken), @@ -286,7 +286,7 @@ function addProviderToDockerCompose(provider, vertical, dockerComposePath) { function handleUpdate(vertical, provider) { createServiceFile(vertical, provider); - addProviderToEnvironmentService(provider, envServiceFilePath); + //addProviderToEnvironmentService(provider, envServiceFilePath); for (const path of paths) { addProviderToDockerCompose(provider, vertical, path); } @@ -298,7 +298,7 @@ if (import.meta.url === process.argv[1]) { const vertical = args[0]; const provider = args[1]; createServiceFile(vertical, provider); - addProviderToEnvironmentService(provider, envServiceFilePath); + //addProviderToEnvironmentService(provider, envServiceFilePath); for (const path of paths) { addProviderToDockerCompose(argv.provider, path); } diff --git a/packages/api/src/@core/connections/accounting/accounting.connection.module.ts b/packages/api/src/@core/connections/accounting/accounting.connection.module.ts new file mode 100644 index 000000000..f705017a9 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/accounting.connection.module.ts @@ -0,0 +1,41 @@ +import { Module } from '@nestjs/common'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { LoggerService } from '@@core/logger/logger.service'; +import { WebhookService } from '@@core/webhook/webhook.service'; +import { WebhookModule } from '@@core/webhook/webhook.module'; +import { EnvironmentService } from '@@core/environment/environment.service'; +import { EncryptionService } from '@@core/encryption/encryption.service'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; +import { ServiceRegistry } from './services/registry.service'; +import { AccountingConnectionsService } from './services/accounting.connection.service'; +import { PennylaneConnectionService } from './services/pennylane/pennylane.service'; +import { FreeagentConnectionService } from './services/freeagent/freeagent.service'; +import { FreshbooksConnectionService } from './services/freshbooks/freshbooks.service'; +import { MoneybirdConnectionService } from './services/moneybird/moneybird.service'; +import { QuickbooksConnectionService } from './services/quickbooks/quickbooks.service'; +import { SageConnectionService } from './services/sage/sage.service'; +import { WaveFinancialConnectionService } from './services/wave_financial/wave_financial.service'; + +@Module({ + imports: [WebhookModule], + providers: [ + AccountingConnectionsService, + PrismaService, + LoggerService, + WebhookService, + EnvironmentService, + EncryptionService, + ServiceRegistry, + ConnectionsStrategiesService, + //PROVIDERS SERVICES, + PennylaneConnectionService, + FreeagentConnectionService, + FreshbooksConnectionService, + MoneybirdConnectionService, + QuickbooksConnectionService, + SageConnectionService, + WaveFinancialConnectionService, + ], + exports: [AccountingConnectionsService], +}) +export class AccountingConnectionModule {} diff --git a/packages/api/src/@core/connections/accounting/services/accounting.connection.service.ts b/packages/api/src/@core/connections/accounting/services/accounting.connection.service.ts new file mode 100644 index 000000000..82fb92811 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/accounting.connection.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; +import { NotFoundError, handleServiceError } from '@@core/utils/errors'; +import { LoggerService } from '@@core/logger/logger.service'; +import { WebhookService } from '@@core/webhook/webhook.service'; +import { connections as Connection } from '@prisma/client'; +import { PrismaService } from '@@core/prisma/prisma.service'; +import { v4 as uuidv4 } from 'uuid'; +import { CallbackParams, RefreshParams } from '../types'; +import { ServiceRegistry } from './registry.service'; + +@Injectable() +export class AccountingConnectionsService { + constructor( + private serviceRegistry: ServiceRegistry, + private webhook: WebhookService, + private logger: LoggerService, + private prisma: PrismaService, + ) { + this.logger.setContext(AccountingConnectionsService.name); + } + //STEP 1:[FRONTEND STEP] + //create a frontend SDK snippet in which an authorization embedded link is set up so when users click + // on it to grant access => they grant US the access and then when confirmed + /*const authUrl = + 'https://app.hubspot.com/oauth/authorize' + + `?client_id=${encodeURIComponent(CLIENT_ID)}` + + `&scope=${encodeURIComponent(SCOPES)}` + + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;*/ //oauth/callback + + // oauth server calls this redirect callback + // WE WOULD HAVE CREATED A DEV ACCOUNT IN THE 5 CRMs (Panora dev account) + // we catch the tmp token and swap it against oauth2 server for access/refresh tokens + // to perform actions on his behalf + // this call pass 1. integrationID 2. CustomerId 3. Panora Api Key + async handleAccountingCallBack( + projectId: string, + linkedUserId: string, + providerName: string, + code: string, + ) { + try { + const serviceName = providerName.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + + if (!service) { + throw new NotFoundError(`Unknown provider, found ${providerName}`); + } + const callbackOpts: CallbackParams = { + linkedUserId: linkedUserId, + projectId: projectId, + code: code, + }; + const data: Connection = await service.handleCallback(callbackOpts); + + const event = await this.prisma.events.create({ + data: { + id_event: uuidv4(), + status: 'success', + type: 'connection.created', + method: 'GET', + url: '/oauth/callback', + provider: providerName.toLowerCase(), + direction: '0', + timestamp: new Date(), + id_linked_user: linkedUserId, + }, + }); + //directly send the webhook + await this.webhook.handlePriorityWebhook( + data, + 'connection.created', + projectId, + event.id_event, + ); + } catch (error) { + handleServiceError(error, this.logger); + } + } + + async handleAccountingTokensRefresh( + connectionId: string, + providerName: string, + refresh_token: string, + id_project: string, + account_url?: string, + ) { + try { + const serviceName = providerName.toLowerCase(); + const service = this.serviceRegistry.getService(serviceName); + if (!service) { + throw new NotFoundError(`Unknown provider, found ${providerName}`); + } + const refreshOpts: RefreshParams = { + connectionId: connectionId, + refreshToken: refresh_token, + account_url: account_url, + projectId: id_project, + }; + const data = await service.handleTokenRefresh(refreshOpts); + } catch (error) { + handleServiceError(error, this.logger); + } + } +} diff --git a/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts new file mode 100644 index 000000000..552b8c859 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts @@ -0,0 +1,166 @@ +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 } 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_at: 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({ + client_id: CREDENTIALS.CLIENT_ID, + client_secret: CREDENTIALS.CLIENT_SECRET, + redirect_uri: REDIRECT_URI, + 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 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: '', + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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: '', + 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, + ), + 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('', formData.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, + }, + }); + 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_at) * 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..189fe6f5a --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts @@ -0,0 +1,171 @@ +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 } 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_at: 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 subdomain = 'panora'; + const res = await axios.post('', 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: '', + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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: '', + 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, + ), + 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 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 Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, + }, + }); + 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_at) * 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/accounting/services/moneybird/moneybird.service.ts b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts new file mode 100644 index 000000000..31b601458 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts @@ -0,0 +1,166 @@ +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 } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type MoneybirdOAuthResponse = { + access_token: string; + refresh_token: string; + expires_at: string; +}; + +@Injectable() +export class MoneybirdConnectionService + 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(MoneybirdConnectionService.name); + this.registry.registerService('moneybird', this); + this.type = providerToType('moneybird', '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: 'moneybird', + 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 subdomain = 'panora'; + const res = await axios.post('', formData.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }); + const data: MoneybirdOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : moneybird 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: '', + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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: 'moneybird', + vertical: 'accounting', + token_type: 'oauth', + account_url: '', + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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, '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 res = await axios.post('', formData.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, + }, + }); + const data: MoneybirdOAuthResponse = 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_at) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : moneybird '); + } catch (error) { + 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..8fe9d49c9 --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts @@ -0,0 +1,166 @@ +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 } 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_at: 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 subdomain = 'panora'; + const res = await axios.post('', 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: '', + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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: '', + 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, + ), + 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 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 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_at) * 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..34a3fca27 --- /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'].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'].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/accounting/services/sage/sage.service.ts b/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts new file mode 100644 index 000000000..967bc4cff --- /dev/null +++ b/packages/api/src/@core/connections/accounting/services/sage/sage.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 SageOAuthResponse = { + access_token: string; + refresh_token: string; + refresh_token_expires_in: number; + expires_in: number; + scope: string; + token_type: string; + requested_by_id: string; +}; + +@Injectable() +export class SageConnectionService 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(SageConnectionService.name); + this.registry.registerService('sage', this); + this.type = providerToType('sage', '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: 'sage', + 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://oauth.accounting.sage.com/token', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: SageOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : sage 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']['sage'].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: 'sage', + vertical: 'accounting', + token_type: 'oauth', + account_url: providersConfig['accounting']['sage'].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, 'sage', 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://oauth.accounting.sage.com/toke', + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, + ); + const data: SageOAuthResponse = 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 : sage '); + } catch (error) { + 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..197e07086 --- /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'].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'].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/ticketing/services/asana/asana.service.ts b/packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts new file mode 100644 index 000000000..87208da0a --- /dev/null +++ b/packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts @@ -0,0 +1,170 @@ + +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 } 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_at: 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 subdomain = 'panora'; + const res = await axios.post( + "", + 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: "", + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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: "", + 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, + ), + 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 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 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_at) * 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/wrike/wrike.service.ts b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts new file mode 100644 index 000000000..9deeac018 --- /dev/null +++ b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts @@ -0,0 +1,164 @@ +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 } from '@panora/shared'; +import { OAuth2AuthData, providerToType } from '@panora/shared'; +import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; + +export type WrikeOAuthResponse = { + access_token: string; + refresh_token: string; + expires_at: string; +}; + +@Injectable() +export class WrikeConnectionService 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(WrikeConnectionService.name); + this.registry.registerService('wrike', this); + this.type = providerToType('wrike', '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: 'wrike', + 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 subdomain = 'panora'; + const res = await axios.post('', formData.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }); + const data: WrikeOAuthResponse = res.data; + this.logger.log( + 'OAuth credentials : wrike 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: '', + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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: 'wrike', + vertical: 'ticketing', + token_type: 'oauth', + account_url: '', + access_token: this.cryptoService.encrypt(data.access_token), + refresh_token: this.cryptoService.encrypt(data.refresh_token), + expiration_timestamp: new Date( + new Date().getTime() + Number(data.expires_at) * 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, '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 res = await axios.post('', formData.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + Authorization: `Basic Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, + }, + }); + const data: WrikeOAuthResponse = 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_at) * 1000, + ), + }, + }); + this.logger.log('OAuth credentials updated : wrike '); + } catch (error) { + handleServiceError(error, this.logger, 'wrike', Action.oauthRefresh); + } + } +} 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], }) From 0699ffb1ec807e496d8da8a16425e630f32cec05 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 14:03:11 +0200 Subject: [PATCH 06/15] :sparkles: Add pennylane oauth --- packages/api/scripts/oauthConnector.js | 8 +-- .../services/pennylane/pennylane.service.ts | 53 +++++++++++-------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/api/scripts/oauthConnector.js b/packages/api/scripts/oauthConnector.js index 6577f6e6d..d6391eecb 100755 --- a/packages/api/scripts/oauthConnector.js +++ b/packages/api/scripts/oauthConnector.js @@ -56,7 +56,7 @@ import { ConnectionsStrategiesService } from '@@core/connections-strategies/conn export type ${providerUpper}OAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string; }; @Injectable() @@ -126,7 +126,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 +144,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(), @@ -198,7 +198,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 1000, + new Date().getTime() + Number(data.expires_in) * 1000, ), }, }); 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 index 8fe9d49c9..873f5c24c 100644 --- a/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts +++ b/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts @@ -12,14 +12,15 @@ import { IAccountingConnectionService, } 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'; export type PennylaneOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: number; + token_type: string; }; @Injectable() @@ -66,12 +67,15 @@ export class PennylaneConnectionService 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://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), @@ -88,9 +92,9 @@ export class PennylaneConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['accounting']['pennylane'].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(), @@ -104,11 +108,11 @@ export class PennylaneConnectionService provider_slug: 'pennylane', vertical: 'accounting', token_type: 'oauth', - account_url: '', + account_url: providersConfig['accounting']['pennylane'].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(), @@ -130,21 +134,28 @@ export class PennylaneConnectionService 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 Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, - }, + 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: { @@ -154,7 +165,7 @@ export class PennylaneConnectionService 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, ), }, }); From 2edc14527cb22d6220bd75215c60b51bbae8170a Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 18:08:42 +0200 Subject: [PATCH 07/15] :sparkles: New providers oauth --- packages/api/scripts/oauthConnector.js | 10 ++-- .../services/freeagent/freeagent.service.ts | 50 ++++++++++------- .../services/freshbooks/freshbooks.service.ts | 54 +++++++++++-------- .../services/moneybird/moneybird.service.ts | 54 +++++++++++-------- .../ticketing/services/asana/asana.service.ts | 51 ++++++++++-------- .../ticketing/services/wrike/wrike.service.ts | 45 +++++++++------- 6 files changed, 159 insertions(+), 105 deletions(-) diff --git a/packages/api/scripts/oauthConnector.js b/packages/api/scripts/oauthConnector.js index d6391eecb..33b572217 100755 --- a/packages/api/scripts/oauthConnector.js +++ b/packages/api/scripts/oauthConnector.js @@ -57,6 +57,7 @@ export type ${providerUpper}OAuthResponse = { access_token: string; refresh_token: string; expires_in: string; + token_type: string; }; @Injectable() @@ -166,14 +167,15 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne 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 formData = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }); const res = await axios.post( "", 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 index 552b8c859..404f1c178 100644 --- a/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts +++ b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts @@ -12,14 +12,16 @@ import { IAccountingConnectionService, } 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'; export type FreeagentOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string | number; + refresh_token_expires_in: number; + token_type: string; }; @Injectable() @@ -60,18 +62,22 @@ export class FreeagentConnectionService )) 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 subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + const res = await axios.post( + 'https://api.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), @@ -88,9 +94,9 @@ export class FreeagentConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['accounting']['freshagent'].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(), @@ -104,11 +110,11 @@ export class FreeagentConnectionService provider_slug: 'freeagent', vertical: 'accounting', token_type: 'oauth', - account_url: '', + account_url: providersConfig['accounting']['freshagent'].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(), @@ -139,12 +145,18 @@ export class FreeagentConnectionService this.type, )) as OAuth2AuthData; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - Authorization: `Basic Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, + 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: { @@ -154,7 +166,7 @@ export class FreeagentConnectionService 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, ), }, }); 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 index 189fe6f5a..f3c06c80d 100644 --- a/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts +++ b/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts @@ -12,14 +12,16 @@ import { IAccountingConnectionService, } 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'; export type FreshbooksOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string; + created_at: number; + token_type: string; }; @Injectable() @@ -66,12 +68,15 @@ export class FreshbooksConnectionService code: code, grant_type: 'authorization_code', }); - const subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + const res = await axios.post( + 'https://api.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), @@ -88,9 +93,9 @@ export class FreshbooksConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['accounting']['freshbooks'].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(), @@ -104,11 +109,11 @@ export class FreshbooksConnectionService provider_slug: 'freshbooks', vertical: 'accounting', token_type: 'oauth', - account_url: '', + account_url: providersConfig['accounting']['freshbooks'].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(), @@ -135,21 +140,28 @@ export class FreshbooksConnectionService 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 Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, - }, + 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: { @@ -159,7 +171,7 @@ export class FreshbooksConnectionService 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, ), }, }); diff --git a/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts index 31b601458..7c6e158a8 100644 --- a/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts +++ b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts @@ -12,14 +12,17 @@ import { IAccountingConnectionService, } 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'; export type MoneybirdOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string; + token_type: string; + scope: string; + created_at: number; }; @Injectable() @@ -66,12 +69,15 @@ export class MoneybirdConnectionService 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: MoneybirdOAuthResponse = res.data; this.logger.log( 'OAuth credentials : moneybird ticketing ' + JSON.stringify(data), @@ -88,9 +94,9 @@ export class MoneybirdConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: '', + account_url: providersConfig['accounting']['moneybird'].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(), @@ -104,11 +110,11 @@ export class MoneybirdConnectionService provider_slug: 'moneybird', vertical: 'accounting', token_type: 'oauth', - account_url: '', + account_url: providersConfig['accounting']['moneybird'].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(), @@ -130,21 +136,25 @@ export class MoneybirdConnectionService 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 Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, - }, + 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://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: { @@ -154,7 +164,7 @@ export class MoneybirdConnectionService 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, ), }, }); 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 index 87208da0a..df90bb82c 100644 --- a/packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts @@ -1,4 +1,3 @@ - import { Injectable } from '@nestjs/common'; import axios from 'axios'; import { PrismaService } from '@@core/prisma/prisma.service'; @@ -7,20 +6,27 @@ 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, 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'; export type AsanaOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: number; + token_type: string; + data: { + id: number; + gid: string; + name: string; + email: string; + }; }; @Injectable() @@ -47,13 +53,16 @@ export class AsanaConnectionService implements ITicketingConnectionService { where: { id_linked_user: linkedUserId, provider_slug: 'asana', - vertical: 'ticketing' + 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 CREDENTIALS = (await this.cService.getCredentials( + projectId, + this.type, + )) as OAuth2AuthData; const formData = new URLSearchParams({ client_id: CREDENTIALS.CLIENT_ID, @@ -62,9 +71,8 @@ export class AsanaConnectionService implements ITicketingConnectionService { code: code, grant_type: 'authorization_code', }); - const subdomain = 'panora'; const res = await axios.post( - "", + 'https://app.asana.com/-/oauth_token', formData.toString(), { headers: { @@ -88,9 +96,9 @@ export class AsanaConnectionService implements ITicketingConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: "", + account_url: providersConfig['ticketing']['asana'].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(), @@ -104,11 +112,11 @@ export class AsanaConnectionService implements ITicketingConnectionService { provider_slug: 'asana', vertical: 'ticketing', token_type: 'oauth', - account_url: "", + account_url: providersConfig['ticketing']['asana'].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(), @@ -126,21 +134,22 @@ export class AsanaConnectionService implements ITicketingConnectionService { handleServiceError(error, this.logger, 'asana', 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 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: { @@ -158,7 +167,7 @@ export class AsanaConnectionService implements ITicketingConnectionService { 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, ), }, }); @@ -167,4 +176,4 @@ export class AsanaConnectionService implements ITicketingConnectionService { handleServiceError(error, this.logger, 'asana', Action.oauthRefresh); } } -} +} diff --git a/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts index 9deeac018..769decfc7 100644 --- a/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts @@ -12,14 +12,16 @@ 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'; export type WrikeOAuthResponse = { access_token: string; refresh_token: string; - expires_at: string; + expires_in: string; + host: string; + token_type: string; }; @Injectable() @@ -64,12 +66,15 @@ export class WrikeConnectionService implements ITicketingConnectionService { 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: WrikeOAuthResponse = res.data; this.logger.log( 'OAuth credentials : wrike ticketing ' + JSON.stringify(data), @@ -86,9 +91,11 @@ export class WrikeConnectionService implements ITicketingConnectionService { 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'].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(), @@ -102,11 +109,13 @@ export class WrikeConnectionService implements ITicketingConnectionService { provider_slug: 'wrike', vertical: 'ticketing', token_type: 'oauth', - account_url: '', + account_url: + `https://${data.host}` + + providersConfig['ticketing']['wriker'].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(), @@ -128,19 +137,19 @@ export class WrikeConnectionService implements ITicketingConnectionService { 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 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 Q1JFREVOVElBTFMuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgICBDUkVERU5USUFMUy5DTElFTlRfU0VDUkVUCiAgICAgICAgICAgICAgfQ==`, }, }); const data: WrikeOAuthResponse = res.data; @@ -152,7 +161,7 @@ export class WrikeConnectionService implements ITicketingConnectionService { 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, ), }, }); From 5bb6b489d8ae8a43138c443cbc0177ea4dee6066 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 18:09:41 +0200 Subject: [PATCH 08/15] :pencil2: Fix type asssertion --- packages/shared/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 8306b28df..b1a7cfa7c 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -7,7 +7,7 @@ export enum AuthStrategy { // TODO : remove clientId export type ProviderConfig = { scopes: string; - authBaseUrl: string; //url used to authorize an application on behalf of the user (only when authStrategy is oauth2) + authBaseUrl: string | null; //url used to authorize an application on behalf of the user (only when authStrategy is oauth2) logoPath: string; description: string; active?: boolean; From 8734f552af215780cb8ead69916bdab929b86d64 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 18:15:01 +0200 Subject: [PATCH 09/15] :pencil2: Fix --- packages/shared/src/envConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/envConfig.ts b/packages/shared/src/envConfig.ts index 174d890d8..89535d3c4 100644 --- a/packages/shared/src/envConfig.ts +++ b/packages/shared/src/envConfig.ts @@ -92,7 +92,7 @@ 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 authBaseUrlStartsWithSlash = providerConfig.authBaseUrl!.startsWith('/'); const apiUrlStartsWithSlash = providerConfig.apiUrl!.startsWith('/'); const apiUrlIsBlank = providerConfig.apiUrl! == ""; From a2ae18d6e4e0f7cb9f47b99b97b3e35aca19d1b1 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 18:16:59 +0200 Subject: [PATCH 10/15] :pencil2: Fix --- packages/api/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 966e28c91..96d8eba08 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -109,6 +109,5 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" - }, - "type": "module" + } } From 419c646b1ce6ce4e4f31f3db5ad667073a046783 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 18:22:18 +0200 Subject: [PATCH 11/15] :bug: Fix build magic link --- packages/shared/src/authUrl.ts | 2 ++ packages/shared/src/utils.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index 2501a29dd..e8723f212 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -84,6 +84,8 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { if (!clientId) throw new Error(`No client id for type ${type}`) const { scopes, authBaseUrl: baseUrl } = config; + + 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; diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index b1a7cfa7c..1561cafdb 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -1692,7 +1692,7 @@ type Provider = { name: string; apiUrl: string; scopes: string; - authBaseUrl: string; + authBaseUrl: string | null; logoPath: string; description?: string; }; From 69853694150b8041a734d50e1f07f2cfe1c688ef Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 18:24:29 +0200 Subject: [PATCH 12/15] :fire: Remove dead action --- .../docker-compose.dev.healthcheck.yml | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/docker-compose.dev.healthcheck.yml 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 From 533c27c94913ad5f8529edbd4a37a35426ed3446 Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 19:48:54 +0200 Subject: [PATCH 13/15] :construction: Wip --- packages/shared/package.json | 9 +- packages/shared/src/authUrl.ts | 9 +- packages/shared/src/utils.ts | 428 ++++++++++++++++++++++----------- 3 files changed, 304 insertions(+), 142 deletions(-) diff --git a/packages/shared/package.json b/packages/shared/package.json index 233b2b6dc..5c3d7499f 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -35,7 +35,8 @@ "@nestjs/common": "^6.0.0" }, "dependencies": { - "dotenv": "^16.3.1" + "dotenv": "^16.3.1", + "randomstring": "^1.3.0" }, "devDependencies": { "@nestjs/common": "^10.0.0", @@ -50,10 +51,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 e8723f212..94dda0ec7 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -1,5 +1,6 @@ import { needsSubdomain, OAuth2AuthData, providerToType } from "./envConfig"; import { AuthStrategy, providersConfig, ProviderConfig } from "./utils"; +import randomstring from "randomstring"; interface AuthParams { projectId: string; @@ -97,8 +98,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)}`; } @@ -111,9 +113,8 @@ 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="; - break; + case "gorgias": + params = `&response_type=code&nonce=${randomstring.generate()}` default: // For most providers, response_type=code is common params += "&response_type=code"; diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 1561cafdb..03db82809 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 | null; //url used to authorize an application on behalf of the user (only when authStrategy is oauth2) 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 = { @@ -31,44 +34,60 @@ 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/crm/v3', - 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://api.attio.com/v2', - customPropertiesUrl: '/docs/standard-objects-people', authStrategy: AuthStrategy.oauth2 }, 'zoho': { scopes: 'ZohoCRM.modules.ALL', - authBaseUrl: '/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: '/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/v1', - 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: '/oauth2/v0/authorize', - apiUrl: '/api/v0', + 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: null, - apiUrl: '/api/3', + 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: null, - 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,8 +153,11 @@ 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, @@ -130,8 +165,11 @@ export const providersConfig: ProvidersConfig = { }, '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,8 +177,10 @@ export const providersConfig: ProvidersConfig = { }, 'insightly': { scopes: '', - authBaseUrl: null, - apiUrl: '/v3.1', + 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, @@ -148,8 +188,11 @@ export const providersConfig: ProvidersConfig = { }, '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, @@ -158,36 +201,44 @@ export const providersConfig: ProvidersConfig = { //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: null, - 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.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: 'https://api.salesflare.com', scopes: '', - authBaseUrl: null, + 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, @@ -195,9 +246,12 @@ export const providersConfig: ProvidersConfig = { }, //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, @@ -205,9 +259,12 @@ export const providersConfig: ProvidersConfig = { }, //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, @@ -215,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, @@ -224,8 +284,11 @@ export const providersConfig: ProvidersConfig = { }, 'teamwork': { scopes: '', - authBaseUrl: 'https://www.teamwork.com/launchpad/login', - apiUrl: '', //everything is contained inside the accountUrl(subdomain) + 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, @@ -233,9 +296,12 @@ export const providersConfig: ProvidersConfig = { }, //todo 'vtiger': { - apiUrl: '', scopes: '', - authBaseUrl: null, + urls: { + docsUrl: "", + authBaseUrl: null, + 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, @@ -243,9 +309,12 @@ export const providersConfig: ProvidersConfig = { }, //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 @@ -254,95 +323,128 @@ export const providersConfig: ProvidersConfig = { 'ticketing': { 'front': { scopes: '', - authBaseUrl: 'https://app.frontapp.com/oauth/authorize', - apiUrl: 'https://api2.frontapp.com', + 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", authStrategy: AuthStrategy.oauth2 }, 'zendesk': { scopes: 'read write', - authBaseUrl: '/oauth/authorizations/new', - apiUrl: '/api/v2', + 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", 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: 'https://api.linear.app/graphql', 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: '/api/v4', scopes: '', - authBaseUrl: '/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: 'https://api.clickup.com/v2', 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: 'https://api.github.com', scopes: '', - authBaseUrl: 'https://github.com/login/oauth/authorize', + 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: '/api/v1', scopes: '', - authBaseUrl: '/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: 'https://app.asana.com/api/1.0', scopes: '', - authBaseUrl: 'https://app.asana.com/-/oauth_authorize', + 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, @@ -350,35 +452,44 @@ export const providersConfig: ProvidersConfig = { }, //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: 'https://dev.dixa.io', scopes: '', - authBaseUrl: null, + 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, @@ -386,26 +497,34 @@ export const providersConfig: ProvidersConfig = { }, //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: '/api/v1', scopes: '', - authBaseUrl: null, + 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, @@ -413,26 +532,33 @@ export const providersConfig: ProvidersConfig = { }, //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: 'https://docsapi.helpscout.net/v1', scopes: '', - authBaseUrl: null, + 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, authStrategy: AuthStrategy.api_key }, 'hive': { - apiUrl: 'https://app.hive.com/api/v1', scopes: '', - authBaseUrl: null, + 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, @@ -440,27 +566,33 @@ export const providersConfig: ProvidersConfig = { }, //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: null, + 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, authStrategy: AuthStrategy.api_key }, 'kustomer': { - apiUrl: 'https://api.kustomerapp.com', scopes: '', - authBaseUrl: null, + 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, @@ -468,26 +600,32 @@ export const providersConfig: ProvidersConfig = { }, //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: '/api/v1', scopes: '', - authBaseUrl: null, + 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, @@ -495,26 +633,32 @@ export const providersConfig: ProvidersConfig = { }, //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: 'https://api.app.shortcut.com', scopes: '', - authBaseUrl: null, + 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, @@ -522,52 +666,65 @@ export const providersConfig: ProvidersConfig = { }, //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: '/api/v4', scopes: '', - authBaseUrl: 'https://login.wrike.com/oauth2/authorize/v4', + 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, 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 @@ -656,9 +813,12 @@ export const providersConfig: ProvidersConfig = { active: false }, 'quickbooks': { - apiUrl: 'https://quickbooks.api.intuit.com/v3', scopes: '', - authBaseUrl: 'https://appcenter.intuit.com/connect/oauth2', + 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, From 133af09aa058c38665c1ff5f7397f9fee57537ec Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 19:49:45 +0200 Subject: [PATCH 14/15] :construction: Wip 2 --- .../crm/services/accelo/accelo.service.ts | 1 - .../crm/services/affinity/affinity.service.ts | 165 ---------------- .../services/insightly/insightly.service.ts | 165 ---------------- .../crm/services/sugarcrm/sugarcrm.service.ts | 187 ------------------ pnpm-lock.yaml | 14 ++ 5 files changed, 14 insertions(+), 518 deletions(-) delete mode 100644 packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts delete mode 100644 packages/api/src/@core/connections/crm/services/insightly/insightly.service.ts delete mode 100644 packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts 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 39573aec2..97ae8ed0d 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 @@ -72,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(), diff --git a/packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts b/packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts deleted file mode 100644 index 226b8be58..000000000 --- a/packages/api/src/@core/connections/crm/services/affinity/affinity.service.ts +++ /dev/null @@ -1,165 +0,0 @@ -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, - ICrmConnectionService, -} from '../../types'; -import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; -import { AuthStrategy } from '@panora/shared'; -import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; - -export type AffinityOAuthResponse = { - access_token: string; - refresh_token: string; - expires_at: string; -}; - -@Injectable() -export class AffinityConnectionService implements ICrmConnectionService { - 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(AffinityConnectionService.name); - this.registry.registerService('affinity', this); - this.type = providerToType('affinity', 'crm', AuthStrategy.oauth2); - } - - async handleCallback(opts: CallbackParams) { - try { - const { linkedUserId, projectId, code } = opts; - const isNotUnique = await this.prisma.connections.findFirst({ - where: { - id_linked_user: linkedUserId, - provider_slug: `affinity`, - vertical: 'crm', - }, - }); - - //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 subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - }, - }); - const data: AffinityOAuthResponse = res.data; - this.logger.log( - 'OAuth credentials : affinity 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: '', - expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 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: 'affinity', - vertical: 'crm', - token_type: 'oauth', - account_url: '', - access_token: this.cryptoService.encrypt(data.access_token), - refresh_token: this.cryptoService.encrypt(data.refresh_token), - expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 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, 'affinity', Action.oauthCallback); - } - } - - async handleTokenRefresh(opts: RefreshParams) { - try { - const { connectionId, refreshToken, projectId } = opts; - const formData = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: this.cryptoService.decrypt(refreshToken), - }); - const CREDENTIALS = (await this.cService.getCredentials( - projectId, - this.type, - )) as OAuth2AuthData; - - //const subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - Authorization: `Basic JHt0aGlzLmVudi5nZXRgQWZmaW5pdHlTZWNyZXRgKCkuQ0xJRU5UX0lEfTokewogICAgICAgICAgICAgICAgdGhpcy5lbnYuZ2V0YEFmZmluaXR5U2VjcmV0YCgpLkNMSUVOVF9TRUNSRVQKICAgICAgICAgICAgICB9`, - }, - }); - const data: AffinityOAuthResponse = 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_at) * 1000, - ), - }, - }); - this.logger.log('OAuth credentials updated : affinity '); - } catch (error) { - handleServiceError(error, this.logger, 'affinity', Action.oauthRefresh); - } - } -} diff --git a/packages/api/src/@core/connections/crm/services/insightly/insightly.service.ts b/packages/api/src/@core/connections/crm/services/insightly/insightly.service.ts deleted file mode 100644 index 223b95cbc..000000000 --- a/packages/api/src/@core/connections/crm/services/insightly/insightly.service.ts +++ /dev/null @@ -1,165 +0,0 @@ -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, - ICrmConnectionService, -} from '../../types'; -import { ServiceRegistry } from '../registry.service'; -import { OAuth2AuthData, providerToType } from '@panora/shared'; -import { AuthStrategy } from '@panora/shared'; -import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; - -export type InsightlyOAuthResponse = { - access_token: string; - refresh_token: string; - expires_at: string; -}; - -@Injectable() -export class InsightlyConnectionService implements ICrmConnectionService { - 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(InsightlyConnectionService.name); - this.registry.registerService('insightly', this); - this.type = providerToType('insightly', 'crm', 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: `insightly`, - vertical: 'crm', - }, - }); - - //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 subdomain = 'panora'; - const res = await axios.post('', formData.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - }, - }); - const data: InsightlyOAuthResponse = res.data; - this.logger.log( - 'OAuth credentials : insightly 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: '', - expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 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: 'insightly', - vertical: 'crm', - token_type: 'oauth', - account_url: '', - access_token: this.cryptoService.encrypt(data.access_token), - refresh_token: this.cryptoService.encrypt(data.refresh_token), - expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_at) * 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, 'insightly', 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 data: InsightlyOAuthResponse = 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_at) * 1000, - ), - }, - }); - this.logger.log('OAuth credentials updated : insightly '); - } catch (error) { - handleServiceError(error, this.logger, 'insightly', Action.oauthRefresh); - } - } -} diff --git a/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts b/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts deleted file mode 100644 index 37dc5c191..000000000 --- a/packages/api/src/@core/connections/crm/services/sugarcrm/sugarcrm.service.ts +++ /dev/null @@ -1,187 +0,0 @@ -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, - ICrmConnectionService, -} from '../../types'; -import { ServiceRegistry } from '../registry.service'; -import { - OAuth2AuthData, - providersConfig, - providerToType, -} from '@panora/shared'; -import { AuthStrategy } from '@panora/shared'; -import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; - -export type SugarcrmOAuthResponse = { - access_token: string; - expires_in: number; - token_type: string; - scope: string; - refresh_token: string; - refresh_expires_in: number; - download_token: string; -}; - -@Injectable() -export class SugarcrmConnectionService implements ICrmConnectionService { - 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(SugarcrmConnectionService.name); - this.registry.registerService('sugarcrm', this); - this.type = providerToType('sugarcrm', 'crm', 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: `sugarcrm`, - vertical: 'crm', - }, - }); - - //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, - grant_type: 'password', - username: '', - password: '', - platform: 'custom', - }); - //const subdomain = 'panora'; - const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN}/rest/v11/oauth2/token`, - formData.toString(), - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - }, - }, - ); - const data: SugarcrmOAuthResponse = res.data; - this.logger.log( - 'OAuth credentials : sugarcrm ticketing ' + JSON.stringify(data), - ); - - let db_res; - const connection_token = uuidv4(); - //get the right BASE URL API - const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['crm']['sugarcrm'].apiUrl; - - 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: BASE_API_URL, - expiration_timestamp: new Date( - new Date().getTime() + Number(data.expires_in) * 1000, - ), - status: 'valid', - created_at: new Date(), - }, - }); - } else { - db_res = await this.prisma.connections.create({ - data: { - id_connection: uuidv4(), - connection_token: connection_token, - provider_slug: 'sugarcrm', - vertical: 'crm', - 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( - 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, 'sugarcrm', 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', - platform: 'custom', - refresh_token: this.cryptoService.decrypt(refreshToken), - }); - //const subdomain = 'panora'; - const res = await axios.post( - `${CREDENTIALS.SUBDOMAIN}/rest/v11/oauth2/token`, - formData.toString(), - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - }, - }, - ); - const data: SugarcrmOAuthResponse = 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 : sugarcrm '); - } catch (error) { - handleServiceError(error, this.logger, 'sugarcrm', Action.oauthRefresh); - } - } -} 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'} From ea880ee273627ae98c49f6f1c35c92c2ec8118ba Mon Sep 17 00:00:00 2001 From: nael Date: Fri, 12 Apr 2024 20:39:20 +0200 Subject: [PATCH 15/15] :sparkles: Added updates --- docker-compose.dev.yml | 7 - .../services/freeagent/freeagent.service.ts | 6 +- .../services/freshbooks/freshbooks.service.ts | 4 +- .../services/moneybird/moneybird.service.ts | 4 +- .../services/pennylane/pennylane.service.ts | 4 +- .../services/quickbooks/quickbooks.service.ts | 4 +- .../accounting/services/sage/sage.service.ts | 4 +- .../wave_financial/wave_financial.service.ts | 4 +- .../crm/services/accelo/accelo.service.ts | 2 +- .../crm/services/attio/attio.service.ts | 2 +- .../crm/services/capsule/capsule.service.ts | 4 +- .../crm/services/close/close.service.ts | 4 +- .../crm/services/copper/copper.service.ts | 2 +- .../crm/services/hubspot/hubspot.service.ts | 2 +- .../crm/services/keap/keap.service.ts | 2 +- .../services/pipedrive/pipedrive.service.ts | 2 +- .../services/teamleader/teamleader.service.ts | 2 +- .../crm/services/teamwork/teamwork.service.ts | 2 +- .../crm/services/zendesk/zendesk.service.ts | 2 +- .../crm/services/zoho/zoho.service.ts | 4 +- .../ticketing/services/aha/aha.service.ts | 2 +- .../ticketing/services/asana/asana.service.ts | 4 +- .../services/clickup/clickup.service.ts | 2 +- .../ticketing/services/front/front.service.ts | 2 +- .../services/github/github.service.ts | 2 +- .../services/gitlab/gitlab.service.ts | 2 +- .../services/gorgias/gorgias.service.ts | 2 +- .../services/linear/linear.service.ts | 2 +- .../ticketing/services/wrike/wrike.service.ts | 4 +- .../services/zendesk/zendesk.service.ts | 2 +- .../field-mapping/field-mapping.service.ts | 4 +- .../@core/passthrough/passthrough.service.ts | 2 +- packages/shared/package.json | 3 +- packages/shared/src/authUrl.ts | 18 +- packages/shared/src/envConfig.ts | 6 +- packages/shared/src/utils.ts | 818 ++++++++++++------ 36 files changed, 614 insertions(+), 328 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e19561635..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} 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 index 404f1c178..fc411d48a 100644 --- a/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts +++ b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts @@ -94,7 +94,8 @@ export class FreeagentConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['freshagent'].apiUrl, + account_url: + providersConfig['accounting']['freshagent'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -110,7 +111,8 @@ export class FreeagentConnectionService provider_slug: 'freeagent', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['freshagent'].apiUrl, + 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( 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 index f3c06c80d..23f0f5e22 100644 --- a/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts +++ b/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts @@ -93,7 +93,7 @@ export class FreshbooksConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['freshbooks'].apiUrl, + account_url: providersConfig['accounting']['freshbooks'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -109,7 +109,7 @@ export class FreshbooksConnectionService provider_slug: 'freshbooks', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['freshbooks'].apiUrl, + 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( diff --git a/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts index 7c6e158a8..4d2085d3a 100644 --- a/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts +++ b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts @@ -94,7 +94,7 @@ export class MoneybirdConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['moneybird'].apiUrl, + account_url: providersConfig['accounting']['moneybird'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -110,7 +110,7 @@ export class MoneybirdConnectionService provider_slug: 'moneybird', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['moneybird'].apiUrl, + 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( 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 index 873f5c24c..af1827251 100644 --- a/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts +++ b/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts @@ -92,7 +92,7 @@ export class PennylaneConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['pennylane'].apiUrl, + account_url: providersConfig['accounting']['pennylane'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -108,7 +108,7 @@ export class PennylaneConnectionService provider_slug: 'pennylane', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['pennylane'].apiUrl, + 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( 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 index 34a3fca27..76b24e868 100644 --- a/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts +++ b/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts @@ -94,7 +94,7 @@ export class QuickbooksConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['quickbooks'].apiUrl, + account_url: providersConfig['accounting']['quickbooks'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -110,7 +110,7 @@ export class QuickbooksConnectionService provider_slug: 'quickbooks', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['quickbooks'].apiUrl, + 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( diff --git a/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts b/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts index 967bc4cff..f29cb1b03 100644 --- a/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts +++ b/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts @@ -93,7 +93,7 @@ export class SageConnectionService implements IAccountingConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['sage'].apiUrl, + account_url: providersConfig['accounting']['sage'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -109,7 +109,7 @@ export class SageConnectionService implements IAccountingConnectionService { provider_slug: 'sage', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['sage'].apiUrl, + 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( 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 index 197e07086..80194129b 100644 --- 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 @@ -99,7 +99,7 @@ export class WaveFinancialConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['wave_financial'].apiUrl, + account_url: providersConfig['accounting']['wave_financial'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -115,7 +115,7 @@ export class WaveFinancialConnectionService provider_slug: 'wave_financial', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['wave_financial'].apiUrl, + 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( 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 97ae8ed0d..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 @@ -92,7 +92,7 @@ export class AcceloConnectionService implements ICrmConnectionService { const connection_token = uuidv4(); //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['crm']['accelo'].apiUrl; + CREDENTIALS.SUBDOMAIN + providersConfig['crm']['accelo'].urls.apiUrl; if (isNotUnique) { // Update existing connection 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 9d010e6cc..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 @@ -108,7 +108,7 @@ export class AttioConnectionService implements ICrmConnectionService { provider_slug: 'attio', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['attio'].apiUrl, + 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 b1e78c49a..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 @@ -94,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: providersConfig['crm']['capsule'].apiUrl, + account_url: providersConfig['crm']['capsule'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -110,7 +110,7 @@ export class CapsuleConnectionService implements ICrmConnectionService { provider_slug: 'capsule', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['capsule'].apiUrl, + 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 27d9ff709..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 @@ -98,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: providersConfig['crm']['close'].apiUrl, + account_url: providersConfig['crm']['close'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -114,7 +114,7 @@ export class CloseConnectionService implements ICrmConnectionService { provider_slug: 'close', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['close'].apiUrl, + 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 c2cbf3eb9..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 @@ -104,7 +104,7 @@ export class CopperConnectionService implements ICrmConnectionService { provider_slug: 'copper', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['copper'].apiUrl, + 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 41b04fa66..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 @@ -111,7 +111,7 @@ export class HubspotConnectionService implements ICrmConnectionService { provider_slug: 'hubspot', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['hubspot'].apiUrl, + 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 4634b92b7..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 @@ -108,7 +108,7 @@ export class KeapConnectionService implements ICrmConnectionService { provider_slug: 'keap', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['keap'].apiUrl, + 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 6449aed72..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 @@ -108,7 +108,7 @@ export class PipedriveConnectionService implements ICrmConnectionService { provider_slug: 'pipedrive', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['pipedrive'].apiUrl, + 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 da52623a7..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 @@ -94,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: providersConfig['crm']['teamleader'].apiUrl, + 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 dcc3060d5..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 @@ -83,7 +83,7 @@ export class TeamworkConnectionService implements ICrmConnectionService { const connection_token = uuidv4(); //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['crm']['teamwork'].apiUrl; + CREDENTIALS.SUBDOMAIN + providersConfig['crm']['teamwork'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ 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 422b55873..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 @@ -112,7 +112,7 @@ export class ZendeskConnectionService implements ICrmConnectionService { provider_slug: 'zendesk', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['zendesk'].apiUrl, + 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 0e122d873..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 @@ -134,7 +134,7 @@ export class ZohoConnectionService implements ICrmConnectionService { ), status: 'valid', created_at: new Date(), - account_url: apiDomain + providersConfig['crm']['zoho'].apiUrl, + account_url: apiDomain + providersConfig['crm']['zoho'].urls.apiUrl, }, }); } else { @@ -160,7 +160,7 @@ export class ZohoConnectionService implements ICrmConnectionService { linked_users: { connect: { id_linked_user: linkedUserId }, }, - account_url: apiDomain + providersConfig['crm']['zoho'].apiUrl, + 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 2297d4ec9..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 @@ -82,7 +82,7 @@ export class AhaConnectionService implements ITicketingConnectionService { const connection_token = uuidv4(); //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['aha'].apiUrl; + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['aha'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ 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 index df90bb82c..5d72478a2 100644 --- a/packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/asana/asana.service.ts @@ -96,7 +96,7 @@ export class AsanaConnectionService implements ITicketingConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['ticketing']['asana'].apiUrl, + account_url: providersConfig['ticketing']['asana'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -112,7 +112,7 @@ export class AsanaConnectionService implements ITicketingConnectionService { provider_slug: 'asana', vertical: 'ticketing', token_type: 'oauth', - account_url: providersConfig['ticketing']['asana'].apiUrl, + 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( 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 666894fa5..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 @@ -102,7 +102,7 @@ export class ClickupConnectionService implements ITicketingConnectionService { provider_slug: 'clickup', vertical: 'ticketing', token_type: 'oauth', - account_url: providersConfig['ticketing']['clickup'].apiUrl, + 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 89373c0f7..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 @@ -106,7 +106,7 @@ export class FrontConnectionService implements ITicketingConnectionService { provider_slug: 'front', vertical: 'ticketing', token_type: 'oauth', - account_url: providersConfig['ticketing']['front'].apiUrl, + 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 59e2be830..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 @@ -110,7 +110,7 @@ export class GithubConnectionService implements ITicketingConnectionService { connection_token: connection_token, provider_slug: 'github', vertical: 'ticketing', - account_url: providersConfig['ticketing']['github'].apiUrl, + 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 9e450c99e..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 @@ -83,7 +83,7 @@ export class GitlabConnectionService implements ITicketingConnectionService { let db_res; const connection_token = uuidv4(); const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['gitlab'].apiUrl; + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['gitlab'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ 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 bc6c89b6a..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 @@ -92,7 +92,7 @@ export class GorgiasConnectionService implements ITicketingConnectionService { //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['gorgias'].apiUrl; + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['gorgias'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ 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 a16afa741..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 @@ -105,7 +105,7 @@ export class LinearConnectionService implements ITicketingConnectionService { provider_slug: 'linear', vertical: 'ticketing', token_type: 'oauth', - account_url: providersConfig['ticketing']['linear'].apiUrl, + 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/ticketing/services/wrike/wrike.service.ts b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts index 769decfc7..4bc527610 100644 --- a/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/wrike/wrike.service.ts @@ -93,7 +93,7 @@ export class WrikeConnectionService implements ITicketingConnectionService { refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: `https://${data.host}` + - providersConfig['ticketing']['wriker'].apiUrl, + providersConfig['ticketing']['wriker'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -111,7 +111,7 @@ export class WrikeConnectionService implements ITicketingConnectionService { token_type: 'oauth', account_url: `https://${data.host}` + - providersConfig['ticketing']['wriker'].apiUrl, + 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( 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 80a03389f..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 @@ -84,7 +84,7 @@ export class ZendeskConnectionService implements ITicketingConnectionService { const connection_token = uuidv4(); //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['zendesk'].apiUrl; + CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['zendesk'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ 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 84c0eb709..0d4bc02b6 100644 --- a/packages/api/src/@core/field-mapping/field-mapping.service.ts +++ b/packages/api/src/@core/field-mapping/field-mapping.service.ts @@ -131,8 +131,10 @@ 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 248f8e55d..44b2e88bf 100644 --- a/packages/api/src/@core/passthrough/passthrough.service.ts +++ b/packages/api/src/@core/passthrough/passthrough.service.ts @@ -42,7 +42,7 @@ 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({ diff --git a/packages/shared/package.json b/packages/shared/package.json index 5c3d7499f..3f6166d87 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -35,8 +35,7 @@ "@nestjs/common": "^6.0.0" }, "dependencies": { - "dotenv": "^16.3.1", - "randomstring": "^1.3.0" + "dotenv": "^16.3.1" }, "devDependencies": { "@nestjs/common": "^10.0.0", diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index 94dda0ec7..b7d6c170f 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -1,7 +1,15 @@ -import { needsSubdomain, OAuth2AuthData, providerToType } from "./envConfig"; +import { OAuth2AuthData, providerToType } from "./envConfig"; import { AuthStrategy, providersConfig, ProviderConfig } from "./utils"; -import randomstring from "randomstring"; +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; @@ -84,7 +92,8 @@ 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}`) @@ -114,7 +123,8 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => { params = `audience=api.atlassian.com&${params}&prompt=consent`; break; case "gorgias": - params = `&response_type=code&nonce=${randomstring.generate()}` + params = `&response_type=code&nonce=${randomString()}`; + break; default: // For most providers, response_type=code is common params += "&response_type=code"; diff --git a/packages/shared/src/envConfig.ts b/packages/shared/src/envConfig.ts index 89535d3c4..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('/'); - const apiUrlIsBlank = providerConfig.apiUrl! == ""; + 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 03db82809..bbf69829e 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -299,7 +299,6 @@ export const providersConfig: ProvidersConfig = { scopes: '', urls: { docsUrl: "", - authBaseUrl: null, apiUrl: '', }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRcUYrYD8lnaFaDN93vwjHhksKJUG3rqlb1TCFC__oPBw&s', @@ -732,18 +731,24 @@ export const providersConfig: ProvidersConfig = { }, 'accounting': { 'pennylane': { - apiUrl: 'https://app.pennylane.com/api/external/v1', scopes: '', - authBaseUrl: 'https://app.pennylane.com/oauth/authorize', + 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, authStrategy: AuthStrategy.oauth2 }, 'freshbooks': { - apiUrl: 'https://api.freshbooks.com', scopes: '', - authBaseUrl: 'https://auth.freshbooks.com/oauth/authorize', + 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, @@ -751,26 +756,35 @@ export const providersConfig: ProvidersConfig = { }, //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: 'https://api.freeagent.com/v2', scopes: '', - authBaseUrl: 'https://api.freeagent.com/v2/approve_app', + 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, authStrategy: AuthStrategy.oauth2 }, 'sage': { - apiUrl: 'https://api.accounting.sage.com/v3.1', scopes: '', - authBaseUrl: 'https://www.sageone.com/oauth2/auth/central?filter=apiv3.1', + 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, @@ -778,26 +792,35 @@ export const providersConfig: ProvidersConfig = { }, //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: 'https://moneybird.com/api/v2', scopes: '', - authBaseUrl: 'https://moneybird.com/oauth/authorize', + 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, @@ -805,9 +828,12 @@ export const providersConfig: ProvidersConfig = { }, //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 @@ -826,17 +852,23 @@ export const providersConfig: ProvidersConfig = { }, //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: 'https://gql.waveapps.com/graphql/public', scopes: '', - authBaseUrl: 'https://api.waveapps.com/oauth2/authorize/', + 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, @@ -846,89 +878,111 @@ export const providersConfig: ProvidersConfig = { //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 @@ -937,377 +991,471 @@ export const providersConfig: ProvidersConfig = { //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 @@ -1316,505 +1464,631 @@ export const providersConfig: ProvidersConfig = { //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 @@ -1850,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 | null; logoPath: string; description?: string; }; @@ -1861,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, }));