mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
feat: add template enhancements (#1154)
## Description General enhancements for templates. ## Changes Made Added the following changes to the template flow: - Allow adding document meta settings - Allow adding email settings - Allow adding document access & action authentication - Allow adding recipient action authentication - Save the state between template steps similar to how it works for documents Other changes: - Extract common fields between document and template flows - Remove the title field from "Use template" since we now have it as part of the template flow - Add new API endpoint for generating templates ## Testing Performed Added E2E tests for templates and creating documents from templates
This commit is contained in:
@ -1,21 +1,32 @@
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { RecipientRole } from '@documenso/prisma/client';
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import {
|
||||
type TRecipientActionAuthTypes,
|
||||
ZRecipientAuthOptionsSchema,
|
||||
} from '../../types/document-auth';
|
||||
import { nanoid } from '../../universal/id';
|
||||
import { createRecipientAuthOptions } from '../../utils/document-auth';
|
||||
|
||||
export type SetRecipientsForTemplateOptions = {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
templateId: number;
|
||||
recipients: {
|
||||
id?: number;
|
||||
email: string;
|
||||
name: string;
|
||||
role: RecipientRole;
|
||||
actionAuth?: TRecipientActionAuthTypes | null;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const setRecipientsForTemplate = async ({
|
||||
userId,
|
||||
teamId,
|
||||
templateId,
|
||||
recipients,
|
||||
}: SetRecipientsForTemplateOptions) => {
|
||||
@ -43,6 +54,23 @@ export const setRecipientsForTemplate = async ({
|
||||
throw new Error('Template not found');
|
||||
}
|
||||
|
||||
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
|
||||
|
||||
// Check if user has permission to set the global action auth.
|
||||
if (recipientsHaveActionAuth) {
|
||||
const isDocumentEnterprise = await isUserEnterprise({
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentEnterprise) {
|
||||
throw new AppError(
|
||||
AppErrorCode.UNAUTHORIZED,
|
||||
'You do not have permission to set the action auth',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedRecipients = recipients.map((recipient) => ({
|
||||
...recipient,
|
||||
email: recipient.email.toLowerCase(),
|
||||
@ -74,31 +102,59 @@ export const setRecipientsForTemplate = async ({
|
||||
};
|
||||
});
|
||||
|
||||
const persistedRecipients = await prisma.$transaction(
|
||||
// Disabling as wrapping promises here causes type issues
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
linkedRecipients.map((recipient) =>
|
||||
prisma.recipient.upsert({
|
||||
where: {
|
||||
id: recipient._persisted?.id ?? -1,
|
||||
templateId,
|
||||
},
|
||||
update: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
templateId,
|
||||
},
|
||||
create: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
token: nanoid(),
|
||||
templateId,
|
||||
},
|
||||
const persistedRecipients = await prisma.$transaction(async (tx) => {
|
||||
return await Promise.all(
|
||||
linkedRecipients.map(async (recipient) => {
|
||||
let authOptions = ZRecipientAuthOptionsSchema.parse(recipient._persisted?.authOptions);
|
||||
|
||||
if (recipient.actionAuth !== undefined) {
|
||||
authOptions = createRecipientAuthOptions({
|
||||
accessAuth: authOptions.accessAuth,
|
||||
actionAuth: recipient.actionAuth,
|
||||
});
|
||||
}
|
||||
|
||||
const upsertedRecipient = await tx.recipient.upsert({
|
||||
where: {
|
||||
id: recipient._persisted?.id ?? -1,
|
||||
templateId,
|
||||
},
|
||||
update: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
templateId,
|
||||
authOptions,
|
||||
},
|
||||
create: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
token: nanoid(),
|
||||
templateId,
|
||||
authOptions,
|
||||
},
|
||||
});
|
||||
|
||||
const recipientId = upsertedRecipient.id;
|
||||
|
||||
// Clear all fields if the recipient role is changed to a type that cannot have fields.
|
||||
if (
|
||||
recipient._persisted &&
|
||||
recipient._persisted.role !== recipient.role &&
|
||||
(recipient.role === RecipientRole.CC || recipient.role === RecipientRole.VIEWER)
|
||||
) {
|
||||
await tx.field.deleteMany({
|
||||
where: {
|
||||
recipientId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return upsertedRecipient;
|
||||
}),
|
||||
),
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
if (removedRecipients.length > 0) {
|
||||
await prisma.recipient.deleteMany({
|
||||
@ -110,5 +166,17 @@ export const setRecipientsForTemplate = async ({
|
||||
});
|
||||
}
|
||||
|
||||
return persistedRecipients;
|
||||
// Filter out recipients that have been removed or have been updated.
|
||||
const filteredRecipients: Recipient[] = existingRecipients.filter((recipient) => {
|
||||
const isRemoved = removedRecipients.find(
|
||||
(removedRecipient) => removedRecipient.id === recipient.id,
|
||||
);
|
||||
const isUpdated = persistedRecipients.find(
|
||||
(persistedRecipient) => persistedRecipient.id === recipient.id,
|
||||
);
|
||||
|
||||
return !isRemoved && !isUpdated;
|
||||
});
|
||||
|
||||
return [...filteredRecipients, ...persistedRecipients];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user