UNPKG

@damourlabs/euro-clinical-trials

Version:

A Nuxt 3 application for visualizing and managing clinical trials data in Europe, built with modern web technologies.

142 lines (126 loc) 5.37 kB
// Drizzle ORM schema for Audit System tables import { pgTable, uuid, timestamp, jsonb, inet, text, index, pgEnum } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; import { z } from 'zod'; import type { Expect, IsSameType } from '~/types/equality'; import { trials, users, patients, gdprConsents } from '.'; import { AuditAction, AuditEntityType, LegalBasisEnum } from './enums'; export const auditFieldDefaultValues = { action: AuditAction.Enum.View, entityType: AuditEntityType.Enum.Trial, }; export const pgAction = pgEnum('audit_action_enum', AuditAction.options); export const pgEntityType = pgEnum('audit_entity_type_enum', AuditEntityType.options); const ValuesShapeSchema = z.object({}).catchall(z.unknown()) const ValueChangesSchema = z.object({ field: z.string().describe("The field that was changed"), oldValue: z.unknown().nullable().describe("The value before the change"), newValue: z.unknown().nullable().describe("The value after the change"), }).describe("Details of a single field change"); type ValuesShape = z.infer<typeof ValuesShapeSchema>; type ValueChangesShape = z.infer<typeof ValueChangesSchema>; export const auditLogs = pgTable('audit_logs', { uuid: uuid('uuid').primaryKey().defaultRandom(), trialUuid: uuid('trial_uuid').references(() => trials.uuid, { onDelete: 'cascade' }).notNull(), userUuid: uuid('user_uuid').references(() => users.uuid, { onDelete: 'set null' }), action: pgAction("audit_action").notNull().default(auditFieldDefaultValues.action), entityType: pgEntityType("audit_entity_type").notNull().default(auditFieldDefaultValues.entityType), entityUuid: uuid('entity_uuid').notNull(), oldValues: jsonb('old_values').$type<ValuesShape>(), newValues: jsonb('new_values').$type<ValuesShape>(), changes: jsonb('changes').$type<ValueChangesShape>().notNull().default(sql`'[]'::jsonb`), timestamp: timestamp('timestamp', { withTimezone: true }).notNull().defaultNow(), ipAddress: inet('ip_address'), userAgent: text('user_agent'), sessionId: uuid('session_id'), }, (table) => [ index('idx_audit_logs_timestamp').on(table.timestamp), index('idx_audit_logs_entity').on(table.entityType, table.entityUuid), ]); // Zod schemas for validation export const auditLogsSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the audit log entry, automatically generated if not provided") , trialUuid: z.string() .uuid() .describe("UUID of the trial this audit log relates to"), userUuid: z.string() .uuid() .nullable() .describe("UUID of the user who performed the action"), action: AuditAction .describe("Action that was performed") .default(auditFieldDefaultValues.action), entityType: AuditEntityType .describe("Type of entity that was affected") .default(auditFieldDefaultValues.entityType), entityUuid: z.string() .uuid() .describe("UUID of the entity that was affected"), oldValues: ValuesShapeSchema .nullable() .describe("Old values before the change"), newValues: ValuesShapeSchema .nullable() .describe("New values after the change"), changes: ValueChangesSchema .describe("Summary of changes made"), timestamp: z.date() .describe("Timestamp when the action was performed"), ipAddress: z.string() .nullable() .describe("IP address of the user who performed the action"), userAgent: z.string() .nullable() .describe("User agent of the client that performed the action"), sessionId: z.string() .uuid() .nullable() .describe("Session ID of the user who performed the action"), }); const auditLogDetailsDefaultValues = { legalBasis: LegalBasisEnum.Enum.Legal_obligation }; export const pgLegalBasis = pgEnum('legal_basis_enum', LegalBasisEnum.options); export const auditLogDetails = pgTable('audit_log_details', { uuid: uuid('uuid').primaryKey().defaultRandom(), auditLogUuid: uuid('audit_log_uuid').notNull().unique().references(() => auditLogs.uuid, { onDelete: 'cascade' }), consentUuid: uuid('consent_uuid').references(() => gdprConsents.uuid, { onDelete: 'cascade' }), patientUuid: uuid('patient_uuid').references(() => patients.uuid, { onDelete: 'cascade' }), legalBasis: pgLegalBasis("legal_basis").notNull().default(auditLogDetailsDefaultValues.legalBasis) }); export const auditLogDetailsSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the audit log details, automatically generated if not provided") , auditLogUuid: z.string() .uuid() .describe("UUID of the audit log this detail belongs to"), consentUuid: z.string() .uuid() .nullable() .describe("UUID of the GDPR consent related to this audit"), patientUuid: z.string() .uuid() .nullable() .describe("UUID of the patient related to this audit"), legalBasis: LegalBasisEnum .describe("Legal basis for the audit action"), }); export type AuditLog = z.infer<typeof auditLogsSchema>; export type AuditLogDetail = z.infer<typeof auditLogDetailsSchema>; // TODO: Fix type mismatches between Zod schemas and Drizzle table types type _auditLogSchemaMatchesTable = Expect<IsSameType<AuditLog, typeof auditLogs.$inferSelect>> type _auditLogDetailSchemaMatchesTable = Expect<IsSameType<AuditLogDetail, typeof auditLogDetails.$inferSelect>>