Skip to content

Commit

Permalink
Merge pull request #81 from panoratech/feat/add-contact-post
Browse files Browse the repository at this point in the history
Feat/add contact post
  • Loading branch information
naelob authored Nov 15, 2023
2 parents d0e45b7 + e96b664 commit 0a7e519
Show file tree
Hide file tree
Showing 12 changed files with 582 additions and 48 deletions.
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,
};
}
19 changes: 17 additions & 2 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 @@ -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

0 comments on commit 0a7e519

Please sign in to comment.