Files
documenso/packages/trpc/server/envelope-router/use-envelope.ts
2025-11-07 14:17:52 +11:00

171 lines
4.7 KiB
TypeScript

import { EnvelopeType } from '@prisma/client';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import { authenticatedProcedure } from '../trpc';
import {
ZUseEnvelopeRequestSchema,
ZUseEnvelopeResponseSchema,
useEnvelopeMeta,
} from './use-envelope.types';
export const useEnvelopeRoute = authenticatedProcedure
.meta(useEnvelopeMeta)
.input(ZUseEnvelopeRequestSchema)
.output(ZUseEnvelopeResponseSchema)
.mutation(async ({ input, ctx }) => {
const { user, teamId } = ctx;
const { payload, files = [] } = input;
const {
envelopeId,
externalId,
recipients,
distributeDocument,
customDocumentData = [],
folderId,
prefillFields,
override,
attachments,
} = payload;
ctx.logger.info({
input: {
envelopeId,
folderId,
},
});
const limits = await getServerLimits({ userId: user.id, teamId });
if (limits.remaining.documents === 0) {
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
message: 'You have reached your document limit.',
});
}
// Verify the template exists and get envelope items
const envelope = await getEnvelopeById({
id: {
type: 'envelopeId',
id: envelopeId,
},
type: EnvelopeType.TEMPLATE,
userId: user.id,
teamId,
});
if (files.length > envelope.envelopeItems.length) {
throw new AppError(AppErrorCode.INVALID_BODY, {
message: `You cannot upload more than ${envelope.envelopeItems.length} envelope items per envelope`,
});
}
const filesToUpload = files.filter(
(file, index) =>
payload.customDocumentData &&
payload.customDocumentData.some(
(mapping) => mapping.identifier === file.name || mapping.identifier === index,
),
);
// Process uploaded files and create document data for them
const uploadedFiles = await Promise.all(
filesToUpload.map(async (file) => {
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
return {
name: file.name,
documentDataId,
};
}),
);
// Map custom document data using identifiers
const customDocumentDataMapped = customDocumentData?.map((mapping) => {
let documentDataId: string | undefined;
// Find the uploaded file by identifier
if (typeof mapping.identifier === 'string') {
documentDataId = uploadedFiles.find(
(file) => file.name === mapping.identifier,
)?.documentDataId;
}
if (typeof mapping.identifier === 'number') {
documentDataId = uploadedFiles.at(mapping.identifier)?.documentDataId;
}
if (mapping.identifier === undefined) {
documentDataId = uploadedFiles.at(0)?.documentDataId;
}
if (!documentDataId) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: `File with identifier "${mapping.identifier}" not found in uploaded files`,
});
}
// Verify that the envelopeItemId exists in the template
const envelopeItem = envelope.envelopeItems.find(
(item) => item.id === mapping.envelopeItemId,
);
if (!envelopeItem) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: `Envelope item with ID "${mapping.envelopeItemId}" not found in template`,
});
}
return {
documentDataId,
envelopeItemId: mapping.envelopeItemId,
};
});
// Create document from template
const createdEnvelope = await createDocumentFromTemplate({
id: {
type: 'envelopeId',
id: envelopeId,
},
externalId,
teamId,
userId: user.id,
recipients,
customDocumentData: customDocumentDataMapped,
requestMetadata: ctx.metadata,
folderId,
prefillFields,
override,
attachments,
});
// Distribute document if requested
if (distributeDocument) {
await sendDocument({
id: {
type: 'envelopeId',
id: createdEnvelope.id,
},
userId: user.id,
teamId,
requestMetadata: ctx.metadata,
}).catch((err) => {
console.error(err);
throw new AppError('DOCUMENT_SEND_FAILED');
});
}
return {
id: createdEnvelope.id,
};
});