mirror of
https://github.com/mfts/papermark.git
synced 2025-12-20 01:03:24 +08:00
feat(webhook): add link.created, document.created triggers
This commit is contained in:
@@ -1,44 +1,59 @@
|
||||
import { receiver } from "@/lib/cron";
|
||||
import { z } from "zod";
|
||||
|
||||
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { recordWebhookEvent } from "@/lib/tinybird/publish";
|
||||
import { getSearchParams } from "@/lib/utils/get-search-params";
|
||||
import { WEBHOOK_TRIGGERS } from "@/lib/webhook/constants";
|
||||
import {
|
||||
webhookCallbackSchema,
|
||||
webhookPayloadSchema,
|
||||
} from "@/lib/zod/schemas/webhooks";
|
||||
|
||||
const searchParamsSchema = z.object({
|
||||
webhookId: z.string(),
|
||||
eventId: z.string(),
|
||||
event: z.enum(WEBHOOK_TRIGGERS),
|
||||
});
|
||||
|
||||
// POST /api/webhooks/callback – listen to webhooks status from QStash
|
||||
export const POST = async (req: Request) => {
|
||||
const rawBody = await req.json();
|
||||
|
||||
const isValid = await receiver.verify({
|
||||
signature: req.headers.get("Upstash-Signature") || "",
|
||||
body: JSON.stringify(rawBody),
|
||||
const rawBody = await req.text();
|
||||
await verifyQstashSignature({
|
||||
req,
|
||||
rawBody,
|
||||
});
|
||||
if (!isValid) {
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
|
||||
const { url, status, body, sourceBody, sourceMessageId } =
|
||||
webhookCallbackSchema.parse(rawBody);
|
||||
webhookCallbackSchema.parse(JSON.parse(rawBody));
|
||||
|
||||
const { webhookId, eventId, event } = searchParamsSchema.parse(
|
||||
getSearchParams(req.url),
|
||||
);
|
||||
|
||||
const webhook = await prisma.webhook.findUnique({
|
||||
where: { id: webhookId },
|
||||
});
|
||||
|
||||
if (!webhook) {
|
||||
console.error("Webhook not found", { webhookId });
|
||||
return new Response("Webhook not found");
|
||||
}
|
||||
|
||||
const request = Buffer.from(sourceBody, "base64").toString("utf-8");
|
||||
const response = Buffer.from(body, "base64").toString("utf-8");
|
||||
|
||||
const { id: eventId, event } = webhookPayloadSchema.parse(
|
||||
JSON.parse(request),
|
||||
);
|
||||
|
||||
const webhookId = new URL(req.url).searchParams.get("webhookId");
|
||||
const isFailed = status >= 400 || status === -1;
|
||||
|
||||
await recordWebhookEvent({
|
||||
url,
|
||||
event,
|
||||
event_id: eventId,
|
||||
http_status: status,
|
||||
http_status: status === -1 ? 503 : status,
|
||||
webhook_id: webhookId || "",
|
||||
request_body: request,
|
||||
response_body: response,
|
||||
message_id: sourceMessageId,
|
||||
});
|
||||
|
||||
return new Response("OK");
|
||||
return new Response(`Webhook ${webhookId} processed`);
|
||||
};
|
||||
|
||||
@@ -129,13 +129,18 @@ export async function sendLinkViewWebhook({
|
||||
documentId
|
||||
? prisma.document.findUnique({
|
||||
where: { id: documentId, teamId },
|
||||
select: { id: true, name: true, contentType: true },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
contentType: true,
|
||||
createdAt: true,
|
||||
},
|
||||
})
|
||||
: null,
|
||||
dataroomId
|
||||
? prisma.dataroom.findUnique({
|
||||
where: { id: dataroomId, teamId },
|
||||
select: { id: true, name: true },
|
||||
select: { id: true, name: true, createdAt: true },
|
||||
})
|
||||
: null,
|
||||
]);
|
||||
@@ -150,6 +155,7 @@ export async function sendLinkViewWebhook({
|
||||
name: document.name,
|
||||
contentType: document.contentType,
|
||||
teamId: teamId,
|
||||
createdAt: document.createdAt.toISOString(),
|
||||
},
|
||||
}),
|
||||
...(dataroom && {
|
||||
@@ -157,6 +163,7 @@ export async function sendLinkViewWebhook({
|
||||
id: dataroom.id,
|
||||
name: dataroom.name,
|
||||
teamId: teamId,
|
||||
createdAt: dataroom.createdAt.toISOString(),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
39
lib/cron/verify-qstash.ts
Normal file
39
lib/cron/verify-qstash.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { receiver } from ".";
|
||||
import { log } from "../utils";
|
||||
|
||||
export const verifyQstashSignature = async ({
|
||||
req,
|
||||
rawBody,
|
||||
}: {
|
||||
req: Request;
|
||||
rawBody: string; // Make sure to pass the raw body not the parsed JSON
|
||||
}) => {
|
||||
// skip verification in local development
|
||||
if (process.env.VERCEL !== "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
const signature = req.headers.get("Upstash-Signature");
|
||||
|
||||
if (!signature) {
|
||||
throw new Error("Upstash-Signature header not found.");
|
||||
}
|
||||
|
||||
const isValid = await receiver.verify({
|
||||
signature,
|
||||
body: rawBody,
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
const url = req.url;
|
||||
const messageId = req.headers.get("Upstash-Message-Id");
|
||||
|
||||
log({
|
||||
message: `Invalid QStash request signature: *${url}* - *${messageId}*`,
|
||||
type: "error",
|
||||
mention: true,
|
||||
});
|
||||
|
||||
throw new Error("Invalid QStash request signature.");
|
||||
}
|
||||
};
|
||||
10
lib/utils/get-search-params.ts
Normal file
10
lib/utils/get-search-params.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const getSearchParams = (url: string) => {
|
||||
// Create a params object
|
||||
let params = {} as Record<string, string>;
|
||||
|
||||
new URL(url).searchParams.forEach(function (val, key) {
|
||||
params[key] = val;
|
||||
});
|
||||
|
||||
return params;
|
||||
};
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Webhook } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
|
||||
import { qstash } from "@/lib/cron";
|
||||
import { webhookPayloadSchema } from "@/lib/zod/schemas/webhooks";
|
||||
|
||||
import { createWebhookSignature } from "./signature";
|
||||
import { prepareWebhookPayload } from "./transform";
|
||||
import { EventDataProps, WebhookTrigger } from "./types";
|
||||
import { EventDataProps, WebhookPayload, WebhookTrigger } from "./types";
|
||||
|
||||
// Send webhooks to multiple webhooks
|
||||
export const sendWebhooks = async ({
|
||||
@@ -37,9 +35,16 @@ const publishWebhookEventToQStash = async ({
|
||||
payload,
|
||||
}: {
|
||||
webhook: Pick<Webhook, "pId" | "url" | "secret">;
|
||||
payload: z.infer<typeof webhookPayloadSchema>;
|
||||
payload: WebhookPayload;
|
||||
}) => {
|
||||
const callbackUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/api/webhooks/callback?webhookId=${webhook.pId}`;
|
||||
// TODO: add proper domain like app.papermark.dev in dev
|
||||
const callbackUrl = new URL(
|
||||
`https://app.papermark.dev/api/webhooks/callback`,
|
||||
);
|
||||
callbackUrl.searchParams.append("webhookId", webhook.pId);
|
||||
callbackUrl.searchParams.append("eventId", payload.id);
|
||||
callbackUrl.searchParams.append("event", payload.event);
|
||||
|
||||
const signature = await createWebhookSignature(webhook.secret, payload);
|
||||
|
||||
const response = await qstash.publishJSON({
|
||||
@@ -49,8 +54,8 @@ const publishWebhookEventToQStash = async ({
|
||||
"X-Papermark-Signature": signature,
|
||||
"Upstash-Hide-Headers": "true",
|
||||
},
|
||||
callback: callbackUrl,
|
||||
failureCallback: callbackUrl,
|
||||
callback: callbackUrl.href,
|
||||
failureCallback: callbackUrl.href,
|
||||
});
|
||||
|
||||
if (!response.messageId) {
|
||||
|
||||
86
lib/webhook/triggers/document-created.ts
Normal file
86
lib/webhook/triggers/document-created.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { getFeatureFlags } from "@/lib/featureFlags";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { log } from "@/lib/utils";
|
||||
import { sendWebhooks } from "@/lib/webhook/send-webhooks";
|
||||
|
||||
export async function sendDocumentCreatedWebhook({
|
||||
teamId,
|
||||
data,
|
||||
}: {
|
||||
teamId: string;
|
||||
data: any;
|
||||
}) {
|
||||
try {
|
||||
const { document_id: documentId } = data;
|
||||
|
||||
if (!documentId || !teamId) {
|
||||
throw new Error("Missing required parameters");
|
||||
}
|
||||
|
||||
const features = await getFeatureFlags({ teamId });
|
||||
if (!features.webhooks) {
|
||||
// webhooks are not enabled for this team
|
||||
return;
|
||||
}
|
||||
|
||||
// Get webhooks for team
|
||||
const webhooks = await prisma.webhook.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
triggers: {
|
||||
array_contains: ["document.created"],
|
||||
},
|
||||
},
|
||||
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 document information
|
||||
const document = await prisma.document.findUnique({
|
||||
where: { id: documentId, teamId },
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
throw new Error("Document not found");
|
||||
}
|
||||
|
||||
// Prepare document data for webhook
|
||||
const documentData = {
|
||||
id: document.id,
|
||||
name: document.name,
|
||||
contentType: document.contentType,
|
||||
teamId: document.teamId,
|
||||
createdAt: document.createdAt.toISOString(),
|
||||
};
|
||||
|
||||
// Prepare webhook payload
|
||||
const webhookData = {
|
||||
document: documentData,
|
||||
};
|
||||
|
||||
// Send webhooks
|
||||
if (webhooks.length > 0) {
|
||||
await sendWebhooks({
|
||||
webhooks,
|
||||
trigger: "document.created",
|
||||
data: webhookData,
|
||||
});
|
||||
}
|
||||
return;
|
||||
} catch (error) {
|
||||
log({
|
||||
message: `Error sending webhooks for document created: ${error}`,
|
||||
type: "error",
|
||||
mention: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
156
lib/webhook/triggers/link-created.ts
Normal file
156
lib/webhook/triggers/link-created.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { getFeatureFlags } from "@/lib/featureFlags";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { log } from "@/lib/utils";
|
||||
import { sendWebhooks } from "@/lib/webhook/send-webhooks";
|
||||
|
||||
export async function sendLinkCreatedWebhook({
|
||||
teamId,
|
||||
data,
|
||||
}: {
|
||||
teamId: string;
|
||||
data: any;
|
||||
}) {
|
||||
try {
|
||||
const {
|
||||
link_id: linkId,
|
||||
document_id: documentId,
|
||||
dataroom_id: dataroomId,
|
||||
} = data;
|
||||
|
||||
if (!linkId || !teamId) {
|
||||
throw new Error("Missing required parameters");
|
||||
}
|
||||
|
||||
const features = await getFeatureFlags({ teamId });
|
||||
if (!features.webhooks) {
|
||||
// webhooks are not enabled for this team
|
||||
return;
|
||||
}
|
||||
|
||||
// Get webhooks for team
|
||||
const webhooks = await prisma.webhook.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
triggers: {
|
||||
array_contains: ["link.created"],
|
||||
},
|
||||
},
|
||||
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://www.papermark.com/view/${link.id}`,
|
||||
domain:
|
||||
link.domainId && link.domainSlug ? link.domainSlug : "papermark.com",
|
||||
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,
|
||||
linkType: link.linkType,
|
||||
teamId: teamId,
|
||||
createdAt: link.createdAt.toISOString(),
|
||||
updatedAt: link.updatedAt.toISOString(),
|
||||
};
|
||||
|
||||
// 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 = {
|
||||
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.created",
|
||||
data: webhookData,
|
||||
});
|
||||
}
|
||||
return;
|
||||
} catch (error) {
|
||||
log({
|
||||
message: `Error sending webhooks for link created: ${error}`,
|
||||
type: "error",
|
||||
mention: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,20 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { webhookPayloadSchema } from "../zod/schemas/webhooks";
|
||||
import {
|
||||
dataroomCreatedWebhookSchema,
|
||||
documentCreatedWebhookSchema,
|
||||
linkCreatedWebhookSchema,
|
||||
webhookPayloadSchema,
|
||||
} from "../zod/schemas/webhooks";
|
||||
import { WEBHOOK_TRIGGER_DESCRIPTIONS } from "./constants";
|
||||
|
||||
export type WebhookTrigger = keyof typeof WEBHOOK_TRIGGER_DESCRIPTIONS;
|
||||
|
||||
export type WebhookPayload = z.infer<typeof webhookPayloadSchema>;
|
||||
export type WebhookPayload =
|
||||
| z.infer<typeof webhookPayloadSchema>
|
||||
| z.infer<typeof linkCreatedWebhookSchema>
|
||||
| z.infer<typeof documentCreatedWebhookSchema>
|
||||
| z.infer<typeof dataroomCreatedWebhookSchema>;
|
||||
|
||||
// TODO: only show the link.viewed data props for now
|
||||
// TODO: only show the link.viewed, link.created, document.created data props for now
|
||||
export type EventDataProps = WebhookPayload["data"];
|
||||
|
||||
@@ -74,30 +74,85 @@ const linkEventSchema = z.object({
|
||||
updatedAt: z.string().datetime(),
|
||||
});
|
||||
|
||||
// Complete webhook payload schema
|
||||
export const webhookPayloadSchema = baseEventSchema.extend({
|
||||
// Document Event schema
|
||||
const documentEventSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
contentType: z.string().nullable(),
|
||||
teamId: z.string(),
|
||||
createdAt: z.string().datetime(),
|
||||
});
|
||||
|
||||
// Dataroom Event schema
|
||||
const dataroomEventSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
teamId: z.string(),
|
||||
createdAt: z.string().datetime(),
|
||||
});
|
||||
|
||||
// link.created
|
||||
export const linkCreatedWebhookSchema = z.object({
|
||||
id: z.string().startsWith("evt_"),
|
||||
event: z.literal("link.created"),
|
||||
createdAt: z.string().datetime(),
|
||||
data: z.object({
|
||||
view: viewEventSchema,
|
||||
link: linkEventSchema,
|
||||
document: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
contentType: z.string().nullable(),
|
||||
teamId: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
dataroom: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
teamId: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
document: documentEventSchema.optional(),
|
||||
dataroom: dataroomEventSchema.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
// document.created
|
||||
export const documentCreatedWebhookSchema = z.object({
|
||||
id: z.string().startsWith("evt_"),
|
||||
event: z.literal("document.created"),
|
||||
createdAt: z.string().datetime(),
|
||||
data: z.object({
|
||||
document: documentEventSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
// dataroom.created
|
||||
export const dataroomCreatedWebhookSchema = z.object({
|
||||
id: z.string().startsWith("evt_"),
|
||||
event: z.literal("dataroom.created"),
|
||||
createdAt: z.string().datetime(),
|
||||
data: z.object({
|
||||
dataroom: dataroomEventSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
// link.viewed
|
||||
export const linkViewedWebhookSchema = z.object({
|
||||
id: z.string().startsWith("evt_"),
|
||||
event: z.literal("link.viewed"),
|
||||
createdAt: z.string().datetime(),
|
||||
data: z.object({
|
||||
view: viewEventSchema,
|
||||
link: linkEventSchema,
|
||||
document: documentEventSchema.optional(),
|
||||
dataroom: dataroomEventSchema.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const webhookPayloadSchema = z.discriminatedUnion("event", [
|
||||
linkCreatedWebhookSchema,
|
||||
documentCreatedWebhookSchema,
|
||||
dataroomCreatedWebhookSchema,
|
||||
linkViewedWebhookSchema,
|
||||
]);
|
||||
|
||||
export type WebhookPayload = z.infer<typeof webhookPayloadSchema>;
|
||||
export type LinkCreatedWebhookPayload = z.infer<
|
||||
typeof linkCreatedWebhookSchema
|
||||
>;
|
||||
export type DocumentCreatedWebhookPayload = z.infer<
|
||||
typeof documentCreatedWebhookSchema
|
||||
>;
|
||||
export type DataroomCreatedWebhookPayload = z.infer<
|
||||
typeof dataroomCreatedWebhookSchema
|
||||
>;
|
||||
|
||||
// Schema of response sent to the webhook callback URL by QStash
|
||||
export const webhookCallbackSchema = z.object({
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import { errorhandler } from "@/lib/errorHandler";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
import { sendLinkCreatedWebhook } from "@/lib/webhook/triggers/link-created";
|
||||
|
||||
import { authOptions } from "../../auth/[...nextauth]";
|
||||
|
||||
export const config = {
|
||||
// in order to enable `waitUntil` function
|
||||
supportsResponseStreaming: true,
|
||||
};
|
||||
|
||||
export default async function handle(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
@@ -69,6 +76,17 @@ export default async function handle(
|
||||
views: [],
|
||||
};
|
||||
|
||||
waitUntil(
|
||||
sendLinkCreatedWebhook({
|
||||
teamId,
|
||||
data: {
|
||||
link_id: newLink.id,
|
||||
document_id: newLink.documentId,
|
||||
dataroom_id: newLink.dataroomId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return res.status(201).json(linkWithView);
|
||||
} catch (error) {
|
||||
errorhandler(error, res);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { LinkAudienceType } from "@prisma/client";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import { errorhandler } from "@/lib/errorHandler";
|
||||
@@ -10,9 +11,15 @@ import {
|
||||
decryptEncrpytedPassword,
|
||||
generateEncrpytedPassword,
|
||||
} from "@/lib/utils";
|
||||
import { sendLinkCreatedWebhook } from "@/lib/webhook/triggers/link-created";
|
||||
|
||||
import { authOptions } from "../auth/[...nextauth]";
|
||||
|
||||
export const config = {
|
||||
// in order to enable `waitUntil` function
|
||||
supportsResponseStreaming: true,
|
||||
};
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
@@ -194,6 +201,17 @@ export default async function handler(
|
||||
return res.status(404).json({ error: "Link not found" });
|
||||
}
|
||||
|
||||
waitUntil(
|
||||
sendLinkCreatedWebhook({
|
||||
teamId,
|
||||
data: {
|
||||
link_id: link.id,
|
||||
document_id: link.documentId,
|
||||
dataroom_id: link.dataroomId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Decrypt the password for the new link
|
||||
if (linkWithView.password !== null) {
|
||||
linkWithView.password = decryptEncrpytedPassword(linkWithView.password);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { DocumentStorageType, Prisma } from "@prisma/client";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { parsePageId } from "notion-utils";
|
||||
|
||||
@@ -19,6 +20,12 @@ import { convertPdfToImageRoute } from "@/lib/trigger/pdf-to-image-route";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
import { getExtension, log } from "@/lib/utils";
|
||||
import { conversionQueue } from "@/lib/utils/trigger-utils";
|
||||
import { sendDocumentCreatedWebhook } from "@/lib/webhook/triggers/document-created";
|
||||
|
||||
export const config = {
|
||||
// in order to enable `waitUntil` function
|
||||
supportsResponseStreaming: true,
|
||||
};
|
||||
|
||||
export default async function handle(
|
||||
req: NextApiRequest,
|
||||
@@ -348,6 +355,15 @@ export default async function handle(
|
||||
);
|
||||
}
|
||||
|
||||
waitUntil(
|
||||
sendDocumentCreatedWebhook({
|
||||
teamId,
|
||||
data: {
|
||||
document_id: document.id,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return res.status(201).json(document);
|
||||
} catch (error) {
|
||||
log({
|
||||
|
||||
@@ -9,6 +9,8 @@ import { ArrowLeft, Check, Copy, WebhookIcon } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { cn, fetcher } from "@/lib/utils";
|
||||
|
||||
import AppLayout from "@/components/layouts/app";
|
||||
import { SettingsHeader } from "@/components/settings/settings-header";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -19,8 +21,6 @@ import { Label } from "@/components/ui/label";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { WebhookEventList } from "@/components/webhooks/webhook-events";
|
||||
|
||||
import { cn, fetcher } from "@/lib/utils";
|
||||
|
||||
import { documentEvents, linkEvents, teamEvents } from "../new";
|
||||
|
||||
type WebhookFormData = {
|
||||
@@ -200,7 +200,10 @@ export default function WebhookDetail() {
|
||||
),
|
||||
}));
|
||||
}}
|
||||
disabled={true}
|
||||
disabled={
|
||||
!isEditing ||
|
||||
event.value !== "document.created"
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={event.id}>{event.label}</Label>
|
||||
</div>
|
||||
@@ -231,7 +234,9 @@ export default function WebhookDetail() {
|
||||
),
|
||||
}));
|
||||
}}
|
||||
disabled={true}
|
||||
disabled={
|
||||
!isEditing || event.value !== "link.created"
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={event.id}>{event.label}</Label>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,9 @@ import { ArrowLeft, Check } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { newId } from "@/lib/id-helper";
|
||||
import { fetcher } from "@/lib/utils";
|
||||
|
||||
import AppLayout from "@/components/layouts/app";
|
||||
import { SettingsHeader } from "@/components/settings/settings-header";
|
||||
import Copy from "@/components/shared/icons/copy";
|
||||
@@ -18,9 +21,6 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
import { newId } from "@/lib/id-helper";
|
||||
import { fetcher } from "@/lib/utils";
|
||||
|
||||
interface WebhookEvent {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -185,7 +185,7 @@ export default function NewWebhook() {
|
||||
checked={formData.triggers.includes(
|
||||
event.value,
|
||||
)}
|
||||
disabled={true}
|
||||
disabled={event.value !== "document.created"}
|
||||
onCheckedChange={(checked) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
@@ -216,7 +216,7 @@ export default function NewWebhook() {
|
||||
checked={formData.triggers.includes(
|
||||
event.value,
|
||||
)}
|
||||
disabled={true}
|
||||
disabled={event.value !== "link.created"}
|
||||
onCheckedChange={(checked) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
|
||||
Reference in New Issue
Block a user