feat: add envelopes (#2025)

This PR is handles the changes required to support envelopes. The new
envelope editor/signing page will be hidden during release.

The core changes here is to migrate the documents and templates model to
a centralized envelopes model.

Even though Documents and Templates are removed, from the user
perspective they will still exist as we remap envelopes to documents and
templates.
This commit is contained in:
David Nguyen
2025-10-14 21:56:36 +11:00
committed by GitHub
parent 7b17156e56
commit 7f09ba72f4
447 changed files with 33467 additions and 9622 deletions

View File

@ -1,5 +1,5 @@
import type { Document } from '@prisma/client';
import { DocumentDataType } from '@prisma/client';
import type { Envelope } from '@prisma/client';
import { DocumentDataType, EnvelopeType } from '@prisma/client';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
@ -7,24 +7,25 @@ import { jobs } from '@documenso/lib/jobs/client';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { duplicateEnvelope } from '@documenso/lib/server-only/envelope/duplicate-envelope';
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
import {
ZCreateDocumentFromDirectTemplateResponseSchema,
createDocumentFromDirectTemplate,
} from '@documenso/lib/server-only/template/create-document-from-direct-template';
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
import {
ZCreateTemplateResponseSchema,
createTemplate,
} from '@documenso/lib/server-only/template/create-template';
import { createTemplateDirectLink } from '@documenso/lib/server-only/template/create-template-direct-link';
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/delete-template-direct-link';
import { duplicateTemplate } from '@documenso/lib/server-only/template/duplicate-template';
import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
import { updateTemplate } from '@documenso/lib/server-only/template/update-template';
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { mapFieldToLegacyField } from '@documenso/lib/utils/fields';
import { mapRecipientToLegacyRecipient } from '@documenso/lib/utils/recipients';
import { mapEnvelopeToTemplateLite } from '@documenso/lib/utils/templates';
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema';
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
@ -36,6 +37,7 @@ import {
ZCreateTemplateDirectLinkRequestSchema,
ZCreateTemplateDirectLinkResponseSchema,
ZCreateTemplateMutationSchema,
ZCreateTemplateResponseSchema,
ZCreateTemplateV2RequestSchema,
ZCreateTemplateV2ResponseSchema,
ZDeleteTemplateDirectLinkRequestSchema,
@ -77,11 +79,44 @@ export const templateRouter = router({
},
});
return await findTemplates({
const result = await findTemplates({
userId: ctx.user.id,
teamId,
...input,
});
// Remapping for backwards compatibility.
return {
...result,
data: result.data.map((envelope) => {
const legacyTemplateId = mapSecondaryIdToTemplateId(envelope.secondaryId);
return {
id: legacyTemplateId,
envelopeId: envelope.id,
type: envelope.templateType,
visibility: envelope.visibility,
externalId: envelope.externalId,
title: envelope.title,
userId: envelope.userId,
teamId: envelope.teamId,
authOptions: envelope.authOptions,
createdAt: envelope.createdAt,
updatedAt: envelope.updatedAt,
publicTitle: envelope.publicTitle,
publicDescription: envelope.publicDescription,
folderId: envelope.folderId,
useLegacyFieldInsertion: envelope.useLegacyFieldInsertion,
team: envelope.team,
fields: envelope.fields.map((field) => mapFieldToLegacyField(field, envelope)),
recipients: envelope.recipients.map((recipient) =>
mapRecipientToLegacyRecipient(recipient, envelope),
),
templateMeta: envelope.documentMeta,
directLink: envelope.directLink,
};
}),
};
}),
/**
@ -109,7 +144,10 @@ export const templateRouter = router({
});
return await getTemplateById({
id: templateId,
id: {
type: 'templateId',
id: templateId,
},
userId: ctx.user.id,
teamId,
});
@ -121,7 +159,7 @@ export const templateRouter = router({
* @private
*/
createTemplate: authenticatedProcedure
// .meta({
// .meta({ // Note before releasing this to public, update the response schema to be correct.
// openapi: {
// method: 'POST',
// path: '/template/create',
@ -142,15 +180,26 @@ export const templateRouter = router({
},
});
return await createTemplate({
const envelope = await createEnvelope({
userId: ctx.user.id,
teamId,
templateDocumentDataId,
internalVersion: 1,
data: {
type: EnvelopeType.TEMPLATE,
title,
folderId,
envelopeItems: [
{
documentDataId: templateDocumentDataId,
},
],
},
requestMetadata: ctx.metadata,
});
return {
legacyTemplateId: mapSecondaryIdToTemplateId(envelope.secondaryId),
};
}),
/**
@ -197,26 +246,38 @@ export const templateRouter = router({
type: DocumentDataType.S3_PATH,
});
const createdTemplate = await createTemplate({
const createdTemplate = await createEnvelope({
userId: user.id,
teamId,
templateDocumentDataId: templateDocumentData.id,
internalVersion: 1,
data: {
type: EnvelopeType.TEMPLATE,
title,
envelopeItems: [
{
documentDataId: templateDocumentData.id,
},
],
folderId,
externalId,
externalId: externalId ?? undefined,
visibility,
globalAccessAuth,
globalActionAuth,
templateType: type,
publicTitle,
publicDescription,
type,
},
meta,
requestMetadata: ctx.metadata,
});
const legacyTemplateId = mapSecondaryIdToTemplateId(createdTemplate.secondaryId);
const fullTemplate = await getTemplateById({
id: createdTemplate.id,
id: {
type: 'templateId',
id: legacyTemplateId,
},
userId: user.id,
teamId,
});
@ -252,13 +313,22 @@ export const templateRouter = router({
},
});
return await updateTemplate({
const envelope = await updateEnvelope({
userId,
teamId,
templateId,
data,
id: {
type: 'templateId',
id: templateId,
},
data: {
...data,
templateType: data?.type, // Backwards compatibility.
},
meta,
requestMetadata: ctx.metadata,
});
return mapEnvelopeToTemplateLite(envelope);
}),
/**
@ -285,11 +355,16 @@ export const templateRouter = router({
},
});
return await duplicateTemplate({
const duplicatedEnvelope = await duplicateEnvelope({
userId: ctx.user.id,
teamId,
templateId,
id: {
type: 'templateId',
id: templateId,
},
});
return mapEnvelopeToTemplateLite(duplicatedEnvelope.envelope);
}),
/**
@ -317,7 +392,14 @@ export const templateRouter = router({
},
});
await deleteTemplate({ userId, id: templateId, teamId });
await deleteTemplate({
userId,
id: {
type: 'templateId',
id: templateId,
},
teamId,
});
return ZGenericSuccessResponse;
}),
@ -360,12 +442,25 @@ export const templateRouter = router({
throw new Error('You have reached your document limit.');
}
const document: Document = await createDocumentFromTemplate({
templateId,
// Backwards compatibility mapping since we need the envelopeItemId for the custom document data.
const customDocumentData = customDocumentDataId
? [
{
documentDataId: customDocumentDataId,
envelopeItemId: undefined,
},
]
: input.customDocumentData || [];
const envelope: Envelope = await createDocumentFromTemplate({
id: {
type: 'templateId',
id: templateId,
},
teamId,
userId: ctx.user.id,
recipients,
customDocumentDataId,
customDocumentData,
requestMetadata: ctx.metadata,
folderId,
prefillFields,
@ -373,7 +468,10 @@ export const templateRouter = router({
if (distributeDocument) {
await sendDocument({
documentId: document.id,
id: {
type: 'envelopeId',
id: envelope.id,
},
userId: ctx.user.id,
teamId,
requestMetadata: ctx.metadata,
@ -385,7 +483,10 @@ export const templateRouter = router({
}
return getDocumentWithDetailsById({
documentId: document.id,
id: {
type: 'envelopeId',
id: envelope.id,
},
userId: ctx.user.id,
teamId,
});
@ -470,7 +571,14 @@ export const templateRouter = router({
},
});
const template = await getTemplateById({ id: templateId, teamId, userId: ctx.user.id });
const template = await getTemplateById({
id: {
type: 'templateId',
id: templateId,
},
teamId,
userId: ctx.user.id,
});
const limits = await getServerLimits({ userId: ctx.user.id, teamId: template.teamId });
@ -480,7 +588,15 @@ export const templateRouter = router({
});
}
return await createTemplateDirectLink({ userId, teamId, templateId, directRecipientId });
return await createTemplateDirectLink({
userId,
teamId,
id: {
type: 'templateId',
id: templateId,
},
directRecipientId,
});
}),
/**
@ -569,7 +685,10 @@ export const templateRouter = router({
}
const template = await getTemplateById({
id: templateId,
id: {
type: 'templateId',
id: templateId,
},
teamId,
userId: user.id,
});