feat: polish envelopes (#2090)

## Description

The rest of the owl
This commit is contained in:
David Nguyen
2025-10-24 16:22:06 +11:00
committed by GitHub
parent 88836404d1
commit 03eb6af69a
141 changed files with 5171 additions and 2402 deletions
@@ -0,0 +1,144 @@
import { DocumentStatus, EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { TDocumentAuthMethods } from '../../types/document-auth';
import { isRecipientAuthorized } from '../document/is-recipient-authorized';
import { getTeamSettings } from '../team/get-team-settings';
import type { EnvelopeForSigningResponse } from './get-envelope-for-recipient-signing';
import { ZEnvelopeForSigningResponse } from './get-envelope-for-recipient-signing';
export type GetRecipientEnvelopeByTokenOptions = {
token: string;
userId?: number;
accessAuth?: TDocumentAuthMethods;
};
/**
* Get all the values and details for a direct template envelope that a recipient requires.
*
* Do not overexpose any information that the recipient should not have.
*/
export const getEnvelopeForDirectTemplateSigning = async ({
token,
userId,
accessAuth,
}: GetRecipientEnvelopeByTokenOptions): Promise<EnvelopeForSigningResponse> => {
if (!token) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Missing token',
});
}
const envelope = await prisma.envelope.findFirst({
where: {
type: EnvelopeType.TEMPLATE,
status: DocumentStatus.DRAFT,
directLink: {
enabled: true,
token,
},
},
include: {
user: {
select: {
id: true,
email: true,
name: true,
},
},
documentMeta: true,
recipients: {
include: {
fields: {
include: {
signature: true,
},
},
},
orderBy: {
signingOrder: 'asc',
},
},
envelopeItems: {
include: {
documentData: true,
},
},
team: {
select: {
id: true,
name: true,
teamEmail: true,
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
directLink: true,
},
});
const recipient = (envelope?.recipients || []).find(
(r) => r.id === envelope?.directLink?.directTemplateRecipientId,
);
if (!envelope || !recipient) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Envelope not found',
});
}
if (envelope.envelopeItems.length === 0) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Envelope has no items',
});
}
const documentAccessValid = await isRecipientAuthorized({
type: 'ACCESS',
documentAuthOptions: envelope.authOptions,
recipient,
userId,
authOptions: accessAuth,
});
if (!documentAccessValid) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Invalid access values',
});
}
const settings = await getTeamSettings({ teamId: envelope.teamId });
const sender = settings.includeSenderDetails
? {
email: envelope.user.email,
name: envelope.user.name || '',
}
: {
email: envelope.team.teamEmail?.email || '',
name: envelope.team.name || '',
};
return ZEnvelopeForSigningResponse.parse({
envelope,
recipient: {
...recipient,
token: envelope.directLink?.token || '',
},
recipientSignature: null,
isRecipientsTurn: true,
isCompleted: false,
isRejected: false,
sender,
settings: {
includeSenderDetails: settings.includeSenderDetails,
brandingEnabled: settings.brandingEnabled,
brandingLogo: settings.brandingLogo,
},
} satisfies EnvelopeForSigningResponse);
};
@@ -23,7 +23,7 @@ export type GetRecipientEnvelopeByTokenOptions = {
accessAuth?: TDocumentAuthMethods;
};
const ZEnvelopeForSigningResponse = z.object({
export const ZEnvelopeForSigningResponse = z.object({
envelope: EnvelopeSchema.pick({
type: true,
status: true,
@@ -31,6 +31,7 @@ const ZEnvelopeForSigningResponse = z.object({
secondaryId: true,
internalVersion: true,
completedAt: true,
updatedAt: true,
deletedAt: true,
title: true,
authOptions: true,
@@ -54,3 +54,54 @@ export const getEnvelopeRequiredAccessData = async ({ token }: { token: string }
recipientHasAccount: Boolean(recipientUserAccount),
} as const;
};
export const getEnvelopeDirectTemplateRequiredAccessData = async ({ token }: { token: string }) => {
const envelope = await prisma.envelope.findFirst({
where: {
type: EnvelopeType.TEMPLATE,
directLink: {
enabled: true,
token,
},
status: DocumentStatus.DRAFT,
},
include: {
recipients: {
where: {
token,
},
},
directLink: true,
},
});
if (!envelope) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Envelope not found',
});
}
const recipient = envelope.recipients.find(
(r) => r.id === envelope.directLink?.directTemplateRecipientId,
);
if (!recipient) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Recipient not found',
});
}
const recipientUserAccount = await prisma.user.findFirst({
where: {
email: recipient.email.toLowerCase(),
},
select: {
id: true,
},
});
return {
recipientEmail: recipient.email,
recipientHasAccount: Boolean(recipientUserAccount),
} as const;
};