package middleware import ( "context" "encoding/json" "net" "net/http" "github.com/jackc/pgx/v5/pgxpool" ) // extractIP returns a clean IP without port. Falls back to "" so the INET // column can take NULL via $5 when the value is empty. func extractIP(r *http.Request) any { addr := r.RemoteAddr if host, _, err := net.SplitHostPort(addr); err == nil { addr = host } if addr == "" { return nil } return addr } // AuditLog records API access to the audit_logs table for important operations. func AuditLog(pool *pgxpool.Pool) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) // Only audit write operations if r.Method == "GET" || r.Method == "OPTIONS" { return } userID := GetUserID(r.Context()) if userID.String() == "00000000-0000-0000-0000-000000000000" { return } details, _ := json.Marshal(map[string]string{ "method": r.Method, "path": r.URL.Path, }) ip := extractIP(r) ua := r.UserAgent() method := r.Method path := r.URL.Path go func() { _, _ = pool.Exec(context.Background(), `INSERT INTO audit_logs (user_id, action, resource_type, resource_id, details, ip_address, user_agent) VALUES ($1, $2, $3, NULL, $4, $5, $6)`, userID, method+"."+path, "api", details, ip, ua, ) }() }) } }