Skip to content

Commit

Permalink
🎉 Added files
Browse files Browse the repository at this point in the history
  • Loading branch information
naelob committed Jul 18, 2024
1 parent 3d0b74f commit 35c7247
Show file tree
Hide file tree
Showing 78 changed files with 4,827 additions and 451 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ export class CoreUnification {
if (sourceObject == null) return [];
let targetType_: TargetObject;
switch (vertical.toLowerCase()) {
case ConnectorCategory.Ecommerce:
targetType_ = targetType as EcommerceObject;
const ecommerceRegistry = this.registry.getService('ecommerce');
return ecommerceRegistry.unify({
sourceObject,
targetType_,
providerName,
connectionId,
customFieldMappings,
extraParams,
});
case ConnectorCategory.Crm:
targetType_ = targetType as CrmObject;
const crmRegistry = this.registry.getService('crm');
Expand Down Expand Up @@ -151,6 +162,15 @@ export class CoreUnification {
try {
let targetType_: TargetObject;
switch (vertical.toLowerCase()) {
case ConnectorCategory.Ecommerce:
targetType_ = targetType as EcommerceObject;
const ecommerceRegistry = this.registry.getService('crm');
return ecommerceRegistry.desunify({
sourceObject,
targetType_,
providerName,
customFieldMappings,
});
case ConnectorCategory.Crm:
targetType_ = targetType as CrmObject;
const crmRegistry = this.registry.getService('crm');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service';
import { LoggerService } from '@@core/@core-services/logger/logger.service';
import { ValidateUserService } from '@@core/utils/services/validate-user.service';
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/@core/connections/connections.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { HrisConnectionModule } from './hris/hris.connection.module';
import { ManagementConnectionsModule } from './management/management.connection.module';
import { MarketingAutomationConnectionsModule } from './marketingautomation/marketingautomation.connection.module';
import { TicketingConnectionModule } from './ticketing/ticketing.connection.module';
import { EcommerceConnectionModule } from './ecommerce/ecommerce.connection.module';

@Module({
controllers: [ConnectionsController],
Expand All @@ -23,6 +24,7 @@ import { TicketingConnectionModule } from './ticketing/ticketing.connection.modu
MarketingAutomationConnectionsModule,
FilestorageConnectionModule,
HrisConnectionModule,
EcommerceConnectionModule,
SyncModule,
],
providers: [ValidateUserService, OAuthTokenRefreshService],
Expand All @@ -35,6 +37,7 @@ import { TicketingConnectionModule } from './ticketing/ticketing.connection.modu
AtsConnectionModule,
MarketingAutomationConnectionsModule,
FilestorageConnectionModule,
EcommerceConnectionModule,
HrisConnectionModule,
ManagementConnectionsModule,
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { EnvironmentService } from '@@core/@core-services/environment/environment.service';
import { BullQueueModule } from '@@core/@core-services/queues/queue.module';
import { WebhookModule } from '@@core/@core-services/webhooks/panora-webhooks/webhook.module';
import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service';
import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service';
import { Module } from '@nestjs/common';
import { EcommerceConnectionsService } from './services/ecommerce.connection.service';
import { ServiceRegistry } from './services/registry.service';
import { ShopifyConnectionService } from './services/shopify/shopify.service';

@Module({
imports: [WebhookModule, BullQueueModule],
providers: [
EcommerceConnectionsService,
WebhookService,
EnvironmentService,
ServiceRegistry,
ConnectionsStrategiesService,
//PROVIDERS SERVICES,
ShopifyConnectionService,
],
exports: [EcommerceConnectionsService],
})
export class EcommerceConnectionModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { LoggerService } from '@@core/@core-services/logger/logger.service';
import { PrismaService } from '@@core/@core-services/prisma/prisma.service';
import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service';
import {
CallbackParams,
IConnectionCategory,
RefreshParams,
} from '@@core/connections/@utils/types';
import { Injectable } from '@nestjs/common';
import { connections as Connection } from '@prisma/client';
import { v4 as uuidv4 } from 'uuid';
import { ServiceRegistry } from './registry.service';
import { CategoryConnectionRegistry } from '@@core/@core-services/registries/connections-categories.registry';

@Injectable()
export class EcommerceConnectionsService implements IConnectionCategory {
constructor(
private serviceRegistry: ServiceRegistry,
private connectionCategoryRegistry: CategoryConnectionRegistry,
private webhook: WebhookService,
private logger: LoggerService,
private prisma: PrismaService,
) {
this.logger.setContext(EcommerceConnectionsService.name);
this.connectionCategoryRegistry.registerService('ecommerce', this);
}
//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 handleCallBack(
providerName: string,
callbackOpts: CallbackParams,
type_strategy: 'oauth' | 'apikey' | 'basic',
) {
try {
const serviceName = providerName.toLowerCase();

const service = this.serviceRegistry.getService(serviceName);

if (!service) {
throw new ReferenceError(`Unknown provider, found ${providerName}`);
}
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: `/${type_strategy}/callback`,
provider: providerName.toLowerCase(),
direction: '0',
timestamp: new Date(),
id_linked_user: callbackOpts.linkedUserId,
},
});
//directly send the webhook
await this.webhook.deliverWebhook(
data,
'connection.created',
callbackOpts.projectId,
event.id_event,
);
} catch (error) {
throw error;
}
}

async handleTokensRefresh(
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 ReferenceError(`Unknown provider, found ${providerName}`);
}
const refreshOpts: RefreshParams = {
connectionId: connectionId,
refreshToken: refresh_token,
account_url: account_url,
projectId: id_project,
};
await service.handleTokenRefresh(refreshOpts);
} catch (error) {
throw error;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Injectable } from '@nestjs/common';
import { IEcommerceConnectionService } from '../types';

@Injectable()
export class ServiceRegistry {
private serviceMap: Map<string, IEcommerceConnectionService>;

constructor() {
this.serviceMap = new Map<string, IEcommerceConnectionService>();
}

registerService(serviceKey: string, service: IEcommerceConnectionService) {
this.serviceMap.set(serviceKey, service);
}

getService(integrationId: string): IEcommerceConnectionService {
const service = this.serviceMap.get(integrationId);
if (!service) {
throw new ReferenceError(
`Service not found for integration ID: ${integrationId}`,
);
}
return service;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service';
import { LoggerService } from '@@core/@core-services/logger/logger.service';
import { PrismaService } from '@@core/@core-services/prisma/prisma.service';
import { ConnectionUtils } from '@@core/connections/@utils';
import { APIKeyCallbackParams } from '@@core/connections/@utils/types';
import { Injectable } from '@nestjs/common';
import { CONNECTORS_METADATA } from '@panora/shared';
import { v4 as uuidv4 } from 'uuid';
import { IEcommerceConnectionService } from '../../types';
import { ServiceRegistry } from '../registry.service';

@Injectable()
export class ShopifyConnectionService implements IEcommerceConnectionService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
private connectionUtils: ConnectionUtils,
) {
this.logger.setContext(ShopifyConnectionService.name);
this.registry.registerService('ashby', this);
}

async handleCallback(opts: APIKeyCallbackParams) {
try {
const { linkedUserId, projectId } = opts;
const isNotUnique = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'ashby',
vertical: 'ecommerce',
},
});

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(opts.apikey),
account_url: CONNECTORS_METADATA['ecommerce']['ashby'].urls
.apiUrl as string,
status: 'valid',
created_at: new Date(),
},
});
} else {
db_res = await this.prisma.connections.create({
data: {
id_connection: uuidv4(),
connection_token: connection_token,
provider_slug: 'ashby',
vertical: 'ecommerce',
token_type: 'api_key',
account_url: CONNECTORS_METADATA['ecommerce']['ashby'].urls
.apiUrl as string,
access_token: this.cryptoService.encrypt(opts.apikey),
status: 'valid',
created_at: new Date(),
projects: {
connect: { id_project: projectId },
},
linked_users: {
connect: {
id_linked_user: await this.connectionUtils.getLinkedUserId(
projectId,
linkedUserId,
),
},
},
},
});
}
return db_res;
} catch (error) {
throw error;
}
}
}
7 changes: 7 additions & 0 deletions packages/api/src/@core/connections/ecommerce/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CallbackParams, RefreshParams } from '@@core/connections/@utils/types';
import { connections as Connection } from '@prisma/client';

export interface IEcommerceConnectionService {
handleCallback(opts: CallbackParams): Promise<Connection>;
handleTokenRefresh?(opts: RefreshParams): Promise<any>;
}
42 changes: 42 additions & 0 deletions packages/api/src/@core/sync/sync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export class CoreSyncService {
case ConnectorCategory.Ats:
await this.handleAtsSync(provider, linkedUserId);
break;
case ConnectorCategory.Ecommerce:
await this.handleEcommerceSync(provider, linkedUserId);
break;
}
} catch (error) {
throw error;
Expand Down Expand Up @@ -278,6 +281,45 @@ export class CoreSyncService {
}
}

async handleEcommerceSync(provider: string, linkedUserId: string) {
const tasks = [
() =>
this.registry.getService('ecommerce', 'order').syncForLinkedUser({
integrationId: provider,
linkedUserId: linkedUserId,
}),
() =>
this.registry
.getService('ecommerce', 'fulfillmentorders')
.syncForLinkedUser({
integrationId: provider,
linkedUserId: linkedUserId,
}),
() =>
this.registry.getService('ecommerce', 'fulfillment').syncForLinkedUser({
integrationId: provider,
linkedUserId: linkedUserId,
}),
() =>
this.registry.getService('ecommerce', 'product').syncForLinkedUser({
integrationId: provider,
linkedUserId: linkedUserId,
}),
() =>
this.registry.getService('ecommerce', 'customer').syncForLinkedUser({
integrationId: provider,
linkedUserId: linkedUserId,
}),
];
for (const task of tasks) {
try {
await task();
} catch (error) {
this.logger.error(`Ecommerce Task failed: ${error.message}`, error);
}
}
}

async handleAtsSync(provider: string, linkedUserId: string) {
const tasks = [
() =>
Expand Down
4 changes: 3 additions & 1 deletion packages/api/src/@core/utils/types/desunify.input.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AccountingObjectInput } from './original/original.accounting';
import { AtsObjectInput } from './original/original.ats';
import { CrmObjectInput } from './original/original.crm';
import { EcommerceObjectInput } from './original/original.ecommerce';
import { FileStorageObjectInput } from './original/original.file-storage';
import { HrisObjectInput } from './original/original.hris';
import { MarketingAutomationObjectInput } from './original/original.marketing-automation';
Expand All @@ -13,4 +14,5 @@ export type DesunifyReturnType =
| MarketingAutomationObjectInput
| AccountingObjectInput
| FileStorageObjectInput
| HrisObjectInput;
| HrisObjectInput
| EcommerceObjectInput;
Loading

0 comments on commit 35c7247

Please sign in to comment.