From 6980db57d3a2d2251aafcc7a62a7ddc29094b58e Mon Sep 17 00:00:00 2001 From: Catalin Documenso Date: Wed, 30 Apr 2025 15:53:58 +0300 Subject: [PATCH] feat: enhance document attachment handling and audit logging - Added support for attachment updates in the updateDocument functionc. - Introduced new audit log type for document attachments updates. - Updated ZDocumentAuditLog schemas to include attachment-related events. - Modified AddSettingsFormPartial to handle attachment IDs and types correctly. - Set default value for attachment type in the Prisma schema. --- .../server-only/document/update-document.ts | 59 ++++++++++++++++--- packages/lib/types/document-audit-logs.ts | 25 ++++++++ packages/lib/types/document.ts | 1 - packages/lib/utils/document-audit-logs.ts | 4 ++ .../migration.sql | 2 + packages/prisma/schema.prisma | 2 +- .../primitives/document-flow/add-settings.tsx | 31 ++++++++-- .../document-flow/add-settings.types.ts | 1 + 8 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 packages/prisma/migrations/20250430063330_add_default_value_attachment_type/migration.sql diff --git a/packages/lib/server-only/document/update-document.ts b/packages/lib/server-only/document/update-document.ts index bf7a3b687..490f72af4 100644 --- a/packages/lib/server-only/document/update-document.ts +++ b/packages/lib/server-only/document/update-document.ts @@ -69,6 +69,7 @@ export const updateDocument = async ({ }, }, }, + attachments: true, }, }); @@ -160,6 +161,8 @@ export const updateDocument = async ({ documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth; const isDocumentVisibilitySame = data.visibility === undefined || data.visibility === document.visibility; + const isAttachmentsSame = + data.attachments === undefined || data.attachments === document.attachments; const auditLogs: CreateDocumentAuditLogDataResponse[] = []; @@ -239,6 +242,20 @@ export const updateDocument = async ({ ); } + if (data.attachments) { + auditLogs.push( + createDocumentAuditLogData({ + type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_ATTACHMENTS_UPDATED, + documentId, + metadata: requestMetadata, + data: { + from: document.attachments, + to: data.attachments, + }, + }), + ); + } + // Early return if nothing is required. if (auditLogs.length === 0 && data.useLegacyFieldInsertion === undefined) { return document; @@ -260,18 +277,42 @@ export const updateDocument = async ({ visibility: data.visibility as DocumentVisibility, useLegacyFieldInsertion: data.useLegacyFieldInsertion, authOptions, - attachments: { - deleteMany: {}, - create: - data.attachments?.map((attachment) => ({ - type: 'LINK', - label: attachment.label, - url: attachment.url, - })) || [], - }, }, }); + if (data.attachments) { + await tx.attachment.deleteMany({ + where: { + documentId, + id: { + notIn: data.attachments.map((a) => a.id), + }, + }, + }); + + await Promise.all( + data.attachments.map( + async (attachment) => + await tx.attachment.upsert({ + where: { + id: attachment.id, + documentId, + }, + update: { + label: attachment.label, + url: attachment.url, + }, + create: { + id: attachment.id, + label: attachment.label, + url: attachment.url, + documentId, + }, + }), + ), + ); + } + await tx.documentAuditLog.createMany({ data: auditLogs, }); diff --git a/packages/lib/types/document-audit-logs.ts b/packages/lib/types/document-audit-logs.ts index cb7873834..cf2a0ecc7 100644 --- a/packages/lib/types/document-audit-logs.ts +++ b/packages/lib/types/document-audit-logs.ts @@ -39,6 +39,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([ 'DOCUMENT_TITLE_UPDATED', // When the document title is updated. 'DOCUMENT_EXTERNAL_ID_UPDATED', // When the document external ID is updated. 'DOCUMENT_MOVED_TO_TEAM', // When the document is moved to a team. + 'DOCUMENT_ATTACHMENTS_UPDATED', // When the document attachments are updated. ]); export const ZDocumentAuditLogEmailTypeSchema = z.enum([ @@ -551,6 +552,29 @@ export const ZDocumentAuditLogEventDocumentMovedToTeamSchema = z.object({ }), }); +/** + * Event: Document attachments updated. + */ +export const ZDocumentAuditLogEventDocumentAttachmentsUpdatedSchema = z.object({ + type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_ATTACHMENTS_UPDATED), + data: z.object({ + from: z.array( + z.object({ + id: z.string(), + label: z.string(), + url: z.string(), + }), + ), + to: z.array( + z.object({ + id: z.string(), + label: z.string(), + url: z.string(), + }), + ), + }), +}); + export const ZDocumentAuditLogBaseSchema = z.object({ id: z.string(), createdAt: z.date(), @@ -582,6 +606,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and( ZDocumentAuditLogEventDocumentSentSchema, ZDocumentAuditLogEventDocumentTitleUpdatedSchema, ZDocumentAuditLogEventDocumentExternalIdUpdatedSchema, + ZDocumentAuditLogEventDocumentAttachmentsUpdatedSchema, ZDocumentAuditLogEventFieldCreatedSchema, ZDocumentAuditLogEventFieldRemovedSchema, ZDocumentAuditLogEventFieldUpdatedSchema, diff --git a/packages/lib/types/document.ts b/packages/lib/types/document.ts index ba48f45a2..512b38fea 100644 --- a/packages/lib/types/document.ts +++ b/packages/lib/types/document.ts @@ -62,7 +62,6 @@ export const ZDocumentSchema = DocumentSchema.pick({ fields: ZFieldSchema.array(), attachments: AttachmentSchema.pick({ id: true, - type: true, label: true, url: true, }) diff --git a/packages/lib/utils/document-audit-logs.ts b/packages/lib/utils/document-audit-logs.ts index bc564370b..d0884c342 100644 --- a/packages/lib/utils/document-audit-logs.ts +++ b/packages/lib/utils/document-audit-logs.ts @@ -388,6 +388,10 @@ export const formatDocumentAuditLogAction = ( anonymous: msg`Document completed`, identified: msg`Document completed`, })) + .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_ATTACHMENTS_UPDATED }, ({ data }) => ({ + anonymous: msg`Document attachments updated`, + identified: msg`${prefix} updated the document attachments ${data.to.map((a) => a.label).join(', ')}`, + })) .exhaustive(); return { diff --git a/packages/prisma/migrations/20250430063330_add_default_value_attachment_type/migration.sql b/packages/prisma/migrations/20250430063330_add_default_value_attachment_type/migration.sql new file mode 100644 index 000000000..2414d1aa3 --- /dev/null +++ b/packages/prisma/migrations/20250430063330_add_default_value_attachment_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Attachment" ALTER COLUMN "type" SET DEFAULT 'LINK'; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 2d8c2c0c9..aa3c2fe85 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -323,7 +323,7 @@ enum AttachmentType { model Attachment { id String @id @default(uuid()) - type AttachmentType + type AttachmentType @default(LINK) label String url String createdAt DateTime @default(now()) diff --git a/packages/ui/primitives/document-flow/add-settings.tsx b/packages/ui/primitives/document-flow/add-settings.tsx index 1a3175c2b..c6920a3e5 100644 --- a/packages/ui/primitives/document-flow/add-settings.tsx +++ b/packages/ui/primitives/document-flow/add-settings.tsx @@ -97,9 +97,11 @@ export const AddSettingsFormPartial = ({ const defaultAttachments = [ { + id: '', formId: initialId, label: '', url: '', + type: 'LINK', }, ]; @@ -123,7 +125,12 @@ export const AddSettingsFormPartial = ({ language: document.documentMeta?.language ?? 'en', signatureTypes: extractTeamSignatureSettings(document.documentMeta), }, - attachments: document.attachments ?? defaultAttachments, + attachments: + document.attachments?.map((attachment) => ({ + ...attachment, + id: String(attachment.id), + formId: String(attachment.id), + })) ?? defaultAttachments, }, }); @@ -136,6 +143,22 @@ export const AddSettingsFormPartial = ({ name: 'attachments', }); + const onRemoveAttachment = (index: number) => { + const attachment = attachments[index]; + + const formStateIndex = + form.getValues('attachments')?.findIndex((a) => a.formId === attachment.formId) ?? -1; + + if (formStateIndex !== -1) { + removeAttachment(formStateIndex); + + const updatedAttachments = + form.getValues('attachments')?.filter((a) => a.formId !== attachment.formId) ?? []; + + form.setValue('attachments', updatedAttachments); + } + }; + const { stepIndex, currentStep, totalSteps, previousStep } = useStep(); const documentHasBeenSent = recipients.some( @@ -144,11 +167,11 @@ export const AddSettingsFormPartial = ({ const onAddAttachment = () => { appendAttachment({ + id: nanoid(12), formId: nanoid(12), label: '', url: '', - // fix this - id: '', + type: 'LINK', }); }; @@ -533,7 +556,7 @@ export const AddSettingsFormPartial = ({