package httpserver import ( "io/fs" "log/slog" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "solopm.com/solopm-server/internal/config" ) // Options bundles the dependencies the router needs to wire routes. type Options struct { Config *config.Config Logger *slog.Logger DB DBPinger Version string Dev bool // permissive CORS in dev; same-origin default otherwise SPA fs.FS // web/dist, rooted so index.html is at the top level; nil disables SPA serving } // New builds the chi router with the full middleware chain (minus the auth // resolver, which is added in Phase 1) and registers Phase 0 routes. func New(opts Options) chi.Router { r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.RealIP) //nolint:staticcheck // middleware order is spec-mandated (01-architecture.md); deploy behind a trusted reverse proxy r.Use(RequestLogger(opts.Logger)) r.Use(middleware.Recoverer) r.Use(corsMiddleware(opts.Dev)) r.Get("/healthz", healthzHandler(opts.Config, opts.DB, opts.Version)) if opts.SPA != nil { r.Get("/*", spaHandler(opts.SPA)) } return r } func corsMiddleware(dev bool) func(http.Handler) http.Handler { if dev { return cors.Handler(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Content-Type", "Authorization"}, AllowCredentials: false, }) } // Same-origin default: no CORS headers granted, browser same-origin // requests work regardless since no cross-origin request needs them. return cors.Handler(cors.Options{ AllowedOrigins: []string{}, }) }