feat: add envelope editor

This commit is contained in:
David Nguyen
2025-10-12 23:35:54 +11:00
parent bf89bc781b
commit 0da8e7dbc6
307 changed files with 24657 additions and 3681 deletions

View File

@ -1,3 +1,4 @@
import { EnvelopeType } from '@prisma/client';
import { TRPCError } from '@trpc/server';
import { DateTime } from 'luxon';
@ -23,8 +24,9 @@ export const accessAuthRequest2FAEmailRoute = procedure
const user = ctx.user;
// Get document and recipient by token
const document = await prisma.document.findFirst({
const envelope = await prisma.envelope.findFirst({
where: {
type: EnvelopeType.DOCUMENT,
recipients: {
some: {
token,
@ -40,17 +42,17 @@ export const accessAuthRequest2FAEmailRoute = procedure
},
});
if (!document) {
if (!envelope) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Document not found',
});
}
const [recipient] = document.recipients;
const [recipient] = envelope.recipients;
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
documentAuth: document.authOptions,
documentAuth: envelope.authOptions,
recipientAuth: recipient.authOptions,
});
@ -72,7 +74,7 @@ export const accessAuthRequest2FAEmailRoute = procedure
await send2FATokenEmail({
token,
documentId: document.id,
envelopeId: envelope.id,
});
return {

View File

@ -61,6 +61,7 @@ export const createDocumentTemporaryRoute = authenticatedProcedure
userId: ctx.user.id,
teamId,
normalizePdf: false, // Not normalizing because of presigned URL.
internalVersion: 1,
data: {
type: EnvelopeType.DOCUMENT,
title,
@ -100,7 +101,11 @@ export const createDocumentTemporaryRoute = authenticatedProcedure
return {
document: {
...createdEnvelope,
documentData: firstDocumentData,
envelopeId: createdEnvelope.id,
documentData: {
...firstDocumentData,
envelopeItemId: envelopeItems[0].id,
},
id: legacyDocumentId,
fields: createdEnvelope.fields.map((field) => ({
...field,

View File

@ -1,4 +1,3 @@
import { DocumentSigningOrder } from '@prisma/client';
import { z } from 'zod';
import { ZDocumentSchema } from '@documenso/lib/types/document';
@ -6,8 +5,8 @@ import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
import { ZDocumentMetaCreateSchema } from '@documenso/lib/types/document-meta';
import {
ZFieldHeightSchema,
ZFieldPageNumberSchema,
@ -21,16 +20,6 @@ import { ZCreateRecipientSchema } from '../recipient-router/schema';
import type { TrpcRouteMeta } from '../trpc';
import {
ZDocumentExternalIdSchema,
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
ZDocumentMetaDrawSignatureEnabledSchema,
ZDocumentMetaLanguageSchema,
ZDocumentMetaMessageSchema,
ZDocumentMetaRedirectUrlSchema,
ZDocumentMetaSubjectSchema,
ZDocumentMetaTimezoneSchema,
ZDocumentMetaTypedSignatureEnabledSchema,
ZDocumentMetaUploadSignatureEnabledSchema,
ZDocumentTitleSchema,
ZDocumentVisibilitySchema,
} from './schema';
@ -80,22 +69,7 @@ export const ZCreateDocumentTemporaryRequestSchema = z.object({
)
.optional(),
meta: z
.object({
subject: ZDocumentMetaSubjectSchema.optional(),
message: ZDocumentMetaMessageSchema.optional(),
timezone: ZDocumentMetaTimezoneSchema.optional(),
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
})
.optional(),
meta: ZDocumentMetaCreateSchema.optional(),
});
export const ZCreateDocumentTemporaryResponseSchema = z.object({

View File

@ -36,6 +36,7 @@ export const createDocumentRoute = authenticatedProcedure
const document = await createEnvelope({
userId: user.id,
teamId,
internalVersion: 1,
data: {
type: EnvelopeType.DOCUMENT,
title,

View File

@ -1,6 +1,8 @@
import { z } from 'zod';
import { ZDocumentMetaTimezoneSchema, ZDocumentTitleSchema } from './schema';
import { ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { ZDocumentTitleSchema } from './schema';
// Currently not in use until we allow passthrough documents on create.
// export const createDocumentMeta: TrpcRouteMeta = {

View File

@ -25,7 +25,10 @@ export const deleteDocumentRoute = authenticatedProcedure
const userId = ctx.user.id;
await deleteDocument({
id: documentId,
id: {
type: 'documentId',
id: documentId,
},
userId,
teamId,
requestMetadata: ctx.metadata,

View File

@ -2,8 +2,6 @@ import { z } from 'zod';
import { ZDocumentLiteSchema } from '@documenso/lib/types/document';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import type { TrpcRouteMeta } from '../trpc';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
@ -12,7 +10,9 @@ import {
ZDocumentMetaRedirectUrlSchema,
ZDocumentMetaSubjectSchema,
ZDocumentMetaTimezoneSchema,
} from './schema';
} from '@documenso/lib/types/document-meta';
import type { TrpcRouteMeta } from '../trpc';
export const distributeDocumentMeta: TrpcRouteMeta = {
openapi: {

View File

@ -1,4 +1,4 @@
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
import { duplicateEnvelope } from '@documenso/lib/server-only/envelope/duplicate-envelope';
import { authenticatedProcedure } from '../trpc';
import {
@ -21,7 +21,7 @@ export const duplicateDocumentRoute = authenticatedProcedure
},
});
return await duplicateDocument({
const duplicatedEnvelope = await duplicateEnvelope({
id: {
type: 'documentId',
id: documentId,
@ -29,4 +29,9 @@ export const duplicateDocumentRoute = authenticatedProcedure
userId: user.id,
teamId,
});
return {
id: duplicatedEnvelope.id,
documentId: duplicatedEnvelope.legacyId.id,
};
});

View File

@ -16,7 +16,8 @@ export const ZDuplicateDocumentRequestSchema = z.object({
});
export const ZDuplicateDocumentResponseSchema = z.object({
documentId: z.number(),
id: z.string().describe('The envelope ID'),
documentId: z.number().describe('The legacy document ID'),
});
export type TDuplicateDocumentRequest = z.infer<typeof ZDuplicateDocumentRequestSchema>;

View File

@ -34,7 +34,6 @@ export const getDocumentByTokenRoute = authenticatedProcedure
},
});
// Todo: Envelopes
const firstDocumentData = envelope?.envelopeItems[0].documentData;
if (!envelope || !firstDocumentData) {

View File

@ -26,7 +26,10 @@ export const redistributeDocumentRoute = authenticatedProcedure
await resendDocument({
userId: ctx.user.id,
teamId,
documentId,
id: {
type: 'documentId',
id: documentId,
},
recipients,
requestMetadata: ctx.metadata,
});

View File

@ -1,10 +1,6 @@
import { DocumentDistributionMethod, DocumentVisibility } from '@prisma/client';
import { DocumentVisibility } from '@prisma/client';
import { z } from 'zod';
import { VALID_DATE_FORMAT_VALUES } from '@documenso/lib/constants/date-formats';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
/**
* Required for empty responses since we currently can't 201 requests for our openapi setup.
*
@ -34,58 +30,3 @@ export const ZDocumentExternalIdSchema = z
export const ZDocumentVisibilitySchema = z
.nativeEnum(DocumentVisibility)
.describe('The visibility of the document.');
export const ZDocumentMetaTimezoneSchema = z
.string()
.describe(
'The timezone to use for date fields and signing the document. Example Etc/UTC, Australia/Melbourne',
);
// Cooked.
// .refine((value) => TIME_ZONES.includes(value), {
// message: 'Invalid timezone. Please provide a valid timezone',
// });
export type TDocumentMetaTimezone = z.infer<typeof ZDocumentMetaTimezoneSchema>;
export const ZDocumentMetaDateFormatSchema = z
.enum(VALID_DATE_FORMAT_VALUES)
.describe('The date format to use for date fields and signing the document.');
export type TDocumentMetaDateFormat = z.infer<typeof ZDocumentMetaDateFormatSchema>;
export const ZDocumentMetaRedirectUrlSchema = z
.string()
.describe('The URL to which the recipient should be redirected after signing the document.')
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
});
export const ZDocumentMetaLanguageSchema = z
.enum(SUPPORTED_LANGUAGE_CODES)
.describe('The language to use for email communications with recipients.');
export const ZDocumentMetaSubjectSchema = z
.string()
.max(254)
.describe('The subject of the email that will be sent to the recipients.');
export const ZDocumentMetaMessageSchema = z
.string()
.max(5000)
.describe('The message of the email that will be sent to the recipients.');
export const ZDocumentMetaDistributionMethodSchema = z
.nativeEnum(DocumentDistributionMethod)
.describe('The distribution method to use when sending the document to the recipients.');
export const ZDocumentMetaTypedSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using a typed signature.');
export const ZDocumentMetaDrawSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using a draw signature.');
export const ZDocumentMetaUploadSignatureEnabledSchema = z
.boolean()
.describe('Whether to allow recipients to sign using an uploaded signature.');

View File

@ -1,5 +1,4 @@
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { authenticatedProcedure } from '../trpc';
@ -28,44 +27,22 @@ export const updateDocumentRoute = authenticatedProcedure
const userId = ctx.user.id;
if (Object.values(meta).length > 0) {
await updateDocumentMeta({
userId: ctx.user.id,
teamId,
id: {
type: 'documentId',
id: documentId,
},
subject: meta.subject,
message: meta.message,
timezone: meta.timezone,
dateFormat: meta.dateFormat,
language: meta.language,
typedSignatureEnabled: meta.typedSignatureEnabled,
uploadSignatureEnabled: meta.uploadSignatureEnabled,
drawSignatureEnabled: meta.drawSignatureEnabled,
redirectUrl: meta.redirectUrl,
distributionMethod: meta.distributionMethod,
signingOrder: meta.signingOrder,
allowDictateNextSigner: meta.allowDictateNextSigner,
emailId: meta.emailId,
emailReplyTo: meta.emailReplyTo,
emailSettings: meta.emailSettings,
requestMetadata: ctx.metadata,
});
}
const envelope = await updateDocument({
const envelope = await updateEnvelope({
userId,
teamId,
documentId,
id: {
type: 'documentId',
id: documentId,
},
data,
meta,
requestMetadata: ctx.metadata,
});
const mappedDocument = {
...envelope,
id: mapSecondaryIdToDocumentId(envelope.secondaryId),
envelopeId: envelope.id,
};
return mappedDocument;

View File

@ -1,4 +1,3 @@
import { DocumentSigningOrder } from '@prisma/client';
// import type { OpenApiMeta } from 'trpc-to-openapi';
import { z } from 'zod';
@ -7,21 +6,11 @@ import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { ZDocumentMetaUpdateSchema } from '@documenso/lib/types/document-meta';
import type { TrpcRouteMeta } from '../trpc';
import {
ZDocumentExternalIdSchema,
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
ZDocumentMetaDrawSignatureEnabledSchema,
ZDocumentMetaLanguageSchema,
ZDocumentMetaMessageSchema,
ZDocumentMetaRedirectUrlSchema,
ZDocumentMetaSubjectSchema,
ZDocumentMetaTimezoneSchema,
ZDocumentMetaTypedSignatureEnabledSchema,
ZDocumentMetaUploadSignatureEnabledSchema,
ZDocumentTitleSchema,
ZDocumentVisibilitySchema,
} from './schema';
@ -48,25 +37,7 @@ export const ZUpdateDocumentRequestSchema = z.object({
folderId: z.string().nullish(),
})
.optional(),
meta: z
.object({
subject: ZDocumentMetaSubjectSchema.optional(),
message: ZDocumentMetaMessageSchema.optional(),
timezone: ZDocumentMetaTimezoneSchema.optional(),
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
allowDictateNextSigner: z.boolean().optional(),
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
language: ZDocumentMetaLanguageSchema.optional(),
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
emailId: z.string().nullish(),
emailReplyTo: z.string().email().nullish(),
emailSettings: ZDocumentEmailSettingsSchema.optional(),
})
.optional(),
meta: ZDocumentMetaUpdateSchema.optional(),
});
export const ZUpdateDocumentResponseSchema = ZDocumentLiteSchema;