mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
The resend document API was not working correctly when filtering recipients. The query was filtering recipients at the database level, which could result in an empty recipients array being returned even when the document had recipients. This prevented the API from properly identifying which recipients needed reminder emails.
224 lines
6.8 KiB
TypeScript
224 lines
6.8 KiB
TypeScript
import { createElement } from 'react';
|
|
|
|
import { msg } from '@lingui/core/macro';
|
|
import { DocumentStatus, OrganisationType, RecipientRole, SigningStatus } from '@prisma/client';
|
|
|
|
import { mailer } from '@documenso/email/mailer';
|
|
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
|
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
|
import {
|
|
RECIPIENT_ROLES_DESCRIPTION,
|
|
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
|
} from '@documenso/lib/constants/recipient-roles';
|
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
|
import { prisma } from '@documenso/prisma';
|
|
|
|
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
|
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
|
import { isDocumentCompleted } from '../../utils/document';
|
|
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
|
import { getEmailContext } from '../email/get-email-context';
|
|
import { getDocumentWhereInput } from './get-document-by-id';
|
|
|
|
export type ResendDocumentOptions = {
|
|
documentId: number;
|
|
userId: number;
|
|
recipients: number[];
|
|
teamId: number;
|
|
requestMetadata: ApiRequestMetadata;
|
|
};
|
|
|
|
export const resendDocument = async ({
|
|
documentId,
|
|
userId,
|
|
recipients,
|
|
teamId,
|
|
requestMetadata,
|
|
}: ResendDocumentOptions): Promise<void> => {
|
|
const user = await prisma.user.findFirstOrThrow({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
});
|
|
|
|
const { documentWhereInput } = await getDocumentWhereInput({
|
|
documentId,
|
|
userId,
|
|
teamId,
|
|
});
|
|
|
|
const document = await prisma.document.findUnique({
|
|
where: documentWhereInput,
|
|
include: {
|
|
recipients: true,
|
|
documentMeta: true,
|
|
team: {
|
|
select: {
|
|
teamEmail: true,
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const customEmail = document?.documentMeta;
|
|
|
|
if (!document) {
|
|
throw new Error('Document not found');
|
|
}
|
|
|
|
if (document.recipients.length === 0) {
|
|
throw new Error('Document has no recipients');
|
|
}
|
|
|
|
if (document.status === DocumentStatus.DRAFT) {
|
|
throw new Error('Can not send draft document');
|
|
}
|
|
|
|
if (isDocumentCompleted(document.status)) {
|
|
throw new Error('Can not send completed document');
|
|
}
|
|
|
|
const recipientsToRemind = document.recipients.filter(
|
|
(recipient) =>
|
|
recipients.includes(recipient.id) && recipient.signingStatus === SigningStatus.NOT_SIGNED,
|
|
);
|
|
|
|
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
|
|
document.documentMeta,
|
|
).recipientSigningRequest;
|
|
|
|
if (!isRecipientSigningRequestEmailEnabled) {
|
|
return;
|
|
}
|
|
|
|
const { branding, settings, organisationType } = await getEmailContext({
|
|
source: {
|
|
type: 'team',
|
|
teamId: document.teamId,
|
|
},
|
|
});
|
|
|
|
await Promise.all(
|
|
recipientsToRemind.map(async (recipient) => {
|
|
if (recipient.role === RecipientRole.CC) {
|
|
return;
|
|
}
|
|
|
|
const lang = document.documentMeta?.language ?? settings.documentLanguage;
|
|
const i18n = await getI18nInstance(lang);
|
|
|
|
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
|
|
|
const { email, name } = recipient;
|
|
const selfSigner = email === user.email;
|
|
|
|
const recipientActionVerb = i18n
|
|
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
|
.toLowerCase();
|
|
|
|
let emailMessage = customEmail?.message || '';
|
|
let emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} this document`);
|
|
|
|
if (selfSigner) {
|
|
emailMessage = i18n._(
|
|
msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
|
|
);
|
|
emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} your document`);
|
|
}
|
|
|
|
if (organisationType === OrganisationType.ORGANISATION) {
|
|
emailSubject = i18n._(
|
|
msg`Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`,
|
|
);
|
|
emailMessage =
|
|
customEmail?.message ||
|
|
i18n._(
|
|
msg`${user.name || user.email} on behalf of "${document.team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
|
);
|
|
}
|
|
|
|
const customEmailTemplate = {
|
|
'signer.name': name,
|
|
'signer.email': email,
|
|
'document.name': document.title,
|
|
};
|
|
|
|
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
|
const signDocumentLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`;
|
|
|
|
const template = createElement(DocumentInviteEmailTemplate, {
|
|
documentName: document.title,
|
|
inviterName: user.name || undefined,
|
|
inviterEmail:
|
|
organisationType === OrganisationType.ORGANISATION
|
|
? document.team?.teamEmail?.email || user.email
|
|
: user.email,
|
|
assetBaseUrl,
|
|
signDocumentLink,
|
|
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
|
role: recipient.role,
|
|
selfSigner,
|
|
organisationType,
|
|
teamName: document.team?.name,
|
|
});
|
|
|
|
const [html, text] = await Promise.all([
|
|
renderEmailWithI18N(template, {
|
|
lang,
|
|
branding,
|
|
}),
|
|
renderEmailWithI18N(template, {
|
|
lang,
|
|
branding,
|
|
plainText: true,
|
|
}),
|
|
]);
|
|
|
|
await prisma.$transaction(
|
|
async (tx) => {
|
|
await mailer.sendMail({
|
|
to: {
|
|
address: email,
|
|
name,
|
|
},
|
|
from: {
|
|
name: FROM_NAME,
|
|
address: FROM_ADDRESS,
|
|
},
|
|
subject: customEmail?.subject
|
|
? renderCustomEmailTemplate(
|
|
i18n._(msg`Reminder: ${customEmail.subject}`),
|
|
customEmailTemplate,
|
|
)
|
|
: emailSubject,
|
|
html,
|
|
text,
|
|
});
|
|
|
|
await tx.documentAuditLog.create({
|
|
data: createDocumentAuditLogData({
|
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
|
documentId: document.id,
|
|
metadata: requestMetadata,
|
|
data: {
|
|
emailType: recipientEmailType,
|
|
recipientEmail: recipient.email,
|
|
recipientName: recipient.name,
|
|
recipientRole: recipient.role,
|
|
recipientId: recipient.id,
|
|
isResending: true,
|
|
},
|
|
}),
|
|
});
|
|
},
|
|
{ timeout: 30_000 },
|
|
);
|
|
}),
|
|
);
|
|
};
|