package web import ( "context" "log/slog" "net/http" "time" "backend/internal/auth" "github.com/go-chi/chi/v5" chimw "github.com/go-chi/chi/v5/middleware" ) // Pinger is the contract /healthz uses to probe the data plane. *pgxpool.Pool // satisfies this interface out of the box, which is why cmd/web passes the // pool directly to NewRouter (no adapter required). type Pinger interface { Ping(ctx context.Context) error } // NewRouter constructs the chi router with the middleware stack locked by // CONTEXT D-24: // // 1. RequestIDMiddleware (UUIDv4 — NOT chi's base32 RequestID) // 2. chi RealIP // 3. SlogLoggerMiddleware (REPLACES chi's middleware.Logger — Pitfall 6) // 4. chi Recoverer (after Logger so panics carry request_id) // 5. auth.ResolveSession (reads session cookie, attaches user to context) // NOTE: csrf.Protect is added in Plan 07. // // Routes: GET / · GET /healthz · GET /demo/time · GET /static/* // GET /signup (auth pages, behind RedirectIfAuthed) · POST /signup. // staticDir is the on-disk path served at /static/*; path traversal is // blocked by http.Dir's default behavior (T-01-08). // // deps.Store may be nil during unit tests for Phase 1 routes (those routes // never exercise session resolution). ResolveSession guards against nil Store. func NewRouter(pinger Pinger, staticDir string, deps AuthDeps) http.Handler { r := chi.NewRouter() r.Use(RequestIDMiddleware) r.Use(chimw.RealIP) r.Use(SlogLoggerMiddleware(slog.Default())) r.Use(chimw.Recoverer) r.Use(auth.ResolveSession(deps.Store)) // Auth pages — redirect to / if already authenticated. r.Group(func(r chi.Router) { r.Use(auth.RedirectIfAuthed) r.Get("/signup", SignupPageHandler()) r.Get("/login", LoginPageHandler()) }) // Signup and login POSTs are intentionally outside the RedirectIfAuthed group: // an authed user submitting the form directly should still get a useful // response; the GET guard handles the common case. r.Post("/signup", SignupPostHandler(deps)) r.Post("/login", LoginPostHandler(deps)) // Protected routes — require an authenticated session (D-23, AUTH-05). // RequireAuth checks the context set by ResolveSession above and redirects // unauthenticated requests to /login (HTMX: HX-Redirect, plain: 303). r.Group(func(r chi.Router) { r.Use(auth.RequireAuth) r.Get("/", IndexHandler()) r.Post("/logout", LogoutHandler(deps)) }) r.Get("/healthz", HealthzHandler(pinger)) r.Get("/demo/time", DemoTimeHandler(func() time.Time { return time.Now() })) fs := http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))) r.Get("/static/*", fs.ServeHTTP) return r }