feat: production hardening — slog, rate limiting, tests, seed data (Phase 4)

This commit is contained in:
m
2026-03-25 14:35:49 +01:00
10 changed files with 694 additions and 17 deletions

View File

@@ -2,13 +2,16 @@ package router
import (
"encoding/json"
"log/slog"
"net/http"
"time"
"github.com/jmoiron/sqlx"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/config"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/handlers"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/middleware"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
)
@@ -113,10 +116,11 @@ func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config, calDAVSvc *se
scoped.HandleFunc("GET /api/documents/{docId}/meta", docH.GetMeta)
scoped.HandleFunc("DELETE /api/documents/{docId}", docH.Delete)
// AI endpoints
// AI endpoints (rate limited: 5 req/min burst 10 per IP)
if aiH != nil {
scoped.HandleFunc("POST /api/ai/extract-deadlines", aiH.ExtractDeadlines)
scoped.HandleFunc("POST /api/ai/summarize-case", aiH.SummarizeCase)
aiLimiter := middleware.NewTokenBucket(5.0/60.0, 10)
scoped.HandleFunc("POST /api/ai/extract-deadlines", aiLimiter.LimitFunc(aiH.ExtractDeadlines))
scoped.HandleFunc("POST /api/ai/summarize-case", aiLimiter.LimitFunc(aiH.SummarizeCase))
}
// CalDAV sync endpoints
@@ -131,7 +135,7 @@ func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config, calDAVSvc *se
mux.Handle("/api/", authMW.RequireAuth(api))
return mux
return requestLogger(mux)
}
func handleHealth(db *sqlx.DB) http.HandlerFunc {
@@ -146,3 +150,34 @@ func handleHealth(db *sqlx.DB) http.HandlerFunc {
}
}
type statusWriter struct {
http.ResponseWriter
status int
}
func (w *statusWriter) WriteHeader(code int) {
w.status = code
w.ResponseWriter.WriteHeader(code)
}
func requestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Skip health checks to reduce noise
if r.URL.Path == "/health" {
next.ServeHTTP(w, r)
return
}
sw := &statusWriter{ResponseWriter: w, status: http.StatusOK}
start := time.Now()
next.ServeHTTP(sw, r)
slog.Info("request",
"method", r.Method,
"path", r.URL.Path,
"status", sw.status,
"duration_ms", time.Since(start).Milliseconds(),
)
})
}