mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 16:51:38 +10:00
feat: add envelope editor
This commit is contained in:
@ -35,6 +35,8 @@ import {
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { isRequiredField } from '../../utils/advanced-fields-helpers';
|
||||
import { extractDerivedDocumentMeta } from '../../utils/document';
|
||||
import type { CreateDocumentAuditLogDataResponse } from '../../utils/document-audit-logs';
|
||||
@ -107,7 +109,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
},
|
||||
directLink: true,
|
||||
envelopeItems: {
|
||||
select: {
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
},
|
||||
@ -129,10 +131,8 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
const directTemplateEnvelopeLegacyId = mapSecondaryIdToTemplateId(
|
||||
directTemplateEnvelope.secondaryId,
|
||||
);
|
||||
const firstEnvelopeItem = directTemplateEnvelope.envelopeItems[0];
|
||||
|
||||
// Todo: Envelopes
|
||||
if (directTemplateEnvelope.envelopeItems.length !== 1 || !firstEnvelopeItem) {
|
||||
if (directTemplateEnvelope.envelopeItems.length < 1) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Invalid number of envelope items',
|
||||
});
|
||||
@ -228,7 +228,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
recipient: {
|
||||
authOptions: directTemplateRecipient.authOptions,
|
||||
email: directRecipientEmail,
|
||||
documentId: template.id,
|
||||
envelopeId: directTemplateEnvelope.id,
|
||||
},
|
||||
field: templateField,
|
||||
userId: user?.id,
|
||||
@ -289,14 +289,43 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
|
||||
const initialRequestTime = new Date();
|
||||
|
||||
// Todo: Envelopes
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: firstEnvelopeItem.documentData.type,
|
||||
data: firstEnvelopeItem.documentData.data,
|
||||
initialData: firstEnvelopeItem.documentData.initialData,
|
||||
},
|
||||
});
|
||||
// Key = original envelope item ID
|
||||
// Value = duplicated envelope item ID.
|
||||
const oldEnvelopeItemToNewEnvelopeItemIdMap: Record<string, string> = {};
|
||||
|
||||
// Duplicate the envelope item data.
|
||||
const envelopeItemsToCreate = await Promise.all(
|
||||
directTemplateEnvelope.envelopeItems.map(async (item, i) => {
|
||||
const buffer = await getFileServerSide(item.documentData);
|
||||
|
||||
const titleToUse = item.title || directTemplateEnvelope.title;
|
||||
|
||||
const duplicatedFile = await putPdfFileServerSide({
|
||||
name: titleToUse,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(buffer),
|
||||
});
|
||||
|
||||
const newDocumentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: duplicatedFile.type,
|
||||
data: duplicatedFile.data,
|
||||
initialData: duplicatedFile.initialData,
|
||||
},
|
||||
});
|
||||
|
||||
const newEnvelopeItemId = prefixedId('envelope_item');
|
||||
|
||||
oldEnvelopeItemToNewEnvelopeItemIdMap[item.id] = newEnvelopeItemId;
|
||||
|
||||
return {
|
||||
id: newEnvelopeItemId,
|
||||
title: titleToUse.endsWith('.pdf') ? titleToUse.slice(0, -4) : titleToUse,
|
||||
documentDataId: newDocumentData.id,
|
||||
order: item.order !== undefined ? item.order : i + 1,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const documentMeta = await prisma.documentMeta.create({
|
||||
data: derivedDocumentMeta,
|
||||
@ -311,6 +340,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
id: prefixedId('envelope'),
|
||||
secondaryId: incrementedDocumentId.formattedDocumentId,
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
internalVersion: 1,
|
||||
qrToken: prefixedId('qr'),
|
||||
source: DocumentSource.TEMPLATE_DIRECT_LINK,
|
||||
templateId: directTemplateEnvelopeLegacyId,
|
||||
@ -322,10 +352,8 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
externalId: directTemplateExternalId,
|
||||
visibility: settings.documentVisibility,
|
||||
envelopeItems: {
|
||||
create: {
|
||||
id: prefixedId('envelope_item'),
|
||||
title: directTemplateEnvelope.title, // Todo: Envelopes use item title instead
|
||||
documentDataId: documentData.id,
|
||||
createMany: {
|
||||
data: envelopeItemsToCreate,
|
||||
},
|
||||
},
|
||||
authOptions: createDocumentAuthOptions({
|
||||
@ -374,8 +402,6 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const envelopeItemId = createdEnvelope.envelopeItems[0].id;
|
||||
|
||||
let nonDirectRecipientFieldsToCreate: Omit<Field, 'id' | 'secondaryId' | 'templateId'>[] = [];
|
||||
|
||||
Object.values(nonDirectTemplateRecipients).forEach((templateRecipient) => {
|
||||
@ -390,7 +416,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
nonDirectRecipientFieldsToCreate = nonDirectRecipientFieldsToCreate.concat(
|
||||
templateRecipient.fields.map((field) => ({
|
||||
envelopeId: createdEnvelope.id,
|
||||
envelopeItemId: envelopeItemId, // Todo: Envelopes
|
||||
envelopeItemId: oldEnvelopeItemToNewEnvelopeItemIdMap[field.envelopeItemId],
|
||||
recipientId: recipient.id,
|
||||
type: field.type,
|
||||
page: field.page,
|
||||
@ -432,7 +458,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
createMany: {
|
||||
data: directTemplateNonSignatureFields.map(({ templateField, customText }) => ({
|
||||
envelopeId: createdEnvelope.id,
|
||||
envelopeItemId: envelopeItemId, // Todo: Envelopes
|
||||
envelopeItemId: oldEnvelopeItemToNewEnvelopeItemIdMap[templateField.envelopeItemId],
|
||||
type: templateField.type,
|
||||
page: templateField.page,
|
||||
positionX: templateField.positionX,
|
||||
@ -463,7 +489,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
const field = await tx.field.create({
|
||||
data: {
|
||||
envelopeId: createdEnvelope.id,
|
||||
envelopeItemId: envelopeItemId, // Todo: Envelopes
|
||||
envelopeItemId: oldEnvelopeItemToNewEnvelopeItemIdMap[templateField.envelopeItemId],
|
||||
recipientId: createdDirectRecipient.id,
|
||||
type: templateField.type,
|
||||
page: templateField.page,
|
||||
|
||||
@ -41,6 +41,8 @@ import {
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { extractDerivedDocumentMeta } from '../../utils/document';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import {
|
||||
@ -77,7 +79,17 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
}[];
|
||||
folderId?: string;
|
||||
prefillFields?: TFieldMetaPrefillFieldsSchema[];
|
||||
customDocumentDataId?: string;
|
||||
|
||||
customDocumentData?: {
|
||||
documentDataId: string;
|
||||
|
||||
/**
|
||||
* The envelope item ID which will be updated to use the custom document data.
|
||||
*
|
||||
* If undefined, will use the first envelope item. This is done for backwards compatibility reasons.
|
||||
*/
|
||||
envelopeItemId?: string;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* Values that will override the predefined values in the template.
|
||||
@ -278,7 +290,7 @@ export const createDocumentFromTemplate = async ({
|
||||
userId,
|
||||
teamId,
|
||||
recipients,
|
||||
customDocumentDataId,
|
||||
customDocumentData = [],
|
||||
override,
|
||||
requestMetadata,
|
||||
folderId,
|
||||
@ -331,11 +343,11 @@ export const createDocumentFromTemplate = async ({
|
||||
}
|
||||
|
||||
const legacyTemplateId = mapSecondaryIdToTemplateId(template.secondaryId);
|
||||
const finalEnvelopeTitle = override?.title || template.title;
|
||||
|
||||
// Todo: Envelopes
|
||||
if (template.envelopeItems.length !== 1) {
|
||||
if (template.envelopeItems.length < 1) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Template must have exactly 1 envelope item',
|
||||
message: 'Template must have at least 1 envelope item',
|
||||
});
|
||||
}
|
||||
|
||||
@ -376,32 +388,73 @@ export const createDocumentFromTemplate = async ({
|
||||
};
|
||||
});
|
||||
|
||||
// Todo: Envelopes
|
||||
let parentDocumentData = template.envelopeItems[0].documentData;
|
||||
const firstEnvelopeItemId = template.envelopeItems[0].id;
|
||||
|
||||
if (customDocumentDataId) {
|
||||
const customDocumentData = await prisma.documentData.findFirst({
|
||||
where: {
|
||||
id: customDocumentDataId,
|
||||
},
|
||||
});
|
||||
// Key = original envelope item ID
|
||||
// Value = duplicated envelope item ID.
|
||||
const oldEnvelopeItemToNewEnvelopeItemIdMap: Record<string, string> = {};
|
||||
|
||||
if (!customDocumentData) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Custom document data not found',
|
||||
// Duplicate the envelope item data.
|
||||
// Todo: Envelopes - Ask if it's okay to just use the documentDataId? Or should it be duplicated?
|
||||
// Note: This is duplicated in createDocumentFromDirectTemplate
|
||||
const envelopeItemsToCreate = await Promise.all(
|
||||
template.envelopeItems.map(async (item, i) => {
|
||||
let documentDataIdToDuplicate = item.documentDataId;
|
||||
|
||||
const foundCustomDocumentData = customDocumentData.find(
|
||||
(customDocumentDataItem) =>
|
||||
customDocumentDataItem.envelopeItemId || firstEnvelopeItemId === item.id,
|
||||
);
|
||||
|
||||
if (foundCustomDocumentData) {
|
||||
documentDataIdToDuplicate = foundCustomDocumentData.documentDataId;
|
||||
}
|
||||
|
||||
const documentDataToDuplicate = await prisma.documentData.findFirst({
|
||||
where: {
|
||||
id: documentDataIdToDuplicate,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
parentDocumentData = customDocumentData;
|
||||
}
|
||||
if (!documentDataToDuplicate) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document data not found',
|
||||
});
|
||||
}
|
||||
|
||||
const documentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: parentDocumentData.type,
|
||||
data: parentDocumentData.data,
|
||||
initialData: parentDocumentData.initialData,
|
||||
},
|
||||
});
|
||||
const buffer = await getFileServerSide(documentDataToDuplicate);
|
||||
|
||||
// Todo: Envelopes [PRE-MAIN] - Should we normalize? Should be part of the upload.
|
||||
// const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||
|
||||
const titleToUse = item.title || finalEnvelopeTitle;
|
||||
|
||||
const duplicatedFile = await putPdfFileServerSide({
|
||||
name: titleToUse,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(buffer),
|
||||
});
|
||||
|
||||
const newDocumentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: duplicatedFile.type,
|
||||
data: duplicatedFile.data,
|
||||
initialData: duplicatedFile.initialData,
|
||||
},
|
||||
});
|
||||
|
||||
const newEnvelopeItemId = prefixedId('envelope_item');
|
||||
|
||||
oldEnvelopeItemToNewEnvelopeItemIdMap[item.id] = newEnvelopeItemId;
|
||||
|
||||
return {
|
||||
id: newEnvelopeItemId,
|
||||
title: titleToUse.endsWith('.pdf') ? titleToUse.slice(0, -4) : titleToUse,
|
||||
documentDataId: newDocumentData.id,
|
||||
order: item.order !== undefined ? item.order : i + 1,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const incrementedDocumentId = await incrementDocumentId();
|
||||
|
||||
@ -433,6 +486,7 @@ export const createDocumentFromTemplate = async ({
|
||||
id: prefixedId('envelope'),
|
||||
secondaryId: incrementedDocumentId.formattedDocumentId,
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
internalVersion: template.internalVersion,
|
||||
qrToken: prefixedId('qr'),
|
||||
source: DocumentSource.TEMPLATE,
|
||||
externalId: externalId || template.externalId,
|
||||
@ -440,12 +494,10 @@ export const createDocumentFromTemplate = async ({
|
||||
userId,
|
||||
folderId,
|
||||
teamId: template.teamId,
|
||||
title: override?.title || template.title,
|
||||
title: finalEnvelopeTitle,
|
||||
envelopeItems: {
|
||||
create: {
|
||||
id: prefixedId('envelope_item'),
|
||||
title: override?.title || template.title,
|
||||
documentDataId: documentData.id,
|
||||
createMany: {
|
||||
data: envelopeItemsToCreate,
|
||||
},
|
||||
},
|
||||
authOptions: createDocumentAuthOptions({
|
||||
@ -495,8 +547,6 @@ export const createDocumentFromTemplate = async ({
|
||||
},
|
||||
});
|
||||
|
||||
const envelopeItemId = envelope.envelopeItems[0].id;
|
||||
|
||||
let fieldsToCreate: Omit<Field, 'id' | 'secondaryId'>[] = [];
|
||||
|
||||
// Get all template field IDs first so we can validate later
|
||||
@ -552,8 +602,8 @@ export const createDocumentFromTemplate = async ({
|
||||
const prefillField = prefillFields?.find((value) => value.id === field.id);
|
||||
|
||||
const payload = {
|
||||
envelopeItemId,
|
||||
envelopeId: envelope.id, // Todo: Envelopes
|
||||
envelopeItemId: oldEnvelopeItemToNewEnvelopeItemIdMap[field.envelopeItemId],
|
||||
envelopeId: envelope.id,
|
||||
recipientId: recipient.id,
|
||||
type: field.type,
|
||||
page: field.page,
|
||||
|
||||
@ -107,5 +107,6 @@ export const createTemplateDirectLink = async ({
|
||||
enabled: createdDirectLink.enabled,
|
||||
directTemplateRecipientId: createdDirectLink.directTemplateRecipientId,
|
||||
templateId: mapSecondaryIdToTemplateId(envelope.secondaryId),
|
||||
envelopeId: envelope.id,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,20 +2,18 @@ import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { type EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
|
||||
export type DeleteTemplateOptions = {
|
||||
id: number;
|
||||
id: EnvelopeIdOptions;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const deleteTemplate = async ({ id, userId, teamId }: DeleteTemplateOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id,
|
||||
},
|
||||
id,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId,
|
||||
teamId,
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
import { DocumentSource, EnvelopeType } from '@prisma/client';
|
||||
import { omit } from 'remeda';
|
||||
|
||||
import { nanoid, prefixedId } from '@documenso/lib/universal/id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { type EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
import { incrementTemplateId } from '../envelope/increment-id';
|
||||
|
||||
export type DuplicateTemplateOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
id: EnvelopeIdOptions;
|
||||
};
|
||||
|
||||
export const duplicateTemplate = async ({ id, userId, teamId }: DuplicateTemplateOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: envelopeWhereInput,
|
||||
include: {
|
||||
recipients: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
role: true,
|
||||
signingOrder: true,
|
||||
fields: true,
|
||||
},
|
||||
},
|
||||
envelopeItems: {
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
const { formattedTemplateId } = await incrementTemplateId();
|
||||
|
||||
const createdDocumentMeta = await prisma.documentMeta.create({
|
||||
data: {
|
||||
...omit(envelope.documentMeta, ['id']),
|
||||
emailSettings: envelope.documentMeta.emailSettings || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const duplicatedEnvelope = await prisma.envelope.create({
|
||||
data: {
|
||||
id: prefixedId('envelope'),
|
||||
secondaryId: formattedTemplateId,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId,
|
||||
teamId,
|
||||
title: envelope.title + ' (copy)',
|
||||
documentMetaId: createdDocumentMeta.id,
|
||||
authOptions: envelope.authOptions || undefined,
|
||||
visibility: envelope.visibility,
|
||||
source: DocumentSource.DOCUMENT, // Todo: Migration what to use here.
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Key = original envelope item ID
|
||||
// Value = duplicated envelope item ID.
|
||||
const oldEnvelopeItemToNewEnvelopeItemIdMap: Record<string, string> = {};
|
||||
|
||||
// Duplicate the envelope items.
|
||||
await Promise.all(
|
||||
envelope.envelopeItems.map(async (envelopeItem) => {
|
||||
const duplicatedDocumentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: envelopeItem.documentData.type,
|
||||
data: envelopeItem.documentData.initialData,
|
||||
initialData: envelopeItem.documentData.initialData,
|
||||
},
|
||||
});
|
||||
|
||||
const duplicatedEnvelopeItem = await prisma.envelopeItem.create({
|
||||
data: {
|
||||
id: prefixedId('envelope_item'),
|
||||
title: envelopeItem.title,
|
||||
envelopeId: duplicatedEnvelope.id,
|
||||
documentDataId: duplicatedDocumentData.id,
|
||||
},
|
||||
});
|
||||
|
||||
oldEnvelopeItemToNewEnvelopeItemIdMap[envelopeItem.id] = duplicatedEnvelopeItem.id;
|
||||
}),
|
||||
);
|
||||
|
||||
for (const recipient of envelope.recipients) {
|
||||
await prisma.recipient.create({
|
||||
data: {
|
||||
envelopeId: duplicatedEnvelope.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
fields: {
|
||||
createMany: {
|
||||
data: recipient.fields.map((field) => ({
|
||||
envelopeId: duplicatedEnvelope.id,
|
||||
envelopeItemId: oldEnvelopeItemToNewEnvelopeItemIdMap[field.envelopeItemId],
|
||||
type: field.type,
|
||||
page: field.page,
|
||||
positionX: field.positionX,
|
||||
positionY: field.positionY,
|
||||
width: field.width,
|
||||
height: field.height,
|
||||
customText: '',
|
||||
inserted: false,
|
||||
fieldMeta: field.fieldMeta as PrismaJson.FieldMeta,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return duplicatedEnvelope;
|
||||
};
|
||||
@ -58,6 +58,7 @@ export const getTemplateByDirectLinkToken = async ({
|
||||
// Backwards compatibility mapping.
|
||||
return {
|
||||
id: mapSecondaryIdToTemplateId(envelope.secondaryId),
|
||||
envelopeId: envelope.id,
|
||||
type: envelope.templateType,
|
||||
visibility: envelope.visibility,
|
||||
externalId: envelope.externalId,
|
||||
@ -70,7 +71,10 @@ export const getTemplateByDirectLinkToken = async ({
|
||||
publicTitle: envelope.publicTitle,
|
||||
publicDescription: envelope.publicDescription,
|
||||
folderId: envelope.folderId,
|
||||
templateDocumentData: firstDocumentData,
|
||||
templateDocumentData: {
|
||||
...firstDocumentData,
|
||||
envelopeItemId: envelope.envelopeItems[0].id,
|
||||
},
|
||||
directLink,
|
||||
templateMeta: envelope.documentMeta,
|
||||
recipients: recipientsWithMappedFields,
|
||||
|
||||
@ -3,21 +3,19 @@ import { EnvelopeType } from '@prisma/client';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
import { mapSecondaryIdToTemplateId } from '../../utils/envelope';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
|
||||
export type GetTemplateByIdOptions = {
|
||||
id: number;
|
||||
id: EnvelopeIdOptions;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id,
|
||||
},
|
||||
id,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId,
|
||||
teamId,
|
||||
@ -30,6 +28,7 @@ export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOpt
|
||||
documentMeta: true,
|
||||
envelopeItems: {
|
||||
select: {
|
||||
id: true,
|
||||
documentData: true,
|
||||
},
|
||||
},
|
||||
@ -52,7 +51,6 @@ export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOpt
|
||||
});
|
||||
}
|
||||
|
||||
// Todo: Envelopes
|
||||
const firstTemplateDocumentData = envelope.envelopeItems[0].documentData;
|
||||
|
||||
if (!firstTemplateDocumentData) {
|
||||
@ -68,8 +66,12 @@ export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOpt
|
||||
|
||||
return {
|
||||
...rest,
|
||||
envelopeId: envelope.id,
|
||||
type: envelope.templateType,
|
||||
templateDocumentData: firstTemplateDocumentData,
|
||||
templateDocumentData: {
|
||||
...firstTemplateDocumentData,
|
||||
envelopeItemId: envelope.envelopeItems[0].id,
|
||||
},
|
||||
templateMeta: envelope.documentMeta,
|
||||
fields: envelope.fields.map((field) => ({
|
||||
...field,
|
||||
|
||||
@ -68,5 +68,6 @@ export const toggleTemplateDirectLink = async ({
|
||||
enabled: updatedDirectLink.enabled,
|
||||
directTemplateRecipientId: updatedDirectLink.directTemplateRecipientId,
|
||||
templateId: mapSecondaryIdToTemplateId(envelope.secondaryId),
|
||||
envelopeId: envelope.id,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,185 +0,0 @@
|
||||
import type { Prisma, TemplateType } from '@prisma/client';
|
||||
import {
|
||||
type DocumentMeta,
|
||||
type DocumentVisibility,
|
||||
EnvelopeType,
|
||||
FolderType,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { TEAM_DOCUMENT_VISIBILITY_MAP } from '../../constants/teams';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||
import { createDocumentAuthOptions, extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
|
||||
export type UpdateTemplateOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
templateId: number;
|
||||
data?: {
|
||||
title?: string;
|
||||
folderId?: string | null;
|
||||
externalId?: string | null;
|
||||
visibility?: DocumentVisibility;
|
||||
globalAccessAuth?: TDocumentAccessAuthTypes[];
|
||||
globalActionAuth?: TDocumentActionAuthTypes[];
|
||||
publicTitle?: string;
|
||||
publicDescription?: string;
|
||||
type?: TemplateType;
|
||||
useLegacyFieldInsertion?: boolean;
|
||||
};
|
||||
meta?: Partial<Omit<DocumentMeta, 'id' | 'templateId'>>;
|
||||
};
|
||||
|
||||
export const updateTemplate = async ({
|
||||
userId,
|
||||
teamId,
|
||||
templateId,
|
||||
meta = {},
|
||||
data = {},
|
||||
}: UpdateTemplateOptions) => {
|
||||
const { envelopeWhereInput, team } = await getEnvelopeWhereInput({
|
||||
id: {
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: envelopeWhereInput,
|
||||
include: {
|
||||
documentMeta: true,
|
||||
team: {
|
||||
select: {
|
||||
organisationId: true,
|
||||
organisation: {
|
||||
select: {
|
||||
organisationClaim: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (Object.values(data).length === 0 && Object.keys(meta).length === 0) {
|
||||
return envelope;
|
||||
}
|
||||
|
||||
const { documentAuthOption } = extractDocumentAuthMethods({
|
||||
documentAuth: envelope.authOptions,
|
||||
});
|
||||
|
||||
const documentGlobalAccessAuth = documentAuthOption?.globalAccessAuth ?? null;
|
||||
const documentGlobalActionAuth = documentAuthOption?.globalActionAuth ?? null;
|
||||
|
||||
// If the new global auth values aren't passed in, fallback to the current document values.
|
||||
const newGlobalAccessAuth =
|
||||
data?.globalAccessAuth === undefined ? documentGlobalAccessAuth : data.globalAccessAuth;
|
||||
const newGlobalActionAuth =
|
||||
data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
|
||||
|
||||
// Check if user has permission to set the global action auth.
|
||||
if (newGlobalActionAuth.length > 0 && !envelope.team.organisation.organisationClaim.flags.cfr21) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have permission to set the action auth',
|
||||
});
|
||||
}
|
||||
|
||||
const authOptions = createDocumentAuthOptions({
|
||||
globalAccessAuth: newGlobalAccessAuth,
|
||||
globalActionAuth: newGlobalActionAuth,
|
||||
});
|
||||
|
||||
const emailId = meta.emailId;
|
||||
|
||||
// Validate the emailId belongs to the organisation.
|
||||
if (emailId) {
|
||||
const email = await prisma.organisationEmail.findFirst({
|
||||
where: {
|
||||
id: emailId,
|
||||
organisationId: envelope.team.organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!email) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Email not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let folderUpdateQuery: Prisma.FolderUpdateOneWithoutEnvelopesNestedInput | undefined = undefined;
|
||||
|
||||
// Validate folder ID.
|
||||
if (data.folderId) {
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: data.folderId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type: FolderType.TEMPLATE,
|
||||
visibility: {
|
||||
in: TEAM_DOCUMENT_VISIBILITY_MAP[team.currentTeamRole],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!folder) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
|
||||
folderUpdateQuery = {
|
||||
connect: {
|
||||
id: data.folderId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Move template to root folder if folderId is null.
|
||||
if (data.folderId === null) {
|
||||
folderUpdateQuery = {
|
||||
disconnect: true,
|
||||
};
|
||||
}
|
||||
|
||||
return await prisma.envelope.update({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
},
|
||||
data: {
|
||||
templateType: data?.type,
|
||||
title: data?.title,
|
||||
externalId: data?.externalId,
|
||||
visibility: data?.visibility,
|
||||
publicDescription: data?.publicDescription,
|
||||
publicTitle: data?.publicTitle,
|
||||
useLegacyFieldInsertion: data?.useLegacyFieldInsertion,
|
||||
folder: folderUpdateQuery,
|
||||
authOptions,
|
||||
documentMeta: {
|
||||
update: {
|
||||
...meta,
|
||||
emailSettings: meta?.emailSettings || undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user