Files
2026-08-14 11:29:11 -06:00

60 lines
1.7 KiB
Go

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{},
})
}