cal/packages/lib/CloseCom.ts
Leo Giovanetti 1d927a8b33
chore: Sync Services to update external tools (#3814)
* WIP close.com app

* Removing leaked dev key (now invalid)

* Misspelled env variable

* Making progress still WIP

* Progress + tests

* Final touches

* More unit tests

* Finished up tests

* Merge main

* Removing unneeded stuff + submodules

* Removing static props, fields fix

* Removing unneeded stuff p2

* Commenting

* Refactoring Close.com Calendar Service + initial structure

* Progress con CloseComService

* Standarizing APIs

* Zodifying

* Expanding sync services

* Sendgrid Sync Service

* using own request for sendgrid + debug logs

* Making get last booking work for console

* Helpscout dynamic app API

* Standarizing calls + adding call from booking creation

* Strategy change for last booking

* Strategy change for last booking on help scout api

* Fixing failing build

* Implementing user deletion

* Fix linting + slight cleaning

* Undoing eslint disable

* Removing more unsupported eslint properties

* Closecom as non-standard sync service

* Finishing closecom lead operations

* Fixing lint

* Guarding app from sync services

* Reverting submodules

* Applying PR feedback

* Reverting API to be plain handler

* Cleaning notes

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-26 15:10:12 -06:00

461 lines
13 KiB
TypeScript

import logger from "@calcom/lib/logger";
export type CloseComLead = {
companyName?: string | null | undefined;
contactName?: string;
contactEmail?: string;
description?: string;
};
export type CloseComFieldOptions = [string, string, boolean, boolean][];
export type CloseComLeadCreateResult = {
status_id: string;
status_label: string;
display_name: string;
addresses: { [key: string]: any }[];
name: string;
contacts: { [key: string]: any }[];
[key: CloseComCustomActivityCustomField<string>]: string;
id: string;
};
export type CloseComStatus = {
id: string;
organization_id: string;
label: string;
};
export type CloseComCustomActivityTypeCreate = {
name: string;
description: string;
};
export type CloseComContactSearch = {
data: {
__object_type: "contact";
emails: {
email: string;
type: string;
}[];
id: string;
lead_id: string;
name: string;
}[];
cursor: null;
};
export type CloseComCustomActivityTypeGet = {
data: {
api_create_only: boolean;
created_by: string;
date_created: string;
date_updated: string;
description: string;
editable_with_roles: string[];
fields: CloseComCustomActivityFieldGet["data"][number][];
id: string;
name: string;
organization_id: string;
updated_by: string;
}[];
cursor: null;
};
export type CloseComCustomActivityFieldCreate = CloseComCustomContactFieldCreate & {
custom_activity_type_id: string;
};
export type CloseComCustomContactFieldGet = {
data: {
id: string;
name: string;
description: string;
type: string;
choices?: string[];
accepts_multiple_values: boolean;
editable_with_roles: string[];
date_created: string;
date_updated: string;
created_by: string;
updated_by: string;
organization_id: string;
}[];
};
export type CloseComCustomContactFieldCreate = {
name: string;
description?: string;
type: string;
required: boolean;
accepts_multiple_values: boolean;
editable_with_roles: string[];
choices?: string[];
};
export type CloseComCustomActivityFieldGet = {
data: {
id: string;
name: string;
description: string;
type: string;
choices?: string[];
accepts_multiple_values: boolean;
editable_with_roles: string[];
date_created: string;
date_updated: string;
created_by: string;
updated_by: string;
organization_id: string;
custom_activity_type_id: string;
}[];
};
export type CloseComCustomActivityCreate = {
custom_activity_type_id: string;
lead_id: string;
[key: CloseComCustomActivityCustomField<string>]: string;
};
export type typeCloseComCustomActivityGet = {
organization_id: string;
contact_id: any;
date_updated: string;
user_name: string;
created_by_name: "Bruce Wayne";
id: string;
created_by: string;
status: string;
user_id: string;
users: any[];
lead_id: string;
_type: string;
updated_by: string;
custom_activity_type_id: string;
date_created: string;
updated_by_name: string;
[key: CloseComCustomActivityCustomField<string>]: string;
};
type CloseComCustomActivityCustomField<T extends string> = `custom.${T}`;
const environmentApiKey = process.env.CLOSECOM_API_KEY || "";
/**
* This class to instance communicating to Close.com APIs requires an API Key.
*
* You can either pass to the constructor an API Key or have one defined as an
* environment variable in case the communication to Close.com is just for
* one account only, not configurable by any user at any moment.
*/
export default class CloseCom {
private apiUrl = "https://api.close.com/api/v1";
private apiKey: string | undefined = undefined;
private log: typeof logger;
constructor(providedApiKey = "") {
this.log = logger.getChildLogger({ prefix: [`[[lib] close.com`] });
if (!providedApiKey && !environmentApiKey) throw Error("Close.com Api Key not present");
this.apiKey = providedApiKey || environmentApiKey;
}
public me = async () => {
return this._get({ urlPath: "/me/" });
};
public contact = {
search: async ({ emails }: { emails: string[] }): Promise<CloseComContactSearch> => {
return this._post({
urlPath: "/data/search/",
data: closeComQueries.contact.search(emails),
});
},
create: async (data: {
person: { name: string | null; email: string };
leadId: string;
}): Promise<CloseComContactSearch["data"][number]> => {
return this._post({ urlPath: "/contact/", data: closeComQueries.contact.create(data) });
},
update: async ({
contactId,
...data
}: {
person: { name: string; email: string };
contactId: string;
leadId?: string;
}): Promise<CloseComContactSearch["data"][number]> => {
return this._put({
urlPath: `/contact/${contactId}/`,
data: closeComQueries.contact.update(data),
});
},
delete: async (contactId: string) => {
return this._delete({
urlPath: `/contact/${contactId}/`,
});
},
};
public lead = {
list: async ({
query,
}: {
query: { [key: string]: any };
}): Promise<{ data: { [key: string]: any }[] }> => {
return this._get({ urlPath: "/lead", query });
},
status: async () => {
return this._get({ urlPath: `/status/lead/` });
},
update: async (leadId: string, data: CloseComLead): Promise<CloseComLeadCreateResult> => {
return this._put({
urlPath: `/lead/${leadId}`,
data,
});
},
create: async (data: CloseComLead): Promise<CloseComLeadCreateResult> => {
return this._post({
urlPath: "/lead/",
data: closeComQueries.lead.create(data),
});
},
delete: async (leadId: string) => {
return this._delete({ urlPath: `/lead/${leadId}/` });
},
};
public customActivity = {
type: {
create: async (
data: CloseComCustomActivityTypeCreate
): Promise<CloseComCustomActivityTypeGet["data"][number]> => {
return this._post({
urlPath: "/custom_activity",
data: closeComQueries.customActivity.type.create(data),
});
},
get: async (): Promise<CloseComCustomActivityTypeGet> => {
return this._get({ urlPath: "/custom_activity" });
},
},
};
public customField = {
activity: {
create: async (
data: CloseComCustomActivityFieldCreate
): Promise<CloseComCustomActivityFieldGet["data"][number]> => {
return this._post({ urlPath: "/custom_field/activity/", data });
},
get: async ({ query }: { query: { [key: string]: any } }): Promise<CloseComCustomActivityFieldGet> => {
return this._get({ urlPath: "/custom_field/activity/", query });
},
},
contact: {
create: async (
data: CloseComCustomContactFieldCreate
): Promise<CloseComCustomContactFieldGet["data"][number]> => {
return this._post({ urlPath: "/custom_field/contact/", data });
},
get: async ({ query }: { query: { [key: string]: any } }): Promise<CloseComCustomContactFieldGet> => {
return this._get({ urlPath: "/custom_field/contact/", query });
},
},
shared: {
get: async ({ query }: { query: { [key: string]: any } }): Promise<CloseComCustomContactFieldGet> => {
return this._get({ urlPath: "/custom_field/shared/", query });
},
},
};
public activity = {
custom: {
create: async (
data: CloseComCustomActivityCreate
): Promise<CloseComCustomActivityTypeGet["data"][number]> => {
return this._post({ urlPath: "/activity/custom/", data });
},
delete: async (uuid: string) => {
return this._delete({ urlPath: `/activity/custom/${uuid}/` });
},
update: async (
uuid: string,
data: Partial<CloseComCustomActivityCreate>
): Promise<CloseComCustomActivityTypeGet["data"][number]> => {
return this._put({ urlPath: `/activity/custom/${uuid}/`, data });
},
},
};
private _get = async ({ urlPath, query }: { urlPath: string; query?: { [key: string]: any } }) => {
return await this._request({ urlPath, method: "get", query });
};
private _post = async ({ urlPath, data }: { urlPath: string; data: Record<string, unknown> }) => {
return this._request({ urlPath, method: "post", data });
};
private _put = async ({ urlPath, data }: { urlPath: string; data: Record<string, unknown> }) => {
return this._request({ urlPath, method: "put", data });
};
private _delete = async ({ urlPath }: { urlPath: string }) => {
return this._request({ urlPath, method: "delete" });
};
private _request = async ({
urlPath,
data,
method,
query,
}: {
urlPath: string;
method: string;
query?: { [key: string]: any };
data?: Record<string, unknown>;
}) => {
this.log.debug(method, urlPath, query, data);
const credentials = Buffer.from(`${this.apiKey}:`).toString("base64");
const headers = {
Authorization: `Basic ${credentials}`,
"Content-Type": "application/json",
};
const queryString = query ? `?${new URLSearchParams(query).toString()}` : "";
return await fetch(`${this.apiUrl}${urlPath}${queryString}`, {
headers,
method,
body: JSON.stringify(data),
}).then(async (response) => {
if (!response.ok) {
const message = `[Close.com app] An error has occured: ${response.status}`;
this.log.error(await response.json());
throw new Error(message);
}
return await response.json();
});
};
}
export const closeComQueries = {
contact: {
search(contactEmails: string[]) {
return {
limit: null,
_fields: {
contact: ["id", "name", "emails"],
},
query: {
negate: false,
queries: [
{
negate: false,
object_type: "contact",
type: "object_type",
},
{
negate: false,
queries: [
{
negate: false,
related_object_type: "contact_email",
related_query: {
negate: false,
queries: contactEmails.map((contactEmail) => ({
condition: {
mode: "full_words",
type: "text",
value: contactEmail,
},
field: {
field_name: "email",
object_type: "contact_email",
type: "regular_field",
},
negate: false,
type: "field_condition",
})),
type: "or",
},
this_object_type: "contact",
type: "has_related",
},
],
type: "and",
},
],
type: "and",
},
results_limit: null,
sort: [],
};
},
create(data: { person: { name: string | null; email: string }; leadId: string }) {
return {
lead_id: data.leadId,
name: data.person.name ?? data.person.email,
emails: [{ email: data.person.email, type: "office" }],
};
},
update({ person, leadId, ...rest }: { person: { name: string; email: string }; leadId?: string }) {
return {
...(leadId && { lead_id: leadId }),
name: person.name ?? person.email,
emails: [{ email: person.email, type: "office" }],
...rest,
};
},
},
lead: {
create({ companyName, contactEmail, contactName, description }: CloseComLead) {
return {
name: companyName,
...(description ? { description } : {}),
...(contactEmail && contactName
? {
contacts: [
{
name: contactName,
email: contactEmail,
emails: [
{
type: "office",
email: contactEmail,
},
],
},
],
}
: {}),
};
},
},
customActivity: {
type: {
create({ name, description }: CloseComCustomActivityTypeCreate) {
return {
name: name,
description: description,
api_create_only: false,
editable_with_roles: ["admin"],
};
},
},
},
customField: {
activity: {
create({
custom_activity_type_id,
name,
type,
required,
accepts_multiple_values,
}: CloseComCustomActivityFieldCreate) {
return {
custom_activity_type_id,
name,
type,
required,
accepts_multiple_values,
editable_with_roles: [],
};
},
},
},
};