Skip to content

Commit f00d012

Browse files
authored
HubSpot Contacts | Lists | Calls (#106)
1 parent 76fc97f commit f00d012

File tree

10 files changed

+1015
-11
lines changed

10 files changed

+1015
-11
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vesselapi/integrations",
3-
"version": "0.0.73",
3+
"version": "0.0.74",
44
"description": "Vessel integrations",
55
"main": "dist/index.js",
66
"module": "dist/index.mjs",

src/platforms/hubspot/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# About
2+
3+
HubSpot is an easy to use CRM that provides flexibility without compromising on features. It is a great tool for small to medium sized businesses that are looking to grow their business and need a CRM that can scale with them.
4+
5+
## References
6+
7+
- API: https://developers.hubspot.com/docs/api/overview
8+
- Rate Limits: https://developers.hubspot.com/docs/api/usage-details

src/platforms/hubspot/client.ts

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
import { Auth, ClientResult } from '@/sdk';
2+
import { makeRequestFactory } from '@/sdk/client';
3+
import { omit, shake } from 'radash';
4+
import * as z from 'zod';
5+
import { API_VERSION, BASE_URL, HUBSPOT_MAX_PAGE_SIZE } from './constants';
6+
import {
7+
BatchReadObjectInput,
8+
callProperties,
9+
companyProperties,
10+
contactProperties,
11+
dealProperties,
12+
emailProperties,
13+
FindObjectInput,
14+
HubspotAssociationCreate,
15+
HubspotAssociationLabelInput,
16+
hubspotAssociationLabelOutputSchema,
17+
HubspotCall,
18+
HubspotCallCreate,
19+
hubspotCallSchema,
20+
HubspotCallUpdate,
21+
HubspotCompany,
22+
HubspotCompanyCreate,
23+
hubspotCompanySchema,
24+
HubspotCompanyUpdate,
25+
HubspotContact,
26+
HubspotContactCreate,
27+
hubspotContactListSchema,
28+
hubspotContactSchema,
29+
HubspotContactUpdate,
30+
HubspotCustomPropertyCreate,
31+
HubspotDeal,
32+
HubspotDealCreate,
33+
hubspotDealSchema,
34+
HubspotDealUpdate,
35+
HubspotEmail,
36+
HubspotEmailCreate,
37+
hubspotEmailSchema,
38+
HubspotEmailUpdate,
39+
HubspotMeeting,
40+
HubspotMeetingCreate,
41+
hubspotMeetingSchema,
42+
HubspotMeetingUpdate,
43+
HubspotModule,
44+
HubspotNote,
45+
HubspotNoteCreate,
46+
hubspotNoteSchema,
47+
HubspotNoteUpdate,
48+
HubspotOwner,
49+
hubspotOwnerSchema,
50+
hubspotPropertySchema,
51+
HubspotTask,
52+
HubspotTaskCreate,
53+
hubspotTaskSchema,
54+
HubspotTaskUpdate,
55+
ListObjectInput,
56+
ListOutput,
57+
listResponseHubspotContactListContactsSchema,
58+
listResponseHubspotContactListSchema,
59+
listResponseSchema,
60+
meetingProperties,
61+
noteProperties,
62+
taskProperties,
63+
} from './schemas';
64+
65+
const request = makeRequestFactory(async (auth, options) => {
66+
return {
67+
...options,
68+
url: `${BASE_URL}${options.url}`,
69+
headers: {
70+
...options.headers,
71+
Authorization: `Bearer ${await auth.getToken()}`,
72+
},
73+
};
74+
});
75+
76+
type requestFunctionType<I, O> = (
77+
auth: Auth,
78+
input: I,
79+
) => Promise<ClientResult<O>>;
80+
81+
const makeClient = () => {
82+
const findObject = <TOutput>(
83+
module: HubspotModule | `objects/${HubspotModule}`,
84+
schema: z.ZodSchema,
85+
properties?: string[],
86+
): requestFunctionType<FindObjectInput, TOutput> =>
87+
request(({ id, associations }: FindObjectInput) => ({
88+
url: `/crm/${API_VERSION}/${module}/${id}`,
89+
method: 'GET',
90+
query: shake({
91+
properties: properties?.join(','),
92+
associations: associations?.join(','),
93+
}),
94+
schema,
95+
}));
96+
97+
const listObject = <TOutput>(
98+
module: HubspotModule | `objects/${HubspotModule}`,
99+
schema: z.ZodSchema,
100+
properties?: string[],
101+
): requestFunctionType<ListObjectInput, TOutput> =>
102+
request(
103+
({
104+
after,
105+
pageSize = HUBSPOT_MAX_PAGE_SIZE,
106+
associations,
107+
}: ListObjectInput) => ({
108+
url: `/crm/${API_VERSION}/${module}`,
109+
method: 'GET',
110+
query: shake({
111+
after,
112+
pageSize,
113+
properties: properties?.join(','),
114+
associations: associations?.join(','),
115+
}),
116+
schema,
117+
}),
118+
);
119+
120+
const createObject = <TInput extends Record<string, unknown>, TOutput>(
121+
module: HubspotModule | `objects/${HubspotModule}`,
122+
schema: z.ZodSchema,
123+
): requestFunctionType<TInput, TOutput> =>
124+
request((body: TInput) => ({
125+
url: `/crm/${API_VERSION}/${module}/`,
126+
method: 'POST',
127+
schema,
128+
json: {
129+
properties: {
130+
hs_timestamp: new Date().toISOString(),
131+
...shake(body),
132+
},
133+
},
134+
}));
135+
136+
const updateObject = <TInput extends Record<string, unknown>, TOutput>(
137+
module: HubspotModule | `objects/${HubspotModule}`,
138+
schema: z.ZodSchema,
139+
): requestFunctionType<TInput, TOutput> =>
140+
request((body: TInput) => ({
141+
url: `/crm/${API_VERSION}/${module}/${body.id}`,
142+
method: 'PATCH',
143+
schema,
144+
json: {
145+
properties: shake(omit(body, ['id'])),
146+
},
147+
}));
148+
149+
const deleteObject = (
150+
module: HubspotModule | `objects/${HubspotModule}`,
151+
schema: z.ZodSchema,
152+
): requestFunctionType<FindObjectInput, void> =>
153+
request((body: FindObjectInput) => ({
154+
url: `/crm/${API_VERSION}/${module}/${body.id}`,
155+
method: 'DELETE',
156+
schema,
157+
}));
158+
159+
const batchReadObject = <TOutput>(
160+
module: HubspotModule | `objects/${HubspotModule}`,
161+
schema: z.ZodSchema,
162+
properties?: string[],
163+
): requestFunctionType<BatchReadObjectInput, TOutput> =>
164+
request(({ ids }: BatchReadObjectInput) => ({
165+
url: `/crm/${API_VERSION}/${module}/batch/read`,
166+
method: 'POST',
167+
json: {
168+
properties: properties?.join(','),
169+
idProperty: 'id',
170+
inputs: ids.map((id) => ({ id })),
171+
propertiesWithHistory: null,
172+
},
173+
schema,
174+
}));
175+
176+
const crud = <
177+
TCreate extends Record<string, unknown>,
178+
TUpdate extends Record<string, unknown> & { id: string },
179+
TOutput extends Record<string, unknown>,
180+
>(
181+
module: HubspotModule | `objects/${HubspotModule}`,
182+
schema: z.ZodSchema,
183+
properties?: string[],
184+
) => ({
185+
find: findObject<TOutput>(module, schema, properties),
186+
list: listObject<ListOutput<TOutput>>(
187+
module,
188+
listResponseSchema(schema),
189+
properties,
190+
),
191+
create: createObject<TCreate, TOutput>(module, schema),
192+
update: updateObject<TUpdate, TOutput>(module, schema),
193+
delete: deleteObject(module, schema),
194+
batchRead: batchReadObject<ListOutput<TOutput>>(
195+
module,
196+
listResponseSchema(schema),
197+
properties,
198+
),
199+
});
200+
201+
return {
202+
owners: {
203+
find: findObject<HubspotOwner>('owners', hubspotOwnerSchema),
204+
list: listObject<ListOutput<HubspotOwner>>(
205+
'owners',
206+
listResponseSchema(hubspotOwnerSchema),
207+
),
208+
},
209+
contacts: crud<HubspotContactCreate, HubspotContactUpdate, HubspotContact>(
210+
'objects/contacts',
211+
hubspotContactSchema,
212+
contactProperties,
213+
),
214+
companies: crud<HubspotCompanyCreate, HubspotCompanyUpdate, HubspotCompany>(
215+
'objects/companies',
216+
hubspotCompanySchema,
217+
companyProperties,
218+
),
219+
deals: crud<HubspotDealCreate, HubspotDealUpdate, HubspotDeal>(
220+
'objects/deals',
221+
hubspotDealSchema,
222+
dealProperties,
223+
),
224+
notes: crud<HubspotNoteCreate, HubspotNoteUpdate, HubspotNote>(
225+
'objects/notes',
226+
hubspotNoteSchema,
227+
noteProperties,
228+
),
229+
tasks: crud<HubspotTaskCreate, HubspotTaskUpdate, HubspotTask>(
230+
'objects/tasks',
231+
hubspotTaskSchema,
232+
taskProperties,
233+
),
234+
meetings: crud<HubspotMeetingCreate, HubspotMeetingUpdate, HubspotMeeting>(
235+
'objects/meetings',
236+
hubspotMeetingSchema,
237+
meetingProperties,
238+
),
239+
emails: crud<HubspotEmailCreate, HubspotEmailUpdate, HubspotEmail>(
240+
'objects/emails',
241+
hubspotEmailSchema,
242+
emailProperties,
243+
),
244+
calls: crud<HubspotCallCreate, HubspotCallUpdate, HubspotCall>(
245+
'objects/calls',
246+
hubspotCallSchema,
247+
callProperties,
248+
),
249+
// HubSpot only has support for contact lists in v1 of the API
250+
contactLists: {
251+
find: request(({ id }: { id: string }) => ({
252+
url: `/contacts/v1/lists/${id}`,
253+
method: 'GET',
254+
schema: hubspotContactListSchema,
255+
})),
256+
list: request(
257+
({
258+
offset,
259+
count = HUBSPOT_MAX_PAGE_SIZE,
260+
}: {
261+
offset?: number;
262+
count?: number;
263+
}) => ({
264+
url: `/contacts/v1/lists`,
265+
method: 'GET',
266+
schema: listResponseHubspotContactListSchema,
267+
query: shake({
268+
offset,
269+
count,
270+
}),
271+
}),
272+
),
273+
contacts: request(
274+
({
275+
listId,
276+
count = HUBSPOT_MAX_PAGE_SIZE,
277+
vidOffset,
278+
}: {
279+
listId: string;
280+
count?: number;
281+
vidOffset?: number;
282+
}) => ({
283+
url: `/contacts/v1/lists/${listId}/contacts/all`,
284+
method: 'GET',
285+
schema: listResponseHubspotContactListContactsSchema,
286+
query: shake({
287+
count,
288+
vidOffset,
289+
}),
290+
}),
291+
),
292+
},
293+
properties: {
294+
list: request(({ objectType }: { objectType: HubspotModule }) => ({
295+
url: `/crm/${API_VERSION}/properties/${objectType}`,
296+
method: 'GET',
297+
schema: z.object({
298+
results: z.array(hubspotPropertySchema),
299+
}),
300+
})),
301+
create: request(
302+
({ objectType, property }: HubspotCustomPropertyCreate) => ({
303+
url: `/crm/${API_VERSION}/properties/${objectType}`,
304+
method: 'POST',
305+
schema: hubspotPropertySchema,
306+
json: shake(property),
307+
}),
308+
),
309+
},
310+
associations: {
311+
create: request(
312+
({
313+
fromId,
314+
fromType,
315+
toId,
316+
toType,
317+
category,
318+
typeId,
319+
}: HubspotAssociationCreate) => ({
320+
url: `/crm/v4/objects/${fromType}/${fromId}/associations/${toType}/${toId}`,
321+
method: 'PUT',
322+
schema: listResponseSchema(hubspotPropertySchema),
323+
json: shake({
324+
associationCategory: category,
325+
associationTypeId: typeId,
326+
}),
327+
}),
328+
),
329+
labels: request(({ fromType, toType }: HubspotAssociationLabelInput) => ({
330+
url: `/crm/v4/associations/${fromType}/${toType}/labels`,
331+
method: 'GET',
332+
schema: listResponseSchema(hubspotAssociationLabelOutputSchema),
333+
})),
334+
},
335+
passthrough: request.passthrough(),
336+
};
337+
};
338+
339+
export default makeClient();

src/platforms/hubspot/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const API_VERSION = 'v3';
2+
export const BASE_URL = `https://api.hubapi.com`;
3+
export const HUBSPOT_MAX_PAGE_SIZE = 100;
4+
export const HUBSPOT_DEFINED_CATEGORY = 'HUBSPOT_DEFINED';
5+
export const HUBSPOT_COMMON_ASSOCIATIONS = [
6+
'companies',
7+
'contacts',
8+
'deals',
9+
] as const;

src/platforms/hubspot/icon.ts

Lines changed: 2 additions & 0 deletions
Large diffs are not rendered by default.

src/platforms/hubspot/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { auth, platform } from '@/sdk';
2+
import client from './client';
3+
import * as constants from './constants';
4+
import { icon } from './icon';
5+
6+
export * as types from './schemas';
7+
export default platform('hubspot', {
8+
auth: auth.oauth2({
9+
authUrl: `https://app.hubspot.com/oauth/authorize`,
10+
tokenUrl: `https://api.hubapi.com/oauth/v1/token`,
11+
tokenAuth: 'body',
12+
isRetryable: async ({ response }) => {
13+
return (await response.json()).category === 'EXPIRED_AUTHENTICATION';
14+
},
15+
default: true,
16+
}),
17+
display: { name: 'HubSpot', iconURI: icon, categories: ['crm'] },
18+
constants,
19+
client,
20+
actions: {},
21+
});

0 commit comments

Comments
 (0)