chore: merge main, resolve biome formatting conflicts

Merge origin/main into feat/document-file-conversion. Conflicts were
format-only (Tailwind class ordering, single-line vs multi-line) plus
two semantic merges:

- files.helpers.ts: combine main's pending-PDF download path with the
  branch's original-source-file (DOCX/PNG/JPEG) download path
- download-pdf.ts: combine main's versionToFilenameSuffix helper with
  the branch's server-provided Content-Disposition filename support
This commit is contained in:
ephraimduncan
2026-05-12 11:28:47 +00:00
1495 changed files with 22068 additions and 33465 deletions
+1 -2
View File
@@ -1,9 +1,8 @@
import { initContract } from '@ts-rest/core';
import {
ZCreateTemplateV2RequestSchema,
ZCreateTemplateV2ResponseSchema,
} from '@documenso/trpc/server/template-router/schema';
import { initContract } from '@ts-rest/core';
import {
ZAuthorizationHeadersSchema,
+202 -230
View File
@@ -1,24 +1,20 @@
import { DocumentDataType, EnvelopeType, SigningStatus } from '@prisma/client';
import { tsr } from '@ts-rest/serverless/fetch';
import { match } from 'ts-pattern';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DocumentDataType, EnvelopeType, SigningStatus } from '@prisma/client';
import { tsr } from '@ts-rest/serverless/fetch';
import { match } from 'ts-pattern';
import '@documenso/lib/constants/time-zones';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { AppError } from '@documenso/lib/errors/app-error';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import {
getEnvelopeById,
getEnvelopeWhereInput,
} from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { getEnvelopeById, getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { deleteDocumentField } from '@documenso/lib/server-only/field/delete-document-field';
import { updateEnvelopeFields } from '@documenso/lib/server-only/field/update-envelope-fields';
import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf';
@@ -42,16 +38,10 @@ import {
} from '@documenso/lib/types/field-meta';
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import {
getPresignGetUrl,
getPresignPostUrl,
} from '@documenso/lib/universal/upload/server-actions';
import { getPresignGetUrl, getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import {
mapSecondaryIdToDocumentId,
mapSecondaryIdToTemplateId,
} from '@documenso/lib/utils/envelope';
import { mapSecondaryIdToDocumentId, mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';
import { ApiContractV1 } from './contract';
@@ -527,9 +517,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}
const timezone = meta?.timezone
? TIME_ZONES.find((tz) => tz === meta?.timezone)
: DEFAULT_DOCUMENT_TIME_ZONE;
const timezone = meta?.timezone ? TIME_ZONES.find((tz) => tz === meta?.timezone) : DEFAULT_DOCUMENT_TIME_ZONE;
const isTimeZoneValid = meta?.timezone ? TIME_ZONES.includes(String(timezone)) : true;
@@ -728,64 +716,178 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
createDocumentFromTemplate: authenticatedMiddleware(
async (args, user, team, { logger, metadata }) => {
const { body, params } = args;
createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { body, params } = args;
logger.info({
input: {
templateId: params.templateId,
logger.info({
input: {
templateId: params.templateId,
},
});
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
},
};
}
const templateId = Number(params.templateId);
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
const template = await getEnvelopeById({
id: {
type: 'templateId',
id: templateId,
},
type: EnvelopeType.TEMPLATE,
userId: user.id,
teamId: team.id,
});
if (template.envelopeItems.length !== 1) {
throw new Error('API V1 does not support templates with multiple documents');
}
// V1 API request schema uses indices for recipients
// So we remap the recipients to attach the IDs
const mappedRecipients = body.recipients.map((recipient, index) => {
const existingRecipient = template.recipients.at(index);
if (!existingRecipient) {
throw new Error('Recipient not found.');
}
return {
id: existingRecipient.id,
name: recipient.name,
email: recipient.email,
signingOrder: recipient.signingOrder,
role: recipient.role, // You probably shouldn't be able to change the role.
};
});
const createdEnvelope = await createDocumentFromTemplate({
id: {
type: 'templateId',
id: templateId,
},
externalId: body.externalId || null,
userId: user.id,
teamId: team.id,
recipients: mappedRecipients,
override: {
...body.meta,
title: body.title,
},
attachments: body.attachments,
formValues: body.formValues,
requestMetadata: metadata,
});
const envelopeItems = await prisma.envelopeItem.findMany({
where: {
envelopeId: createdEnvelope.id,
},
include: {
documentData: true,
},
});
const firstEnvelopeItemData = envelopeItems[0].documentData;
if (!firstEnvelopeItemData) {
throw new Error('Document data not found.');
}
if (body.formValues) {
const pdf = await getFileServerSide(firstEnvelopeItemData);
const prefilled = await insertFormValuesInPdf({
pdf: Buffer.from(pdf),
formValues: body.formValues,
});
const newDocumentData = await putNormalizedPdfFileServerSide({
file: {
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
},
});
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
},
};
}
const templateId = Number(params.templateId);
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
const template = await getEnvelopeById({
id: {
type: 'templateId',
id: templateId,
await prisma.envelopeItem.update({
where: {
id: firstEnvelopeItemData.id,
},
data: {
title: body.title || fileName,
documentDataId: newDocumentData.id,
},
type: EnvelopeType.TEMPLATE,
userId: user.id,
teamId: team.id,
});
}
if (template.envelopeItems.length !== 1) {
throw new Error('API V1 does not support templates with multiple documents');
}
if (body.authOptions || body.formValues) {
await prisma.envelope.update({
where: {
id: createdEnvelope.id,
},
data: {
formValues: body.formValues,
authOptions: body.authOptions,
},
});
}
// V1 API request schema uses indices for recipients
// So we remap the recipients to attach the IDs
const mappedRecipients = body.recipients.map((recipient, index) => {
const existingRecipient = template.recipients.at(index);
if (!existingRecipient) {
throw new Error('Recipient not found.');
}
return {
id: existingRecipient.id,
return {
status: 200,
body: {
documentId: mapSecondaryIdToDocumentId(createdEnvelope.secondaryId),
recipients: createdEnvelope.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
role: recipient.role, // You probably shouldn't be able to change the role.
};
});
const createdEnvelope = await createDocumentFromTemplate({
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
}),
generateDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { body, params } = args;
logger.info({
input: {
templateId: params.templateId,
},
});
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
},
};
}
const templateId = Number(params.templateId);
let envelope: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
try {
envelope = await createDocumentFromTemplate({
id: {
type: 'templateId',
id: templateId,
@@ -793,167 +895,49 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
externalId: body.externalId || null,
userId: user.id,
teamId: team.id,
recipients: mappedRecipients,
recipients: body.recipients,
prefillFields: body.prefillFields,
folderId: body.folderId,
override: {
...body.meta,
title: body.title,
...body.meta,
},
attachments: body.attachments,
formValues: body.formValues,
requestMetadata: metadata,
});
} catch (err) {
return AppError.toRestAPIError(err);
}
const envelopeItems = await prisma.envelopeItem.findMany({
if (body.authOptions) {
await prisma.envelope.update({
where: {
envelopeId: createdEnvelope.id,
id: envelope.id,
},
include: {
documentData: true,
data: {
authOptions: body.authOptions,
},
});
}
const firstEnvelopeItemData = envelopeItems[0].documentData;
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
if (!firstEnvelopeItemData) {
throw new Error('Document data not found.');
}
if (body.formValues) {
const pdf = await getFileServerSide(firstEnvelopeItemData);
const prefilled = await insertFormValuesInPdf({
pdf: Buffer.from(pdf),
formValues: body.formValues,
});
const newDocumentData = await putNormalizedPdfFileServerSide({
file: {
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
},
});
await prisma.envelopeItem.update({
where: {
id: firstEnvelopeItemData.id,
},
data: {
title: body.title || fileName,
documentDataId: newDocumentData.id,
},
});
}
if (body.authOptions || body.formValues) {
await prisma.envelope.update({
where: {
id: createdEnvelope.id,
},
data: {
formValues: body.formValues,
authOptions: body.authOptions,
},
});
}
return {
status: 200,
body: {
documentId: mapSecondaryIdToDocumentId(createdEnvelope.secondaryId),
recipients: createdEnvelope.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
},
),
generateDocumentFromTemplate: authenticatedMiddleware(
async (args, user, team, { logger, metadata }) => {
const { body, params } = args;
logger.info({
input: {
templateId: params.templateId,
},
});
const { remaining } = await getServerLimits({ userId: user.id, teamId: team.id });
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
},
};
}
const templateId = Number(params.templateId);
let envelope: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
try {
envelope = await createDocumentFromTemplate({
id: {
type: 'templateId',
id: templateId,
},
externalId: body.externalId || null,
userId: user.id,
teamId: team.id,
recipients: body.recipients,
prefillFields: body.prefillFields,
folderId: body.folderId,
override: {
title: body.title,
...body.meta,
},
formValues: body.formValues,
requestMetadata: metadata,
});
} catch (err) {
return AppError.toRestAPIError(err);
}
if (body.authOptions) {
await prisma.envelope.update({
where: {
id: envelope.id,
},
data: {
authOptions: body.authOptions,
},
});
}
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
return {
status: 200,
body: {
documentId: legacyDocumentId,
recipients: envelope.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
},
),
return {
status: 200,
body: {
documentId: legacyDocumentId,
recipients: envelope.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
}),
sendDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId } = args.params;
@@ -1376,16 +1360,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
const createdFields = await prisma.$transaction(async (tx) => {
return Promise.all(
fields.map(async (fieldData) => {
const {
recipientId,
type,
pageNumber,
pageWidth,
pageHeight,
pageX,
pageY,
fieldMeta,
} = fieldData;
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } = fieldData;
if (pageNumber <= 0) {
throw new Error('Invalid page number');
@@ -1406,9 +1381,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
throw new Error('Recipient has already signed the document');
}
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(
type,
);
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(type);
if (advancedField && !fieldMeta) {
throw new Error(
@@ -1512,8 +1485,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
updateField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId, fieldId } = args.params;
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } =
args.body;
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } = args.body;
logger.info({
input: {
+3 -4
View File
@@ -1,7 +1,3 @@
import type { Team, User } from '@prisma/client';
import type { TsRestRequest } from '@ts-rest/serverless';
import type { Logger } from 'pino';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
import type { BaseApiLog, RootApiLog } from '@documenso/lib/types/api-logs';
@@ -9,6 +5,9 @@ import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-reques
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { nanoid } from '@documenso/lib/universal/id';
import { logger } from '@documenso/lib/utils/logger';
import type { Team, User } from '@prisma/client';
import type { TsRestRequest } from '@ts-rest/serverless';
import type { Logger } from 'pino';
type B = {
// appRoute: any;
+1 -2
View File
@@ -1,6 +1,5 @@
import { generateOpenApi } from '@ts-rest/open-api';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { generateOpenApi } from '@ts-rest/open-api';
import { ApiContractV1 } from './contract';
+21 -40
View File
@@ -1,17 +1,4 @@
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
import {
DocumentDataType,
DocumentDistributionMethod,
DocumentSigningOrder,
FieldType,
ReadStatus,
RecipientRole,
SendStatus,
SigningStatus,
} from '@prisma/client';
import { TemplateType } from '@prisma/client';
import { z } from 'zod';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
@@ -25,6 +12,18 @@ import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-emai
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
import { ZFieldMetaPrefillFieldsSchema, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { zEmail } from '@documenso/lib/utils/zod';
import {
DocumentDataType,
DocumentDistributionMethod,
DocumentSigningOrder,
FieldType,
ReadStatus,
RecipientRole,
SendStatus,
SigningStatus,
TemplateType,
} from '@prisma/client';
import { z } from 'zod';
extendZodWithOpenApi(z);
@@ -36,10 +35,7 @@ export const ZNoBodyMutationSchema = null;
export const ZGetDocumentsQuerySchema = z.object({
page: z.coerce.number().min(1).optional().default(1),
perPage: z.coerce.number().min(1).optional().default(10),
folderId: z
.string()
.describe('Filter documents by folder ID. When omitted, returns root documents.')
.optional(),
folderId: z.string().describe('Filter documents by folder ID. When omitted, returns root documents.').optional(),
});
export type TGetDocumentsQuerySchema = z.infer<typeof ZGetDocumentsQuerySchema>;
@@ -84,9 +80,7 @@ export const ZSuccessfulGetDocumentResponseSchema = ZSuccessfulDocumentResponseS
),
});
export type TSuccessfulGetDocumentResponseSchema = z.infer<
typeof ZSuccessfulGetDocumentResponseSchema
>;
export type TSuccessfulGetDocumentResponseSchema = z.infer<typeof ZSuccessfulGetDocumentResponseSchema>;
export type TSuccessfulDocumentResponseSchema = z.infer<typeof ZSuccessfulDocumentResponseSchema>;
@@ -109,9 +103,7 @@ export const ZResendDocumentForSigningMutationSchema = z.object({
recipients: z.array(z.number()),
});
export type TResendDocumentForSigningMutationSchema = z.infer<
typeof ZResendDocumentForSigningMutationSchema
>;
export type TResendDocumentForSigningMutationSchema = z.infer<typeof ZResendDocumentForSigningMutationSchema>;
export const ZSuccessfulResendDocumentResponseSchema = z.object({
message: z.string(),
@@ -161,16 +153,14 @@ export const ZCreateDocumentMutationSchema = z.object({
subject: z.string(),
message: z.string(),
timezone: z.string().default(DEFAULT_DOCUMENT_TIME_ZONE).openapi({
description:
'The timezone of the date. Must be one of the options listed in the list below.',
description: 'The timezone of the date. Must be one of the options listed in the list below.',
enum: TIME_ZONES,
}),
dateFormat: z
.string()
.default(DEFAULT_DOCUMENT_DATE_FORMAT)
.openapi({
description:
'The format of the date. Must be one of the options listed in the list below.',
description: 'The format of the date. Must be one of the options listed in the list below.',
enum: DATE_FORMATS.map((format) => format.value),
}),
redirectUrl: z.string(),
@@ -235,9 +225,7 @@ export const ZCreateDocumentMutationResponseSchema = z.object({
),
});
export type TCreateDocumentMutationResponseSchema = z.infer<
typeof ZCreateDocumentMutationResponseSchema
>;
export type TCreateDocumentMutationResponseSchema = z.infer<typeof ZCreateDocumentMutationResponseSchema>;
export const ZCreateDocumentFromTemplateMutationSchema = z.object({
title: z.string().min(1),
@@ -289,9 +277,7 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
.optional(),
});
export type TCreateDocumentFromTemplateMutationSchema = z.infer<
typeof ZCreateDocumentFromTemplateMutationSchema
>;
export type TCreateDocumentFromTemplateMutationSchema = z.infer<typeof ZCreateDocumentFromTemplateMutationSchema>;
export const ZCreateDocumentFromTemplateMutationResponseSchema = z.object({
documentId: z.number(),
@@ -376,9 +362,7 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
prefillFields: z.array(ZFieldMetaPrefillFieldsSchema).optional(),
});
export type TGenerateDocumentFromTemplateMutationSchema = z.infer<
typeof ZGenerateDocumentFromTemplateMutationSchema
>;
export type TGenerateDocumentFromTemplateMutationSchema = z.infer<typeof ZGenerateDocumentFromTemplateMutationSchema>;
export const ZGenerateDocumentFromTemplateMutationResponseSchema = z.object({
documentId: z.number(),
@@ -469,10 +453,7 @@ const ZCreateFieldSchema = z.object({
fieldMeta: ZFieldMetaSchema.openapi({}),
});
export const ZCreateFieldMutationSchema = z.union([
ZCreateFieldSchema,
z.array(ZCreateFieldSchema).min(1),
]);
export const ZCreateFieldMutationSchema = z.union([ZCreateFieldSchema, z.array(ZCreateFieldSchema).min(1)]);
export type TCreateFieldMutationSchema = z.infer<typeof ZCreateFieldMutationSchema>;