diff --git a/docs/core-concepts/embedded-catalog.mdx b/docs/core-concepts/embedded-catalog.mdx
index 183eb42c4..2b0502dc1 100644
--- a/docs/core-concepts/embedded-catalog.mdx
+++ b/docs/core-concepts/embedded-catalog.mdx
@@ -13,7 +13,8 @@ icon: "wand-magic-sparkles"
## Import our pre-built React Component
- You can find the component on NPM [here](https://www.npmjs.com/package/@panora/embedded-card-react)
+ You can find the component on NPM
+ [here](https://www.npmjs.com/package/@panora/embedded-card-react)
@@ -49,6 +50,7 @@ icon: "wand-magic-sparkles"
```javascript React
-
- ```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 cp .env.example .env ```
-
- ```bash
- echo -e "node-linker=hoisted\npackage-import-method=clone-or-copy" > .npmrc
- ```
-
+
+
+ ```bash rm -rf node_modules .pnpm-store ./packages/api/dist
+ ./packages/api/node_modules ./apps/webapp/node_modules
+ ./apps/frontend_snippet/node_modules ```
+
+
+
+ ```bash echo -e "node-linker=hoisted\npackage-import-method=clone-or-copy" >
+ .npmrc ```
+
```bash
docker compose -f docker-compose.dev.yml up
```
- That's all! You can find the backend and other services running at their usual location. Editing code locally will immediately reflect.
+That's all! You can find the backend and other services running at their usual location. Editing code locally will immediately reflect.
@@ -56,109 +53,30 @@ First choose wisely which vertical the 3rd party belongs to among these:
- ticketing
- accounting
- ats
-- file-storage
+- file_storage
- hris
-- marketing-automation
+- marketing_automation
For the sake of the guide, now on we'll consider adding a 3rd party belonging to the `crm` vertical.
-## 1. Adding a new connection service for your 3rd Party
-
-Create a new folder with the name of your 3rd party. Let's call it _my3rdParty_.
-
-`cd @core/connections/crm/services/my3rdParty`
-
-Create a new file containing the core logic of your service.
-
-`cd @core/connections/crm/services/my3rdParty/my3rdParty.service.ts`
-
-It must implement the `ICrmConnectionService` interface.
-
-```ts
-export interface ICrmConnectionService {
- handleCallback(opts: CallbackParams): Promise;
- handleTokenRefresh(opts: RefreshParams): Promise;
-}
-
-export type CallbackParams = {
- linkedUserId: string;
- projectId: string;
- code: string;
- location?: string; //for zoho
-};
-
-export type RefreshParams = {
- connectionId: string;
- refreshToken: string;
- account_url?: string;
-};
-```
-
-```ts
-import { Injectable } from "@nestjs/common";
-import {
- CallbackParams,
- ICrmConnectionService,
- RefreshParams,
-} from "../../types";
-
-@Injectable()
-export class My3rdPartyConnectionService implements ICrmConnectionService {
- constructor(
- private prisma: PrismaService,
- private logger: LoggerService,
- private env: EnvironmentService,
- private cryptoService: EncryptionService,
- private registry: ServiceRegistry
- ) {
- this.logger.setContext(My3rdPartyConnectionService.name);
- this.registry.registerService("insert_the_name_of_your_3rd_party", this);
- }
-
- async handleCallback(opts: CallbackParams) {
- return;
- }
- async handleTokenRefresh(opts: RefreshParams) {
- return;
- }
-}
-```
+## 1. Look into the `packages/shared/src/utils.ts` file and check if the provider you want to build has its metadata set inside the `providersConfig` object.
-Now that you have the structure, check other 3rd parties implementations under `/@core/connections/crm/services` to build your functions.
+It should be available (if not contact Panora team) with active field set to false meaning the integration has not been built.
-## 2. Enable your connection service to handle oAuth granting access
+Actually an integration is built in 2 parts :
-Add your service to the `CrmConnectionModule` under @core/connections/crm/crm.connection.module.ts module !
+- the authentication part (oauth, api key, basic etc) which is built by the Panora team
+- the service integration where the mapping is created with our unified model which is what you'll build
-```ts
-@Module({
- imports: [WebhookModule],
- providers: [
- CrmConnectionsService,
- PrismaService,
- ServiceConnectionRegistry,
- LoggerService,
- WebhookService,
- EnvironmentService,
- EncryptionService,
- FreshsalesConnectionService,
- HubspotConnectionService,
- ZohoConnectionService,
- ZendeskConnectionService,
- PipedriveConnectionService,
- //INSERT YOUR SERVICE HERE
- My3rdPartyConnectionService,
- ],
- exports: [CrmConnectionsService],
-})
-export class CrmConnectionModule {}
-```
+## 2. Build your provider service
# You want to map a common object to your new 3rd Party ? π©βπ€
_Ie: Contact, Ticket, Deal, Company ..._
-For the sake of this guide, let's map the common object `contact` under `crm` vertical to _my3rdParty_ just defined just before.
+For the sake of this guide, let's map the common object `contact` under `crm` vertical to _my3rdParty_ (in reality it would be a real 3rd party name).
+
+### DISCLAIMER: an integration is considered valid when all common objects have been mapped. Then, after the PR is accepted we'll be able to set `active` field to `true` inside `providersConfig`.
## 1. Add a new service to map your common object to your 3rd party
@@ -168,7 +86,7 @@ Create a new service folder with the name of your 3rd party. Let's call it _my3r
You'll now create 3 files.
-`index.ts` _where your service is created and direct interaction with your 3rd party API is handled_
+`index.ts` \_where your service is created and direct interaction with your 3rd party API is handled
It must implement the `IContactService` interface.
@@ -197,8 +115,7 @@ export class My3rdPartyService implements IContactService {
this.logger.setContext(
CrmObject.contact.toUpperCase() + ':' + My3rdPartyService.name,
);
- this.registry.registerService('my3rdPartyService', this);
-
+ this.registry.registerService('my3rdParty', this);
}
async addContact(
contactData: 3rdPartyContactInput,
@@ -273,80 +190,14 @@ export class My3rdPartyMapper implements IContactMapper {
Check other implementations under `/crm/contacts/services` to fill the core functions.
-## 2. Enable your service
-
-`cd crm/contact/types/mappingsTypes.ts`
+## 2. Enable your new service
-Add your new 3rd party service to the `contactUnificationMapping` object.
+To make sure the service is enabled, dependencies and imports must be added.
+We built a script that does it in seconds.
-```ts
-// ADD YOUR IMPORT HERE
-import { My3rdPartyContactMapper } from '@contact/services/my3rdParty/mappers';
-
-const hubspotContactMapper = new HubspotContactMapper();
-const zendeskContactMapper = new ZendeskContactMapper();
-const zohoContactMapper = new ZohoContactMapper();
-const pipedriveContactMapper = new PipedriveContactMapper();
-const freshSalesContactMapper = new FreshsalesContactMapper();
-// INSERT BELOW YOUR 3rd PARTY HERE
-const my3rdPartyContactMapper = new My3rdPartyContactMapper();
-
-
-export const contactUnificationMapping = {
- hubspot: {
- unify: hubspotContactMapper.unify,
- desunify: hubspotContactMapper.desunify,
- },
- pipedrive: {
- unify: pipedriveContactMapper.unify,
- desunify: pipedriveContactMapper.desunify,
- },
- zoho: {
- unify: zohoContactMapper.unify,
- desunify: zohoContactMapper.desunify,
- },
- ....,
- ....,
- // INSERT BELOW YOUR 3rd PARTY HERE
- my3rdParty: {
- unify: my3rdPartyContactMapper.unify,
- desunify: my3rdPartyContactMapper.desunify,
- },
-};
-```
-
-Don't forget to add your service you've defined at step 1 inside the module under `/crm/contacts/contact.module.ts`.
+`pnpm run validate-connectors --vertical="crm" --objectType="contact"`
-```ts
-@Module({
- imports: [
- BullModule.registerQueue({
- name: "webhookDelivery",
- }),
- ],
- controllers: [ContactController],
- providers: [
- ContactService,
- PrismaService,
- LoggerService,
- FieldMappingService,
- SyncService,
- WebhookService,
- EncryptionService,
- ServiceRegistry,
- /* PROVIDERS SERVICES */
- FreshSalesService,
- ZendeskService,
- ZohoService,
- PipedriveService,
- HubspotService,
- //INSERT YOUR SERVICE HERE
- My3rdPartyService,
- ],
- exports: [SyncService],
-})
-export class ContactModule {}
-```
+The script will automatically scan the `/crm/contact/services` folder and detect any new service folder so all dependencies and imports are updated across the codebase.
### Congrats Hero ! π¦ΈββοΈ
diff --git a/docs/open-source/glossary.mdx b/docs/open-source/glossary.mdx
new file mode 100644
index 000000000..866abaf5e
--- /dev/null
+++ b/docs/open-source/glossary.mdx
@@ -0,0 +1,140 @@
+---
+title: "Glossary"
+description: "We welcome all contributions to Panora; this glossary is meant for people aiming to build core features"
+icon: "star"
+---
+
+## Introduction
+
+### The source of truth
+
+The `/packages/shared/src/utils.ts` file contains the most important object of the project which is `providersConfig`.
+
+```ts
+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',
+ logoPath: "https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png",
+ description: "Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users",
+ apiUrl: 'https://api.hubapi.com',
+ customPropertiesUrl: '/properties/v1/contacts/properties',
+ authStrategy: AuthStrategy.oauth2
+ },
+ ....
+ },
+ 'ticketing': {
+ 'zendesk': {
+ scopes: 'read write',
+ authBaseUrl: '/oauth/authorizations/new',
+ 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
+ },
+ ....
+ },
+ ....
+}
+```
+
+where
+
+- `authBaseUrl` is the api endpoint to authorize oauth connections if the provider has `authStrategy` == `AuthStrategy.oauth2`
+- `logoPath` is the logo address displayed across Panora
+- `apiUrl`is the base api endpoint used
+- `authStrategy` is the authentication strategy used, it is of type authStrategy
+
+```ts
+export enum AuthStrategy {
+ oauth2,
+ api_key,
+ basic,
+}
+```
+
+DISCLAIMER: it may be updated in the future if other auth methods are figured out.
+
+### The authentication flow
+
+By default, we focused the core on building the oAuth2 authentication strategy.
+But other strategies exist as well.
+
+All utils data could be found inside `/packages/shared/src/envConfig.ts`
+
+The important thing to note is the `type` string variable.
+
+We handle all connections strategies logic in the `packages/api/src/@core/connections-strategies`.
+
+One may want to call either
+
+- `https://${PANORA_API_URL}/connections-strategies/getCredentials` (used to get either custom credentials or default managed panora environment variables as a fallback)
+ or
+- `https://${PANORA_API_URL}/connections-strategies/createConnectionStrategy` (used in the webapp to let clients add custom connections credentials)
+
+Both endpoints ask for a `type` variable argument which is a `string`.
+
+## IT IS VERY IMPORTANT TO MAKE SURE THE FORMAT CONVENTION IS AS FOLLOWS IN UPPERCASE:
+
+`{PROVIDERNAME}_{VERTICALNAME}_{SOFTWAREMODE}_{AUTHMODE}` where
+`PROVIDERNAME` is the name of the provider
+`VERTICALNAME` is the vertical the provider belongs to
+`SOFTWAREMODE` is the software mode the client wants to run the provider on (onpremise, cloud). By default, set it to `CLOUD`.
+`AUTHMODE` is the authentication strategy of the provider (For now we have => OAUTH, API, BASIC)
+
+// i.e HUBSPOT_CRM_CLOUD_OAUTH
+// i.e ZENDESK_TICKETING_CLOUD_OAUTH
+
+You'll find this type in .env files, and in some db tables.
+
+### The authentication contributor guide
+
+If you don't see your provider inside the `providersConfig` object within the file `/packages/shared/src/utils.ts` then you may get in touch with Panora team members or build it yourself.
+
+Here are the steps to do so:
+
+# 1. Add the provider metadata to `providersConfig` object
+
+Choose the right vertical and add it given that it has to contain these potential fields:
+
+```ts
+export type ProviderConfig = {
+ scopes: string;
+ authBaseUrl: string;
+ logoPath: string;
+ description: string;
+ active?: boolean;
+ apiUrl: string;
+ customPropertiesUrl?: string;
+ authStrategy?: AuthStrategy;
+};
+```
+
+If `authStrategy` is not `AuthStrategy.oauth2`, set `authBaseUrl` to blank.
+
+NB: Some providers may use subdomains instead of plain auth api endpoints (i.e https://my_panora_domain/auth)
+In this case just write `authBaseUrl: /auth` with a starting `/`.
+It is important to start with a `/` as our code use that to detect if subomain is needed across the project.
+
+# 2. Update the construction of the authentication url (used in the frontend)
+
+Modify the `/packages/shared/src/authUrl.ts` file.
+
+Right now we only coded the oAuth2 side so we'll focus on it here.
+
+Update `handleOAuth2Url` function and switch/case found inside if specific params are needed for your provider.
+
+# 3. Generate boilerplate and code the service to build the connection
+
+Now you have to build the connection code inside `/packages/api/@core/src/connections/your_vertical/services`
+
+We have a script that automates all the code + the import across the app.
+
+Run `pnpm run prebuild-oauth-connector --vertical="crm" --provider="hubspot"` for instance.
+
+All imports, dependencies, environment variables are handled.
+
+All you have to do is to check similar services and finish the code.
+
+NB: If your provider needs a subdomain, you have to manually add the environment variable inside the docker-compose and .env .env.example files.
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/scripts/oauthConnector.js b/packages/api/scripts/oauthConnector.js
index f7a656cbe..017b46c95 100755
--- a/packages/api/scripts/oauthConnector.js
+++ b/packages/api/scripts/oauthConnector.js
@@ -220,7 +220,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne
}
// Function to add provider to EnvironmentService.ts
-function addProviderToEnvironmentService(provider, envServicePath) {
+/*function addProviderToEnvironmentService(provider, envServicePath) {
const providerMethodName_ = provider
.split('_')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
@@ -250,23 +250,25 @@ function addProviderToEnvironmentService(provider, envServicePath) {
fs.writeFileSync(envServicePath, content);
console.log(`Added ${providerMethodName}() to EnvironmentService.`);
-}
+}*/
// Function to add provider to docker-compose.dev.yml
function addProviderToDockerCompose(provider, vertical, dockerComposePath) {
const providerEnvPrefix = provider.toUpperCase();
const newEnvVariables = `
- ${providerEnvPrefix}_${vertical.toUpperCase()}_CLIENT_ID: $\{${providerEnvPrefix}_${vertical.toUpperCase()}_CLIENT_ID}
- ${providerEnvPrefix}_${vertical.toUpperCase()}_CLIENT_SECRET: $\{${providerEnvPrefix}_${vertical.toUpperCase()}_CLIENT_SECRET}
+ ${providerEnvPrefix}_${vertical.toUpperCase()}_CLOUD_CLIENT_ID: $\{${providerEnvPrefix}_${vertical.toUpperCase()}_CLOUD_CLIENT_ID}
+ ${providerEnvPrefix}_${vertical.toUpperCase()}_CLOUD_CLIENT_SECRET: $\{${providerEnvPrefix}_${vertical.toUpperCase()}_CLOUD_CLIENT_SECRET}
`;
let content = fs.readFileSync(dockerComposePath, { encoding: 'utf8' });
if (
- content.includes(`${providerEnvPrefix}_${vertical.toUpperCase()}_CLIENT_ID`)
+ content.includes(
+ `${providerEnvPrefix}_${vertical.toUpperCase()}_CLOUD_CLIENT_ID`,
+ )
) {
console.log(
- `${providerEnvPrefix}_${vertical.toUpperCase()}_CLIENT_ID already exists in docker-compose.dev.yml.`,
+ `${providerEnvPrefix}_${vertical.toUpperCase()}_CLOUD_CLIENT_ID already exists in docker-compose.dev.yml.`,
);
return;
}
diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json
index 4cc369848..13ab06529 100644
--- a/packages/api/tsconfig.json
+++ b/packages/api/tsconfig.json
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
+ "moduleResolution": "node",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
@@ -26,6 +27,7 @@
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
- "noFallthroughCasesInSwitch": false
+ "noFallthroughCasesInSwitch": false,
+ //"moduleResolution": "Bundler"
}
}
diff --git a/packages/shared/.eslintrc.cjs b/packages/shared/.eslintrc.cjs
deleted file mode 100644
index cc9c00124..000000000
--- a/packages/shared/.eslintrc.cjs
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-env node */
-module.exports = {
- extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
- parser: '@typescript-eslint/parser',
- plugins: ['@typescript-eslint'],
- root: true,
-};
\ No newline at end of file
diff --git a/packages/shared/package.json b/packages/shared/package.json
index db5695360..16c5486da 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -10,9 +10,7 @@
"eslint": "^8.55.0",
"typescript": "^5.3.3"
},
- "type": "module",
"dependencies": {
- "axios": "^1.5.1",
"dotenv": "^16.3.1"
}
}
\ No newline at end of file