fix: merge conflicts

This commit is contained in:
Ephraim Atta-Duncan
2025-10-20 14:54:42 +00:00
448 changed files with 33524 additions and 9229 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

@ -1,11 +1,13 @@
import { DocumentDataType } 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';
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { isValidExpirySettings } from '@documenso/lib/utils/expiry';
import { prisma } from '@documenso/prisma';
import { authenticatedProcedure } from '../trpc';
import {
@ -65,28 +67,86 @@ export const createDocumentTemporaryRoute = authenticatedProcedure
type: DocumentDataType.S3_PATH,
});
const createdDocument = await createDocumentV2({
const createdEnvelope = await createEnvelope({
userId: ctx.user.id,
teamId,
documentDataId: documentData.id,
normalizePdf: false, // Not normalizing because of presigned URL.
internalVersion: 1,
data: {
type: EnvelopeType.DOCUMENT,
title,
externalId,
visibility,
globalAccessAuth,
globalActionAuth,
recipients,
recipients: (recipients || []).map((recipient) => ({
...recipient,
fields: (recipient.fields || []).map((field) => ({
...field,
page: field.pageNumber,
positionX: field.pageX,
positionY: field.pageY,
documentDataId: documentData.id,
})),
})),
folderId,
envelopeItems: [
{
documentDataId: documentData.id,
},
],
},
meta: {
...meta,
expiryAmount,
expiryUnit,
},
meta,
requestMetadata: ctx.metadata,
});
const envelopeItems = await prisma.envelopeItem.findMany({
where: {
envelopeId: createdEnvelope.id,
},
include: {
documentData: true,
},
});
const legacyDocumentId = mapSecondaryIdToDocumentId(createdEnvelope.secondaryId);
const firstDocumentData = envelopeItems[0].documentData;
if (!firstDocumentData) {
throw new Error('Document data not found');
}
return {
document: createdDocument,
document: {
...createdEnvelope,
envelopeId: createdEnvelope.id,
documentDataId: firstDocumentData.id,
documentData: {
...firstDocumentData,
envelopeItemId: envelopeItems[0].id,
},
documentMeta: {
...createdEnvelope.documentMeta,
documentId: legacyDocumentId,
},
id: legacyDocumentId,
fields: createdEnvelope.fields.map((field) => ({
...field,
documentId: legacyDocumentId,
templateId: null,
})),
recipients: createdEnvelope.recipients.map((recipient) => ({
...recipient,
documentId: legacyDocumentId,
templateId: null,
})),
},
folder: createdEnvelope.folder, // Todo: Remove this prior to api-v2 release.
uploadUrl: url,
};
});

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,
@ -23,16 +22,6 @@ import {
ZDocumentExpiryAmountSchema,
ZDocumentExpiryUnitSchema,
ZDocumentExternalIdSchema,
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
ZDocumentMetaDrawSignatureEnabledSchema,
ZDocumentMetaLanguageSchema,
ZDocumentMetaMessageSchema,
ZDocumentMetaRedirectUrlSchema,
ZDocumentMetaSubjectSchema,
ZDocumentMetaTimezoneSchema,
ZDocumentMetaTypedSignatureEnabledSchema,
ZDocumentMetaUploadSignatureEnabledSchema,
ZDocumentTitleSchema,
ZDocumentVisibilitySchema,
} from './schema';
@ -84,22 +73,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

@ -1,6 +1,9 @@
import { EnvelopeType } from '@prisma/client';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { createDocument } from '@documenso/lib/server-only/document/create-document';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { isValidExpirySettings } from '@documenso/lib/utils/expiry';
import { authenticatedProcedure } from '../trpc';
@ -10,7 +13,7 @@ import {
} from './create-document.types';
export const createDocumentRoute = authenticatedProcedure
.input(ZCreateDocumentRequestSchema)
.input(ZCreateDocumentRequestSchema) // Note: Before releasing this to public, update the response schema to be correct.
.output(ZCreateDocumentResponseSchema)
.mutation(async ({ input, ctx }) => {
const { user, teamId } = ctx;
@ -38,20 +41,30 @@ export const createDocumentRoute = authenticatedProcedure
});
}
const document = await createDocument({
const document = await createEnvelope({
userId: user.id,
teamId,
title,
documentDataId,
internalVersion: 1,
data: {
type: EnvelopeType.DOCUMENT,
title,
userTimezone: timezone,
folderId,
envelopeItems: [
{
documentDataId,
},
],
},
meta: {
expiryAmount,
expiryUnit,
},
normalizePdf: true,
userTimezone: timezone,
requestMetadata: ctx.metadata,
folderId,
expiryAmount,
expiryUnit,
});
return {
id: document.id,
legacyDocumentId: mapSecondaryIdToDocumentId(document.secondaryId),
};
});

View File

@ -4,8 +4,9 @@ import {
ZDocumentExpiryAmountSchema,
ZDocumentExpiryUnitSchema,
ZDocumentMetaTimezoneSchema,
ZDocumentTitleSchema,
} from './schema';
} 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 = {
@ -27,7 +28,7 @@ export const ZCreateDocumentRequestSchema = z.object({
});
export const ZCreateDocumentResponseSchema = z.object({
id: z.number(),
legacyDocumentId: z.number(),
});
export type TCreateDocumentRequest = z.infer<typeof ZCreateDocumentRequestSchema>;

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

@ -1,5 +1,6 @@
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { mapEnvelopeToDocumentLite } from '@documenso/lib/utils/document';
import { authenticatedProcedure } from '../trpc';
import {
@ -23,10 +24,13 @@ export const distributeDocumentRoute = authenticatedProcedure
});
if (Object.values(meta).length > 0) {
await upsertDocumentMeta({
await updateDocumentMeta({
userId: ctx.user.id,
teamId,
documentId,
id: {
type: 'documentId',
id: documentId,
},
subject: meta.subject,
message: meta.message,
dateFormat: meta.dateFormat,
@ -41,10 +45,15 @@ export const distributeDocumentRoute = authenticatedProcedure
});
}
return await sendDocument({
const envelope = await sendDocument({
userId: ctx.user.id,
documentId,
id: {
type: 'documentId',
id: documentId,
},
teamId,
requestMetadata: ctx.metadata,
});
return mapEnvelopeToDocumentLite(envelope);
});

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,9 +1,11 @@
import { EnvelopeType } from '@prisma/client';
import { DateTime } from 'luxon';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { authenticatedProcedure } from '../trpc';
import {
@ -24,20 +26,24 @@ export const downloadDocumentAuditLogsRoute = authenticatedProcedure
},
});
const document = await getDocumentById({
documentId,
const envelope = await getEnvelopeById({
id: {
type: 'documentId',
id: documentId,
},
type: EnvelopeType.DOCUMENT,
userId: ctx.user.id,
teamId,
}).catch(() => null);
if (!document || (teamId && document.teamId !== teamId)) {
if (!envelope) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have access to this document.',
});
}
const encrypted = encryptSecondaryData({
data: document.id.toString(),
data: mapSecondaryIdToDocumentId(envelope.secondaryId).toString(),
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
});

View File

@ -1,10 +1,12 @@
import { EnvelopeType } from '@prisma/client';
import { DateTime } from 'luxon';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { AppError } from '@documenso/lib/errors/app-error';
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { authenticatedProcedure } from '../trpc';
import {
@ -25,18 +27,22 @@ export const downloadDocumentCertificateRoute = authenticatedProcedure
},
});
const document = await getDocumentById({
documentId,
const envelope = await getEnvelopeById({
id: {
type: 'documentId',
id: documentId,
},
type: EnvelopeType.DOCUMENT,
userId: ctx.user.id,
teamId,
});
if (!isDocumentCompleted(document.status)) {
if (!isDocumentCompleted(envelope.status)) {
throw new AppError('DOCUMENT_NOT_COMPLETE');
}
const encrypted = encryptSecondaryData({
data: document.id.toString(),
data: mapSecondaryIdToDocumentId(envelope.secondaryId).toString(),
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
});

View File

@ -1,7 +1,8 @@
import { DocumentDataType } from '@prisma/client';
import type { DocumentData } from '@prisma/client';
import { DocumentDataType, EnvelopeType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
@ -27,45 +28,51 @@ export const downloadDocumentRoute = authenticatedProcedure
},
});
const envelope = await getEnvelopeById({
id: {
type: 'documentId',
id: documentId,
},
type: EnvelopeType.DOCUMENT,
userId: user.id,
teamId,
});
// This error is done AFTER the get envelope so we can test access controls without S3.
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document downloads are only available when S3 storage is configured.',
});
}
const document = await getDocumentById({
documentId,
userId: user.id,
teamId,
});
const documentData: DocumentData | undefined = envelope.envelopeItems[0]?.documentData;
if (!document.documentData) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document data not found',
if (envelope.envelopeItems.length !== 1 || !documentData) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message:
'This endpoint only supports documents with a single item. Use envelopes API instead.',
});
}
if (document.documentData.type !== DocumentDataType.S3_PATH) {
if (documentData.type !== DocumentDataType.S3_PATH) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document is not stored in S3 and cannot be downloaded via URL.',
});
}
if (version === 'signed' && !isDocumentCompleted(document.status)) {
if (version === 'signed' && !isDocumentCompleted(envelope.status)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document is not completed yet.',
});
}
try {
const documentData =
version === 'original'
? document.documentData.initialData || document.documentData.data
: document.documentData.data;
const data =
version === 'original' ? documentData.initialData || documentData.data : documentData.data;
const { url } = await getPresignGetUrl(documentData);
const { url } = await getPresignGetUrl(data);
const baseTitle = document.title.replace(/\.pdf$/, '');
const baseTitle = envelope.title.replace(/\.pdf$/, '');
const suffix = version === 'signed' ? '_signed.pdf' : '.pdf';
const filename = `${baseTitle}${suffix}`;

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,9 +21,17 @@ export const duplicateDocumentRoute = authenticatedProcedure
},
});
return await duplicateDocument({
const duplicatedEnvelope = await duplicateEnvelope({
id: {
type: 'documentId',
id: documentId,
},
userId: user.id,
teamId,
documentId,
});
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

@ -2,6 +2,7 @@ import { findDocuments } from '@documenso/lib/server-only/document/find-document
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
import { getStats } from '@documenso/lib/server-only/document/get-stats';
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
import { authenticatedProcedure } from '../trpc';
import {
@ -69,6 +70,7 @@ export const findDocumentsInternalRoute = authenticatedProcedure
return {
...documents,
data: documents.data.map((envelope) => mapEnvelopesToDocumentMany(envelope)),
stats,
};
});

View File

@ -1,4 +1,5 @@
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
import { authenticatedProcedure } from '../trpc';
import {
@ -39,5 +40,8 @@ export const findDocumentsRoute = authenticatedProcedure
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
});
return documents;
return {
...documents,
data: documents.data.map((envelope) => mapEnvelopesToDocumentMany(envelope)),
};
});

View File

@ -1,7 +1,8 @@
import type { Document, Prisma } from '@prisma/client';
import { DocumentStatus, RecipientRole } from '@prisma/client';
import type { Envelope, Prisma } from '@prisma/client';
import { DocumentStatus, EnvelopeType, RecipientRole } from '@prisma/client';
import type { FindResultResponse } from '@documenso/lib/types/search-params';
import { mapEnvelopesToDocumentMany } from '@documenso/lib/utils/document';
import { maskRecipientTokensForDocument } from '@documenso/lib/utils/mask-recipient-tokens-for-document';
import { prisma } from '@documenso/prisma';
@ -16,11 +17,16 @@ export const findInboxRoute = authenticatedProcedure
const userId = ctx.user.id;
return await findInbox({
const envelopes = await findInbox({
userId,
page,
perPage,
});
return {
...envelopes,
data: envelopes.data.map(mapEnvelopesToDocumentMany),
};
});
export type FindInboxOptions = {
@ -28,7 +34,7 @@ export type FindInboxOptions = {
page?: number;
perPage?: number;
orderBy?: {
column: keyof Omit<Document, 'document'>;
column: keyof Omit<Envelope, 'envelope'>;
direction: 'asc' | 'desc';
};
};
@ -38,12 +44,17 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, orderBy }: Fin
where: {
id: userId,
},
select: {
id: true,
email: true,
},
});
const orderByColumn = orderBy?.column ?? 'createdAt';
const orderByDirection = orderBy?.direction ?? 'desc';
const whereClause: Prisma.DocumentWhereInput = {
const whereClause: Prisma.EnvelopeWhereInput = {
type: EnvelopeType.DOCUMENT,
status: {
not: DocumentStatus.DRAFT,
},
@ -59,7 +70,7 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, orderBy }: Fin
};
const [data, count] = await Promise.all([
prisma.document.findMany({
prisma.envelope.findMany({
where: whereClause,
skip: Math.max(page - 1, 0) * perPage,
take: perPage,
@ -83,7 +94,7 @@ export const findInbox = async ({ userId, page = 1, perPage = 10, orderBy }: Fin
},
},
}),
prisma.document.count({
prisma.envelope.count({
where: whereClause,
}),
]);

View File

@ -1,3 +1,5 @@
import { EnvelopeType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
@ -13,8 +15,9 @@ export const getDocumentByTokenRoute = authenticatedProcedure
.query(async ({ input, ctx }) => {
const { token } = input;
const document = await prisma.document.findFirst({
const envelope = await prisma.envelope.findFirst({
where: {
type: EnvelopeType.DOCUMENT,
recipients: {
some: {
token,
@ -23,21 +26,33 @@ export const getDocumentByTokenRoute = authenticatedProcedure
},
},
include: {
documentData: true,
envelopeItems: {
include: {
documentData: true,
},
},
},
});
if (!document) {
const firstDocumentData = envelope?.envelopeItems[0].documentData;
if (!envelope || !firstDocumentData) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
if (envelope.envelopeItems.length !== 1) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'This endpoint does not support multiple items',
});
}
ctx.logger.info({
documentId: document.id,
documentId: envelope.id,
});
return {
documentData: document.documentData,
documentData: firstDocumentData,
};
});

View File

@ -24,6 +24,9 @@ export const getDocumentRoute = authenticatedProcedure
return await getDocumentWithDetailsById({
userId: user.id,
teamId,
documentId,
id: {
type: 'documentId',
id: documentId,
},
});
});

View File

@ -1,4 +1,4 @@
import { DocumentStatus, RecipientRole } from '@prisma/client';
import { DocumentStatus, EnvelopeType, RecipientRole } from '@prisma/client';
import { prisma } from '@documenso/prisma';
@ -20,7 +20,8 @@ export const getInboxCountRoute = authenticatedProcedure
role: {
not: RecipientRole.CC,
},
document: {
envelope: {
type: EnvelopeType.DOCUMENT,
status: {
not: DocumentStatus.DRAFT,
},

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

@ -17,6 +17,7 @@ import { getDocumentByTokenRoute } from './get-document-by-token';
import { getInboxCountRoute } from './get-inbox-count';
import { redistributeDocumentRoute } from './redistribute-document';
import { searchDocumentRoute } from './search-document';
import { shareDocumentRoute } from './share-document';
import { updateDocumentRoute } from './update-document';
export const documentRouter = router({
@ -30,6 +31,7 @@ export const documentRouter = router({
distribute: distributeDocumentRoute,
redistribute: redistributeDocumentRoute,
search: searchDocumentRoute,
share: shareDocumentRoute,
// Temporary v2 beta routes to be removed once V2 is fully released.
download: downloadDocumentRoute,

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,68 +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.');
export const ZDocumentExpiryAmountSchema = z
.number()
.int()
.min(1)
.describe('The amount for expiry duration (e.g., 3 for "3 days").');
export const ZDocumentExpiryUnitSchema = z
.enum(['minutes', 'hours', 'days', 'weeks', 'months'])
.describe('The unit for expiry duration (e.g., "days" for "3 days").');

View File

@ -0,0 +1,28 @@
import { createOrGetShareLink } from '@documenso/lib/server-only/share/create-or-get-share-link';
import { procedure } from '../trpc';
import { ZShareDocumentRequestSchema, ZShareDocumentResponseSchema } from './share-document.types';
// Note: This is an unauthenticated route.
export const shareDocumentRoute = procedure
.input(ZShareDocumentRequestSchema)
.output(ZShareDocumentResponseSchema)
.mutation(async ({ input, ctx }) => {
const { documentId, token } = input;
ctx.logger.info({
input: {
documentId,
},
});
if (token) {
return await createOrGetShareLink({ documentId, token });
}
if (!ctx.user?.id) {
throw new Error('You must either provide a token or be logged in to create a sharing link.');
}
return await createOrGetShareLink({ documentId, userId: ctx.user.id });
});

View File

@ -0,0 +1,16 @@
import { z } from 'zod';
import DocumentShareLinkSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentShareLinkSchema';
export const ZShareDocumentRequestSchema = z.object({
documentId: z.number(),
token: z.string().optional(),
});
export const ZShareDocumentResponseSchema = DocumentShareLinkSchema.pick({
slug: true,
email: true,
});
export type TShareDocumentRequest = z.infer<typeof ZShareDocumentRequestSchema>;
export type TShareDocumentResponse = z.infer<typeof ZShareDocumentResponseSchema>;

View File

@ -1,6 +1,6 @@
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { upsertDocumentMeta } 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 { isValidExpirySettings } from '@documenso/lib/utils/expiry';
import { authenticatedProcedure } from '../trpc';
@ -8,13 +8,13 @@ import {
ZUpdateDocumentRequestSchema,
ZUpdateDocumentResponseSchema,
} from './update-document.types';
import { updateDocumentMeta } from './update-document.types';
import { updateDocumentMeta as updateDocumentTrpcMeta } from './update-document.types';
/**
* Public route.
*/
export const updateDocumentRoute = authenticatedProcedure
.meta(updateDocumentMeta)
.meta(updateDocumentTrpcMeta)
.input(ZUpdateDocumentRequestSchema)
.output(ZUpdateDocumentResponseSchema)
.mutation(async ({ input, ctx }) => {
@ -38,37 +38,23 @@ export const updateDocumentRoute = authenticatedProcedure
});
}
if (Object.values(meta).length > 0) {
await upsertDocumentMeta({
userId: ctx.user.id,
teamId,
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,
expiryAmount: meta.expiryAmount,
expiryUnit: meta.expiryUnit,
requestMetadata: ctx.metadata,
});
}
return 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,23 +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 {
ZDocumentExpiryAmountSchema,
ZDocumentExpiryUnitSchema,
ZDocumentExternalIdSchema,
ZDocumentMetaDateFormatSchema,
ZDocumentMetaDistributionMethodSchema,
ZDocumentMetaDrawSignatureEnabledSchema,
ZDocumentMetaLanguageSchema,
ZDocumentMetaMessageSchema,
ZDocumentMetaRedirectUrlSchema,
ZDocumentMetaSubjectSchema,
ZDocumentMetaTimezoneSchema,
ZDocumentMetaTypedSignatureEnabledSchema,
ZDocumentMetaUploadSignatureEnabledSchema,
ZDocumentTitleSchema,
ZDocumentVisibilitySchema,
} from './schema';
@ -47,29 +34,10 @@ export const ZUpdateDocumentRequestSchema = z.object({
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
useLegacyFieldInsertion: z.boolean().optional(),
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(),
expiryAmount: ZDocumentExpiryAmountSchema.optional(),
expiryUnit: ZDocumentExpiryUnitSchema.optional(),
})
.optional(),
meta: ZDocumentMetaUpdateSchema.optional(),
});
export const ZUpdateDocumentResponseSchema = ZDocumentLiteSchema;