Skip to content

Commit

Permalink
Merge pull request #214 from panoratech/feat/zendesk-front-integration
Browse files Browse the repository at this point in the history
✨ Integrations for ticket WIP
  • Loading branch information
naelob authored Jan 8, 2024
2 parents 57cc29b + 8a18f09 commit b156c21
Show file tree
Hide file tree
Showing 174 changed files with 10,650 additions and 1,020 deletions.
3 changes: 2 additions & 1 deletion apps/frontend-snippet/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
VITE_BACKEND_DOMAIN=
VITE_BACKEND_DOMAIN=
VITE_ML_FRONTEND_URL=
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions apps/frontend-snippet/src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ export const providersConfig: ProvidersConfig = {
},

},
'Ticketing': {
'front': {
clientId: '5f1d8d963c77285f339a',
scopes: '',
authBaseUrl: 'https://app.frontapp.com/oauth/authorize',
logoPath: 'assets/ticketing/front.png',
},
'zendesk_tcg': {
clientId: 'panora_ticketing',
scopes: 'read write',
authBaseUrl: 'https://panora3702.zendesk.com/oauth/authorizations/new',
logoPath: 'assets/crm/zendesk_logo.png',
},
},
'Accounting': {
'pennylane': {
clientId: '',
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend-snippet/src/hooks/useOAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ const useOAuth = ({ providerName, returnUrl, projectId, linkedUserId, onSuccess
console.log(finalAuth);
} else if(providerName == "zendesk"){
finalAuth = `${baseUrl}?client_id=${encodeURIComponent(clientId)}&response_type=code&redirect_uri=${encodedRedirectUrl}&state=${state}`
} else {
} else if(providerName == "zendesk_tcg" || providerName=="front") {
finalAuth = `${baseUrl}?client_id=${encodeURIComponent(clientId)}&response_type=code&redirect_uri=${encodedRedirectUrl}&scope=${encodeURIComponent(scopes)}&state=${state}`
}else{
finalAuth = addScope ?
`${baseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&scope=${encodeURIComponent(scopes)}&state=${state}`
: `${baseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&state=${state}`;
Expand Down
Binary file added apps/webapp/public/providers/crm/zendesk_tcg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ ZENDESK_SELL_CLIENT_SECRET=
ZENDESK_TICKETING_SUBDOMAIN=
ZENDESK_TICKETING_CLIENT_ID=
ZENDESK_TICKETING_CLIENT_SECRET=
FRONT_CLIENT_ID=
FRONT_CLIENT_SECRET=

OAUTH_REDIRECT_BASE='https://api-staging.panora.dev/'

Expand Down
182 changes: 120 additions & 62 deletions packages/api/prisma/schema.prisma

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions packages/api/scripts/commonObject.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class ${ObjectCap}Service {
integrationId: string,
linkedUserId: string,
remote_data?: boolean,
): Promise<ApiResponse<${ObjectCap}Response>> {
): Promise<${ObjectCap}Response> {
return
}
Expand All @@ -97,29 +97,29 @@ export class ${ObjectCap}Service {
integrationId: string,
linkedUserId: string,
remote_data?: boolean,
): Promise<ApiResponse<${ObjectCap}Response>> {
): Promise<${ObjectCap}Response> {
return;
}
async get${ObjectCap}(
id_${VerticalLow}_${objectType}: string,
remote_data?: boolean,
): Promise<ApiResponse<${ObjectCap}Response>> {
): Promise<${ObjectCap}Response> {
return;
}
async get${ObjectCap}s(
integrationId: string,
linkedUserId: string,
remote_data?: boolean,
): Promise<ApiResponse<${ObjectCap}Response>> {
): Promise<${ObjectCap}Response> {
return;
}
async update${ObjectCap}(
id: string,
update${ObjectCap}Data: Partial<Unified${ObjectCap}Input>,
): Promise<ApiResponse<${ObjectCap}Response>> {
): Promise<${ObjectCap}Response> {
return;
}
}
Expand Down Expand Up @@ -352,7 +352,7 @@ export class ${ObjectCap}Controller {
name: 'id',
required: true,
type: String,
description: 'id of the `${objectType}` you want to retrive.',
description: 'id of the ${objectType} you want to retrieve.',
})
@ApiQuery({
name: 'remoteData',
Expand Down Expand Up @@ -398,13 +398,13 @@ export class ${ObjectCap}Controller {
//@ApiCustomResponse(${ObjectCap}Response)
@Post()
add${ObjectCap}(
@Body() unfied${ObjectCap}Data: Unified${ObjectCap}Input,
@Body() unified${ObjectCap}Data: Unified${ObjectCap}Input,
@Headers('integrationId') integrationId: string,
@Headers('linkedUserId') linkedUserId: string,
@Query('remoteData') remote_data?: boolean,
) {
return this.${objectType}Service.add${ObjectCap}(
unfied${ObjectCap}Data,
unified${ObjectCap}Data,
integrationId,
linkedUserId,
remote_data,
Expand Down
10 changes: 6 additions & 4 deletions packages/api/scripts/seed.webapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ const prisma = new PrismaClient();
async function main() {
const org = await prisma.organizations.create({
data: {
id_organization: uuidv4(),
id_organization: `55222419-795d-4183-8478-361626363e58`,
name: `Acme Inc`,
stripe_customer_id: `cust_stripe_acme_${uuidv4()}`,
stripe_customer_id: `cust_stripe_acme_56604f75-7bf8-4541-9ab4-5928aade4bb8`,
},
});

await prisma.users.create({
data: {
id_user: uuidv4(),
id_user: `0ce39030-2901-4c56-8db0-5e326182ec6b`,
email: '[email protected]',
password_hash: 'password_hashed_her',
password_hash:
'$2b$10$Nxcp3x0yDaCrMrhZQ6IiNeqk0BxxDTnfn9iGG2UK5nWMh/UB6LgZu',
first_name: 'audrey',
last_name: 'aubry',
id_organization: '55222419-795d-4183-8478-361626363e58',
},
});

Expand Down
2 changes: 1 addition & 1 deletion packages/api/scripts/webhook.testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async function main() {
id_webhook_endpoint: 'a18682af-43f6-4ed2-8bde-b84298f51dde',
},
data: {
scope: 'crm.contact.pulled',
scope: ['crm.contact.pulled'],
},
});
}
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 @@ -6,12 +6,14 @@ import { NotFoundError, handleServiceError } from '@@core/utils/errors';
import { PrismaService } from '@@core/prisma/prisma.service';
import { ProviderVertical, getProviderVertical } from '@@core/utils/types';
import { ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { TicketingConnectionsService } from './ticketing/services/ticketing.connection.service';

@ApiTags('connections')
@Controller('connections')
export class ConnectionsController {
constructor(
private readonly crmConnectionsService: CrmConnectionsService,
private readonly ticketingConnectionsService: TicketingConnectionsService,
private logger: LoggerService,
private prisma: PrismaService,
) {
Expand Down Expand Up @@ -67,6 +69,12 @@ export class ConnectionsController {
case ProviderVertical.MarketingAutomation:
break;
case ProviderVertical.Ticketing:
this.ticketingConnectionsService.handleTicketingCallBack(
projectId,
linkedUserId,
providerName,
code,
);
break;
case ProviderVertical.Unknown:
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { PipedriveConnectionService } from './services/pipedrive/pipedrive.servi
WebhookService,
EnvironmentService,
EncryptionService,
// PROVIDERS SERVICES
FreshsalesConnectionService,
HubspotConnectionService,
ZohoConnectionService,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
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,
FrontOAuthResponse,
} from '../../types';
import { ServiceRegistry } from '../registry.service';

@Injectable()
export class FrontConnectionService implements ITicketingConnectionService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private env: EnvironmentService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(FrontConnectionService.name);
this.registry.registerService('front', this);
}

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

//reconstruct the redirect URI that was passed in the frontend it must be the same
const REDIRECT_URI = `${this.env.getOAuthRredirectBaseUrl()}/connections/oauth/callback`;

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

let db_res;

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),
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(),
provider_slug: 'front',
token_type: 'oauth',
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, 'front', Action.oauthCallback);
}
}

async handleTokenRefresh(opts: RefreshParams) {
try {
const { connectionId, refreshToken } = opts;
const formData = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.cryptoService.decrypt(refreshToken),
});
const res = await axios.post(
`https://app.frontapp.com/oauth/token`,
formData.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Authorization: `Basic ${Buffer.from(
`${this.env.getFrontSecret().CLIENT_ID}:${
this.env.getFrontSecret().CLIENT_SECRET
}`,
).toString('base64')}`,
},
},
);
const data: FrontOAuthResponse = 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 : front ');
} catch (error) {
handleServiceError(error, this.logger, 'front', Action.oauthRefresh);
}
}
}
Loading

0 comments on commit b156c21

Please sign in to comment.