Skip to content

Commit

Permalink
feat(hubspot): add hubspot integrations (#49)
Browse files Browse the repository at this point in the history
## Describe your changes
- Add Integrations for hubspot
## Actions

- create-contact
- create-company
- create-deal
- create-task
- create-note
- update-contact
- update-company
- update-deal
- update-task
- delete-contact
- delete-company
- delete-deal
- delete-task

## Syncs

- contacts
- companies
- deals
- tasks

## Issue ticket number and link

## Checklist before requesting a review (skip if just adding/editing
APIs & templates)
- [x] I added tests, otherwise the reason is:
- [x] External API requests have `retries`
- [x] Pagination is used where appropriate
- [x] The built in `nango.paginate` call is used instead of a `while
(true)` loop
- [x] Third party requests are NOT parallelized (this can cause issues
with rate limits)
- [ ] If a sync requires metadata the `nango.yaml` has `auto_start:
false`
- [x] If the sync is a `full` sync then `track_deletes: true` is set

---------

Co-authored-by: Khaliq <khaliqgant@gmail.com>
  • Loading branch information
hassan254-prog and khaliqgant authored Oct 16, 2024
1 parent e0a44f0 commit d4501e4
Show file tree
Hide file tree
Showing 118 changed files with 5,183 additions and 10,164 deletions.
469 changes: 321 additions & 148 deletions flows.yaml

Large diffs are not rendered by default.

56 changes: 12 additions & 44 deletions integrations/hubspot/actions/create-company.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,16 @@
import type { NangoAction, Company, CreateCompany } from '../../models';
import type { HubspotCompanyResponse } from '../types';
import { createCompanySchema } from '../schema.zod.js';
import type { NangoAction, ProxyConfiguration, CreateUpdateCompanyOutput, CreateCompanyInput } from '../../models';
import { createUpdateCompany, toHubspotCompany } from '../mappers/toCompany.js';

/**
* Executes an action to create a company based on the provided input.
* It validates the input against the defined schema, logs any validation errors,
* and then sends a POST request to create a company if the input is valid.
* @param nango - An instance of NangoAction used for logging and making API requests.
* @param input - The input data required to create a company.
* @returns A promise that resolves to the response from the API after creating the company.
* @throws An ActionError if the input validation fails.
*/
export default async function runAction(nango: NangoAction, input: CreateCompany): Promise<Company> {
const parsedInput = createCompanySchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to create a company: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError({
message: 'Invalid input provided to create a company'
});
}

for (const key in parsedInput.data.properties) {
const lowerKey = key.toLowerCase();
if (lowerKey !== key) {
parsedInput.data.properties[lowerKey] = parsedInput.data.properties[key];
delete parsedInput.data.properties[key];
}
}
export default async function runAction(nango: NangoAction, input: CreateCompanyInput): Promise<CreateUpdateCompanyOutput> {
const hubSpotCompany = toHubspotCompany(input);
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/companies#create-companies
endpoint: 'crm/v3/objects/companies',
data: hubSpotCompany,
retries: 10
};

const response = await nango.post<HubspotCompanyResponse>({
endpoint: `/crm/v3/objects/companies`,
retries: 10,
data: parsedInput.data
});
const response = await nango.post(config);

return {
id: response.data.id,
createdAt: response.data.createdAt,
updatedAt: response.data.updatedAt,
name: response.data.properties.name,
domain: response.data.properties.domain,
archived: response.data.archived
};
return createUpdateCompany(response.data);
}
51 changes: 13 additions & 38 deletions integrations/hubspot/actions/create-contact.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import type { NangoAction, Contact, CreateContact } from '../../models';
import { createContactSchema } from '../schema.zod.js';
import type { HubspotContactResponse } from '../types';
import type { NangoAction, ProxyConfiguration, CreateUpdateContactOutput, CreateContactInput } from '../../models';
import { CreateContactInputSchema } from '../schema.js';
import { createUpdatetoContact, toHubspotContact } from '../mappers/toContact.js';

/**
* Executes an action to create a contact based on the provided input.
* It validates the input against the defined schema, logs any validation errors,
* and then sends a POST request to create a contact if the input is valid.
* @param nango - An instance of NangoAction used for logging and making API requests.
* @param input - The input data required to create a contact.
* @returns A promise that resolves to the response from the API after creating the contact.
* @throws An ActionError if the input validation fails.
*/
export default async function runAction(nango: NangoAction, input: CreateContact): Promise<Contact> {
const parsedInput = createContactSchema.safeParse(input);
export default async function runAction(nango: NangoAction, input: CreateContactInput): Promise<CreateUpdateContactOutput> {
const parsedInput = CreateContactInputSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
Expand All @@ -23,30 +14,14 @@ export default async function runAction(nango: NangoAction, input: CreateContact
});
}

// lowercase all properties parsedInput.data.properties keys
for (const key in parsedInput.data.properties) {
const lowerKey = key.toLowerCase();
if (lowerKey !== key) {
parsedInput.data.properties[lowerKey] = parsedInput.data.properties[key];
delete parsedInput.data.properties[key];
}
}

const response = await nango.post<HubspotContactResponse>({
// https://developers.hubspot.com/docs/api/crm/contacts
endpoint: `/crm/v3/objects/contacts`,
retries: 10,
data: parsedInput.data
});

const contact: Contact = {
id: response.data.id,
createdAt: response.data.createdAt,
updatedAt: response.data.updatedAt,
firstName: response.data.properties.firstname || '',
lastName: response.data.properties.lastname || '',
email: response.data.properties.email || ''
const hubSpotContact = toHubspotContact(parsedInput.data);
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/contacts#create-contacts
endpoint: 'crm/v3/objects/contacts',
data: hubSpotContact,
retries: 10
};
const response = await nango.post(config);

return contact;
return createUpdatetoContact(response.data);
}
13 changes: 8 additions & 5 deletions integrations/hubspot/actions/create-deal.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { NangoAction, ProxyConfiguration, CreateDeal, CreatedDeal } from '../../models';
import type { NangoAction, ProxyConfiguration, CreateDealInput, CreateUpdateDealOutput } from '../../models';
import { createUpdateDeal, toHubspotDeal } from '../mappers/toDeal.js';

export default async function runAction(nango: NangoAction, input: CreateDeal): Promise<CreatedDeal> {
export default async function runAction(nango: NangoAction, input: CreateDealInput): Promise<CreateUpdateDealOutput> {
const hubSpotDeal = toHubspotDeal(input);
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/deals
endpoint: 'crm/v3/objects/deals',
data: input,
data: hubSpotDeal,
retries: 10
};

// https://developers.hubspot.com/docs/api/crm/deals#create-deals
const response = await nango.post(config);

return response.data;
return createUpdateDeal(response.data);
}
15 changes: 15 additions & 0 deletions integrations/hubspot/actions/create-note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { NangoAction, ProxyConfiguration, Note } from '../../models';
import { toNote, toHubspotNote } from '../mappers/toNote.js';

export default async function runAction(nango: NangoAction, input: Note): Promise<Note> {
const hubSpotNote = toHubspotNote(input);
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/notes
endpoint: 'crm/v3/objects/notes',
data: hubSpotNote,
retries: 10
};
const response = await nango.post(config);

return toNote(response.data);
}
27 changes: 27 additions & 0 deletions integrations/hubspot/actions/create-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { CreateTaskInput, NangoAction, ProxyConfiguration, CreateUpdateTaskOutput } from '../../models';
import { createUpdateTask, toHubspotTask } from '../mappers/toTask.js';
import { CreateTaskInputSchema } from '../schema.js';

export default async function runAction(nango: NangoAction, input: CreateTaskInput): Promise<CreateUpdateTaskOutput> {
const parsedInput = CreateTaskInputSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to create a task: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError({
message: 'Invalid input provided to create a task'
});
}

const hubSpotNote = toHubspotTask(parsedInput.data);
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/tasks
endpoint: 'crm/v3/objects/tasks',
data: hubSpotNote,
retries: 10
};
const response = await nango.post(config);

return createUpdateTask(response.data);
}
15 changes: 15 additions & 0 deletions integrations/hubspot/actions/delete-company.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { NangoAction, ProxyConfiguration, Id, SuccessResponse } from '../../models';

export default async function runAction(nango: NangoAction, input: Id): Promise<SuccessResponse> {
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/companies#delete-companies
endpoint: `/crm/v3/objects/companies/${input.id}`,
retries: 10
};

await nango.delete(config);

return {
success: true
};
}
14 changes: 14 additions & 0 deletions integrations/hubspot/actions/delete-contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { NangoAction, ProxyConfiguration, Id, SuccessResponse } from '../../models';

export default async function runAction(nango: NangoAction, input: Id): Promise<SuccessResponse> {
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/contacts#delete-contacts
endpoint: `/crm/v3/objects/contacts/${input.id}`,
retries: 10
};
await nango.delete(config);

return {
success: true
};
}
14 changes: 14 additions & 0 deletions integrations/hubspot/actions/delete-deal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { NangoAction, ProxyConfiguration, Id, SuccessResponse } from '../../models';

export default async function runAction(nango: NangoAction, input: Id): Promise<SuccessResponse> {
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/deals#delete-deals
endpoint: `/crm/v3/objects/deals/${input.id}`,
retries: 10
};
await nango.delete(config);

return {
success: true
};
}
14 changes: 14 additions & 0 deletions integrations/hubspot/actions/delete-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { NangoAction, ProxyConfiguration, Id, SuccessResponse } from '../../models';

export default async function runAction(nango: NangoAction, input: Id): Promise<SuccessResponse> {
const config: ProxyConfiguration = {
// https://developers.hubspot.com/docs/api/crm/tasks
endpoint: `/crm/v3/objects/tasks/${input.id}`,
retries: 10
};
await nango.delete(config);

return {
success: true
};
}
51 changes: 14 additions & 37 deletions integrations/hubspot/actions/update-company.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import type { NangoAction, Company, UpdateCompany } from '../../models';
import { updateCompanySchema } from '../schema.zod.js';
import type { HubspotCompanyResponse } from '../types';
import type { NangoAction, ProxyConfiguration, CreateUpdateCompanyOutput, UpdateCompanyInput } from '../../models';
import { UpdateCompanyInputSchema } from '../schema.js';
import { createUpdateCompany, toHubspotCompany } from '../mappers/toCompany.js';

/**
* Executes an action to update a company based on the provided input.
* It validates the input against the defined schema, logs any validation errors,
* and then sends a PATCH request to update a company if the input is valid.
* @param nango - An instance of NangoAction used for logging and making API requests.
* @param input - The input data required to update a company.
* @returns A promise that resolves to the response from the API after creating the company.
* @throws An ActionError if the input validation fails.
*/
export default async function runAction(nango: NangoAction, input: UpdateCompany): Promise<Company> {
const parsedInput = updateCompanySchema.safeParse(input);
export default async function runAction(nango: NangoAction, input: UpdateCompanyInput): Promise<CreateUpdateCompanyOutput> {
const parsedInput = UpdateCompanyInputSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
Expand All @@ -23,29 +14,15 @@ export default async function runAction(nango: NangoAction, input: UpdateCompany
});
}

for (const key in parsedInput.data.input.properties) {
const lowerKey = key.toLowerCase();
if (lowerKey !== key) {
parsedInput.data.input.properties[lowerKey] = parsedInput.data.input.properties[key];
delete parsedInput.data.input.properties[key];
}
}

const inputData = parsedInput.data;
const hubSpotCompany = toHubspotCompany(parsedInput.data);
const config: ProxyConfiguration = {
endpoint: `crm/v3/objects/companies/${parsedInput.data.id}`,
data: hubSpotCompany,
retries: 10
};

const response = await nango.patch<HubspotCompanyResponse>({
endpoint: `/crm/v3/objects/companies/${inputData.companyId}`,
retries: 10,
data: inputData.input,
...(inputData.input.idProperty ? { params: { idProperty: inputData.input.idProperty } } : {})
});
//https://developers.hubspot.com/docs/api/crm/companies#update-companies
const response = await nango.patch(config);

return {
id: response.data.id,
createdAt: response.data.createdAt,
updatedAt: response.data.updatedAt,
name: response.data.properties.name,
domain: response.data.properties.domain,
archived: response.data.archived
};
return createUpdateCompany(response.data);
}
52 changes: 15 additions & 37 deletions integrations/hubspot/actions/update-contact.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import type { NangoAction, Contact, UpdateContact } from '../../models';
import { updateContactSchema } from '../schema.zod.js';
import type { NangoAction, ProxyConfiguration, CreateUpdateContactOutput, UpdateContactInput } from '../../models';
import { UpdateContactInputSchema } from '../schema.js';
import { createUpdatetoContact, toHubspotContact } from '../mappers/toContact.js';

/**
* Executes an action to update a contact based on the provided input.
* It validates the input against the defined schema, logs any validation errors,
* and then sends a PATCH request to update a contact if the input is valid.
* @param nango - An instance of NangoAction used for logging and making API requests.
* @param input - The input data required to update a contact.
* @returns A promise that resolves to the response from the API after updating the contact.
* @throws An ActionError if the input validation fails.
*/
export default async function runAction(nango: NangoAction, input: UpdateContact): Promise<Contact> {
const parsedInput = updateContactSchema.safeParse(input);
export default async function runAction(nango: NangoAction, input: UpdateContactInput): Promise<CreateUpdateContactOutput> {
const parsedInput = UpdateContactInputSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
Expand All @@ -22,32 +14,18 @@ export default async function runAction(nango: NangoAction, input: UpdateContact
});
}

// lowercase all properties parsedInput.data.properties keys
for (const key in parsedInput.data.input.properties) {
const lowerKey = key.toLowerCase();
if (lowerKey !== key) {
parsedInput.data.input.properties[lowerKey] = parsedInput.data.input.properties[key];
delete parsedInput.data.input.properties[key];
}
}

const inputData = parsedInput.data;
const hubSpotContact = toHubspotContact(parsedInput.data);
const endpoint = parsedInput.data.id ? `crm/v3/objects/contacts/${parsedInput.data.id}` : `crm/v3/objects/contacts/${parsedInput.data.email}`;

const response = await nango.patch({
endpoint: `/crm/v3/objects/contacts/${inputData.contactId}`,
data: inputData.input,
const config: ProxyConfiguration = {
endpoint,
data: hubSpotContact,
retries: 10,
...(inputData.input.idProperty ? { params: { idProperty: inputData.input.idProperty } } : {})
});

const contact: Contact = {
id: response.data.id,
createdAt: response.data.createdAt,
updatedAt: response.data.updatedAt,
firstName: response.data.properties.firstname || '',
lastName: response.data.properties.lastname || '',
email: response.data.properties.email || ''
...(parsedInput.data.id ? {} : { params: { idProperty: 'email' } })
};

return contact;
// https://developers.hubspot.com/docs/api/crm/contacts#update-contacts
const response = await nango.patch(config);

return createUpdatetoContact(response.data);
}
Loading

0 comments on commit d4501e4

Please sign in to comment.