This commit is contained in:
David Nguyen
2025-05-07 15:03:20 +10:00
parent 419bc02171
commit 7abfc9e271
390 changed files with 21254 additions and 12607 deletions

View File

@ -10,6 +10,7 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
@ -38,12 +39,15 @@ export const run = async ({
teamEmail: true,
name: true,
url: true,
teamGlobalSettings: true,
},
},
},
});
const teamSettings = await getTeamSettings({
teamId: document.teamId,
});
const { documentMeta, user: documentOwner } = document;
// Check if document cancellation emails are enabled
@ -53,7 +57,10 @@ export const run = async ({
return;
}
const i18n = await getI18nInstance(documentMeta?.language);
const branding = teamGlobalSettingsToBranding(teamSettings, document.teamId);
const lang = documentMeta?.language ?? teamSettings.documentLanguage;
const i18n = await getI18nInstance(lang);
// Send cancellation emails to all recipients who have been sent the document or viewed it
const recipientsToNotify = document.recipients.filter(
@ -73,14 +80,10 @@ export const run = async ({
cancellationReason: cancellationReason || 'The document has been cancelled.',
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(template, { lang, branding }),
renderEmailWithI18N(template, {
lang: documentMeta?.language,
lang,
branding,
plainText: true,
}),

View File

@ -9,6 +9,7 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
@ -41,11 +42,6 @@ export const run = async ({
},
user: true,
documentMeta: true,
team: {
include: {
teamGlobalSettings: true,
},
},
},
});
@ -76,8 +72,16 @@ export const run = async ({
return;
}
const settings = await getTeamSettings({
userId: owner.id,
teamId: document.teamId,
});
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
const i18n = await getI18nInstance(document.documentMeta?.language);
const branding = teamGlobalSettingsToBranding(settings, document.teamId);
const lang = document.documentMeta?.language ?? settings.documentLanguage;
const i18n = await getI18nInstance(lang);
const template = createElement(DocumentRecipientSignedEmailTemplate, {
documentName: document.title,
@ -87,14 +91,10 @@ export const run = async ({
});
await io.runTask('send-recipient-signed-email', async () => {
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: document.documentMeta?.language, branding }),
renderEmailWithI18N(template, { lang, branding }),
renderEmailWithI18N(template, {
lang: document.documentMeta?.language,
lang,
branding,
plainText: true,
}),

View File

@ -11,6 +11,7 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
@ -40,7 +41,6 @@ export const run = async ({
teamEmail: true,
name: true,
url: true,
teamGlobalSettings: true,
},
},
},
@ -63,7 +63,15 @@ export const run = async ({
return;
}
const i18n = await getI18nInstance(documentMeta?.language);
const settings = await getTeamSettings({
userId: documentOwner.id,
teamId: document.teamId,
});
const branding = teamGlobalSettingsToBranding(settings, document.teamId);
const lang = documentMeta?.language ?? settings.documentLanguage;
const i18n = await getI18nInstance(lang);
// Send confirmation email to the recipient who rejected
await io.runTask('send-rejection-confirmation-email', async () => {
@ -75,14 +83,10 @@ export const run = async ({
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(recipientTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(recipientTemplate, { lang, branding }),
renderEmailWithI18N(recipientTemplate, {
lang: documentMeta?.language,
lang,
branding,
plainText: true,
}),
@ -115,14 +119,10 @@ export const run = async ({
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(ownerTemplate, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(ownerTemplate, { lang, branding }),
renderEmailWithI18N(ownerTemplate, {
lang: documentMeta?.language,
lang,
branding,
plainText: true,
}),

View File

@ -14,6 +14,7 @@ import {
RECIPIENT_ROLES_DESCRIPTION,
RECIPIENT_ROLE_TO_EMAIL_TYPE,
} from '../../../constants/recipient-roles';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
@ -49,7 +50,6 @@ export const run = async ({
select: {
teamEmail: true,
name: true,
teamGlobalSettings: true,
},
},
},
@ -75,6 +75,11 @@ export const run = async ({
return;
}
const settings = await getTeamSettings({
userId,
teamId: document.teamId,
});
const customEmail = document?.documentMeta;
const isDirectTemplate = document.source === DocumentSource.TEMPLATE_DIRECT_LINK;
const isTeamDocument = document.teamId !== null;
@ -84,7 +89,10 @@ export const run = async ({
const { email, name } = recipient;
const selfSigner = email === user.email;
const i18n = await getI18nInstance(documentMeta?.language);
const branding = teamGlobalSettingsToBranding(settings, document.teamId);
const lang = documentMeta?.language ?? settings.documentLanguage;
const i18n = await getI18nInstance(lang);
const recipientActionVerb = i18n
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
@ -117,7 +125,7 @@ export const run = async ({
const inviterName = user.name || '';
emailMessage = i18n._(
team.teamGlobalSettings?.includeSenderDetails
settings.includeSenderDetails
? msg`${inviterName} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
);
@ -145,18 +153,14 @@ export const run = async ({
isTeamInvite: isTeamDocument,
teamName: team?.name,
teamEmail: team?.teamEmail?.email,
includeSenderDetails: team?.teamGlobalSettings?.includeSenderDetails,
includeSenderDetails: settings.includeSenderDetails,
});
await io.runTask('send-signing-email', async () => {
const branding = document.team?.teamGlobalSettings
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
: undefined;
const [html, text] = await Promise.all([
renderEmailWithI18N(template, { lang: documentMeta?.language, branding }),
renderEmailWithI18N(template, { lang, branding }),
renderEmailWithI18N(template, {
lang: documentMeta?.language,
lang,
branding,
plainText: true,
}),

View File

@ -16,7 +16,6 @@ export const run = async ({
await sendTeamDeleteEmail({
email: member.email,
team,
isOwner: member.id === team.ownerUserId,
});
});
}

View File

@ -9,7 +9,6 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
team: z.object({
name: z.string(),
url: z.string(),
ownerUserId: z.number(),
teamGlobalSettings: z
.object({
documentVisibility: z.nativeEnum(DocumentVisibility),

View File

@ -10,6 +10,7 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobRunIO } from '../../client/_internal/job';
@ -37,10 +38,14 @@ export const run = async ({
user: true,
},
},
teamGlobalSettings: true,
},
});
const settings = await getTeamSettings({
userId: payload.userId,
teamId: payload.teamId,
});
const invitedMember = await prisma.teamMember.findFirstOrThrow({
where: {
id: payload.memberId,
@ -68,11 +73,8 @@ export const run = async ({
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const branding = teamGlobalSettingsToBranding(settings, team.id);
const lang = settings.documentLanguage;
// !: Replace with the actual language of the recipient later
const [html, text] = await Promise.all([

View File

@ -10,6 +10,7 @@ import { prisma } from '@documenso/prisma';
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobRunIO } from '../../client/_internal/job';
@ -37,10 +38,13 @@ export const run = async ({
user: true,
},
},
teamGlobalSettings: true,
},
});
const settings = await getTeamSettings({
teamId: payload.teamId,
});
const oldMember = await prisma.user.findFirstOrThrow({
where: {
id: payload.memberUserId,
@ -58,11 +62,8 @@ export const run = async ({
teamUrl: team.url,
});
const branding = team.teamGlobalSettings
? teamGlobalSettingsToBranding(team.teamGlobalSettings)
: undefined;
const lang = team.teamGlobalSettings?.documentLanguage;
const branding = teamGlobalSettingsToBranding(settings, team.id);
const lang = settings.documentLanguage;
const [html, text] = await Promise.all([
renderEmailWithI18N(emailContent, {

View File

@ -1,7 +1,6 @@
import { createElement } from 'react';
import { msg } from '@lingui/macro';
import type { TeamGlobalSettings } from '@prisma/client';
import { parse } from 'csv-parse/sync';
import { z } from 'zod';
@ -16,6 +15,7 @@ import { getI18nInstance } from '../../../client-only/providers/i18n-server';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
import { AppError } from '../../../errors/app-error';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding';
import type { JobRunIO } from '../../client/_internal/job';
@ -163,29 +163,23 @@ export const run = async ({
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
});
let teamGlobalSettings: TeamGlobalSettings | undefined | null;
const settings = await getTeamSettings({
userId,
teamId,
});
if (template.teamId) {
teamGlobalSettings = await prisma.teamGlobalSettings.findUnique({
where: {
teamId: template.teamId,
},
});
}
const branding = teamGlobalSettingsToBranding(settings, template.teamId);
const lang = template.templateMeta?.language ?? settings.documentLanguage;
const branding = teamGlobalSettings
? teamGlobalSettingsToBranding(teamGlobalSettings)
: undefined;
const i18n = await getI18nInstance(teamGlobalSettings?.documentLanguage);
const i18n = await getI18nInstance(lang);
const [html, text] = await Promise.all([
renderEmailWithI18N(completionTemplate, {
lang: teamGlobalSettings?.documentLanguage,
lang,
branding,
}),
renderEmailWithI18N(completionTemplate, {
lang: teamGlobalSettings?.documentLanguage,
lang,
branding,
plainText: true,
}),

View File

@ -7,7 +7,7 @@ const BULK_SEND_TEMPLATE_JOB_DEFINITION_ID = 'internal.bulk-send-template';
const BULK_SEND_TEMPLATE_JOB_DEFINITION_SCHEMA = z.object({
userId: z.number(),
teamId: z.number().optional(),
teamId: z.number(),
templateId: z.number(),
csvContent: z.string(),
sendImmediately: z.boolean(),

View File

@ -15,6 +15,7 @@ import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations
import { flattenForm } from '../../../server-only/pdf/flatten-form';
import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf';
import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances';
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import {
@ -45,18 +46,14 @@ export const run = async ({
include: {
documentMeta: true,
recipients: true,
team: {
select: {
teamGlobalSettings: {
select: {
includeSigningCertificate: true,
},
},
},
},
},
});
const settings = await getTeamSettings({
userId: document.userId,
teamId: document.teamId,
});
const isComplete =
document.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
document.recipients.every((recipient) => recipient.signingStatus === SigningStatus.SIGNED);
@ -131,13 +128,12 @@ export const run = async ({
const pdfData = await getFileServerSide(documentData);
const certificateData =
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const certificateData = settings.includeSigningCertificate
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
const pdfDoc = await PDFDocument.load(pdfData);