mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 01:01:49 +10:00
Merge branch 'main' into feat/document-table-filters
This commit is contained in:
@ -16,6 +16,7 @@ import { prefixedId } from '../../universal/id';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
@ -58,8 +59,10 @@ export const createDocument = async ({
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
visibility: true,
|
||||
|
||||
@ -54,14 +54,7 @@ export const resendDocument = async ({
|
||||
const document = await prisma.document.findUnique({
|
||||
where: documentWhereInput,
|
||||
include: {
|
||||
recipients: {
|
||||
where: {
|
||||
id: {
|
||||
in: recipients,
|
||||
},
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
},
|
||||
recipients: true,
|
||||
documentMeta: true,
|
||||
team: {
|
||||
select: {
|
||||
@ -90,6 +83,11 @@ export const resendDocument = async ({
|
||||
throw new Error('Can not send completed document');
|
||||
}
|
||||
|
||||
const recipientsToRemind = document.recipients.filter(
|
||||
(recipient) =>
|
||||
recipients.includes(recipient.id) && recipient.signingStatus === SigningStatus.NOT_SIGNED,
|
||||
);
|
||||
|
||||
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
document.documentMeta,
|
||||
).recipientSigningRequest;
|
||||
@ -106,7 +104,7 @@ export const resendDocument = async ({
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
document.recipients.map(async (recipient) => {
|
||||
recipientsToRemind.map(async (recipient) => {
|
||||
if (recipient.role === RecipientRole.CC) {
|
||||
return;
|
||||
}
|
||||
@ -26,7 +26,6 @@ export const deleteField = async ({
|
||||
id: fieldId,
|
||||
document: {
|
||||
id: documentId,
|
||||
userId,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
},
|
||||
|
||||
@ -48,7 +48,6 @@ export const updateField = async ({
|
||||
id: fieldId,
|
||||
document: {
|
||||
id: documentId,
|
||||
userId,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
},
|
||||
|
||||
@ -4,6 +4,7 @@ import { match } from 'ts-pattern';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export interface DeleteFolderOptions {
|
||||
@ -18,8 +19,10 @@ export const deleteFolder = async ({ userId, teamId, folderId }: DeleteFolderOpt
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
include: {
|
||||
documents: true,
|
||||
|
||||
@ -2,6 +2,8 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface MoveFolderOptions {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
@ -15,8 +17,10 @@ export const moveFolder = async ({ userId, teamId, folderId, parentId }: MoveFol
|
||||
const folder = await tx.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface MoveTemplateToFolderOptions {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
@ -15,45 +17,47 @@ export const moveTemplateToFolder = async ({
|
||||
templateId,
|
||||
folderId,
|
||||
}: MoveTemplateToFolderOptions) => {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const template = await tx.template.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
userId,
|
||||
const template = await prisma.template.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
},
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (folderId !== null) {
|
||||
const folder = await tx.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
type: FolderType.TEMPLATE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!folder) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return await tx.template.update({
|
||||
if (folderId !== null) {
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
},
|
||||
data: {
|
||||
folderId,
|
||||
id: folderId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type: FolderType.TEMPLATE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!folder) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return await prisma.template.update({
|
||||
where: {
|
||||
id: templateId,
|
||||
},
|
||||
data: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface PinFolderOptions {
|
||||
userId: number;
|
||||
@ -14,8 +15,10 @@ export const pinFolder = async ({ userId, teamId, folderId, type }: PinFolderOpt
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface UnpinFolderOptions {
|
||||
userId: number;
|
||||
@ -14,8 +15,10 @@ export const unpinFolder = async ({ userId, teamId, folderId, type }: UnpinFolde
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import { DocumentVisibility } from '@documenso/prisma/generated/types';
|
||||
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
import { FolderType } from '../../types/folder-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface UpdateFolderOptions {
|
||||
userId: number;
|
||||
@ -25,8 +26,10 @@ export const updateFolder = async ({
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type CreateTeamBillingPortalOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const createTeamBillingPortal = async ({
|
||||
userId,
|
||||
teamId,
|
||||
}: CreateTeamBillingPortalOptions) => {
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new Error('Billing is not enabled');
|
||||
}
|
||||
|
||||
const team = await prisma.team.findFirstOrThrow({
|
||||
where: {
|
||||
id: teamId,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
role: {
|
||||
in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_BILLING'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
subscription: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!team.subscription) {
|
||||
throw new Error('Team has no subscription');
|
||||
}
|
||||
|
||||
if (!team.customerId) {
|
||||
throw new Error('Team has no customerId');
|
||||
}
|
||||
|
||||
return getPortalSession({
|
||||
customerId: team.customerId,
|
||||
});
|
||||
};
|
||||
@ -240,35 +240,79 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
}));
|
||||
|
||||
const selected: string[] = fromCheckboxValue(field.customText);
|
||||
const direction = meta.data.direction ?? 'vertical';
|
||||
|
||||
const topPadding = 12;
|
||||
const leftCheckboxPadding = 8;
|
||||
const leftCheckboxLabelPadding = 12;
|
||||
const checkboxSpaceY = 13;
|
||||
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * checkboxSpaceY + topPadding;
|
||||
if (direction === 'horizontal') {
|
||||
// Horizontal layout: arrange checkboxes side by side with wrapping
|
||||
let currentX = leftCheckboxPadding;
|
||||
let currentY = topPadding;
|
||||
const maxWidth = pageWidth - fieldX - leftCheckboxPadding * 2;
|
||||
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
|
||||
if (selected.includes(item.value)) {
|
||||
checkbox.check();
|
||||
if (selected.includes(item.value)) {
|
||||
checkbox.check();
|
||||
}
|
||||
|
||||
const labelText = item.value.includes('empty-value-') ? '' : item.value;
|
||||
const labelWidth = font.widthOfTextAtSize(labelText, 12);
|
||||
const itemWidth = leftCheckboxLabelPadding + labelWidth + 16; // checkbox + padding + label + margin
|
||||
|
||||
// Check if item fits on current line, if not wrap to next line
|
||||
if (currentX + itemWidth > maxWidth && index > 0) {
|
||||
currentX = leftCheckboxPadding;
|
||||
currentY += checkboxSpaceY;
|
||||
}
|
||||
|
||||
page.drawText(labelText, {
|
||||
x: fieldX + currentX + leftCheckboxLabelPadding,
|
||||
y: pageHeight - (fieldY + currentY),
|
||||
size: 12,
|
||||
font,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
|
||||
checkbox.addToPage(page, {
|
||||
x: fieldX + currentX,
|
||||
y: pageHeight - (fieldY + currentY),
|
||||
height: 8,
|
||||
width: 8,
|
||||
});
|
||||
|
||||
currentX += itemWidth;
|
||||
}
|
||||
} else {
|
||||
// Vertical layout: original behavior
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * checkboxSpaceY + topPadding;
|
||||
|
||||
page.drawText(item.value.includes('empty-value-') ? '' : item.value, {
|
||||
x: fieldX + leftCheckboxPadding + leftCheckboxLabelPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
size: 12,
|
||||
font,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
|
||||
checkbox.addToPage(page, {
|
||||
x: fieldX + leftCheckboxPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
height: 8,
|
||||
width: 8,
|
||||
});
|
||||
if (selected.includes(item.value)) {
|
||||
checkbox.check();
|
||||
}
|
||||
|
||||
page.drawText(item.value.includes('empty-value-') ? '' : item.value, {
|
||||
x: fieldX + leftCheckboxPadding + leftCheckboxLabelPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
size: 12,
|
||||
font,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
|
||||
checkbox.addToPage(page, {
|
||||
x: fieldX + leftCheckboxPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
height: 8,
|
||||
width: 8,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.with({ type: FieldType.RADIO }, (field) => {
|
||||
|
||||
@ -228,6 +228,7 @@ const getUpdatedFieldMeta = (field: Field, prefillField?: TFieldMetaPrefillField
|
||||
type: 'checkbox',
|
||||
label: field.label,
|
||||
values: newValues,
|
||||
direction: checkboxMeta.direction ?? 'vertical',
|
||||
};
|
||||
|
||||
return meta;
|
||||
|
||||
@ -1,16 +1,31 @@
|
||||
import type { DocumentVisibility, Template, TemplateMeta } from '@prisma/client';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TemplateSchema } from '@documenso/prisma/generated/zod/modelSchema//TemplateSchema';
|
||||
import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||
import { createDocumentAuthOptions } from '../../utils/document-auth';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
|
||||
export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
|
||||
export type CreateTemplateOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
templateDocumentDataId: string;
|
||||
data: {
|
||||
title: string;
|
||||
folderId?: string;
|
||||
externalId?: string | null;
|
||||
visibility?: DocumentVisibility;
|
||||
globalAccessAuth?: TDocumentAccessAuthTypes[];
|
||||
globalActionAuth?: TDocumentActionAuthTypes[];
|
||||
publicTitle?: string;
|
||||
publicDescription?: string;
|
||||
type?: Template['type'];
|
||||
};
|
||||
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
||||
};
|
||||
|
||||
export const ZCreateTemplateResponseSchema = TemplateSchema;
|
||||
@ -18,12 +33,14 @@ export const ZCreateTemplateResponseSchema = TemplateSchema;
|
||||
export type TCreateTemplateResponse = z.infer<typeof ZCreateTemplateResponseSchema>;
|
||||
|
||||
export const createTemplate = async ({
|
||||
title,
|
||||
userId,
|
||||
teamId,
|
||||
templateDocumentDataId,
|
||||
folderId,
|
||||
data,
|
||||
meta = {},
|
||||
}: CreateTemplateOptions) => {
|
||||
const { title, folderId } = data;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({ teamId, userId }),
|
||||
});
|
||||
@ -55,16 +72,27 @@ export const createTemplate = async ({
|
||||
return await prisma.template.create({
|
||||
data: {
|
||||
title,
|
||||
teamId,
|
||||
userId,
|
||||
templateDocumentDataId,
|
||||
teamId,
|
||||
folderId: folderId,
|
||||
folderId,
|
||||
externalId: data.externalId,
|
||||
visibility: data.visibility ?? settings.documentVisibility,
|
||||
authOptions: createDocumentAuthOptions({
|
||||
globalAccessAuth: data.globalAccessAuth || [],
|
||||
globalActionAuth: data.globalActionAuth || [],
|
||||
}),
|
||||
publicTitle: data.publicTitle,
|
||||
publicDescription: data.publicDescription,
|
||||
type: data.type,
|
||||
templateMeta: {
|
||||
create: {
|
||||
language: settings.documentLanguage,
|
||||
typedSignatureEnabled: settings.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: settings.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: settings.drawSignatureEnabled,
|
||||
...meta,
|
||||
language: meta?.language ?? settings.documentLanguage,
|
||||
typedSignatureEnabled: meta?.typedSignatureEnabled ?? settings.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: meta?.uploadSignatureEnabled ?? settings.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: meta?.drawSignatureEnabled ?? settings.drawSignatureEnabled,
|
||||
emailSettings: meta?.emailSettings || undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,6 +2,8 @@ import type { WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export type GetAllWebhooksByEventTriggerOptions = {
|
||||
event: WebhookTriggerEvents;
|
||||
userId: number;
|
||||
@ -19,22 +21,10 @@ export const getAllWebhooksByEventTrigger = async ({
|
||||
eventTriggers: {
|
||||
has: event,
|
||||
},
|
||||
team: {
|
||||
id: teamId,
|
||||
teamGroups: {
|
||||
some: {
|
||||
organisationGroup: {
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export type GetWebhookByIdOptions = {
|
||||
id: string;
|
||||
userId: number;
|
||||
@ -10,23 +13,11 @@ export const getWebhookById = async ({ id, userId, teamId }: GetWebhookByIdOptio
|
||||
return await prisma.webhook.findFirstOrThrow({
|
||||
where: {
|
||||
id,
|
||||
userId,
|
||||
team: {
|
||||
id: teamId,
|
||||
teamGroups: {
|
||||
some: {
|
||||
organisationGroup: {
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
roles: TEAM_MEMBER_ROLE_PERMISSIONS_MAP.MANAGE_TEAM,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
44
packages/lib/server-only/webhooks/trigger-test-webhook.ts
Normal file
44
packages/lib/server-only/webhooks/trigger-test-webhook.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { getWebhookById } from './get-webhook-by-id';
|
||||
import { generateSampleWebhookPayload } from './trigger/generate-sample-data';
|
||||
import { triggerWebhook } from './trigger/trigger-webhook';
|
||||
|
||||
export type TriggerTestWebhookOptions = {
|
||||
id: string;
|
||||
event: WebhookTriggerEvents;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const triggerTestWebhook = async ({
|
||||
id,
|
||||
event,
|
||||
userId,
|
||||
teamId,
|
||||
}: TriggerTestWebhookOptions) => {
|
||||
const webhook = await getWebhookById({ id, userId, teamId });
|
||||
|
||||
if (!webhook.enabled) {
|
||||
throw new Error('Webhook is disabled');
|
||||
}
|
||||
|
||||
if (!webhook.eventTriggers.includes(event)) {
|
||||
throw new Error(`Webhook does not support event: ${event}`);
|
||||
}
|
||||
|
||||
const samplePayload = generateSampleWebhookPayload(event, webhook.webhookUrl);
|
||||
|
||||
try {
|
||||
await triggerWebhook({
|
||||
event,
|
||||
data: samplePayload,
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return { success: true, message: 'Test webhook triggered successfully' };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,485 @@
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
DocumentSource,
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import type { WebhookPayload } from '../../../types/webhook-payload';
|
||||
|
||||
export const generateSampleWebhookPayload = (
|
||||
event: WebhookTriggerEvents,
|
||||
webhookUrl: string,
|
||||
): WebhookPayload => {
|
||||
const now = new Date();
|
||||
const basePayload = {
|
||||
id: 10,
|
||||
externalId: null,
|
||||
userId: 1,
|
||||
authOptions: null,
|
||||
formValues: null,
|
||||
visibility: DocumentVisibility.EVERYONE,
|
||||
title: 'documenso.pdf',
|
||||
status: DocumentStatus.DRAFT,
|
||||
documentDataId: 'hs8qz1ktr9204jn7mg6c5dxy0',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
completedAt: null,
|
||||
deletedAt: null,
|
||||
teamId: null,
|
||||
templateId: null,
|
||||
source: DocumentSource.DOCUMENT,
|
||||
documentMeta: {
|
||||
id: 'doc_meta_123',
|
||||
subject: 'Please sign this document',
|
||||
message: 'Hello, please review and sign this document.',
|
||||
timezone: 'UTC',
|
||||
password: null,
|
||||
dateFormat: 'MM/DD/YYYY',
|
||||
redirectUrl: null,
|
||||
signingOrder: DocumentSigningOrder.PARALLEL,
|
||||
allowDictateNextSigner: false,
|
||||
typedSignatureEnabled: true,
|
||||
uploadSignatureEnabled: true,
|
||||
drawSignatureEnabled: true,
|
||||
language: 'en',
|
||||
distributionMethod: DocumentDistributionMethod.EMAIL,
|
||||
emailSettings: null,
|
||||
},
|
||||
recipients: [
|
||||
{
|
||||
id: 52,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer@documenso.com',
|
||||
name: 'John Doe',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.NOT_SENT,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
id: 52,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer@documenso.com',
|
||||
name: 'John Doe',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.NOT_SENT,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_CREATED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.DRAFT,
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_SENT) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.PENDING,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
role: RecipientRole.VIEWER,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
signingOrder: 2,
|
||||
role: RecipientRole.SIGNER,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
rejectionReason: null,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_OPENED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.PENDING,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_SIGNED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
completedAt: now,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
id: 51,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
id: 51,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_COMPLETED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
completedAt: now,
|
||||
recipients: [
|
||||
{
|
||||
id: 50,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
{
|
||||
id: 51,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 2,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
id: 50,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
{
|
||||
id: 51,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 2,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_REJECTED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.PENDING,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
rejectionReason: 'I do not agree with the terms',
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.REJECTED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
rejectionReason: 'I do not agree with the terms',
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.REJECTED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_CANCELLED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
id: 7,
|
||||
externalId: null,
|
||||
userId: 3,
|
||||
status: DocumentStatus.PENDING,
|
||||
documentDataId: 'cm6exvn93006hi02ru90a265a',
|
||||
documentMeta: {
|
||||
...basePayload.documentMeta,
|
||||
id: 'cm6exvn96006ji02rqvzjvwoy',
|
||||
subject: '',
|
||||
message: '',
|
||||
timezone: 'Etc/UTC',
|
||||
dateFormat: 'yyyy-MM-dd hh:mm a',
|
||||
redirectUrl: '',
|
||||
emailSettings: {
|
||||
documentDeleted: true,
|
||||
documentPending: true,
|
||||
recipientSigned: true,
|
||||
recipientRemoved: true,
|
||||
documentCompleted: true,
|
||||
ownerDocumentCompleted: true,
|
||||
recipientSigningRequest: true,
|
||||
},
|
||||
},
|
||||
recipients: [
|
||||
{
|
||||
id: 7,
|
||||
documentId: 7,
|
||||
templateId: null,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
id: 7,
|
||||
documentId: 7,
|
||||
templateId: null,
|
||||
email: 'signer@documenso.com',
|
||||
name: 'Signer',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported event type: ${event}`);
|
||||
};
|
||||
Reference in New Issue
Block a user