Merge branch 'main' into feat/unlink-documents-deleted-org

This commit is contained in:
Lucas Smith
2025-11-25 11:45:42 +11:00
committed by GitHub
7 changed files with 99 additions and 73 deletions

View File

@ -1,3 +1,4 @@
// Todo: [Webhooks] delete file after deployment.
import { handlerTriggerWebhooks } from '@documenso/lib/server-only/webhooks/trigger/handler'; import { handlerTriggerWebhooks } from '@documenso/lib/server-only/webhooks/trigger/handler';
import type { Route } from './+types/webhook.trigger'; import type { Route } from './+types/webhook.trigger';

View File

@ -23,7 +23,7 @@ export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
</Text> </Text>
)} )}
{branding.brandingCompanyDetails ? ( {branding.brandingEnabled && branding.brandingCompanyDetails && (
<Text className="my-8 text-sm text-slate-400"> <Text className="my-8 text-sm text-slate-400">
{branding.brandingCompanyDetails.split('\n').map((line, idx) => { {branding.brandingCompanyDetails.split('\n').map((line, idx) => {
return ( return (
@ -34,7 +34,9 @@ export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
); );
})} })}
</Text> </Text>
) : ( )}
{!branding.brandingEnabled && (
<Text className="my-8 text-sm text-slate-400"> <Text className="my-8 text-sm text-slate-400">
Documenso, Inc. Documenso, Inc.
<br /> <br />

View File

@ -22,53 +22,51 @@ export const run = async ({
const { webhookUrl: url, secret } = webhook; const { webhookUrl: url, secret } = webhook;
await io.runTask('execute-webhook', async () => { const payloadData = {
const payloadData = { event,
event, payload: data,
payload: data, createdAt: new Date().toISOString(),
createdAt: new Date().toISOString(), webhookEndpoint: url,
webhookEndpoint: url, };
};
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', method: 'POST',
body: JSON.stringify(payloadData), body: JSON.stringify(payloadData),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Documenso-Secret': secret ?? '', 'X-Documenso-Secret': secret ?? '',
}, },
});
const body = await response.text();
let responseBody: Prisma.InputJsonValue | Prisma.JsonNullValueInput = Prisma.JsonNull;
try {
responseBody = JSON.parse(body);
} catch (err) {
responseBody = body;
}
await prisma.webhookCall.create({
data: {
url,
event,
status: response.ok ? WebhookCallStatus.SUCCESS : WebhookCallStatus.FAILED,
requestBody: payloadData as Prisma.InputJsonValue,
responseCode: response.status,
responseBody,
responseHeaders: Object.fromEntries(response.headers.entries()),
webhookId: webhook.id,
},
});
if (!response.ok) {
throw new Error(`Webhook execution failed with status ${response.status}`);
}
return {
success: response.ok,
status: response.status,
};
}); });
const body = await response.text();
let responseBody: Prisma.InputJsonValue | Prisma.JsonNullValueInput = Prisma.JsonNull;
try {
responseBody = JSON.parse(body);
} catch (err) {
responseBody = body;
}
await prisma.webhookCall.create({
data: {
url,
event,
status: response.ok ? WebhookCallStatus.SUCCESS : WebhookCallStatus.FAILED,
requestBody: payloadData as Prisma.InputJsonValue,
responseCode: response.status,
responseBody,
responseHeaders: Object.fromEntries(response.headers.entries()),
webhookId: webhook.id,
},
});
if (!response.ok) {
throw new Error(`Webhook execution failed with status ${response.status}`);
}
return {
success: response.ok,
status: response.status,
};
}; };

View File

@ -13,6 +13,7 @@ export type HandlerTriggerWebhooksResponse =
error: string; error: string;
}; };
// Todo: [Webhooks] delete after deployment.
export const handlerTriggerWebhooks = async (req: Request) => { export const handlerTriggerWebhooks = async (req: Request) => {
const signature = req.headers.get('x-webhook-signature'); const signature = req.headers.get('x-webhook-signature');

View File

@ -1,7 +1,6 @@
import type { WebhookTriggerEvents } from '@prisma/client'; import type { WebhookTriggerEvents } from '@prisma/client';
import { NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../../constants/app'; import { jobs } from '../../../jobs/client';
import { sign } from '../../crypto/sign';
import { getAllWebhooksByEventTrigger } from '../get-all-webhooks-by-event-trigger'; import { getAllWebhooksByEventTrigger } from '../get-all-webhooks-by-event-trigger';
export type TriggerWebhookOptions = { export type TriggerWebhookOptions = {
@ -13,35 +12,26 @@ export type TriggerWebhookOptions = {
export const triggerWebhook = async ({ event, data, userId, teamId }: TriggerWebhookOptions) => { export const triggerWebhook = async ({ event, data, userId, teamId }: TriggerWebhookOptions) => {
try { try {
const body = {
event,
data,
userId,
teamId,
};
const registeredWebhooks = await getAllWebhooksByEventTrigger({ event, userId, teamId }); const registeredWebhooks = await getAllWebhooksByEventTrigger({ event, userId, teamId });
if (registeredWebhooks.length === 0) { if (registeredWebhooks.length === 0) {
return; return;
} }
const signature = sign(body); await Promise.allSettled(
registeredWebhooks.map(async (webhook) => {
await Promise.race([ await jobs.triggerJob({
fetch(`${NEXT_PRIVATE_INTERNAL_WEBAPP_URL()}/api/webhook/trigger`, { name: 'internal.execute-webhook',
method: 'POST', payload: {
headers: { event,
'content-type': 'application/json', webhookId: webhook.id,
'x-webhook-signature': signature, data,
}, },
body: JSON.stringify(body), });
}), }),
new Promise((_, reject) => { );
setTimeout(() => reject(new Error('Request timeout')), 500);
}),
]).catch(() => null);
} catch (err) { } catch (err) {
console.error(err);
throw new Error(`Failed to trigger webhook`); throw new Error(`Failed to trigger webhook`);
} }
}; };

View File

@ -457,8 +457,10 @@ export const templateRouter = router({
recipients, recipients,
distributeDocument, distributeDocument,
customDocumentDataId, customDocumentDataId,
prefillFields,
folderId, folderId,
prefillFields,
override,
attachments,
} = input; } = input;
ctx.logger.info({ ctx.logger.info({
@ -495,6 +497,8 @@ export const templateRouter = router({
requestMetadata: ctx.metadata, requestMetadata: ctx.metadata,
folderId, folderId,
prefillFields, prefillFields,
override,
attachments,
}); });
if (distributeDocument) { if (distributeDocument) {

View File

@ -133,12 +133,42 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.', 'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
) )
.optional(), .optional(),
prefillFields: z prefillFields: z
.array(ZFieldMetaPrefillFieldsSchema) .array(ZFieldMetaPrefillFieldsSchema)
.describe( .describe(
'The fields to prefill on the document before sending it out. Useful when you want to create a document from an existing template and pre-fill the fields with specific values.', 'The fields to prefill on the document before sending it out. Useful when you want to create a document from an existing template and pre-fill the fields with specific values.',
) )
.optional(), .optional(),
override: z
.object({
title: z.string().min(1).max(255).optional(),
subject: ZDocumentMetaSubjectSchema.optional(),
message: ZDocumentMetaMessageSchema.optional(),
timezone: ZDocumentMetaTimezoneSchema.optional(),
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
allowDictateNextSigner: z.boolean().optional(),
})
.describe('Override values from the template for the created document.')
.optional(),
attachments: z
.array(
z.object({
label: z.string().min(1, 'Label is required'),
data: z.string().url('Must be a valid URL'),
type: ZEnvelopeAttachmentTypeSchema.optional().default('link'),
}),
)
.optional(),
}); });
export const ZCreateDocumentFromTemplateResponseSchema = ZDocumentSchema; export const ZCreateDocumentFromTemplateResponseSchema = ZDocumentSchema;