mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
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.
This commit is contained in:
@ -69,6 +69,7 @@ export const updateDocument = async ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
attachments: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,6 +161,8 @@ export const updateDocument = async ({
|
|||||||
documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth;
|
documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth;
|
||||||
const isDocumentVisibilitySame =
|
const isDocumentVisibilitySame =
|
||||||
data.visibility === undefined || data.visibility === document.visibility;
|
data.visibility === undefined || data.visibility === document.visibility;
|
||||||
|
const isAttachmentsSame =
|
||||||
|
data.attachments === undefined || data.attachments === document.attachments;
|
||||||
|
|
||||||
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
|
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.
|
// Early return if nothing is required.
|
||||||
if (auditLogs.length === 0 && data.useLegacyFieldInsertion === undefined) {
|
if (auditLogs.length === 0 && data.useLegacyFieldInsertion === undefined) {
|
||||||
return document;
|
return document;
|
||||||
@ -260,18 +277,42 @@ export const updateDocument = async ({
|
|||||||
visibility: data.visibility as DocumentVisibility,
|
visibility: data.visibility as DocumentVisibility,
|
||||||
useLegacyFieldInsertion: data.useLegacyFieldInsertion,
|
useLegacyFieldInsertion: data.useLegacyFieldInsertion,
|
||||||
authOptions,
|
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({
|
await tx.documentAuditLog.createMany({
|
||||||
data: auditLogs,
|
data: auditLogs,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
|
|||||||
'DOCUMENT_TITLE_UPDATED', // When the document title is updated.
|
'DOCUMENT_TITLE_UPDATED', // When the document title is updated.
|
||||||
'DOCUMENT_EXTERNAL_ID_UPDATED', // When the document external ID 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_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([
|
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({
|
export const ZDocumentAuditLogBaseSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
@ -582,6 +606,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
|
|||||||
ZDocumentAuditLogEventDocumentSentSchema,
|
ZDocumentAuditLogEventDocumentSentSchema,
|
||||||
ZDocumentAuditLogEventDocumentTitleUpdatedSchema,
|
ZDocumentAuditLogEventDocumentTitleUpdatedSchema,
|
||||||
ZDocumentAuditLogEventDocumentExternalIdUpdatedSchema,
|
ZDocumentAuditLogEventDocumentExternalIdUpdatedSchema,
|
||||||
|
ZDocumentAuditLogEventDocumentAttachmentsUpdatedSchema,
|
||||||
ZDocumentAuditLogEventFieldCreatedSchema,
|
ZDocumentAuditLogEventFieldCreatedSchema,
|
||||||
ZDocumentAuditLogEventFieldRemovedSchema,
|
ZDocumentAuditLogEventFieldRemovedSchema,
|
||||||
ZDocumentAuditLogEventFieldUpdatedSchema,
|
ZDocumentAuditLogEventFieldUpdatedSchema,
|
||||||
|
|||||||
@ -62,7 +62,6 @@ export const ZDocumentSchema = DocumentSchema.pick({
|
|||||||
fields: ZFieldSchema.array(),
|
fields: ZFieldSchema.array(),
|
||||||
attachments: AttachmentSchema.pick({
|
attachments: AttachmentSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
type: true,
|
|
||||||
label: true,
|
label: true,
|
||||||
url: true,
|
url: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -388,6 +388,10 @@ export const formatDocumentAuditLogAction = (
|
|||||||
anonymous: msg`Document completed`,
|
anonymous: msg`Document completed`,
|
||||||
identified: 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();
|
.exhaustive();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Attachment" ALTER COLUMN "type" SET DEFAULT 'LINK';
|
||||||
@ -323,7 +323,7 @@ enum AttachmentType {
|
|||||||
|
|
||||||
model Attachment {
|
model Attachment {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
type AttachmentType
|
type AttachmentType @default(LINK)
|
||||||
label String
|
label String
|
||||||
url String
|
url String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|||||||
@ -97,9 +97,11 @@ export const AddSettingsFormPartial = ({
|
|||||||
|
|
||||||
const defaultAttachments = [
|
const defaultAttachments = [
|
||||||
{
|
{
|
||||||
|
id: '',
|
||||||
formId: initialId,
|
formId: initialId,
|
||||||
label: '',
|
label: '',
|
||||||
url: '',
|
url: '',
|
||||||
|
type: 'LINK',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -123,7 +125,12 @@ export const AddSettingsFormPartial = ({
|
|||||||
language: document.documentMeta?.language ?? 'en',
|
language: document.documentMeta?.language ?? 'en',
|
||||||
signatureTypes: extractTeamSignatureSettings(document.documentMeta),
|
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',
|
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 { stepIndex, currentStep, totalSteps, previousStep } = useStep();
|
||||||
|
|
||||||
const documentHasBeenSent = recipients.some(
|
const documentHasBeenSent = recipients.some(
|
||||||
@ -144,11 +167,11 @@ export const AddSettingsFormPartial = ({
|
|||||||
|
|
||||||
const onAddAttachment = () => {
|
const onAddAttachment = () => {
|
||||||
appendAttachment({
|
appendAttachment({
|
||||||
|
id: nanoid(12),
|
||||||
formId: nanoid(12),
|
formId: nanoid(12),
|
||||||
label: '',
|
label: '',
|
||||||
url: '',
|
url: '',
|
||||||
// fix this
|
type: 'LINK',
|
||||||
id: '',
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -533,7 +556,7 @@ export const AddSettingsFormPartial = ({
|
|||||||
|
|
||||||
<div className="flex-none pt-8">
|
<div className="flex-none pt-8">
|
||||||
<button
|
<button
|
||||||
onClick={() => removeAttachment(index)}
|
onClick={() => onRemoveAttachment(index)}
|
||||||
className="hover:bg-muted rounded-md"
|
className="hover:bg-muted rounded-md"
|
||||||
>
|
>
|
||||||
<Trash className="h-4 w-4" />
|
<Trash className="h-4 w-4" />
|
||||||
|
|||||||
@ -63,6 +63,7 @@ export const ZAddSettingsFormSchema = z.object({
|
|||||||
id: true,
|
id: true,
|
||||||
label: true,
|
label: true,
|
||||||
url: true,
|
url: true,
|
||||||
|
type: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
formId: z.string().min(1),
|
formId: z.string().min(1),
|
||||||
|
|||||||
Reference in New Issue
Block a user