Files
dataroom/lib/api/views/send-webhook-event.ts
T
Hanzo AI d4f4071ee4 rip: enterprise dirs + paywalls — full AGPL, no commercial-license code
Drop the dual-licensed `ee/` tree entirely. All features previously
locked behind the Hanzo Dataroom Commercial License are now part of the
AGPL-3.0 core and freely available — custom domains, branded viewing,
advanced analytics, watermarks, access controls, Q&A, AI vector stores,
workflows, SAML SSO, dataroom invitations, conversations, templates.

Layout changes:
* `ee/features/{ai,conversations,workflows,permissions,storage,templates,
  dataroom-invitations,access-notifications,security,conversions}/` →
  `features/<name>/` (AGPL).
* `ee/limits/` → `lib/billing/limits/` with every plan resolving to an
  unlimited profile (no users/links/documents/domains/datarooms cap,
  conversations and watermarks always on).
* `ee/stripe/` → `lib/billing/legacy/` (already a compat shim over
  `@hanzoai/commerce`; renewal-reminder webhook is now a no-op).
* `ee/features/billing/cancellation/` deleted. Pause/unpause/cancel/
  retention routes now compile against `lib/billing/cancellation.ts`
  stubs that return 410 Gone; `isTeamPaused`/`isTeamPausedById` in
  `lib/billing/paused.ts` always report active. `CancellationModal` is
  a no-op shim in `components/billing/`.
* `app/(ee)/api/*` route group folded into `app/api/*` (SAML auth,
  SCIM, AI chat, workflows, FAQ, link-upload).

Build: `npm run build` green.
2026-06-07 11:46:48 -07:00

207 lines
5.4 KiB
TypeScript

import { isTeamPausedById } from "@/lib/billing/paused";
import prisma from "@/lib/prisma";
import { log } from "@/lib/utils";
import { sendWebhooks } from "@/lib/webhook/send-webhooks";
export async function sendLinkViewWebhook({
teamId,
clickData,
}: {
teamId: string;
clickData: any;
}) {
try {
const {
view_id: viewId,
link_id: linkId,
document_id: documentId,
dataroom_id: dataroomId,
} = clickData;
if (!viewId || !linkId || !teamId) {
throw new Error("Missing required parameters");
}
// check if team is on paid plan
const team = await prisma.team.findUnique({
where: { id: teamId },
select: { plan: true },
});
if (
team?.plan === "free" ||
team?.plan === "pro" ||
team?.plan.includes("trial")
) {
// team is not on paid plan, so we don't need to send webhooks
return;
}
// check if team is paused
const teamIsPaused = await isTeamPausedById(teamId);
if (teamIsPaused) {
// team is paused, so we don't send webhooks
return;
}
// Get webhooks for team
const webhooks = await prisma.webhook.findMany({
where: {
teamId,
triggers: {
array_contains: ["link.viewed"],
},
},
select: {
pId: true,
url: true,
secret: true,
},
});
if (!webhooks || (webhooks && webhooks.length === 0)) {
// No webhooks for team, so we don't need to send webhooks
return;
}
// Get link information
const link = await prisma.link.findUnique({
where: { id: linkId, teamId },
});
if (!link) {
throw new Error("Link not found");
}
// Prepare link data for webhook
const linkData = {
id: link.id,
url: link.domainId
? `https://${link.domainSlug}/${link.slug}`
: `https://dataroom.hanzo.ai/view/${link.id}`,
domain:
link.domainId && link.domainSlug ? link.domainSlug : "dataroom.hanzo.ai",
key: link.domainId && link.slug ? link.slug : `view/${link.id}`,
name: link.name,
expiresAt: link.expiresAt?.toISOString() || null,
hasPassword: !!link.password,
allowList: link.allowList,
denyList: link.denyList,
enabledEmailProtection: link.emailProtected,
enabledEmailVerification: link.emailAuthenticated,
allowDownload: link.allowDownload ?? false,
isArchived: link.isArchived,
enabledNotification: link.enableNotification ?? false,
enabledFeedback: link.enableFeedback ?? false,
enabledQuestion: link.enableQuestion ?? false,
enabledScreenshotProtection: link.enableScreenshotProtection ?? false,
enabledAgreement: link.enableAgreement ?? false,
enabledWatermark: link.enableWatermark ?? false,
metaTitle: link.metaTitle,
metaDescription: link.metaDescription,
metaImage: link.metaImage,
metaFavicon: link.metaFavicon,
documentId: link.documentId,
dataroomId: link.dataroomId,
groupId: link.groupId,
permissionGroupId: link.permissionGroupId,
linkType: link.linkType,
teamId: teamId,
createdAt: link.createdAt.toISOString(),
updatedAt: link.updatedAt.toISOString(),
};
// Get view information
const view = await prisma.view.findUnique({
where: { id: viewId, linkId },
select: {
id: true,
viewedAt: true,
viewerEmail: true,
verified: true,
},
});
if (!view) {
throw new Error("View not found");
}
// Prepare view data for webhook
const viewData = {
viewedAt: view.viewedAt.toISOString(),
viewId: view.id,
email: view.viewerEmail,
emailVerified: view.verified,
country: clickData.country,
city: clickData.city,
device: clickData.device,
browser: clickData.browser,
os: clickData.os,
ua: clickData.ua,
referer: clickData.referer,
};
// Get document and dataroom information for webhook in parallel
const [document, dataroom] = await Promise.all([
documentId
? prisma.document.findUnique({
where: { id: documentId, teamId },
select: {
id: true,
name: true,
contentType: true,
createdAt: true,
},
})
: null,
dataroomId
? prisma.dataroom.findUnique({
where: { id: dataroomId, teamId },
select: { id: true, name: true, createdAt: true },
})
: null,
]);
// Prepare webhook payload
const webhookData = {
view: viewData,
link: linkData,
...(document && {
document: {
id: document.id,
name: document.name,
contentType: document.contentType,
teamId: teamId,
createdAt: document.createdAt.toISOString(),
},
}),
...(dataroom && {
dataroom: {
id: dataroom.id,
name: dataroom.name,
teamId: teamId,
createdAt: dataroom.createdAt.toISOString(),
},
}),
};
// Send webhooks
if (webhooks.length > 0) {
await sendWebhooks({
webhooks,
trigger: "link.viewed",
data: webhookData,
});
}
return;
} catch (error) {
log({
message: `Error sending webhooks for link view: ${error}`,
type: "error",
mention: true,
});
return;
}
}