package handlers import ( "context" "net/http" "strconv" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response" "git.netcell-it.de/projekte/edgeguard-native/internal/services/audit" ) type AuditHandler struct { Repo *audit.Repo } func NewAuditHandler(repo *audit.Repo) *AuditHandler { return &AuditHandler{Repo: repo} } func (h *AuditHandler) Register(rg *gin.RouterGroup) { g := rg.Group("/audit") g.GET("/recent", h.Recent) g.GET("/live", h.Live) } // Recent returns the most recent audit_log entries — used by the // dashboard fallback path (z.B. wenn WebSocket nicht verbinden kann). // ?limit=N (1–100, default 10). func (h *AuditHandler) Recent(c *gin.Context) { limit := 10 if v := c.Query("limit"); v != "" { if n, err := strconv.Atoi(v); err == nil { limit = n } } rows, err := h.Repo.ListRecent(c.Request.Context(), limit) if err != nil { response.Internal(c, err) return } response.OK(c, gin.H{"entries": rows}) } // auditUpgrader: same-origin durch HAProxy, kein CheckOrigin. var auditUpgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 4 * 1024, CheckOrigin: func(r *http.Request) bool { return true }, } // Live upgraded auf WebSocket: sendet einen Snapshot der letzten 50 // audit_log-Rows, danach jeden neuen Eintrag direkt aus dem // Repo.broadcast()-Channel. func (h *AuditHandler) Live(c *gin.Context) { conn, err := auditUpgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { return } defer conn.Close() // Snapshot if rows, err := h.Repo.ListRecent(c.Request.Context(), 50); err == nil { // In aufsteigender Reihenfolge schicken (newest last) damit der // Client nach unten scrollt + neue Events natürlich anhängt. for i := len(rows) - 1; i >= 0; i-- { if err := conn.WriteJSON(rows[i]); err != nil { return } } } // Live-Subscribe ch, unsub := h.Repo.Subscribe() defer unsub() ctx, cancel := context.WithCancel(c.Request.Context()) defer cancel() // Read-Loop für close-frame + ping-pong go func() { defer cancel() for { if _, _, err := conn.NextReader(); err != nil { return } } }() ping := time.NewTicker(30 * time.Second) defer ping.Stop() _ = conn.SetWriteDeadline(time.Now().Add(60 * time.Second)) for { select { case <-ctx.Done(): return case e, ok := <-ch: if !ok { return } _ = conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) if err := conn.WriteJSON(e); err != nil { return } case <-ping.C: _ = conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(5*time.Second)); err != nil { return } } } }