Files
documenso/packages/lib/server-only/field/create-document-fields.ts
2025-01-11 15:33:20 +11:00

149 lines
4.1 KiB
TypeScript

import { z } from 'zod';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import type { FieldType } from '@documenso/prisma/client';
import { FieldSchema } from '@documenso/prisma/generated/zod';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { canRecipientFieldsBeModified } from '../../utils/recipients';
export interface CreateDocumentFieldsOptions {
userId: number;
teamId?: number;
documentId: number;
fields: {
recipientId: number;
type: FieldType;
pageNumber: number;
pageX: number;
pageY: number;
width: number;
height: number;
fieldMeta?: TFieldMetaSchema;
}[];
requestMetadata: ApiRequestMetadata;
}
export const ZCreateDocumentFieldsResponseSchema = z.object({
fields: z.array(FieldSchema),
});
export type TCreateDocumentFieldsResponse = z.infer<typeof ZCreateDocumentFieldsResponseSchema>;
export const createDocumentFields = async ({
userId,
teamId,
documentId,
fields,
requestMetadata,
}: CreateDocumentFieldsOptions): Promise<TCreateDocumentFieldsResponse> => {
const document = await prisma.document.findFirst({
where: {
id: documentId,
...(teamId
? {
team: {
id: teamId,
members: {
some: {
userId,
},
},
},
}
: {
userId,
teamId: null,
}),
},
include: {
Recipient: true,
Field: true,
},
});
if (!document) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
if (document.completedAt) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Document already complete',
});
}
// Field validation.
const validatedFields = fields.map((field) => {
const recipient = document.Recipient.find((recipient) => recipient.id === field.recipientId);
// Each field MUST have a recipient associated with it.
if (!recipient) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: `Recipient ${field.recipientId} not found`,
});
}
// Check whether the recipient associated with the field can have new fields created.
if (!canRecipientFieldsBeModified(recipient, document.Field)) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message:
'Recipient type cannot have fields, or they have already interacted with the document.',
});
}
return {
...field,
recipientEmail: recipient.email,
};
});
const createdFields = await prisma.$transaction(async (tx) => {
return await Promise.all(
validatedFields.map(async (field) => {
const createdField = await tx.field.create({
data: {
type: field.type,
page: field.pageNumber,
positionX: field.pageX,
positionY: field.pageY,
width: field.width,
height: field.height,
customText: '',
inserted: false,
fieldMeta: field.fieldMeta,
documentId,
recipientId: field.recipientId,
},
});
// Handle field created audit log.
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
documentId,
metadata: requestMetadata,
data: {
fieldId: createdField.secondaryId,
fieldRecipientEmail: field.recipientEmail,
fieldRecipientId: createdField.recipientId,
fieldType: createdField.type,
},
}),
});
return createdField;
}),
);
});
return {
fields: createdFields,
};
};