feat: migrate templates and documents to envelope model

This commit is contained in:
David Nguyen
2025-09-11 18:23:38 +10:00
parent eec2307634
commit bf89bc781b
234 changed files with 8677 additions and 6054 deletions

View File

@ -1,4 +1,4 @@
import { RecipientRole } from '@prisma/client';
import { EnvelopeType, RecipientRole } from '@prisma/client';
import { SendStatus, SigningStatus } from '@prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
@ -11,12 +11,13 @@ import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { getDocumentWhereInput } from '../document/get-document-by-id';
import type { EnvelopeIdOptions } from '../../utils/envelope';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export interface CreateDocumentRecipientsOptions {
userId: number;
teamId: number;
documentId: number;
id: EnvelopeIdOptions;
recipients: {
email: string;
name: string;
@ -31,18 +32,19 @@ export interface CreateDocumentRecipientsOptions {
export const createDocumentRecipients = async ({
userId,
teamId,
documentId,
id,
recipients: recipientsToCreate,
requestMetadata,
}: CreateDocumentRecipientsOptions) => {
const { documentWhereInput } = await getDocumentWhereInput({
documentId,
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id,
type: EnvelopeType.DOCUMENT,
userId,
teamId,
});
const document = await prisma.document.findFirst({
where: documentWhereInput,
const envelope = await prisma.envelope.findFirst({
where: envelopeWhereInput,
include: {
recipients: true,
team: {
@ -57,13 +59,13 @@ export const createDocumentRecipients = async ({
},
});
if (!document) {
if (!envelope) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
if (document.completedAt) {
if (envelope.completedAt) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document already complete',
});
@ -74,7 +76,7 @@ export const createDocumentRecipients = async ({
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth && !document.team.organisation.organisationClaim.flags.cfr21) {
if (recipientsHaveActionAuth && !envelope.team.organisation.organisationClaim.flags.cfr21) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to set the action auth',
});
@ -95,7 +97,7 @@ export const createDocumentRecipients = async ({
const createdRecipient = await tx.recipient.create({
data: {
documentId,
envelopeId: envelope.id,
name: recipient.name,
email: recipient.email,
role: recipient.role,
@ -112,7 +114,7 @@ export const createDocumentRecipients = async ({
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED,
documentId: documentId,
envelopeId: envelope.id,
metadata: requestMetadata,
data: {
recipientEmail: createdRecipient.email,

View File

@ -1,4 +1,4 @@
import { RecipientRole } from '@prisma/client';
import { EnvelopeType, RecipientRole } from '@prisma/client';
import { SendStatus, SigningStatus } from '@prisma/client';
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
@ -8,7 +8,7 @@ import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { buildTeamWhereQuery } from '../../utils/teams';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export interface CreateTemplateRecipientsOptions {
userId: number;
@ -30,11 +30,18 @@ export const createTemplateRecipients = async ({
templateId,
recipients: recipientsToCreate,
}: CreateTemplateRecipientsOptions) => {
const template = await prisma.template.findFirst({
where: {
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id: {
type: 'templateId',
id: templateId,
team: buildTeamWhereQuery({ teamId, userId }),
},
type: EnvelopeType.TEMPLATE,
userId,
teamId,
});
const template = await prisma.envelope.findFirst({
where: envelopeWhereInput,
include: {
recipients: true,
team: {
@ -81,7 +88,7 @@ export const createTemplateRecipients = async ({
const createdRecipient = await tx.recipient.create({
data: {
templateId,
envelopeId: template.id,
name: recipient.name,
email: recipient.email,
role: recipient.role,

View File

@ -1,7 +1,7 @@
import { createElement } from 'react';
import { msg } from '@lingui/core/macro';
import { SendStatus } from '@prisma/client';
import { EnvelopeType, SendStatus } from '@prisma/client';
import { mailer } from '@documenso/email/mailer';
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
@ -30,9 +30,10 @@ export const deleteDocumentRecipient = async ({
teamId,
recipientId,
requestMetadata,
}: DeleteDocumentRecipientOptions): Promise<void> => {
const document = await prisma.document.findFirst({
}: DeleteDocumentRecipientOptions) => {
const envelope = await prisma.envelope.findFirst({
where: {
type: EnvelopeType.DOCUMENT,
recipients: {
some: {
id: recipientId,
@ -62,13 +63,13 @@ export const deleteDocumentRecipient = async ({
},
});
if (!document) {
if (!envelope) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
if (document.completedAt) {
if (envelope.completedAt) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document already complete',
});
@ -80,7 +81,7 @@ export const deleteDocumentRecipient = async ({
});
}
const recipientToDelete = document.recipients[0];
const recipientToDelete = envelope.recipients[0];
if (!recipientToDelete || recipientToDelete.id !== recipientId) {
throw new AppError(AppErrorCode.NOT_FOUND, {
@ -88,17 +89,11 @@ export const deleteDocumentRecipient = async ({
});
}
await prisma.$transaction(async (tx) => {
await tx.recipient.delete({
where: {
id: recipientId,
},
});
const deletedRecipient = await prisma.$transaction(async (tx) => {
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED,
documentId: document.id,
envelopeId: envelope.id,
metadata: requestMetadata,
data: {
recipientEmail: recipientToDelete.email,
@ -108,10 +103,16 @@ export const deleteDocumentRecipient = async ({
},
}),
});
return await tx.recipient.delete({
where: {
id: recipientId,
},
});
});
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
envelope.documentMeta,
).recipientRemoved;
// Send email to deleted recipient.
@ -119,8 +120,8 @@ export const deleteDocumentRecipient = async ({
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
const template = createElement(RecipientRemovedFromDocumentTemplate, {
documentName: document.title,
inviterName: document.team?.name || user.name || undefined,
documentName: envelope.title,
inviterName: envelope.team?.name || user.name || undefined,
assetBaseUrl,
});
@ -128,9 +129,9 @@ export const deleteDocumentRecipient = async ({
emailType: 'RECIPIENT',
source: {
type: 'team',
teamId: document.teamId,
teamId: envelope.teamId,
},
meta: document.documentMeta,
meta: envelope.documentMeta,
});
const [html, text] = await Promise.all([
@ -152,4 +153,6 @@ export const deleteDocumentRecipient = async ({
text,
});
}
return deletedRecipient;
};

View File

@ -1,88 +0,0 @@
import { SendStatus } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import { buildTeamWhereQuery } from '../../utils/teams';
export type DeleteRecipientOptions = {
documentId: number;
recipientId: number;
userId: number;
teamId: number;
requestMetadata?: RequestMetadata;
};
export const deleteRecipient = async ({
documentId,
recipientId,
userId,
teamId,
requestMetadata,
}: DeleteRecipientOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
id: recipientId,
document: {
id: documentId,
userId,
team: buildTeamWhereQuery({ teamId, userId }),
},
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
if (recipient.sendStatus !== SendStatus.NOT_SENT) {
throw new Error('Can not delete a recipient that has already been sent a document');
}
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
});
const team = await prisma.team.findFirst({
where: buildTeamWhereQuery({ teamId, userId }),
});
if (!team) {
throw new AppError(AppErrorCode.NOT_FOUND);
}
const deletedRecipient = await prisma.$transaction(async (tx) => {
const deleted = await tx.recipient.delete({
where: {
id: recipient.id,
},
});
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: 'RECIPIENT_DELETED',
documentId,
user: {
id: team?.id ?? user.id,
email: team?.name ?? user.email,
name: team ? '' : user.name,
},
data: {
recipientEmail: recipient.email,
recipientName: recipient.name,
recipientId: recipient.id,
recipientRole: recipient.role,
},
requestMetadata,
}),
});
return deleted;
});
return deletedRecipient;
};

View File

@ -1,7 +1,10 @@
import { EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { buildTeamWhereQuery } from '../../utils/teams';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export interface DeleteTemplateRecipientOptions {
userId: number;
@ -14,31 +17,31 @@ export const deleteTemplateRecipient = async ({
teamId,
recipientId,
}: DeleteTemplateRecipientOptions): Promise<void> => {
const template = await prisma.template.findFirst({
const recipientToDelete = await prisma.recipient.findFirst({
where: {
recipients: {
some: {
id: recipientId,
},
},
team: buildTeamWhereQuery({ teamId, userId }),
},
include: {
recipients: {
where: {
id: recipientId,
},
id: recipientId,
envelope: {
type: EnvelopeType.TEMPLATE,
team: buildTeamWhereQuery({ teamId, userId }),
},
},
});
if (!template) {
if (!recipientToDelete) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Template not found',
message: 'Recipient not found',
});
}
const recipientToDelete = template.recipients[0];
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id: {
type: 'envelopeId',
id: recipientToDelete.envelopeId,
},
type: EnvelopeType.TEMPLATE,
userId,
teamId,
});
if (!recipientToDelete || recipientToDelete.id !== recipientId) {
throw new AppError(AppErrorCode.NOT_FOUND, {
@ -49,6 +52,7 @@ export const deleteTemplateRecipient = async ({
await prisma.recipient.delete({
where: {
id: recipientId,
envelope: envelopeWhereInput,
},
});
};

View File

@ -1,4 +1,4 @@
import { DocumentSigningOrder, SigningStatus } from '@prisma/client';
import { DocumentSigningOrder, EnvelopeType, SigningStatus } from '@prisma/client';
import { prisma } from '@documenso/prisma';
@ -7,8 +7,9 @@ export type GetIsRecipientTurnOptions = {
};
export async function getIsRecipientsTurnToSign({ token }: GetIsRecipientTurnOptions) {
const document = await prisma.document.findFirstOrThrow({
const envelope = await prisma.envelope.findFirstOrThrow({
where: {
type: EnvelopeType.DOCUMENT,
recipients: {
some: {
token,
@ -25,11 +26,11 @@ export async function getIsRecipientsTurnToSign({ token }: GetIsRecipientTurnOpt
},
});
if (document.documentMeta?.signingOrder !== DocumentSigningOrder.SEQUENTIAL) {
if (envelope.documentMeta?.signingOrder !== DocumentSigningOrder.SEQUENTIAL) {
return true;
}
const { recipients } = document;
const { recipients } = envelope;
const currentRecipientIndex = recipients.findIndex((r) => r.token === token);

View File

@ -1,5 +1,9 @@
import { EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { mapDocumentIdToSecondaryId } from '../../utils/envelope';
export const getNextPendingRecipient = async ({
documentId,
currentRecipientId,
@ -9,7 +13,10 @@ export const getNextPendingRecipient = async ({
}) => {
const recipients = await prisma.recipient.findMany({
where: {
documentId,
envelope: {
type: EnvelopeType.DOCUMENT,
secondaryId: mapDocumentIdToSecondaryId(documentId),
},
},
orderBy: [
{

View File

@ -1,21 +0,0 @@
import { prisma } from '@documenso/prisma';
export type GetRecipientByEmailOptions = {
documentId: number;
email: string;
};
export const getRecipientByEmail = async ({ documentId, email }: GetRecipientByEmailOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
documentId,
email: email.toLowerCase(),
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
return recipient;
};

View File

@ -1,21 +0,0 @@
import { prisma } from '@documenso/prisma';
export type GetRecipientByIdOptions = {
id: number;
documentId: number;
};
export const getRecipientByIdV1Api = async ({ documentId, id }: GetRecipientByIdOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
documentId,
id,
},
});
if (!recipient) {
throw new Error('Recipient not found');
}
return recipient;
};

View File

@ -1,12 +1,17 @@
import { EnvelopeType } from '@prisma/client';
import { match } from 'ts-pattern';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { mapSecondaryIdToDocumentId, mapSecondaryIdToTemplateId } from '../../utils/envelope';
import { buildTeamWhereQuery } from '../../utils/teams';
export type GetRecipientByIdOptions = {
recipientId: number;
userId: number;
teamId: number;
type: EnvelopeType;
};
/**
@ -17,16 +22,23 @@ export const getRecipientById = async ({
recipientId,
userId,
teamId,
type,
}: GetRecipientByIdOptions) => {
const recipient = await prisma.recipient.findFirst({
where: {
id: recipientId,
document: {
envelope: {
type,
team: buildTeamWhereQuery({ teamId, userId }),
},
},
include: {
fields: true,
envelope: {
select: {
secondaryId: true,
},
},
},
});
@ -36,5 +48,24 @@ export const getRecipientById = async ({
});
}
return recipient;
const legacyId = match(type)
.with(EnvelopeType.DOCUMENT, () => ({
documentId: mapSecondaryIdToDocumentId(recipient.envelope.secondaryId),
}))
.with(EnvelopeType.TEMPLATE, () => ({
templateId: mapSecondaryIdToTemplateId(recipient.envelope.secondaryId),
}))
.exhaustive();
// Backwards compatibility mapping.
return {
...recipient,
...legacyId,
// eslint-disable-next-line unused-imports/no-unused-vars
fields: recipient.fields.map((field) => ({
...field,
...legacyId,
})),
};
};

View File

@ -1,11 +1,11 @@
import { Prisma } from '@prisma/client';
import { EnvelopeType, Prisma } from '@prisma/client';
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
import { prisma } from '@documenso/prisma';
export type GetRecipientSuggestionsOptions = {
userId: number;
teamId?: number;
teamId: number;
query: string;
};
@ -37,7 +37,8 @@ export const getRecipientSuggestions = async ({
const recipients = await prisma.recipient.findMany({
where: {
document: {
envelope: {
type: EnvelopeType.DOCUMENT,
team: buildTeamWhereQuery({ teamId, userId }),
},
...nameEmailFilter,
@ -45,7 +46,7 @@ export const getRecipientSuggestions = async ({
select: {
name: true,
email: true,
document: {
envelope: {
select: {
createdAt: true,
},
@ -53,7 +54,7 @@ export const getRecipientSuggestions = async ({
},
distinct: ['email'],
orderBy: {
document: {
envelope: {
createdAt: 'desc',
},
},

View File

@ -23,7 +23,7 @@ export const getRecipientsForAssistant = async ({ token }: GetRecipientsForAssis
let recipients = await prisma.recipient.findMany({
where: {
documentId: assistant.documentId,
envelopeId: assistant.envelopeId,
signingOrder: {
gte: assistant.signingOrder ?? 0,
},
@ -39,7 +39,7 @@ export const getRecipientsForAssistant = async ({ token }: GetRecipientsForAssis
type: {
not: FieldType.SIGNATURE,
},
documentId: assistant.documentId,
envelopeId: assistant.envelopeId,
},
],
},

View File

@ -1,6 +1,8 @@
import { EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { getDocumentWhereInput } from '../document/get-document-by-id';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export interface GetRecipientsForDocumentOptions {
documentId: number;
@ -13,15 +15,19 @@ export const getRecipientsForDocument = async ({
userId,
teamId,
}: GetRecipientsForDocumentOptions) => {
const { documentWhereInput } = await getDocumentWhereInput({
documentId,
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id: {
type: 'documentId',
id: documentId,
},
type: EnvelopeType.DOCUMENT,
userId,
teamId,
});
const recipients = await prisma.recipient.findMany({
where: {
document: documentWhereInput,
envelope: envelopeWhereInput,
},
orderBy: {
id: 'asc',

View File

@ -2,7 +2,7 @@ import { createElement } from 'react';
import { msg } from '@lingui/core/macro';
import type { Recipient } from '@prisma/client';
import { RecipientRole } from '@prisma/client';
import { EnvelopeType, RecipientRole } from '@prisma/client';
import { SendStatus, SigningStatus } from '@prisma/client';
import { isDeepEqual } from 'remeda';
@ -29,8 +29,8 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import { canRecipientBeModified } from '../../utils/recipients';
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
import { getDocumentWhereInput } from '../document/get-document-by-id';
import { getEmailContext } from '../email/get-email-context';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export interface SetDocumentRecipientsOptions {
userId: number;
@ -47,14 +47,18 @@ export const setDocumentRecipients = async ({
recipients,
requestMetadata,
}: SetDocumentRecipientsOptions) => {
const { documentWhereInput } = await getDocumentWhereInput({
documentId,
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id: {
type: 'documentId',
id: documentId,
},
type: EnvelopeType.DOCUMENT,
userId,
teamId,
});
const document = await prisma.document.findFirst({
where: documentWhereInput,
const envelope = await prisma.envelope.findFirst({
where: envelopeWhereInput,
include: {
fields: true,
documentMeta: true,
@ -67,6 +71,7 @@ export const setDocumentRecipients = async ({
},
},
},
recipients: true,
},
});
@ -81,11 +86,11 @@ export const setDocumentRecipients = async ({
},
});
if (!document) {
if (!envelope) {
throw new Error('Document not found');
}
if (document.completedAt) {
if (envelope.completedAt) {
throw new Error('Document already complete');
}
@ -95,7 +100,7 @@ export const setDocumentRecipients = async ({
type: 'team',
teamId,
},
meta: document.documentMeta,
meta: envelope.documentMeta,
});
const recipientsHaveActionAuth = recipients.some(
@ -103,7 +108,7 @@ export const setDocumentRecipients = async ({
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth && !document.team.organisation.organisationClaim.flags.cfr21) {
if (recipientsHaveActionAuth && !envelope.team.organisation.organisationClaim.flags.cfr21) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to set the action auth',
});
@ -114,11 +119,7 @@ export const setDocumentRecipients = async ({
email: recipient.email.toLowerCase(),
}));
const existingRecipients = await prisma.recipient.findMany({
where: {
documentId,
},
});
const existingRecipients = envelope.recipients;
const removedRecipients = existingRecipients.filter(
(existingRecipient) =>
@ -131,12 +132,12 @@ export const setDocumentRecipients = async ({
);
const canPersistedRecipientBeModified =
existing && canRecipientBeModified(existing, document.fields);
existing && canRecipientBeModified(existing, envelope.fields);
if (
existing &&
hasRecipientBeenChanged(existing, recipient) &&
!canRecipientBeModified(existing, document.fields)
!canRecipientBeModified(existing, envelope.fields)
) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Cannot modify a recipient who has already interacted with the document',
@ -172,14 +173,14 @@ export const setDocumentRecipients = async ({
const upsertedRecipient = await tx.recipient.upsert({
where: {
id: recipient._persisted?.id ?? -1,
documentId,
envelopeId: envelope.id,
},
update: {
name: recipient.name,
email: recipient.email,
role: recipient.role,
signingOrder: recipient.signingOrder,
documentId,
envelopeId: envelope.id,
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
signingStatus:
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
@ -191,7 +192,7 @@ export const setDocumentRecipients = async ({
role: recipient.role,
signingOrder: recipient.signingOrder,
token: nanoid(),
documentId,
envelopeId: envelope.id,
sendStatus: recipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
signingStatus:
recipient.role === RecipientRole.CC ? SigningStatus.SIGNED : SigningStatus.NOT_SIGNED,
@ -230,7 +231,7 @@ export const setDocumentRecipients = async ({
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
documentId: documentId,
envelopeId: envelope.id,
metadata: requestMetadata,
data: {
changes,
@ -245,7 +246,7 @@ export const setDocumentRecipients = async ({
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED,
documentId: documentId,
envelopeId: envelope.id,
metadata: requestMetadata,
data: {
...baseAuditLog,
@ -278,7 +279,7 @@ export const setDocumentRecipients = async ({
data: removedRecipients.map((recipient) =>
createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_DELETED,
documentId: documentId,
envelopeId: envelope.id,
metadata: requestMetadata,
data: {
recipientEmail: recipient.email,
@ -292,7 +293,7 @@ export const setDocumentRecipients = async ({
});
const isRecipientRemovedEmailEnabled = extractDerivedDocumentEmailSettings(
document.documentMeta,
envelope.documentMeta,
).recipientRemoved;
// Send emails to deleted recipients.
@ -305,7 +306,7 @@ export const setDocumentRecipients = async ({
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
const template = createElement(RecipientRemovedFromDocumentTemplate, {
documentName: document.title,
documentName: envelope.title,
inviterName: user.name || undefined,
assetBaseUrl,
});

View File

@ -1,5 +1,5 @@
import type { Recipient } from '@prisma/client';
import { RecipientRole } from '@prisma/client';
import { EnvelopeType, RecipientRole } from '@prisma/client';
import {
DIRECT_TEMPLATE_RECIPIENT_EMAIL,
@ -14,12 +14,13 @@ import {
} from '../../types/document-auth';
import { nanoid } from '../../universal/id';
import { createRecipientAuthOptions } from '../../utils/document-auth';
import { buildTeamWhereQuery } from '../../utils/teams';
import type { EnvelopeIdOptions } from '../../utils/envelope';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export type SetTemplateRecipientsOptions = {
userId: number;
teamId: number;
templateId: number;
id: EnvelopeIdOptions;
recipients: {
id?: number;
email: string;
@ -33,14 +34,18 @@ export type SetTemplateRecipientsOptions = {
export const setTemplateRecipients = async ({
userId,
teamId,
templateId,
id,
recipients,
}: SetTemplateRecipientsOptions) => {
const template = await prisma.template.findFirst({
where: {
id: templateId,
team: buildTeamWhereQuery({ teamId, userId }),
},
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id,
type: EnvelopeType.TEMPLATE,
userId,
teamId,
});
const envelope = await prisma.envelope.findFirst({
where: envelopeWhereInput,
include: {
directLink: true,
team: {
@ -52,10 +57,11 @@ export const setTemplateRecipients = async ({
},
},
},
recipients: true,
},
});
if (!template) {
if (!envelope) {
throw new Error('Template not found');
}
@ -64,7 +70,7 @@ export const setTemplateRecipients = async ({
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth && !template.team.organisation.organisationClaim.flags.cfr21) {
if (recipientsHaveActionAuth && !envelope.team.organisation.organisationClaim.flags.cfr21) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to set the action auth',
});
@ -72,7 +78,7 @@ export const setTemplateRecipients = async ({
const normalizedRecipients = recipients.map((recipient) => {
// Force replace any changes to the name or email of the direct recipient.
if (template.directLink && recipient.id === template.directLink.directTemplateRecipientId) {
if (envelope.directLink && recipient.id === envelope.directLink.directTemplateRecipientId) {
return {
...recipient,
email: DIRECT_TEMPLATE_RECIPIENT_EMAIL,
@ -86,24 +92,20 @@ export const setTemplateRecipients = async ({
};
});
const existingRecipients = await prisma.recipient.findMany({
where: {
templateId,
},
});
const existingRecipients = envelope.recipients;
const removedRecipients = existingRecipients.filter(
(existingRecipient) =>
!normalizedRecipients.find((recipient) => recipient.id === existingRecipient.id),
);
if (template.directLink !== null) {
if (envelope.directLink !== null) {
const updatedDirectRecipient = recipients.find(
(recipient) => recipient.id === template.directLink?.directTemplateRecipientId,
(recipient) => recipient.id === envelope.directLink?.directTemplateRecipientId,
);
const deletedDirectRecipient = removedRecipients.find(
(recipient) => recipient.id === template.directLink?.directTemplateRecipientId,
(recipient) => recipient.id === envelope.directLink?.directTemplateRecipientId,
);
if (updatedDirectRecipient?.role === RecipientRole.CC) {
@ -145,14 +147,14 @@ export const setTemplateRecipients = async ({
const upsertedRecipient = await tx.recipient.upsert({
where: {
id: recipient._persisted?.id ?? -1,
templateId,
envelopeId: envelope.id,
},
update: {
name: recipient.name,
email: recipient.email,
role: recipient.role,
signingOrder: recipient.signingOrder,
templateId,
envelopeId: envelope.id,
authOptions,
},
create: {
@ -161,7 +163,7 @@ export const setTemplateRecipients = async ({
role: recipient.role,
signingOrder: recipient.signingOrder,
token: nanoid(),
templateId,
envelopeId: envelope.id,
authOptions,
},
});

View File

@ -1,4 +1,4 @@
import { RecipientRole } from '@prisma/client';
import { EnvelopeType, RecipientRole } from '@prisma/client';
import { SendStatus, SigningStatus } from '@prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
@ -16,13 +16,14 @@ import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import type { EnvelopeIdOptions } from '../../utils/envelope';
import { canRecipientBeModified } from '../../utils/recipients';
import { getDocumentWhereInput } from '../document/get-document-by-id';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export interface UpdateDocumentRecipientsOptions {
userId: number;
teamId: number;
documentId: number;
id: EnvelopeIdOptions;
recipients: RecipientData[];
requestMetadata: ApiRequestMetadata;
}
@ -30,18 +31,19 @@ export interface UpdateDocumentRecipientsOptions {
export const updateDocumentRecipients = async ({
userId,
teamId,
documentId,
id,
recipients,
requestMetadata,
}: UpdateDocumentRecipientsOptions) => {
const { documentWhereInput } = await getDocumentWhereInput({
documentId,
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id,
type: EnvelopeType.DOCUMENT,
userId,
teamId,
});
const document = await prisma.document.findFirst({
where: documentWhereInput,
const envelope = await prisma.envelope.findFirst({
where: envelopeWhereInput,
include: {
fields: true,
recipients: true,
@ -57,13 +59,13 @@ export const updateDocumentRecipients = async ({
},
});
if (!document) {
if (!envelope) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
if (document.completedAt) {
if (envelope.completedAt) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document already complete',
});
@ -74,14 +76,14 @@ export const updateDocumentRecipients = async ({
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth && !document.team.organisation.organisationClaim.flags.cfr21) {
if (recipientsHaveActionAuth && !envelope.team.organisation.organisationClaim.flags.cfr21) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to set the action auth',
});
}
const recipientsToUpdate = recipients.map((recipient) => {
const originalRecipient = document.recipients.find(
const originalRecipient = envelope.recipients.find(
(existingRecipient) => existingRecipient.id === recipient.id,
);
@ -91,7 +93,7 @@ export const updateDocumentRecipients = async ({
});
}
if (!canRecipientBeModified(originalRecipient, document.fields)) {
if (!canRecipientBeModified(originalRecipient, envelope.fields)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Cannot modify a recipient who has already interacted with the document',
});
@ -123,14 +125,14 @@ export const updateDocumentRecipients = async ({
const updatedRecipient = await tx.recipient.update({
where: {
id: originalRecipient.id,
documentId,
envelopeId: envelope.id,
},
data: {
name: mergedRecipient.name,
email: mergedRecipient.email,
role: mergedRecipient.role,
signingOrder: mergedRecipient.signingOrder,
documentId,
envelopeId: envelope.id,
sendStatus:
mergedRecipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
signingStatus:
@ -164,7 +166,7 @@ export const updateDocumentRecipients = async ({
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
documentId: documentId,
envelopeId: envelope.id,
metadata: requestMetadata,
data: {
recipientEmail: updatedRecipient.email,

View File

@ -1,4 +1,4 @@
import { RecipientRole } from '@prisma/client';
import { EnvelopeType, RecipientRole } from '@prisma/client';
import { SendStatus, SigningStatus } from '@prisma/client';
import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth';
@ -10,7 +10,7 @@ import { createRecipientAuthOptions } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { buildTeamWhereQuery } from '../../utils/teams';
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
export interface UpdateTemplateRecipientsOptions {
userId: number;
@ -33,11 +33,18 @@ export const updateTemplateRecipients = async ({
templateId,
recipients,
}: UpdateTemplateRecipientsOptions) => {
const template = await prisma.template.findFirst({
where: {
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id: {
type: 'templateId',
id: templateId,
team: buildTeamWhereQuery({ teamId, userId }),
},
type: EnvelopeType.TEMPLATE,
userId,
teamId,
});
const envelope = await prisma.envelope.findFirst({
where: envelopeWhereInput,
include: {
recipients: true,
team: {
@ -52,7 +59,7 @@ export const updateTemplateRecipients = async ({
},
});
if (!template) {
if (!envelope) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Template not found',
});
@ -63,14 +70,14 @@ export const updateTemplateRecipients = async ({
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth && !template.team.organisation.organisationClaim.flags.cfr21) {
if (recipientsHaveActionAuth && !envelope.team.organisation.organisationClaim.flags.cfr21) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to set the action auth',
});
}
const recipientsToUpdate = recipients.map((recipient) => {
const originalRecipient = template.recipients.find(
const originalRecipient = envelope.recipients.find(
(existingRecipient) => existingRecipient.id === recipient.id,
);
@ -109,14 +116,14 @@ export const updateTemplateRecipients = async ({
const updatedRecipient = await tx.recipient.update({
where: {
id: originalRecipient.id,
templateId,
envelopeId: envelope.id,
},
data: {
name: mergedRecipient.name,
email: mergedRecipient.email,
role: mergedRecipient.role,
signingOrder: mergedRecipient.signingOrder,
templateId,
envelopeId: envelope.id,
sendStatus:
mergedRecipient.role === RecipientRole.CC ? SendStatus.SENT : SendStatus.NOT_SENT,
signingStatus: