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

Feat/add contact post #81

Merged
merged 7 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions packages/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ model jobs {
id_job Int @id(map: "pk_jobs") @default(autoincrement())
status String
timestamp DateTime @default(now()) @db.Timestamp(6)
id_linked_user BigInt
crm_contacts crm_contacts[]
linked_users linked_users @relation(fields: [id_linked_user], references: [id_linked_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_12")
jobs_status_history jobs_status_history[]

@@index([id_linked_user], map: "fk_linkeduserid_projectid")
}

/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
Expand Down Expand Up @@ -136,6 +140,7 @@ model linked_users {
status String
id_project BigInt
connections connections[]
jobs jobs[]
projects projects @relation(fields: [id_project], references: [id_project], onDelete: NoAction, onUpdate: NoAction, map: "fk_10")

@@index([id_project], map: "fk_proectid_linked_users")
Expand Down
35 changes: 35 additions & 0 deletions packages/api/src/@core/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { LoggerService } from '../logger/logger.service';
import axios, { AxiosError } from 'axios';
import { Prisma } from '@prisma/client';
import { TargetObject } from './unification/types';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';

// Custom error for general application errors
export class AppError extends Error {
Expand Down Expand Up @@ -31,3 +36,33 @@ export class UnauthorizedError extends HttpException {
this.name = 'UnauthorizedError';
}
}

type ServiceError = AxiosError | PrismaClientKnownRequestError | Error;

export function handleServiceError(
error: ServiceError,
logger: LoggerService,
providerName: string,
action: TargetObject,
) {
let statusCode = 500; // Default to internal server error
let errorMessage = error.message;

if (axios.isAxiosError(error)) {
statusCode = error.response?.status || 500; // Use HTTP status code from Axios error or default to 500
errorMessage = error.response?.data || error.message;
logger.error('Error with Axios request:', errorMessage);
} else if (error instanceof Prisma.PrismaClientKnownRequestError) {
// Handle Prisma errors
logger.error('Error with Prisma request:', errorMessage);
} else {
logger.error('An unknown error occurred...', errorMessage);
}

return {
data: null,
error: errorMessage,
message: `Failed to create ${action} for ${providerName}.`,
statusCode: statusCode,
};
}
21 changes: 18 additions & 3 deletions packages/api/src/crm/contact/services/contact.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ import {
HubspotContactInput,
HubspotContactOutput,
PipedriveContactInput,
PipedriveContactOutput,
ZendeskContactInput,
ZendeskContactOutput,
ZohoContactInput,
ZohoContactOutput,
} from 'src/crm/@types';

export type ContactOutput = FreshsalesContactOutput | HubspotContactOutput;
export type ContactOutput =
| FreshsalesContactOutput
| HubspotContactOutput
| ZohoContactOutput
| ZendeskContactOutput
| PipedriveContactOutput;

@Injectable()
export class ContactService {
Expand Down Expand Up @@ -79,9 +87,9 @@ export class ContactService {
integrationId: string,
linkedUserId: string,
) {
//TODO; customerId must be passed here
const job_resp_create = await this.prisma.jobs.create({
data: {
id_linked_user: BigInt(linkedUserId),
status: 'initialized',
},
});
Expand All @@ -107,28 +115,35 @@ export class ContactService {
case 'freshsales':
resp = await this.freshsales.addContact(
desunifiedObject as FreshsalesContactInput,
linkedUserId,
);
break;

case 'zoho':
resp = await this.zoho.addContact(desunifiedObject as ZohoContactInput);
resp = await this.zoho.addContact(
desunifiedObject as ZohoContactInput,
linkedUserId,
);
break;

case 'zendesk':
resp = await this.zendesk.addContact(
desunifiedObject as ZendeskContactInput,
linkedUserId,
);
break;

case 'hubspot':
resp = await this.hubspot.addContact(
desunifiedObject as HubspotContactInput,
linkedUserId,
);
break;

case 'pipedrive':
resp = await this.pipedrive.addContact(
desunifiedObject as PipedriveContactInput,
linkedUserId,
);
break;

Expand Down
67 changes: 32 additions & 35 deletions packages/api/src/crm/contact/services/freshsales/index.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,52 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { HttpStatus, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { ApiResponse } from '../../types';
import axios, { AxiosResponse } from 'axios';
import axios from 'axios';
import {
CrmObject,
FreshsalesContactInput,
FreshsalesContactOutput,
} from 'src/crm/@types';
import { PrismaService } from 'src/@core/prisma/prisma.service';
import { LoggerService } from 'src/@core/logger/logger.service';
import { handleServiceError } from 'src/@core/utils/errors';

@Injectable()
export class FreshSalesService {
constructor(private prisma: PrismaService, private logger: LoggerService) {
this.logger.setContext(FreshSalesService.name);
}

async addContact(
contactData: FreshsalesContactInput,
linkedUserId: string,
): Promise<ApiResponse<FreshsalesContactOutput>> {
const mobile = contactData.phone_numbers[0];
const url = 'https://domain.freshsales.io/api/contacts';
const data = {
contact: {
first_name: contactData.first_name,
last_name: contactData.last_name,
mobile_number: mobile,
},
};
const token = process.env.FRESHSALES_API_KEY;
const headers = {
Authorization: `Token token=${token}`,
'Content-Type': 'application/json',
};

try {
const response: AxiosResponse<FreshsalesContactOutput> = await axios.post(
url,
data,
{ headers: headers },
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: BigInt(linkedUserId),
},
});
const dataBody = {
contact: contactData,
};
const resp = await axios.post(
'https://domain.freshsales.io/api/contacts',
JSON.stringify(dataBody),
{
headers: {
Authorization: `Token token=${connection.access_token}`,
'Content-Type': 'application/json',
},
},
);
console.log(response.data);
return {
data: response.data,
message: 'Contact created successfully.',
statusCode: HttpStatus.OK,
data: resp.data,
message: 'Freshsales contact created',
statusCode: 201,
};
} catch (error) {
console.error(error.response ? error.response.data : error.message);
const status: number = error.response
? error.response.status
: HttpStatus.INTERNAL_SERVER_ERROR;
return {
data: null,
error: error.message,
message: 'Failed to create contact.',
statusCode: status,
};
handleServiceError(error, this.logger, 'Freshsales', CrmObject.contact);
}
}
}
2 changes: 1 addition & 1 deletion packages/api/src/crm/contact/services/freshsales/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export interface FreshsalesContactInput {
first_name: string;
last_name: string;
phone_numbers: string[];
mobile_number: string | string[];
}
export interface FreshsalesContactOutput {
id: number;
Expand Down
42 changes: 41 additions & 1 deletion packages/api/src/crm/contact/services/hubspot/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
import { Injectable } from '@nestjs/common';
import { ApiResponse } from '../../types';
import { HubspotContactInput, HubspotContactOutput } from 'src/crm/@types';
import {
CrmObject,
HubspotContactInput,
HubspotContactOutput,
} from 'src/crm/@types';
import axios from 'axios';
import { PrismaService } from 'src/@core/prisma/prisma.service';
import { LoggerService } from 'src/@core/logger/logger.service';
import { handleServiceError } from 'src/@core/utils/errors';

@Injectable()
export class HubspotService {
constructor(private prisma: PrismaService, private logger: LoggerService) {
this.logger.setContext(HubspotService.name);
}
async addContact(
contactData: HubspotContactInput,
linkedUserId: string,
): Promise<ApiResponse<HubspotContactOutput>> {
try {
//TODO: check required scope => crm.objects.contacts.write
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: BigInt(linkedUserId),
},
});
const dataBody = {
properties: contactData,
};
const resp = await axios.post(
`https://api.hubapi.com/crm/v3/objects/contacts/`,
JSON.stringify(dataBody),
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${connection.access_token}`,
},
},
);
return {
data: resp.data,
message: 'Hubspot contact created',
statusCode: 201,
};
} catch (error) {
handleServiceError(error, this.logger, 'Hubspot', CrmObject.contact);
}
return;
}
}
25 changes: 23 additions & 2 deletions packages/api/src/crm/contact/services/hubspot/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
export interface HubspotContactInput {
company_size: string;
email?: string;
firstname?: string;
phone?: string;
lastname?: string;
city?: string;
country?: string;
zip?: string;
state?: string;
address?: string;
mobilephone?: string;
hubspot_owner_id?: string;
associatedcompanyid?: string;
fax?: string;
jobtitle?: string;
}

export interface HubspotContactOutput {
id: number;
company: string;
createdate: string; // Use 'Date' if you prefer a Date object
email: string;
firstname: string;
lastmodifieddate: string; // Use 'Date' if you prefer a Date object
lastname: string;
phone: string;
website: string;
}
40 changes: 39 additions & 1 deletion packages/api/src/crm/contact/services/pipedrive/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
import { Injectable } from '@nestjs/common';
import { ApiResponse } from '../../types';
import { PipedriveContactInput, PipedriveContactOutput } from 'src/crm/@types';
import {
CrmObject,
PipedriveContactInput,
PipedriveContactOutput,
} from 'src/crm/@types';
import axios from 'axios';
import { PrismaService } from 'src/@core/prisma/prisma.service';
import { LoggerService } from 'src/@core/logger/logger.service';
import { handleServiceError } from 'src/@core/utils/errors';

@Injectable()
export class PipedriveService {
constructor(private prisma: PrismaService, private logger: LoggerService) {
this.logger.setContext(PipedriveService.name);
}

async addContact(
contactData: PipedriveContactInput,
linkedUserId: string,
): Promise<ApiResponse<PipedriveContactOutput>> {
try {
//TODO: check required scope => crm.objects.contacts.write
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: BigInt(linkedUserId),
},
});
const resp = await axios.post(
`https://api.pipedrive.com/v1/persons`,
JSON.stringify(contactData),
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${connection.access_token}`,
},
},
);
return {
data: resp.data,
message: 'Pipedrive contact created',
statusCode: 201,
};
} catch (error) {
handleServiceError(error, this.logger, 'Pipedrive', CrmObject.contact);
}
return;
}
}
Loading
Loading