- Database: kanzlai.audit_log table with RLS, append-only policies (no UPDATE/DELETE), indexes for entity, user, and time queries - Backend: AuditService.Log() with context-based tenant/user/IP/UA extraction, wired into all 7 services (case, deadline, appointment, document, note, party, tenant) - API: GET /api/audit-log with entity_type, entity_id, user_id, from/to date, and pagination filters - Frontend: Protokoll tab on case detail page with chronological audit entries, diff preview, and pagination Required by § 50 BRAO and DSGVO Art. 5(2).
64 lines
1.4 KiB
Go
64 lines
1.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
)
|
|
|
|
type AuditLogHandler struct {
|
|
svc *services.AuditService
|
|
}
|
|
|
|
func NewAuditLogHandler(svc *services.AuditService) *AuditLogHandler {
|
|
return &AuditLogHandler{svc: svc}
|
|
}
|
|
|
|
func (h *AuditLogHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
q := r.URL.Query()
|
|
page, _ := strconv.Atoi(q.Get("page"))
|
|
limit, _ := strconv.Atoi(q.Get("limit"))
|
|
|
|
filter := services.AuditFilter{
|
|
EntityType: q.Get("entity_type"),
|
|
From: q.Get("from"),
|
|
To: q.Get("to"),
|
|
Page: page,
|
|
Limit: limit,
|
|
}
|
|
|
|
if idStr := q.Get("entity_id"); idStr != "" {
|
|
if id, err := uuid.Parse(idStr); err == nil {
|
|
filter.EntityID = &id
|
|
}
|
|
}
|
|
if idStr := q.Get("user_id"); idStr != "" {
|
|
if id, err := uuid.Parse(idStr); err == nil {
|
|
filter.UserID = &id
|
|
}
|
|
}
|
|
|
|
entries, total, err := h.svc.List(r.Context(), tenantID, filter)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to fetch audit log")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"entries": entries,
|
|
"total": total,
|
|
"page": filter.Page,
|
|
"limit": filter.Limit,
|
|
})
|
|
}
|