// Package api wires pansy's HTTP surface: a gin engine with structured logging // and panic recovery, the versioned JSON API under /api/v1, and (via spa.go) the // embedded single-page-app fallback. Handlers stay thin — decode, call the // service layer, encode — so all business logic and permission checks live in // internal/service (added by later issues). package api import ( "log/slog" "net/http" "github.com/gin-gonic/gin" sloggin "github.com/samber/slog-gin" "gitea.stevedudenhoeffer.com/steve/pansy/internal/config" ) // New builds the gin engine with the standard middleware stack and registers the // API routes. The embedded SPA fallback is registered separately by the caller // via RegisterSPA (see spa.go) so the API can be built and tested without a web // build present. func New(cfg *config.Config) *gin.Engine { gin.SetMode(gin.ReleaseMode) r := gin.New() r.Use(sloggin.New(slog.Default()), gin.Recovery()) if err := r.SetTrustedProxies(cfg.TrustedProxies); err != nil { // Do not leave gin's trust-everyone default active on a parse failure — // that would let any client spoof X-Forwarded-For. Fall back to trusting // no proxies, which is also the behavior when none are configured. slog.Error("api: invalid trusted proxies, trusting none", "error", err) _ = r.SetTrustedProxies(nil) } v1 := r.Group("/api/v1") v1.GET("/healthz", healthz) return r } // healthz is a liveness probe: always returns {"ok": true} when the server is up. func healthz(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }