package middleware import ( "fmt" "net/http" "strings" "time" "hyapp-admin-server/internal/model" "github.com/gin-gonic/gin" ) const contextAuditLogged = "auditLogged" type AuditStore interface { CreateOperationLog(log model.OperationLog) error } func Audit(store AuditStore) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() if store == nil || c.GetBool(contextAuditLogged) || !shouldAudit(c.Request.Method) { return } status := "success" if c.Writer.Status() >= http.StatusBadRequest { status = "failed" } path := c.FullPath() if path == "" { path = c.Request.URL.Path } _ = store.CreateOperationLog(model.OperationLog{ UserID: CurrentUserID(c), RequestID: CurrentRequestID(c), Username: CurrentUsername(c), Action: strings.ToLower(c.Request.Method) + " " + path, Resource: auditResource(path), ResourceID: auditResourceID(c), Method: c.Request.Method, Path: c.Request.URL.Path, IP: c.ClientIP(), UserAgent: c.Request.UserAgent(), Status: status, HTTPStatus: c.Writer.Status(), RiskLevel: auditRisk(c.Request.Method, path), LatencyMS: time.Since(start).Milliseconds(), Detail: fmt.Sprintf("http_status=%d", c.Writer.Status()), }) } } func auditResourceID(c *gin.Context) string { if id := c.Param("id"); id != "" { return id } return "" } func auditRisk(method string, path string) string { if method == http.MethodDelete || strings.Contains(path, "reset-password") || strings.Contains(path, "permissions") || strings.Contains(path, "data-scopes") { return "high" } if shouldAudit(method) { return "normal" } return "low" } func MarkAuditLogged(c *gin.Context) { c.Set(contextAuditLogged, true) } func shouldAudit(method string) bool { switch method { case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete: return true default: return false } } func auditResource(path string) string { parts := strings.Split(strings.Trim(path, "/"), "/") for _, part := range parts { if part != "" && !strings.HasPrefix(part, ":") && part != "api" && part != "v1" { return part } } return "unknown" }