Files
documenso/packages/prisma/seed/templates.ts
T
David Nguyen ba0dead96f fix: render error messages for invalid templates (#3088)
Currently direct templates can be created without the required
signatures fields for signers.

This means that the document can be fully signed by everyone but will
ultimately fail the sealing step which leaves the document in an
unrecoverable state.
2026-07-20 16:57:38 +10:00

267 lines
6.8 KiB
TypeScript

import fs from 'node:fs';
import path from 'node:path';
import {
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
DIRECT_TEMPLATE_RECIPIENT_NAME,
} from '@documenso/lib/constants/direct-templates';
import { incrementTemplateId } from '@documenso/lib/server-only/envelope/increment-id';
import { FIELD_SIGNATURE_META_DEFAULT_VALUES } from '@documenso/lib/types/field-meta';
import { SignatureLevel } from '@documenso/lib/types/signature-level';
import { prefixedId } from '@documenso/lib/universal/id';
import { prisma } from '..';
import type { Prisma, User } from '../client';
import {
DocumentDataType,
DocumentSource,
EnvelopeType,
FieldType,
ReadStatus,
RecipientRole,
SendStatus,
SigningStatus,
} from '../client';
const examplePdf = fs.readFileSync(path.join(__dirname, '../../../assets/example.pdf')).toString('base64');
type SeedTemplateOptions = {
title?: string;
userId: number;
teamId: number;
internalVersion?: 1 | 2;
createTemplateOptions?: Partial<Prisma.EnvelopeUncheckedCreateInput>;
/**
* Only used by seedDirectTemplate. Creates a signature field for the direct
* recipient so the seeded direct template is valid. Defaults to true.
*/
createDirectRecipientSignatureField?: boolean;
};
type CreateTemplateOptions = {
key?: string | number;
createTemplateOptions?: Partial<Prisma.EnvelopeUncheckedCreateInput>;
};
export const seedBlankTemplate = async (owner: User, teamId: number, options: CreateTemplateOptions = {}) => {
const { key, createTemplateOptions = {} } = options;
const documentData = await prisma.documentData.create({
data: {
type: DocumentDataType.BYTES_64,
data: examplePdf,
initialData: examplePdf,
},
});
const templateId = await incrementTemplateId();
const documentMeta = await prisma.documentMeta.create({
data: {},
});
return await prisma.envelope.create({
data: {
id: prefixedId('envelope'),
secondaryId: templateId.formattedTemplateId,
internalVersion: 1,
signatureLevel: SignatureLevel.SES,
type: EnvelopeType.TEMPLATE,
title: `[TEST] Template ${key}`,
teamId,
envelopeItems: {
create: {
id: prefixedId('envelope_item'),
title: `[TEST] Template ${key}`,
documentDataId: documentData.id,
order: 1,
},
},
userId: owner.id,
source: DocumentSource.TEMPLATE,
documentMetaId: documentMeta.id,
...createTemplateOptions,
},
include: {
envelopeItems: {
include: {
documentData: true,
},
},
},
});
};
export const seedTemplate = async (options: SeedTemplateOptions) => {
const { title = 'Untitled', userId, teamId } = options;
const documentData = await prisma.documentData.create({
data: {
type: DocumentDataType.BYTES_64,
data: examplePdf,
initialData: examplePdf,
},
});
const templateId = await incrementTemplateId();
const documentMeta = await prisma.documentMeta.create({
data: {},
});
return await prisma.envelope.create({
data: {
id: prefixedId('envelope'),
secondaryId: templateId.formattedTemplateId,
internalVersion: options.internalVersion ?? 1,
signatureLevel: SignatureLevel.SES,
type: EnvelopeType.TEMPLATE,
title,
envelopeItems: {
create: {
id: prefixedId('envelope_item'),
title,
documentDataId: documentData.id,
order: 1,
},
},
source: DocumentSource.TEMPLATE,
documentMetaId: documentMeta.id,
userId,
teamId,
recipients: {
create: {
email: 'recipient.1@documenso.com',
name: 'Recipient 1',
token: Math.random().toString().slice(2, 7),
sendStatus: SendStatus.NOT_SENT,
signingStatus: SigningStatus.NOT_SIGNED,
readStatus: ReadStatus.NOT_OPENED,
role: RecipientRole.SIGNER,
},
},
},
include: {
envelopeItems: {
include: {
documentData: true,
},
},
recipients: true,
},
});
};
export const seedDirectTemplate = async (options: SeedTemplateOptions) => {
const { title = 'Untitled', userId, teamId } = options;
const documentData = await prisma.documentData.create({
data: {
type: DocumentDataType.BYTES_64,
data: examplePdf,
initialData: examplePdf,
},
});
const templateId = await incrementTemplateId();
const documentMeta = await prisma.documentMeta.create({
data: {},
});
const template = await prisma.envelope.create({
data: {
id: prefixedId('envelope'),
secondaryId: templateId.formattedTemplateId,
internalVersion: options.internalVersion ?? 1,
signatureLevel: SignatureLevel.SES,
type: EnvelopeType.TEMPLATE,
title,
envelopeItems: {
create: {
id: prefixedId('envelope_item'),
title,
documentDataId: documentData.id,
order: 1,
},
},
source: DocumentSource.TEMPLATE,
documentMetaId: documentMeta.id,
userId,
teamId,
recipients: {
create: {
signingOrder: 1,
email: DIRECT_TEMPLATE_RECIPIENT_EMAIL,
name: DIRECT_TEMPLATE_RECIPIENT_NAME,
token: Math.random().toString().slice(2, 7),
},
},
...options.createTemplateOptions,
},
include: {
recipients: true,
user: true,
},
});
const directTemplateRecipient = template.recipients.find(
(recipient) => recipient.email === DIRECT_TEMPLATE_RECIPIENT_EMAIL,
);
if (!directTemplateRecipient) {
throw new Error('Need to create a direct template recipient');
}
await prisma.templateDirectLink.create({
data: {
envelopeId: template.id,
enabled: true,
token: Math.random().toString(),
directTemplateRecipientId: directTemplateRecipient.id,
},
});
const { createDirectRecipientSignatureField = true } = options;
if (createDirectRecipientSignatureField) {
const envelopeItem = await prisma.envelopeItem.findFirstOrThrow({
where: { envelopeId: template.id },
});
await prisma.field.create({
data: {
envelopeId: template.id,
envelopeItemId: envelopeItem.id,
recipientId: directTemplateRecipient.id,
type: FieldType.SIGNATURE,
page: 1,
positionX: 5,
positionY: 10,
width: 20,
height: 5,
customText: '',
inserted: false,
fieldMeta: FIELD_SIGNATURE_META_DEFAULT_VALUES,
},
});
}
return await prisma.envelope.findFirstOrThrow({
where: {
id: template.id,
},
include: {
directLink: true,
fields: true,
recipients: true,
team: true,
envelopeItems: {
select: {
documentData: true,
},
},
},
});
};