Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Integrations for ticket WIP #214

Merged
merged 25 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0bbbf2a
:sparkles: Integrations for ticket WIP
naelob Jan 2, 2024
0f73174
:construction: Added github oauth service
naelob Jan 2, 2024
572158d
:construction: WIP for integrations in ticket
naelob Jan 3, 2024
5cc8221
:construction: Fixed build
naelob Jan 3, 2024
d499efe
:construction: Partial mapping done
naelob Jan 3, 2024
c2a3e60
:construction: Added user object boilerplate
naelob Jan 4, 2024
0250d81
:construction: Added boilerplate for account and user
naelob Jan 4, 2024
bd10468
:construction: All objects have their boilerplate: TODO are the attac…
naelob Jan 4, 2024
369c602
:construction: WIP on ticketing objects
naelob Jan 5, 2024
13ef887
:construction: Updated ticket object
naelob Jan 5, 2024
40caa42
:construction: Comment object updated
naelob Jan 5, 2024
3285079
:construction: Attachment working
naelob Jan 5, 2024
a9b030b
:construction: Updated jobs syntax
naelob Jan 5, 2024
c23a457
:construction: Upadted attachment, comment and ticket relationship
naelob Jan 5, 2024
d43ba6b
:construction: Fixed some build issues
naelob Jan 5, 2024
16d7435
:construction: BUILD works
naelob Jan 6, 2024
4d1605e
:construction: Comment <> Attachment <> Ticket Sync
naelob Jan 6, 2024
02592a3
:construction: Little fixes
naelob Jan 6, 2024
8ec2a50
:construction: Updated all responses types
naelob Jan 6, 2024
371bced
:construction: Added integrations front and zendesk
naelob Jan 6, 2024
e0b1829
:construction: Added zendesk and front mappings
naelob Jan 6, 2024
ad74d63
:construction: Added tags
naelob Jan 6, 2024
6d355a0
:construction: All internal objects should be mapped
naelob Jan 7, 2024
cde2962
:construction: Added fixes to the whole app
naelob Jan 7, 2024
8a18f09
:sparkles: Zendesk ticketing working
naelob Jan 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading