diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index 62db516fa..b1438f0ce 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -2,7 +2,9 @@ import { prisma } from '@documenso/prisma'; import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; +import { WebhookTriggerEvents } from '@documenso/prisma/client'; +import { triggerWebhook } from '../../universal/trigger-webhook'; import { sealDocument } from './seal-document'; import { sendPendingEmail } from './send-pending-email'; @@ -11,13 +13,8 @@ export type CompleteDocumentWithTokenOptions = { documentId: number; }; -export const completeDocumentWithToken = async ({ - token, - documentId, -}: CompleteDocumentWithTokenOptions) => { - 'use server'; - - const document = await prisma.document.findFirstOrThrow({ +const getDocument = async ({ token, documentId }: CompleteDocumentWithTokenOptions) => { + return await prisma.document.findFirstOrThrow({ where: { id: documentId, Recipient: { @@ -34,6 +31,15 @@ export const completeDocumentWithToken = async ({ }, }, }); +}; + +export const completeDocumentWithToken = async ({ + token, + documentId, +}: CompleteDocumentWithTokenOptions) => { + 'use server'; + + const document = await getDocument({ token, documentId }); if (document.status === DocumentStatus.COMPLETED) { throw new Error(`Document ${document.id} has already been completed`); @@ -101,4 +107,11 @@ export const completeDocumentWithToken = async ({ if (documents.count > 0) { await sealDocument({ documentId: document.id }); } + + const updatedDocument = await getDocument({ token, documentId }); + + await triggerWebhook({ + eventTrigger: WebhookTriggerEvents.DOCUMENT_SIGNED, + documentData: updatedDocument, + }); }; diff --git a/packages/lib/server-only/document/create-document.ts b/packages/lib/server-only/document/create-document.ts index 93307a7b4..82dacfba7 100644 --- a/packages/lib/server-only/document/create-document.ts +++ b/packages/lib/server-only/document/create-document.ts @@ -1,6 +1,9 @@ 'use server'; import { prisma } from '@documenso/prisma'; +import { WebhookTriggerEvents } from '@documenso/prisma/client'; + +import { triggerWebhook } from '../../universal/trigger-webhook'; export type CreateDocumentOptions = { title: string; @@ -29,7 +32,7 @@ export const createDocument = async ({ }); } - return await tx.document.create({ + const createdDocument = await tx.document.create({ data: { title, documentDataId, @@ -37,5 +40,12 @@ export const createDocument = async ({ teamId, }, }); + + await triggerWebhook({ + eventTrigger: WebhookTriggerEvents.DOCUMENT_CREATED, + documentData: createdDocument, + }); + + return createdDocument; }); }; diff --git a/packages/lib/server-only/webhooks/get-all-webhooks.ts b/packages/lib/server-only/webhooks/get-all-webhooks.ts new file mode 100644 index 000000000..a6c88a086 --- /dev/null +++ b/packages/lib/server-only/webhooks/get-all-webhooks.ts @@ -0,0 +1,17 @@ +import { prisma } from '@documenso/prisma'; +import type { WebhookTriggerEvents } from '@documenso/prisma/client'; + +export type GetAllWebhooksOptions = { + eventTrigger: WebhookTriggerEvents; +}; + +export const getAllWebhooks = async ({ eventTrigger }: GetAllWebhooksOptions) => { + return prisma.webhook.findMany({ + where: { + eventTriggers: { + has: eventTrigger, + }, + enabled: true, + }, + }); +}; diff --git a/packages/lib/universal/post-webhook-payload.ts b/packages/lib/universal/post-webhook-payload.ts new file mode 100644 index 000000000..80ddea80d --- /dev/null +++ b/packages/lib/universal/post-webhook-payload.ts @@ -0,0 +1,39 @@ +import type { Document, Webhook } from '@documenso/prisma/client'; + +export type PostWebhookPayloadOptions = { + webhookData: Pick; + documentData: Document; +}; + +export const postWebhookPayload = async ({ + webhookData, + documentData, +}: PostWebhookPayloadOptions) => { + const { webhookUrl, secret } = webhookData; + + const payload = { + event: webhookData.eventTriggers.toString(), + createdAt: new Date().toISOString(), + webhookEndpoint: webhookUrl, + payload: documentData, + }; + + const response = await fetch(webhookUrl, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + 'X-Documenso-Secret': secret ?? '', + }, + }); + + if (!response.ok) { + throw new Error(`Webhook failed with the status code ${response.status}`); + } + + return { + status: response.status, + statusText: response.statusText, + message: 'Webhook sent successfully', + }; +}; diff --git a/packages/lib/universal/trigger-webhook.ts b/packages/lib/universal/trigger-webhook.ts new file mode 100644 index 000000000..025a154bc --- /dev/null +++ b/packages/lib/universal/trigger-webhook.ts @@ -0,0 +1,30 @@ +import type { Document, WebhookTriggerEvents } from '@documenso/prisma/client'; + +import { getAllWebhooks } from '../server-only/webhooks/get-all-webhooks'; +import { postWebhookPayload } from './post-webhook-payload'; + +export type TriggerWebhookOptions = { + eventTrigger: WebhookTriggerEvents; + documentData: Document; +}; + +export const triggerWebhook = async ({ eventTrigger, documentData }: TriggerWebhookOptions) => { + try { + const allWebhooks = await getAllWebhooks({ eventTrigger }); + + const webhookPromises = allWebhooks.map((webhook) => { + const { webhookUrl, secret } = webhook; + + postWebhookPayload({ + webhookData: { webhookUrl, secret, eventTriggers: [eventTrigger] }, + documentData, + }).catch((_err) => { + throw new Error(`Failed to send webhook to ${webhookUrl}`); + }); + }); + + return Promise.all(webhookPromises); + } catch (err) { + throw new Error(`Failed to trigger webhook`); + } +};