From 8f1fa2b8c86bb12ab7e96bd90a60b0cf09523adf Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 14 Jan 2025 14:35:30 -0500 Subject: [PATCH 1/7] Upgrade hubspot package --- packages/app-store/hubspot/package.json | 2 +- yarn.lock | 44 +++++++++++++++---------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/packages/app-store/hubspot/package.json b/packages/app-store/hubspot/package.json index 9cf90e5cd35532..0ce6ad0829ebdd 100644 --- a/packages/app-store/hubspot/package.json +++ b/packages/app-store/hubspot/package.json @@ -7,7 +7,7 @@ "dependencies": { "@calcom/lib": "*", "@calcom/prisma": "*", - "@hubspot/api-client": "^6.0.1" + "@hubspot/api-client": "^12.0.1" }, "devDependencies": { "@calcom/types": "*" diff --git a/yarn.lock b/yarn.lock index 6d4065163142a9..6c9ef23d88cd84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4220,6 +4220,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/granola@workspace:packages/app-store/granola": + version: 0.0.0-use.local + resolution: "@calcom/granola@workspace:packages/app-store/granola" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/gtm@workspace:packages/app-store/gtm": version: 0.0.0-use.local resolution: "@calcom/gtm@workspace:packages/app-store/gtm" @@ -4245,7 +4254,7 @@ __metadata: "@calcom/lib": "*" "@calcom/prisma": "*" "@calcom/types": "*" - "@hubspot/api-client": ^6.0.1 + "@hubspot/api-client": ^12.0.1 languageName: unknown linkType: soft @@ -6694,19 +6703,19 @@ __metadata: languageName: node linkType: hard -"@hubspot/api-client@npm:^6.0.1": - version: 6.0.1 - resolution: "@hubspot/api-client@npm:6.0.1" +"@hubspot/api-client@npm:^12.0.1": + version: 12.0.1 + resolution: "@hubspot/api-client@npm:12.0.1" dependencies: - bluebird: ^3.7.2 + "@types/node-fetch": ^2.5.7 bottleneck: ^2.19.5 - btoa: ^1.2.1 es6-promise: ^4.2.4 form-data: ^2.5.0 - lodash: ^4.17.21 + lodash.get: ^4.4.2 + lodash.merge: ^4.6.2 node-fetch: ^2.6.0 url-parse: ^1.4.3 - checksum: 82cd1da20b8962ed1ee7b33d594c4bce1efd6f47e654747386c33aad33f66ad463a47b4a7e413a9164a1ef3a0e3afae8f20a1265cc7d4da5aed8de743b211f2c + checksum: a8f78aa7267522c3788f1476baacd77b130cb395c070d184b86549410edaa15716087103c1556a9437ca960b61fe6bf1a51f9fb84063fe00528d8f279e5935bb languageName: node linkType: hard @@ -16579,6 +16588,16 @@ __metadata: languageName: node linkType: hard +"@types/node-fetch@npm:^2.5.7": + version: 2.6.12 + resolution: "@types/node-fetch@npm:2.6.12" + dependencies: + "@types/node": "*" + form-data: ^4.0.0 + checksum: 9647e68f9a125a090220c38d77b3c8e669c488658ae7506f1b4f9568214beba087624b1705bba1dc76649a65281ce3fd5b400e15266cbef8088027fb88777557 + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.6.4": version: 2.6.4 resolution: "@types/node-fetch@npm:2.6.4" @@ -20087,15 +20106,6 @@ __metadata: languageName: node linkType: hard -"btoa@npm:^1.2.1": - version: 1.2.1 - resolution: "btoa@npm:1.2.1" - bin: - btoa: bin/btoa.js - checksum: afbf004fb1b1d530e053ffa66ef5bd3878b101c59d808ac947fcff96810b4452abba2b54be687adadea2ba9efc7af48b04228742789bf824ef93f103767e690c - languageName: node - linkType: hard - "buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" From 99bd587e9a405e69e2787132eb69756f67c4a195 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 14 Jan 2025 19:59:11 -0500 Subject: [PATCH 2/7] Migrate `hubspotClient.oauth.tokensApi.createToken` to `hubspotClient.oauth.tokensApi.create()` --- packages/app-store/hubspot/api/callback.ts | 2 +- packages/app-store/hubspot/lib/CrmService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app-store/hubspot/api/callback.ts b/packages/app-store/hubspot/api/callback.ts index f391bb186dad03..e3878b4654d58e 100644 --- a/packages/app-store/hubspot/api/callback.ts +++ b/packages/app-store/hubspot/api/callback.ts @@ -38,7 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." }); if (!client_secret) return res.status(400).json({ message: "HubSpot client secret missing." }); - const hubspotToken: HubspotToken = await hubspotClient.oauth.tokensApi.createToken( + const hubspotToken: HubspotToken = await hubspotClient.oauth.tokensApi.create( "authorization_code", code, `${WEBAPP_URL_FOR_OAUTH}/api/integrations/hubspot/callback`, diff --git a/packages/app-store/hubspot/lib/CrmService.ts b/packages/app-store/hubspot/lib/CrmService.ts index 80847f08892515..1fe653a2fb0078 100644 --- a/packages/app-store/hubspot/lib/CrmService.ts +++ b/packages/app-store/hubspot/lib/CrmService.ts @@ -120,7 +120,7 @@ export default class HubspotCalendarService implements CRM { try { const hubspotRefreshToken: HubspotToken = await refreshOAuthTokens( async () => - await hubspotClient.oauth.tokensApi.createToken( + await hubspotClient.oauth.tokensApi.create( "refresh_token", undefined, `${WEBAPP_URL}/api/integrations/hubspot/callback`, From 2add85df7e3871479d51626ae7bd6b487dc873a8 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 14 Jan 2025 20:17:04 -0500 Subject: [PATCH 3/7] `add` and `callback` endpoints import Hubspot client --- packages/app-store/hubspot/api/add.ts | 6 +++--- packages/app-store/hubspot/api/callback.ts | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/app-store/hubspot/api/add.ts b/packages/app-store/hubspot/api/add.ts index 1e8d6a4f520356..350bb153c2a319 100644 --- a/packages/app-store/hubspot/api/add.ts +++ b/packages/app-store/hubspot/api/add.ts @@ -1,4 +1,4 @@ -import * as hubspot from "@hubspot/api-client"; +import { Client } from "@hubspot/api-client"; import type { NextApiRequest, NextApiResponse } from "next"; import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; @@ -9,7 +9,7 @@ import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"]; let client_id = ""; -const hubspotClient = new hubspot.Client(); +const oauth = new Client().oauth; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" }); @@ -19,7 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." }); const redirectUri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/hubspot/callback`; - const url = hubspotClient.oauth.getAuthorizationUrl( + const url = oauth.getAuthorizationUrl( client_id, redirectUri, scopes.join(" "), diff --git a/packages/app-store/hubspot/api/callback.ts b/packages/app-store/hubspot/api/callback.ts index e3878b4654d58e..8adf1e87274d86 100644 --- a/packages/app-store/hubspot/api/callback.ts +++ b/packages/app-store/hubspot/api/callback.ts @@ -1,4 +1,4 @@ -import * as hubspot from "@hubspot/api-client"; +import { Client } from "@hubspot/api-client"; import type { TokenResponseIF } from "@hubspot/api-client/lib/codegen/oauth/models/TokenResponseIF"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -13,7 +13,6 @@ import metadata from "../_metadata"; let client_id = ""; let client_secret = ""; -const hubspotClient = new hubspot.Client(); export interface HubspotToken extends TokenResponseIF { expiryDate?: number; @@ -23,6 +22,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const { code } = req.query; const state = decodeOAuthState(req); + const oauth = new Client().oauth; + if (code && typeof code !== "string") { res.status(400).json({ message: "`code` must be a string" }); return; @@ -38,7 +39,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." }); if (!client_secret) return res.status(400).json({ message: "HubSpot client secret missing." }); - const hubspotToken: HubspotToken = await hubspotClient.oauth.tokensApi.create( + const hubspotToken: HubspotToken = await oauth.tokensApi.create( "authorization_code", code, `${WEBAPP_URL_FOR_OAUTH}/api/integrations/hubspot/callback`, From 86f2edba3d1d0fcab2dee7d8e8bacf2aefc62d17 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 14 Jan 2025 21:25:57 -0500 Subject: [PATCH 4/7] Add associations to new event creation - new package migration --- packages/app-store/hubspot/lib/CrmService.ts | 93 ++++++++++---------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/packages/app-store/hubspot/lib/CrmService.ts b/packages/app-store/hubspot/lib/CrmService.ts index 1fe653a2fb0078..f14a6014784647 100644 --- a/packages/app-store/hubspot/lib/CrmService.ts +++ b/packages/app-store/hubspot/lib/CrmService.ts @@ -1,11 +1,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as hubspot from "@hubspot/api-client"; -import type { BatchInputPublicAssociation } from "@hubspot/api-client/lib/codegen/crm/associations"; +import { AssociationTypes } from "@hubspot/api-client"; +// import { AssociationSpecAssociationCategoryEnum } from "@hubspot/api-client" import type { PublicObjectSearchRequest } from "@hubspot/api-client/lib/codegen/crm/contacts"; import type { - SimplePublicObject, SimplePublicObjectInput, + SimplePublicObjectInputForCreate, + PublicAssociationsForObject, } from "@hubspot/api-client/lib/codegen/crm/objects/meetings"; +import { AssociationSpecAssociationCategoryEnum } from "@hubspot/api-client/lib/codegen/crm/objects/meetings"; import { getLocation } from "@calcom/lib/CalEventParser"; import { WEBAPP_URL } from "@calcom/lib/constants"; @@ -50,35 +53,40 @@ export default class HubspotCalendarService implements CRM { }`; }; - private hubspotCreateMeeting = async (event: CalendarEvent) => { - const simplePublicObjectInput: SimplePublicObjectInput = { - properties: { - hs_timestamp: Date.now().toString(), - hs_meeting_title: event.title, - hs_meeting_body: this.getHubspotMeetingBody(event), - hs_meeting_location: getLocation(event), - hs_meeting_start_time: new Date(event.startTime).toISOString(), - hs_meeting_end_time: new Date(event.endTime).toISOString(), - hs_meeting_outcome: "SCHEDULED", - }, - }; - - return hubspotClient.crm.objects.meetings.basicApi.create(simplePublicObjectInput); - }; + private hubspotCreateMeeting = async (event: CalendarEvent, contacts: Contact[]) => { + try { + const simplePublicObjectInput: SimplePublicObjectInputForCreate = { + properties: { + hs_timestamp: Date.now().toString(), + hs_meeting_title: event.title, + hs_meeting_body: this.getHubspotMeetingBody(event), + hs_meeting_location: getLocation(event), + hs_meeting_start_time: new Date(event.startTime).toISOString(), + hs_meeting_end_time: new Date(event.endTime).toISOString(), + hs_meeting_outcome: "SCHEDULED", + }, + associations: contacts.reduce((associations, contact) => { + if (contact.id) { + associations.push({ + to: { + id: contact.id, + }, + types: [ + { + associationCategory: AssociationSpecAssociationCategoryEnum.HubspotDefined, + associationTypeId: AssociationTypes.meetingToContact, + }, + ], + }); + } + return associations; + }, [] as PublicAssociationsForObject[]), + }; - private hubspotAssociate = async (meeting: SimplePublicObject, contacts: Array<{ id: string }>) => { - const batchInputPublicAssociation: BatchInputPublicAssociation = { - inputs: contacts.map((contact: { id: string }) => ({ - _from: { id: meeting.id }, - to: { id: contact.id }, - type: "meeting_event_to_contact", - })), - }; - return hubspotClient.crm.associations.batchApi.create( - "meetings", - "contacts", - batchInputPublicAssociation - ); + return await hubspotClient.crm.objects.meetings.basicApi.create(simplePublicObjectInput); + } catch (e) { + this.log.warn(`error creating event for bookingUid ${event.uid}, ${e}`); + } }; private hubspotUpdateMeeting = async (uid: string, event: CalendarEvent) => { @@ -156,23 +164,18 @@ export default class HubspotCalendarService implements CRM { }; async handleMeetingCreation(event: CalendarEvent, contacts: Contact[]) { - const contactIds: { id?: string }[] = contacts.map((contact) => ({ id: contact.id })); - const meetingEvent = await this.hubspotCreateMeeting(event); + const meetingEvent = await this.hubspotCreateMeeting(event, contacts); + if (meetingEvent) { this.log.debug("meeting:creation:ok", { meetingEvent }); - const associatedMeeting = await this.hubspotAssociate(meetingEvent, contactIds as any); - if (associatedMeeting) { - this.log.debug("association:creation:ok", { associatedMeeting }); - return Promise.resolve({ - uid: meetingEvent.id, - id: meetingEvent.id, - type: "hubspot_other_calendar", - password: "", - url: "", - additionalInfo: { contacts, associatedMeeting }, - }); - } - return Promise.reject("Something went wrong when associating the meeting and attendees in HubSpot"); + return Promise.resolve({ + uid: meetingEvent.id, + id: meetingEvent.id, + type: "hubspot_other_calendar", + password: "", + url: "", + additionalInfo: { contacts, meetingEvent }, + }); } this.log.debug("meeting:creation:notOk", { meetingEvent, event, contacts }); return Promise.reject("Something went wrong when creating a meeting in HubSpot"); From 7cda8d6b76a6b022df58c43490f0363365f77e7d Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 14 Jan 2025 21:43:30 -0500 Subject: [PATCH 5/7] Type fixes --- packages/app-store/hubspot/lib/CrmService.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/app-store/hubspot/lib/CrmService.ts b/packages/app-store/hubspot/lib/CrmService.ts index f14a6014784647..f5c49a2a211039 100644 --- a/packages/app-store/hubspot/lib/CrmService.ts +++ b/packages/app-store/hubspot/lib/CrmService.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as hubspot from "@hubspot/api-client"; import { AssociationTypes } from "@hubspot/api-client"; -// import { AssociationSpecAssociationCategoryEnum } from "@hubspot/api-client" import type { PublicObjectSearchRequest } from "@hubspot/api-client/lib/codegen/crm/contacts"; +import { FilterOperatorEnum } from "@hubspot/api-client/lib/codegen/crm/contacts"; import type { SimplePublicObjectInput, SimplePublicObjectInputForCreate, @@ -213,14 +213,13 @@ export default class HubspotCalendarService implements CRM { { value: attendeeEmail, propertyName: "email", - operator: "EQ", + operator: FilterOperatorEnum.Eq, }, ], })), sorts: ["hs_object_id"], properties: ["hs_object_id", "email"], limit: 10, - after: 0, }; const contacts = await hubspotClient.crm.contacts.searchApi @@ -230,7 +229,7 @@ export default class HubspotCalendarService implements CRM { return contacts.map((contact) => { return { id: contact.id, - email: contact.properties.email, + email: contact.properties.email || "", }; }); } @@ -268,7 +267,7 @@ export default class HubspotCalendarService implements CRM { return createdContacts.map((contact) => { return { id: contact.id, - email: contact.properties.email, + email: contact.properties.email || "", }; }); } From 276f7e5ab2325621095d7599c67957ca2bbc3447 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 14 Jan 2025 21:52:00 -0500 Subject: [PATCH 6/7] Remove unused variables --- packages/app-store/hubspot/lib/CrmService.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/app-store/hubspot/lib/CrmService.ts b/packages/app-store/hubspot/lib/CrmService.ts index f5c49a2a211039..38f1b67394570e 100644 --- a/packages/app-store/hubspot/lib/CrmService.ts +++ b/packages/app-store/hubspot/lib/CrmService.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import * as hubspot from "@hubspot/api-client"; -import { AssociationTypes } from "@hubspot/api-client"; +import { Client, AssociationTypes } from "@hubspot/api-client"; import type { PublicObjectSearchRequest } from "@hubspot/api-client/lib/codegen/crm/contacts"; import { FilterOperatorEnum } from "@hubspot/api-client/lib/codegen/crm/contacts"; import type { @@ -23,14 +22,9 @@ import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import type { HubspotToken } from "../api/callback"; -const hubspotClient = new hubspot.Client(); - -interface CustomPublicObjectInput extends SimplePublicObjectInput { - id?: string; -} +const hubspotClient = new Client(); export default class HubspotCalendarService implements CRM { - private url = ""; private integrationName = ""; private auth: Promise<{ getToken: () => Promise }>; private log: typeof logger; From c62394bfe4f02e30450a7193f966e7501fa4db86 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Tue, 21 Jan 2025 04:41:18 -0500 Subject: [PATCH 7/7] feat: Hubspot - create new contacts under an account (#18723) * Add new scopes * Add FE option to create contacts under a company * Find company and associate with contact --- apps/web/public/static/locales/en/common.json | 1 + packages/app-store/hubspot/api/add.ts | 7 +- .../components/EventTypeAppCardInterface.tsx | 25 +++++- packages/app-store/hubspot/lib/CrmService.ts | 79 ++++++++++++++++--- packages/app-store/hubspot/zod.ts | 4 +- 5 files changed, 101 insertions(+), 15 deletions(-) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 8133ae2c6e8178..f9898ef016634a 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2926,5 +2926,6 @@ "managed_users": "Managed Users", "managed_users_description": "See all the managed users created by your OAuth client", "select_oAuth_client": "Select Oauth Client", + "hubspot_create_contact_under_company": "Create new contacts under a company where the website matches the contact email domain", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/packages/app-store/hubspot/api/add.ts b/packages/app-store/hubspot/api/add.ts index 350bb153c2a319..b81e2d8fb44dac 100644 --- a/packages/app-store/hubspot/api/add.ts +++ b/packages/app-store/hubspot/api/add.ts @@ -6,7 +6,12 @@ import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants"; import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState"; -const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"]; +const scopes = [ + "crm.objects.contacts.read", + "crm.objects.contacts.write", + "crm.schemas.companies.read", + "crm.objects.companies.read", +]; let client_id = ""; const oauth = new Client().oauth; diff --git a/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx b/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx index 53a8297d6fca40..165d0d6d0425ac 100644 --- a/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/hubspot/components/EventTypeAppCardInterface.tsx @@ -1,14 +1,23 @@ import { usePathname } from "next/navigation"; +import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext"; import AppCard from "@calcom/app-store/_components/AppCard"; import useIsAppEnabled from "@calcom/app-store/_utils/useIsAppEnabled"; import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Switch } from "@calcom/ui"; + +import type { appDataSchema } from "../zod"; const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { const pathname = usePathname(); + const { t } = useLocale(); const { enabled, updateEnabled } = useIsAppEnabled(app); + const { getAppData, setAppData, disabled } = useAppContextWithSchema(); + + const createContactUnderCompany = getAppData("createContactUnderCompany"); return ( + hideSettingsIcon> + <> +
+ { + setAppData("createContactUnderCompany", checked); + }} + /> +
+ +
); }; diff --git a/packages/app-store/hubspot/lib/CrmService.ts b/packages/app-store/hubspot/lib/CrmService.ts index 38f1b67394570e..2693759ee247b5 100644 --- a/packages/app-store/hubspot/lib/CrmService.ts +++ b/packages/app-store/hubspot/lib/CrmService.ts @@ -1,13 +1,20 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Client, AssociationTypes } from "@hubspot/api-client"; -import type { PublicObjectSearchRequest } from "@hubspot/api-client/lib/codegen/crm/contacts"; -import { FilterOperatorEnum } from "@hubspot/api-client/lib/codegen/crm/contacts"; +import { FilterOperatorEnum as CompanyFilterOperatorEnum } from "@hubspot/api-client/lib/codegen/crm/companies"; +import type { + PublicObjectSearchRequest as ContactSearchInput, + SimplePublicObjectInputForCreate as HubspotContactCreateInput, + PublicObjectSearchRequest as CompanySearchInput, +} from "@hubspot/api-client/lib/codegen/crm/contacts"; +import { FilterOperatorEnum as ContactFilterOperatorEnum } from "@hubspot/api-client/lib/codegen/crm/contacts"; +import { AssociationSpecAssociationCategoryEnum as ContactAssociationCategoryEnum } from "@hubspot/api-client/lib/codegen/crm/contacts"; import type { SimplePublicObjectInput, - SimplePublicObjectInputForCreate, + SimplePublicObjectInputForCreate as MeetingCreateInput, PublicAssociationsForObject, } from "@hubspot/api-client/lib/codegen/crm/objects/meetings"; -import { AssociationSpecAssociationCategoryEnum } from "@hubspot/api-client/lib/codegen/crm/objects/meetings"; +import { AssociationSpecAssociationCategoryEnum as MeetingAssociationCategoryEnum } from "@hubspot/api-client/lib/codegen/crm/objects/meetings"; +import type z from "zod"; import { getLocation } from "@calcom/lib/CalEventParser"; import { WEBAPP_URL } from "@calcom/lib/constants"; @@ -21,6 +28,7 @@ import type { CRM, ContactCreateInput, Contact, CrmEvent } from "@calcom/types/C import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens"; import type { HubspotToken } from "../api/callback"; +import type { appDataSchema } from "../zod"; const hubspotClient = new Client(); @@ -30,13 +38,15 @@ export default class HubspotCalendarService implements CRM { private log: typeof logger; private client_id = ""; private client_secret = ""; + private appOptions: z.infer; - constructor(credential: CredentialPayload) { + constructor(credential: CredentialPayload, appOptions: any) { this.integrationName = "hubspot_other_calendar"; this.auth = this.hubspotAuth(credential).then((r) => r); this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] }); + this.appOptions = appOptions; } private getHubspotMeetingBody = (event: CalendarEvent): string => { @@ -49,7 +59,7 @@ export default class HubspotCalendarService implements CRM { private hubspotCreateMeeting = async (event: CalendarEvent, contacts: Contact[]) => { try { - const simplePublicObjectInput: SimplePublicObjectInputForCreate = { + const simplePublicObjectInput: MeetingCreateInput = { properties: { hs_timestamp: Date.now().toString(), hs_meeting_title: event.title, @@ -67,7 +77,7 @@ export default class HubspotCalendarService implements CRM { }, types: [ { - associationCategory: AssociationSpecAssociationCategoryEnum.HubspotDefined, + associationCategory: MeetingAssociationCategoryEnum.HubspotDefined, associationTypeId: AssociationTypes.meetingToContact, }, ], @@ -201,13 +211,13 @@ export default class HubspotCalendarService implements CRM { const emailArray = Array.isArray(emails) ? emails : [emails]; - const publicObjectSearchRequest: PublicObjectSearchRequest = { + const publicObjectSearchRequest: ContactSearchInput = { filterGroups: emailArray.map((attendeeEmail) => ({ filters: [ { value: attendeeEmail, propertyName: "email", - operator: FilterOperatorEnum.Eq, + operator: ContactFilterOperatorEnum.Eq, }, ], })), @@ -232,7 +242,39 @@ export default class HubspotCalendarService implements CRM { const auth = await this.auth; await auth.getToken(); - const simplePublicObjectInputs = contactsToCreate.map((attendee) => { + const appOptions = this.getAppOptions(); + let companyId: string; + + // Check for a company to associate the contact with + if (appOptions?.createContactUnderCompany) { + const emailDomain = contactsToCreate[0].email.split("@")[1]; + + const companySearchInput: CompanySearchInput = { + filterGroups: [ + { + filters: [ + { + propertyName: "website", + operator: CompanyFilterOperatorEnum.ContainsToken, + value: emailDomain, + }, + ], + }, + ], + properties: ["id"], + limit: 1, + }; + + const companyQuery = await hubspotClient.crm.companies.searchApi + .doSearch(companySearchInput) + .then((apiResponse) => apiResponse.results); + if (companyQuery.length > 0) { + const company = companyQuery[0]; + companyId = company.id; + } + } + + const simplePublicObjectInputs: HubspotContactCreateInput[] = contactsToCreate.map((attendee) => { const [firstname, lastname] = attendee.name ? attendee.name.split(" ") : [attendee.email, ""]; return { properties: { @@ -240,6 +282,21 @@ export default class HubspotCalendarService implements CRM { lastname, email: attendee.email, }, + ...(companyId && { + associations: [ + { + to: { + id: companyId, + }, + types: [ + { + associationCategory: ContactAssociationCategoryEnum.HubspotDefined, + associationTypeId: AssociationTypes.contactToCompany, + }, + ], + }, + ], + }), }; }); const createdContacts = await Promise.all( @@ -267,6 +324,6 @@ export default class HubspotCalendarService implements CRM { } getAppOptions() { - console.log("No options implemented"); + return this.appOptions; } } diff --git a/packages/app-store/hubspot/zod.ts b/packages/app-store/hubspot/zod.ts index 8cfdc5ee22810c..56297b05d26db3 100644 --- a/packages/app-store/hubspot/zod.ts +++ b/packages/app-store/hubspot/zod.ts @@ -2,7 +2,9 @@ import { z } from "zod"; import { eventTypeAppCardZod } from "../eventTypeAppCardZod"; -export const appDataSchema = eventTypeAppCardZod; +export const appDataSchema = eventTypeAppCardZod.extend({ + createContactUnderCompany: z.boolean().optional(), +}); export const appKeysSchema = z.object({ client_id: z.string().min(1),