Skip to content

Commit

Permalink
✨ Added provider connections
Browse files Browse the repository at this point in the history
  • Loading branch information
naelob committed Apr 15, 2024
1 parent b733635 commit 43e60cd
Show file tree
Hide file tree
Showing 16 changed files with 747 additions and 12 deletions.
9 changes: 9 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ services:
QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET}
WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID}
WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET}
GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}
MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}
PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}



restart:
unless-stopped
ports:
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.source.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ services:
QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET}
WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID}
WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET}
GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}
MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}
PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}




restart: unless-stopped
ports:
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ services:
QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET: ${QUICKBOOKS_ACCOUNTING_CLOUD_CLIENT_SECRET}
WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_ID}
WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET: ${WAVE_FINANCIAL_ACCOUNTING_CLOUD_CLIENT_SECRET}
GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${GETRESPONSE_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}
MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${MAILCHIMP_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}
PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_ID}
PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET: ${PODIUM_MARKETING_AUTOMATION_CLOUD_CLIENT_SECRET}




restart:
unless-stopped
Expand Down
1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,4 @@
"testEnvironment": "node"
}
}

12 changes: 3 additions & 9 deletions packages/api/scripts/oauthConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne
refresh_token: this.cryptoService.encrypt(data.refresh_token),
account_url: "",
expiration_timestamp: new Date(
new Date().getTime() + Number(data.expires_ina) * 1000,
new Date().getTime() + Number(data.expires_in) * 1000,
),
status: 'valid',
created_at: new Date(),
Expand All @@ -145,7 +145,7 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne
access_token: this.cryptoService.encrypt(data.access_token),
refresh_token: this.cryptoService.encrypt(data.refresh_token),
expiration_timestamp: new Date(
new Date().getTime() + Number(data.expires_ina) * 1000,
new Date().getTime() + Number(data.expires_in) * 1000,
),
status: 'valid',
created_at: new Date(),
Expand All @@ -166,12 +166,6 @@ export class ${providerUpper}ConnectionService implements I${verticalUpper}Conne
async handleTokenRefresh(opts: RefreshParams) {
try {
const { connectionId, refreshToken } = opts;
const formData = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.cryptoService.decrypt(refreshToken),
});
const { connectionId, refreshToken, projectId } = opts;
const CREDENTIALS = (await this.cService.getCredentials(
projectId,
Expand Down Expand Up @@ -204,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_ina) * 1000,
new Date().getTime() + Number(data.expires_in) * 1000,
),
},
});
Expand Down
8 changes: 8 additions & 0 deletions packages/api/src/@core/connections/connections.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { TicketingConnectionsService } from './ticketing/services/ticketing.connection.service';
import { ProviderVertical } from '@panora/shared';
import { AccountingConnectionsService } from './accounting/services/accounting.connection.service';
import { MarketingAutomationConnectionsService } from './marketing_automation/services/marketing_automation.connection.service';

export type StateDataType = {
projectId: string;
Expand All @@ -24,6 +25,7 @@ export class ConnectionsController {
private readonly crmConnectionsService: CrmConnectionsService,
private readonly ticketingConnectionsService: TicketingConnectionsService,
private readonly accountingConnectionsService: AccountingConnectionsService,
private readonly marketingAutomationConnectionsService: MarketingAutomationConnectionsService,
private logger: LoggerService,
private prisma: PrismaService,
) {
Expand Down Expand Up @@ -84,6 +86,12 @@ export class ConnectionsController {
case ProviderVertical.HRIS:
break;
case ProviderVertical.MarketingAutomation:
this.marketingAutomationConnectionsService.handleMarketingAutomationCallBack(
projectId,
linkedUserId,
providerName,
code,
);
break;
case ProviderVertical.Ticketing:
this.ticketingConnectionsService.handleTicketingCallBack(
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 @@ -5,19 +5,22 @@ import { LoggerService } from '@@core/logger/logger.service';
import { PrismaService } from '@@core/prisma/prisma.service';
import { TicketingConnectionModule } from './ticketing/ticketing.connection.module';
import { AccountingConnectionModule } from './accounting/accounting.connection.module';
import { MarketingAutomationConnectionsModule } from './marketing_automation/marketing_automation.connection.module';

@Module({
controllers: [ConnectionsController],
imports: [
CrmConnectionModule,
TicketingConnectionModule,
AccountingConnectionModule,
MarketingAutomationConnectionsModule,
],
providers: [LoggerService, PrismaService],
exports: [
CrmConnectionModule,
TicketingConnectionModule,
AccountingConnectionModule,
MarketingAutomationConnectionsModule,
],
})
export class ConnectionsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import { PrismaService } from '@@core/prisma/prisma.service';
import { LoggerService } from '@@core/logger/logger.service';
import { WebhookService } from '@@core/webhook/webhook.service';
import { WebhookModule } from '@@core/webhook/webhook.module';
import { EnvironmentService } from '@@core/environment/environment.service';
import { EncryptionService } from '@@core/encryption/encryption.service';
import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service';
import { MarketingAutomationConnectionsService } from './services/marketing_automation.connection.service';
import { ServiceRegistry } from './services/registry.service';

@Module({
imports: [WebhookModule],
providers: [
MarketingAutomationConnectionsService,
PrismaService,
LoggerService,
WebhookService,
EnvironmentService,
EncryptionService,
ServiceRegistry,
ConnectionsStrategiesService,
//PROVIDERS SERVICES
],
exports: [MarketingAutomationConnectionsService],
})
export class MarketingAutomationConnectionsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { PrismaService } from '@@core/prisma/prisma.service';
import { Action, handleServiceError } from '@@core/utils/errors';
import { LoggerService } from '@@core/logger/logger.service';
import { v4 as uuidv4 } from 'uuid';
import { EnvironmentService } from '@@core/environment/environment.service';
import { EncryptionService } from '@@core/encryption/encryption.service';
import {
CallbackParams,
RefreshParams,
IMarketingAutomationConnectionService,
} from '../../types';
import { ServiceRegistry } from '../registry.service';
import { AuthStrategy, providersConfig } from '@panora/shared';
import { OAuth2AuthData, providerToType } from '@panora/shared';
import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service';

export type GetresponseOAuthResponse = {
access_token: string;
refresh_token: string;
expires_in: string;
token_type: string;
scope: any;
};

@Injectable()
export class GetresponseConnectionService
implements IMarketingAutomationConnectionService
{
private readonly type: string;

constructor(
private prisma: PrismaService,
private logger: LoggerService,
private env: EnvironmentService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
private cService: ConnectionsStrategiesService,
) {
this.logger.setContext(GetresponseConnectionService.name);
this.registry.registerService('getresponse', this);
this.type = providerToType(
'getresponse',
'marketing_automation',
AuthStrategy.oauth2,
);
}

async handleCallback(opts: CallbackParams) {
try {
const { linkedUserId, projectId, code } = opts;
const isNotUnique = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'getresponse',
vertical: 'marketing_automation',
},
});

//reconstruct the redirect URI that was passed in the githubend it must be the same
const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`;
const CREDENTIALS = (await this.cService.getCredentials(
projectId,
this.type,
)) as OAuth2AuthData;

const formData = new URLSearchParams({
code: code,
grant_type: 'authorization_code',
});
const res = await axios.post(
'https://api.getresponse.com/v3/token',
formData.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Authorization: `Basic ${Buffer.from(
`${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`,
).toString('base64')}`,
},
},
);
const data: GetresponseOAuthResponse = res.data;
this.logger.log(
'OAuth credentials : getresponse ticketing ' + JSON.stringify(data),
);

let db_res;
const connection_token = uuidv4();

if (isNotUnique) {
db_res = await this.prisma.connections.update({
where: {
id_connection: isNotUnique.id_connection,
},
data: {
access_token: this.cryptoService.encrypt(data.access_token),
refresh_token: this.cryptoService.encrypt(data.refresh_token),
account_url:
providersConfig['marketing_automation']['getresponse'].urls
.apiUrl,
expiration_timestamp: new Date(
new Date().getTime() + Number(data.expires_in) * 1000,
),
status: 'valid',
created_at: new Date(),
},
});
} else {
db_res = await this.prisma.connections.create({
data: {
id_connection: uuidv4(),
connection_token: connection_token,
provider_slug: 'getresponse',
vertical: 'marketing_automation',
token_type: 'oauth',
account_url:
providersConfig['marketing_automation']['getresponse'].urls
.apiUrl,
access_token: this.cryptoService.encrypt(data.access_token),
refresh_token: this.cryptoService.encrypt(data.refresh_token),
expiration_timestamp: new Date(
new Date().getTime() + Number(data.expires_in) * 1000,
),
status: 'valid',
created_at: new Date(),
projects: {
connect: { id_project: projectId },
},
linked_users: {
connect: { id_linked_user: linkedUserId },
},
},
});
}
return db_res;
} catch (error) {
handleServiceError(
error,
this.logger,
'getresponse',
Action.oauthCallback,
);
}
}

async handleTokenRefresh(opts: RefreshParams) {
try {
const { connectionId, refreshToken, projectId } = opts;
const CREDENTIALS = (await this.cService.getCredentials(
projectId,
this.type,
)) as OAuth2AuthData;
const formData = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.cryptoService.decrypt(refreshToken),
});
const res = await axios.post(
'https://api.getresponse.com/v3/token',
formData.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Authorization: `Basic ${Buffer.from(
`${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`,
).toString('base64')}`,
},
},
);
const data: GetresponseOAuthResponse = res.data;
await this.prisma.connections.update({
where: {
id_connection: connectionId,
},
data: {
access_token: this.cryptoService.encrypt(data.access_token),
refresh_token: this.cryptoService.encrypt(data.refresh_token),
expiration_timestamp: new Date(
new Date().getTime() + Number(data.expires_in) * 1000,
),
},
});
this.logger.log('OAuth credentials updated : getresponse ');
} catch (error) {
handleServiceError(
error,
this.logger,
'getresponse',
Action.oauthRefresh,
);
}
}
}
Loading

0 comments on commit 43e60cd

Please sign in to comment.