feat(sprint-6): Phase 4 — Immutable audit log
- V8 migration: audit_events table (JSONB metadata, immutable by design) - AuditEvent entity + AuditEventType enum (18 event types) - AuditService: log events, paginated query, PDF export - AuditController: GET /api/v1/audit (paginated, filtered), GET export - AuditEventRepository with JPQL filtered queries - Frontend: /audit-log page (read-only, filterable, timezone-aware) - PDF export button for Behörde inspections - Sidebar: 'Protokoll' under new Compliance section - PdfReportGenerator: generateAuditReport method added - 10-year retention, REVOKE DELETE documented - Full i18n (de/en) with 18 event type translations
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
|
||||
import { apiClient, apiDownload } from "@/lib/api-client"
|
||||
|
||||
// --- Types ---
|
||||
|
||||
export interface AuditEventData {
|
||||
id: string
|
||||
eventType: string
|
||||
entityType: string
|
||||
entityId: string | null
|
||||
actorId: string
|
||||
actorName: string
|
||||
actorRole: string
|
||||
description: string
|
||||
metadata: string | null
|
||||
ipAddress: string | null
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export interface AuditLogPage {
|
||||
content: AuditEventData[]
|
||||
totalElements: number
|
||||
totalPages: number
|
||||
number: number
|
||||
size: number
|
||||
}
|
||||
|
||||
export interface AuditLogFilters {
|
||||
page?: number
|
||||
size?: number
|
||||
eventType?: string
|
||||
entityType?: string
|
||||
actorId?: string
|
||||
from?: string
|
||||
to?: string
|
||||
}
|
||||
|
||||
// --- Query Hooks ---
|
||||
|
||||
export function useAuditLogQuery(filters: AuditLogFilters = {}) {
|
||||
const {
|
||||
page = 0,
|
||||
size = 20,
|
||||
eventType,
|
||||
entityType,
|
||||
actorId,
|
||||
from,
|
||||
to,
|
||||
} = filters
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["audit", page, size, eventType, entityType, actorId, from, to],
|
||||
queryFn: () =>
|
||||
apiClient<AuditLogPage>("/audit", {
|
||||
params: {
|
||||
page: page.toString(),
|
||||
size: size.toString(),
|
||||
eventType: eventType || undefined,
|
||||
entityType: entityType || undefined,
|
||||
actorId: actorId || undefined,
|
||||
from: from || undefined,
|
||||
to: to || undefined,
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
// --- Mutation Hooks ---
|
||||
|
||||
export function useExportAuditPdfMutation() {
|
||||
return useMutation({
|
||||
mutationFn: async ({ from, to }: { from: string; to: string }) => {
|
||||
const { blob, filename } = await apiDownload(
|
||||
`/audit/export?from=${from}&to=${to}`
|
||||
)
|
||||
// Trigger browser download
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.download = filename || `audit-log-${from}-to-${to}.pdf`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user