mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 03:01:59 +10:00
feat: audit logS
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Team } from '@documenso/prisma/client';
|
||||
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData, diffRecipientChanges } from '../../utils/document-audit-logs';
|
||||
|
||||
export type SetRecipientExpiryOptions = {
|
||||
documentId: number;
|
||||
@ -70,7 +72,7 @@ export const setRecipientExpiry = async ({
|
||||
}
|
||||
|
||||
const updatedRecipient = await prisma.$transaction(async (tx) => {
|
||||
const updated = await tx.recipient.update({
|
||||
const persisted = await tx.recipient.update({
|
||||
where: {
|
||||
id: recipient.id,
|
||||
},
|
||||
@ -79,28 +81,31 @@ export const setRecipientExpiry = async ({
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: fix the audit logs
|
||||
// await tx.documentAuditLog.create({
|
||||
// data: createDocumentAuditLogData({
|
||||
// type: 'RECIPIENT_EXPIRY_UPDATED',
|
||||
// documentId,
|
||||
// user: {
|
||||
// id: team?.id ?? user.id,
|
||||
// email: team?.name ?? user.email,
|
||||
// name: team ? '' : user.name,
|
||||
// },
|
||||
// data: {
|
||||
// recipientEmail: recipient.email,
|
||||
// recipientName: recipient.name,
|
||||
// recipientId: recipient.id,
|
||||
// recipientRole: recipient.role,
|
||||
// expiry,
|
||||
// },
|
||||
// requestMetadata,
|
||||
// }),
|
||||
// });
|
||||
const changes = diffRecipientChanges(recipient, persisted);
|
||||
|
||||
return updated;
|
||||
if (changes.length > 0) {
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
||||
documentId: documentId,
|
||||
user: {
|
||||
id: team?.id ?? user.id,
|
||||
name: team?.name ?? user.name,
|
||||
email: team ? '' : user.email,
|
||||
},
|
||||
requestMetadata,
|
||||
data: {
|
||||
changes,
|
||||
recipientId,
|
||||
recipientEmail: persisted.email,
|
||||
recipientName: persisted.name,
|
||||
recipientRole: persisted.role,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return persisted;
|
||||
}
|
||||
});
|
||||
|
||||
return updatedRecipient;
|
||||
|
||||
@ -34,6 +34,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
|
||||
'DOCUMENT_META_UPDATED', // When the document meta data is updated.
|
||||
'DOCUMENT_OPENED', // When the document is opened by a recipient.
|
||||
'DOCUMENT_RECIPIENT_REJECTED', // When a recipient rejects the document.
|
||||
'DOCUMENT_RECIPIENT_EXPIRED', // When the recipient cannot access the document anymore.
|
||||
'DOCUMENT_RECIPIENT_COMPLETED', // When a recipient completes all their required tasks for the document.
|
||||
'DOCUMENT_SENT', // When the document transitions from DRAFT to PENDING.
|
||||
'DOCUMENT_TITLE_UPDATED', // When the document title is updated.
|
||||
@ -65,6 +66,7 @@ export const ZRecipientDiffTypeSchema = z.enum([
|
||||
'EMAIL',
|
||||
'ACCESS_AUTH',
|
||||
'ACTION_AUTH',
|
||||
'EXPIRY',
|
||||
]);
|
||||
|
||||
export const DOCUMENT_AUDIT_LOG_TYPE = ZDocumentAuditLogTypeSchema.Enum;
|
||||
@ -146,12 +148,17 @@ export const ZRecipientDiffEmailSchema = ZGenericFromToSchema.extend({
|
||||
type: z.literal(RECIPIENT_DIFF_TYPE.EMAIL),
|
||||
});
|
||||
|
||||
export const ZRecipientDiffExpirySchema = ZGenericFromToSchema.extend({
|
||||
type: z.literal(RECIPIENT_DIFF_TYPE.EXPIRY),
|
||||
});
|
||||
|
||||
export const ZDocumentAuditLogRecipientDiffSchema = z.discriminatedUnion('type', [
|
||||
ZRecipientDiffActionAuthSchema,
|
||||
ZRecipientDiffAccessAuthSchema,
|
||||
ZRecipientDiffNameSchema,
|
||||
ZRecipientDiffRoleSchema,
|
||||
ZRecipientDiffEmailSchema,
|
||||
ZRecipientDiffExpirySchema,
|
||||
]);
|
||||
|
||||
const ZBaseFieldEventDataSchema = z.object({
|
||||
@ -365,7 +372,7 @@ export const ZDocumentAuditLogEventDocumentRecipientCompleteSchema = z.object({
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document recipient completed the document (the recipient has fully actioned and completed their required steps for the document).
|
||||
* Event: Document recipient rejected the document
|
||||
*/
|
||||
export const ZDocumentAuditLogEventDocumentRecipientRejectedSchema = z.object({
|
||||
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED),
|
||||
@ -374,6 +381,14 @@ export const ZDocumentAuditLogEventDocumentRecipientRejectedSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Recipient expired
|
||||
*/
|
||||
export const ZDocumentAuditLogEventDocumentRecipientExpiredSchema = z.object({
|
||||
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_EXPIRED),
|
||||
data: ZBaseRecipientDataSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document sent.
|
||||
*/
|
||||
@ -499,6 +514,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
|
||||
ZDocumentAuditLogEventDocumentOpenedSchema,
|
||||
ZDocumentAuditLogEventDocumentRecipientCompleteSchema,
|
||||
ZDocumentAuditLogEventDocumentRecipientRejectedSchema,
|
||||
ZDocumentAuditLogEventDocumentRecipientExpiredSchema,
|
||||
ZDocumentAuditLogEventDocumentSentSchema,
|
||||
ZDocumentAuditLogEventDocumentTitleUpdatedSchema,
|
||||
ZDocumentAuditLogEventDocumentExternalIdUpdatedSchema,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { I18n } from '@lingui/core';
|
||||
import { type I18n, i18n } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import type { DocumentAuditLog, DocumentMeta, Field, Recipient } from '@documenso/prisma/client';
|
||||
@ -73,7 +74,7 @@ export const parseDocumentAuditLogData = (auditLog: DocumentAuditLog): TDocument
|
||||
return data.data;
|
||||
};
|
||||
|
||||
type PartialRecipient = Pick<Recipient, 'email' | 'name' | 'role' | 'authOptions'>;
|
||||
type PartialRecipient = Pick<Recipient, 'email' | 'name' | 'role' | 'authOptions' | 'expired'>;
|
||||
|
||||
export const diffRecipientChanges = (
|
||||
oldRecipient: PartialRecipient,
|
||||
@ -131,6 +132,18 @@ export const diffRecipientChanges = (
|
||||
});
|
||||
}
|
||||
|
||||
if (oldRecipient.expired !== newRecipient.expired) {
|
||||
diffs.push({
|
||||
type: RECIPIENT_DIFF_TYPE.EXPIRY,
|
||||
from: DateTime.fromJSDate(oldRecipient.expired!)
|
||||
.setLocale(i18n.locales?.[0] || i18n.locale)
|
||||
.toLocaleString(DateTime.DATETIME_FULL),
|
||||
to: DateTime.fromJSDate(newRecipient.expired!)
|
||||
.setLocale(i18n.locales?.[0] || i18n.locale)
|
||||
.toLocaleString(DateTime.DATETIME_FULL),
|
||||
});
|
||||
}
|
||||
|
||||
return diffs;
|
||||
};
|
||||
|
||||
@ -349,7 +362,7 @@ export const formatDocumentAuditLogAction = (
|
||||
identified: result,
|
||||
};
|
||||
})
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED }, ({ data }) => {
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED }, () => {
|
||||
const userName = prefix || _(msg`Recipient`);
|
||||
|
||||
const result = msg`${userName} rejected the document`;
|
||||
@ -359,6 +372,16 @@ export const formatDocumentAuditLogAction = (
|
||||
identified: result,
|
||||
};
|
||||
})
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_EXPIRED }, () => {
|
||||
const userName = prefix || _(msg`Recipient`);
|
||||
|
||||
const result = msg`${userName} expired`;
|
||||
|
||||
return {
|
||||
anonymous: result,
|
||||
identified: result,
|
||||
};
|
||||
})
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT }, ({ data }) => ({
|
||||
anonymous: data.isResending ? msg`Email resent` : msg`Email sent`,
|
||||
identified: data.isResending
|
||||
|
||||
@ -66,7 +66,7 @@ type DocumentExpiryDialogProps = {
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export default function DocumentExpiryDialog({
|
||||
export function DocumentExpiryDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
signer,
|
||||
@ -98,12 +98,12 @@ export default function DocumentExpiryDialog({
|
||||
const watchUnit = periodForm.watch('unit');
|
||||
|
||||
const { mutateAsync: setSignerExpiry, isLoading } = trpc.recipient.setSignerExpiry.useMutation({
|
||||
onSuccess: ({ expired }) => {
|
||||
onSuccess: (updatedRecipient) => {
|
||||
router.refresh();
|
||||
|
||||
periodForm.reset(
|
||||
expired
|
||||
? calculatePeriod(expired)
|
||||
updatedRecipient?.expired
|
||||
? calculatePeriod(updatedRecipient.expired)
|
||||
: {
|
||||
amount: undefined,
|
||||
unit: undefined,
|
||||
@ -112,7 +112,7 @@ export default function DocumentExpiryDialog({
|
||||
|
||||
dateForm.reset(
|
||||
{
|
||||
expiry: expired ?? undefined,
|
||||
expiry: updatedRecipient?.expired ?? undefined,
|
||||
},
|
||||
{
|
||||
keepValues: false,
|
||||
@ -167,8 +167,6 @@ export default function DocumentExpiryDialog({
|
||||
}
|
||||
}
|
||||
|
||||
console.log('finalll expiry date', expiryDate);
|
||||
|
||||
await setSignerExpiry({
|
||||
documentId,
|
||||
signerId: signer.nativeId,
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import type { TAddSignerSchema as Signer } from './add-signers.types';
|
||||
import DocumentExpiryDialog from './document-expiry-dialog';
|
||||
import { DocumentExpiryDialog } from './document-expiry-dialog';
|
||||
|
||||
type SignerActionDropdownProps = {
|
||||
onDelete: () => void;
|
||||
@ -29,6 +29,7 @@ export function SignerActionDropdown({
|
||||
className,
|
||||
signer,
|
||||
documentId,
|
||||
onDelete,
|
||||
}: SignerActionDropdownProps) {
|
||||
const [isExpiryDialogOpen, setExpiryDialogOpen] = useState(false);
|
||||
|
||||
@ -45,7 +46,7 @@ export function SignerActionDropdown({
|
||||
<Timer className="h-4 w-4" />
|
||||
Expiry
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled={deleteDisabled} className="gap-x-2">
|
||||
<DropdownMenuItem disabled={deleteDisabled} className="gap-x-2" onClick={onDelete}>
|
||||
<Trash className="h-4 w-4" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
||||
Reference in New Issue
Block a user