feat: append-only audit trail for all mutations (P0)
This commit is contained in:
63
backend/internal/handlers/audit_log.go
Normal file
63
backend/internal/handlers/audit_log.go
Normal file
@@ -0,0 +1,63 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
@@ -115,7 +115,7 @@ func (h *DeadlineHandlers) Create(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
deadline, err := h.deadlines.Create(tenantID, input)
|
||||
deadline, err := h.deadlines.Create(r.Context(), tenantID, input)
|
||||
if err != nil {
|
||||
internalError(w, "failed to create deadline", err)
|
||||
return
|
||||
@@ -144,7 +144,7 @@ func (h *DeadlineHandlers) Update(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
deadline, err := h.deadlines.Update(tenantID, deadlineID, input)
|
||||
deadline, err := h.deadlines.Update(r.Context(), tenantID, deadlineID, input)
|
||||
if err != nil {
|
||||
internalError(w, "failed to update deadline", err)
|
||||
return
|
||||
@@ -171,7 +171,7 @@ func (h *DeadlineHandlers) Complete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
deadline, err := h.deadlines.Complete(tenantID, deadlineID)
|
||||
deadline, err := h.deadlines.Complete(r.Context(), tenantID, deadlineID)
|
||||
if err != nil {
|
||||
internalError(w, "failed to complete deadline", err)
|
||||
return
|
||||
@@ -198,8 +198,18 @@ func (h *DeadlineHandlers) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
if err := h.deadlines.Delete(tenantID, deadlineID); err != nil {
|
||||
writeError(w, http.StatusNotFound, "deadline not found")
|
||||
||||||| 82878df
|
||||
err = h.deadlines.Delete(tenantID, deadlineID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
=======
|
||||
err = h.deadlines.Delete(r.Context(), tenantID, deadlineID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
>>>>>>> mai/knuth/p0-audit-trail-append
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user