package handlers import ( "context" "encoding/json" "log" "net/http" "github.com/gin-gonic/gin" "git.gobha.me/xcaliber/chat-switchboard/models" "git.gobha.me/xcaliber/chat-switchboard/store" ) // AuditLog writes an audit entry via the store interface. // Non-blocking: errors are logged but don't break the caller. func AuditLog(auditStore store.AuditStore, c *gin.Context, action, resourceType, resourceID string, metadata map[string]interface{}) { if auditStore == nil { return } actorID := getUserID(c) if actorID == "" { return } var meta models.JSONMap if metadata != nil { b, _ := json.Marshal(metadata) json.Unmarshal(b, &meta) } entry := &models.AuditEntry{ ActorID: &actorID, Action: action, ResourceType: resourceType, ResourceID: resourceID, Metadata: meta, IPAddress: c.ClientIP(), UserAgent: c.GetHeader("User-Agent"), } if err := auditStore.Log(context.Background(), entry); err != nil { log.Printf("audit log error: %v", err) } } // ── Admin Audit Viewer ────────────────────── // ListAuditLog returns paginated audit entries with optional filters. // GET /api/v1/admin/audit?page=1&per_page=50&action=user.create&actor_id=...&resource_type=... func (h *AdminHandler) ListAuditLog(c *gin.Context) { page, perPage, offset := parsePagination(c) opts := store.AuditListOptions{ ListOptions: store.ListOptions{ Limit: perPage, Offset: offset, }, Action: c.Query("action"), ActorID: c.Query("actor_id"), ResourceType: c.Query("resource_type"), } entries, total, err := h.stores.Audit.List(c.Request.Context(), opts) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) return } if entries == nil { entries = []models.AuditEntry{} } c.JSON(http.StatusOK, gin.H{ "data": entries, "total": total, "page": page, "per_page": perPage, }) } // ListAuditActions returns distinct action names for filter dropdowns. // GET /api/v1/admin/audit/actions func (h *AdminHandler) ListAuditActions(c *gin.Context) { actions, err := h.stores.Audit.ListActions(c.Request.Context()) if err != nil { c.JSON(http.StatusOK, gin.H{"actions": []string{}}) return } c.JSON(http.StatusOK, gin.H{"actions": actions}) }