diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index a0dac2e00..e6d753293 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -68,7 +68,6 @@ export * from "./utils/backups/postgres"; export * from "./utils/backups/utils"; export * from "./utils/backups/web-server"; export * from "./utils/builders/compose"; -export * from "./utils/startup/cancell-deployments"; export * from "./utils/builders/docker-file"; export * from "./utils/builders/drop"; export * from "./utils/builders/heroku"; @@ -77,7 +76,6 @@ export * from "./utils/builders/nixpacks"; export * from "./utils/builders/paketo"; export * from "./utils/builders/static"; export * from "./utils/builders/utils"; - export * from "./utils/cluster/upload"; export * from "./utils/databases/rebuild"; export * from "./utils/docker/collision"; @@ -113,6 +111,8 @@ export * from "./utils/providers/raw"; export * from "./utils/schedules/index"; export * from "./utils/schedules/utils"; export * from "./utils/servers/remote-docker"; +export * from "./utils/startup/cancell-deployments"; +export * from "./utils/tracking/hubspot"; export * from "./utils/traefik/application"; export * from "./utils/traefik/domain"; export * from "./utils/traefik/file-types"; diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index 1561c6a2b..739a666f7 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -10,6 +10,7 @@ import { db } from "../db"; import * as schema from "../db/schema"; import { getUserByToken } from "../services/admin"; import { updateUser } from "../services/user"; +import { getHubSpotUTK, submitToHubSpot } from "../utils/tracking/hubspot"; import { sendEmail } from "../verification/send-verification-email"; import { getPublicIpWithFallback } from "../wss/utils"; @@ -115,7 +116,7 @@ const { handler, api } = betterAuth({ } } }, - after: async (user) => { + after: async (user, context) => { const isAdminPresent = await db.query.member.findFirst({ where: eq(schema.member.role, "owner"), }); @@ -126,6 +127,27 @@ const { handler, api } = betterAuth({ }); } + if (IS_CLOUD) { + try { + const hutk = getHubSpotUTK( + context?.request?.headers?.get("cookie") || undefined, + ); + const hubspotSuccess = await submitToHubSpot( + { + email: user.email, + firstName: user.name, + lastName: user.name, + }, + hutk, + ); + if (!hubspotSuccess) { + console.error("Failed to submit to HubSpot"); + } + } catch (error) { + console.error("Error submitting to HubSpot", error); + } + } + if (IS_CLOUD || !isAdminPresent) { await db.transaction(async (tx) => { const organization = await tx diff --git a/packages/server/src/utils/tracking/hubspot.ts b/packages/server/src/utils/tracking/hubspot.ts new file mode 100644 index 000000000..d22a9591c --- /dev/null +++ b/packages/server/src/utils/tracking/hubspot.ts @@ -0,0 +1,125 @@ +interface HubSpotFormField { + objectTypeId: string; + name: string; + value: string; +} + +interface HubSpotFormData { + fields: HubSpotFormField[]; + context: { + pageUri: string; + pageName: string; + hutk?: string; // HubSpot UTK from cookies + }; +} + +interface SignUpFormData { + firstName?: string; + lastName?: string; + email?: string; +} + +/** + * Extract HubSpot UTK (User Token) from cookies + * This is used for tracking and attribution in HubSpot + */ +export function getHubSpotUTK(cookieHeader?: string): string | null { + if (!cookieHeader) return null; + + const name = "hubspotutk="; + const decodedCookie = decodeURIComponent(cookieHeader); + const cookieArray = decodedCookie.split(";"); + + for (let i = 0; i < cookieArray.length; i++) { + const cookie = cookieArray[i]?.trim(); + if (!cookie) continue; + if (cookie.indexOf(name) === 0) { + return cookie.substring(name.length, cookie.length); + } + } + return null; +} + +/** + * Convert contact form data to HubSpot form format + */ +export function formatContactDataForHubSpot( + contactData: SignUpFormData, + hutk?: string | null, +): HubSpotFormData { + const formData: HubSpotFormData = { + fields: [ + { + objectTypeId: "0-1", // Contact object type + name: "firstname", + value: contactData.firstName || "", + }, + { + objectTypeId: "0-1", + name: "lastname", + value: contactData.lastName || "", + }, + { + objectTypeId: "0-1", + name: "email", + value: contactData.email || "", + }, + ], + context: { + pageUri: "https://app.dokploy.com/register", + pageName: "Sign Up", + }, + }; + + // Add HubSpot UTK if available + if (hutk) { + formData.context.hutk = hutk; + } + + return formData; +} + +/** + * Submit form data to HubSpot Forms API + */ +export async function submitToHubSpot( + contactData: SignUpFormData, + hutk?: string | null, +): Promise { + try { + const portalId = process.env.HUBSPOT_PORTAL_ID; + const formGuid = process.env.HUBSPOT_FORM_GUID; + + if (!portalId || !formGuid) { + console.error( + "HubSpot configuration missing: HUBSPOT_PORTAL_ID or HUBSPOT_FORM_GUID not set", + ); + return false; + } + + const formData = formatContactDataForHubSpot(contactData, hutk); + const response = await fetch( + `https://api.hsforms.com/submissions/v3/integration/submit/${portalId}/${formGuid}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }, + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error("HubSpot API error:", response.status, errorText); + return false; + } + + const result = await response.json(); + console.log("HubSpot submission successful:", result); + return true; + } catch (error) { + console.error("Error submitting to HubSpot:", error); + return false; + } +}