feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
package web
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-05-15 16:54:49 +00:00
|
|
|
"fmt"
|
2026-05-15 16:14:33 +00:00
|
|
|
"io/fs"
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
"log/slog"
|
|
|
|
|
"net/http"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-05-14 20:17:50 +00:00
|
|
|
"backend/internal/auth"
|
|
|
|
|
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
|
chimw "github.com/go-chi/chi/v5/middleware"
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-15 16:14:33 +00:00
|
|
|
// Pinger is the contract /readyz uses to probe the data plane. *pgxpool.Pool
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
// 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
|
2026-05-14 20:17:50 +00:00
|
|
|
// CONTEXT D-24:
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
//
|
2026-05-14 20:17:50 +00:00
|
|
|
// 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)
|
2026-05-14 20:59:06 +00:00
|
|
|
// 5. auth.ResolveSession (reads session cookie, attaches user to context) — D-24
|
|
|
|
|
// 6. auth.Mount (gorilla/csrf — MUST come after ResolveSession, before routes) — D-24, Pitfall 7
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
//
|
2026-05-15 16:14:33 +00:00
|
|
|
// Routes: GET / · GET /healthz (liveness) · GET /readyz (readiness) · GET /demo/time · GET /static/*
|
2026-05-15 19:03:30 +00:00
|
|
|
//
|
|
|
|
|
// GET /signup (auth pages, behind RedirectIfAuthed) · POST /signup.
|
|
|
|
|
//
|
2026-05-15 16:14:33 +00:00
|
|
|
// staticFS is the embedded FS (or os.DirFS in tests) served at /static/*; the
|
|
|
|
|
// embedded FS pattern blocks path traversal at the http.FS layer (T-01-08).
|
2026-05-14 20:17:50 +00:00
|
|
|
//
|
|
|
|
|
// deps.Store may be nil during unit tests for Phase 1 routes (those routes
|
|
|
|
|
// never exercise session resolution). ResolveSession guards against nil Store.
|
2026-05-14 20:59:06 +00:00
|
|
|
//
|
|
|
|
|
// csrfKey is the 32-byte CSRF authentication key loaded from SESSION_SECRET.
|
|
|
|
|
// env is the runtime environment string (e.g. "dev", "development", "production").
|
|
|
|
|
// When env == "dev", the CSRF cookie Secure flag is disabled for plain-HTTP
|
|
|
|
|
// local development (D-15, D-24).
|
|
|
|
|
// trustedOrigins is an optional list of additional origins for the CSRF
|
|
|
|
|
// referer check (used in integration tests to allow localhost requests without
|
|
|
|
|
// a Referer header). In production, pass no extra args — leave empty.
|
2026-05-16 08:11:14 +00:00
|
|
|
func NewRouter(pinger Pinger, staticFS fs.FS, deps AuthDeps, tabloDeps TablosDeps, taskDeps TasksDeps, etapeDeps EtapesDeps, eventDeps EventsDeps, discussionDeps DiscussionDeps, planningDeps PlanningDeps, fileDeps FilesDeps, csrfKey []byte, env string, trustedOrigins ...string) (http.Handler, error) {
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
r := chi.NewRouter()
|
|
|
|
|
r.Use(RequestIDMiddleware)
|
|
|
|
|
r.Use(chimw.RealIP)
|
|
|
|
|
r.Use(SlogLoggerMiddleware(slog.Default()))
|
|
|
|
|
r.Use(chimw.Recoverer)
|
2026-05-14 20:59:06 +00:00
|
|
|
// D-24 locked order: ResolveSession BEFORE csrf.Protect (auth.Mount).
|
2026-05-14 20:17:50 +00:00
|
|
|
r.Use(auth.ResolveSession(deps.Store))
|
2026-05-14 20:59:06 +00:00
|
|
|
// D-24: gorilla/csrf runs after ResolveSession and before all route groups (Pitfall 7).
|
|
|
|
|
r.Use(auth.Mount(env, csrfKey, trustedOrigins...))
|
2026-05-14 20:17:50 +00:00
|
|
|
|
|
|
|
|
// Auth pages — redirect to / if already authenticated.
|
|
|
|
|
r.Group(func(r chi.Router) {
|
|
|
|
|
r.Use(auth.RedirectIfAuthed)
|
2026-05-15 19:09:14 +00:00
|
|
|
r.Get("/signup", SignupPageHandler(deps))
|
|
|
|
|
r.Get("/login", LoginPageHandler(deps))
|
2026-05-14 20:17:50 +00:00
|
|
|
})
|
|
|
|
|
|
2026-05-14 20:27:54 +00:00
|
|
|
// Signup and login POSTs are intentionally outside the RedirectIfAuthed group:
|
2026-05-14 20:17:50 +00:00
|
|
|
// 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))
|
2026-05-14 20:27:54 +00:00
|
|
|
r.Post("/login", LoginPostHandler(deps))
|
2026-05-15 19:03:30 +00:00
|
|
|
r.Get("/auth/google/start", GoogleStartHandler(deps))
|
|
|
|
|
r.Get("/auth/google/callback", GoogleCallbackHandler(deps))
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
|
2026-05-14 20:40:10 +00:00
|
|
|
// 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).
|
feat(03-02): tablo handlers + router wiring — list/new/create green
- Implement TablosListHandler, TablosNewHandler, TablosCreateHandler in
handlers_tablos.go replacing the Plan 01 stub
- TablosCreateHandler: reads via r.PostFormValue, validates title (required,
<=255), inserts with pgtype.Text nullable params, sends HX-Retarget +
HX-Reswap on HTMX success, 303 redirect on non-HTMX success
- router.go: replace r.Get("/", IndexHandler()) with TablosListHandler;
add GET /tablos/new and POST /tablos (static before parametric — Pitfall 1)
- handlers.go: remove IndexHandler + unused auth/csrf imports
- index.templ: reduced to bare package declaration (dashboard moved to tablos.templ)
- index_templ.go: deleted (empty templ file generates broken import)
- TestTabloList, TestTabloList_Empty, TestTabloCreate, TestTabloCreate_Validation: PASS
- TestSignup, TestLogin, TestLogout, TestCSRF: still PASS (no regression)
2026-05-14 22:20:25 +00:00
|
|
|
// Route ordering: static segments (/tablos/new) declared BEFORE parametric
|
|
|
|
|
// (/tablos/{id}) so chi v5 resolves them correctly (Pitfall 1).
|
2026-05-14 20:40:10 +00:00
|
|
|
r.Group(func(r chi.Router) {
|
|
|
|
|
r.Use(auth.RequireAuth)
|
feat(03-02): tablo handlers + router wiring — list/new/create green
- Implement TablosListHandler, TablosNewHandler, TablosCreateHandler in
handlers_tablos.go replacing the Plan 01 stub
- TablosCreateHandler: reads via r.PostFormValue, validates title (required,
<=255), inserts with pgtype.Text nullable params, sends HX-Retarget +
HX-Reswap on HTMX success, 303 redirect on non-HTMX success
- router.go: replace r.Get("/", IndexHandler()) with TablosListHandler;
add GET /tablos/new and POST /tablos (static before parametric — Pitfall 1)
- handlers.go: remove IndexHandler + unused auth/csrf imports
- index.templ: reduced to bare package declaration (dashboard moved to tablos.templ)
- index_templ.go: deleted (empty templ file generates broken import)
- TestTabloList, TestTabloList_Empty, TestTabloCreate, TestTabloCreate_Validation: PASS
- TestSignup, TestLogin, TestLogout, TestCSRF: still PASS (no regression)
2026-05-14 22:20:25 +00:00
|
|
|
r.Get("/", TablosListHandler(tabloDeps))
|
2026-05-14 20:40:10 +00:00
|
|
|
r.Post("/logout", LogoutHandler(deps))
|
2026-05-15 19:10:45 +00:00
|
|
|
r.Get("/account/providers", AccountProvidersHandler(deps))
|
2026-05-16 05:26:49 +00:00
|
|
|
r.Get("/planning", PlanningPageHandler(planningDeps))
|
2026-05-15 06:02:43 +00:00
|
|
|
// Static segments BEFORE parametric (Pitfall 1 — chi v5 route resolution).
|
feat(03-02): tablo handlers + router wiring — list/new/create green
- Implement TablosListHandler, TablosNewHandler, TablosCreateHandler in
handlers_tablos.go replacing the Plan 01 stub
- TablosCreateHandler: reads via r.PostFormValue, validates title (required,
<=255), inserts with pgtype.Text nullable params, sends HX-Retarget +
HX-Reswap on HTMX success, 303 redirect on non-HTMX success
- router.go: replace r.Get("/", IndexHandler()) with TablosListHandler;
add GET /tablos/new and POST /tablos (static before parametric — Pitfall 1)
- handlers.go: remove IndexHandler + unused auth/csrf imports
- index.templ: reduced to bare package declaration (dashboard moved to tablos.templ)
- index_templ.go: deleted (empty templ file generates broken import)
- TestTabloList, TestTabloList_Empty, TestTabloCreate, TestTabloCreate_Validation: PASS
- TestSignup, TestLogin, TestLogout, TestCSRF: still PASS (no regression)
2026-05-14 22:20:25 +00:00
|
|
|
r.Get("/tablos/new", TablosNewHandler(tabloDeps))
|
|
|
|
|
r.Post("/tablos", TablosCreateHandler(tabloDeps))
|
2026-05-15 06:02:43 +00:00
|
|
|
// Parametric routes — must come after /tablos/new and /tablos POST.
|
|
|
|
|
r.Get("/tablos/{id}", TabloDetailHandler(tabloDeps))
|
|
|
|
|
r.Post("/tablos/{id}", TabloUpdateHandler(tabloDeps))
|
|
|
|
|
r.Get("/tablos/{id}/edit-title", TabloEditTitleHandler(tabloDeps))
|
|
|
|
|
r.Get("/tablos/{id}/show-title", TabloShowTitleHandler(tabloDeps))
|
|
|
|
|
r.Get("/tablos/{id}/edit-desc", TabloEditDescHandler(tabloDeps))
|
|
|
|
|
r.Get("/tablos/{id}/show-desc", TabloShowDescHandler(tabloDeps))
|
|
|
|
|
r.Get("/tablos/{id}/delete-confirm", TabloDeleteConfirmHandler(tabloDeps))
|
|
|
|
|
r.Get("/tablos/{id}/delete-cancel", TabloDeleteCancelHandler(tabloDeps))
|
|
|
|
|
r.Post("/tablos/{id}/delete", TabloDeleteHandler(tabloDeps))
|
2026-05-15 10:28:33 +00:00
|
|
|
// Tasks tab entry point — must be BEFORE static task sub-routes (Pitfall 1).
|
|
|
|
|
r.Get("/tablos/{id}/tasks", TabloTasksTabHandler(fileDeps))
|
2026-05-15 07:31:59 +00:00
|
|
|
// Task routes — static segments BEFORE parametric (Pitfall 1).
|
|
|
|
|
// /tablos/{id}/tasks/new and /tablos/{id}/tasks/cancel-new are static
|
|
|
|
|
// segments relative to /tablos/{id}/tasks/* and must come first.
|
|
|
|
|
r.Get("/tablos/{id}/tasks/new", TaskNewFormHandler(taskDeps))
|
|
|
|
|
r.Get("/tablos/{id}/tasks/cancel-new", TaskCancelNewHandler(taskDeps))
|
|
|
|
|
r.Post("/tablos/{id}/tasks", TaskCreateHandler(taskDeps))
|
|
|
|
|
r.Post("/tablos/{id}/tasks/reorder", TaskReorderHandler(taskDeps))
|
2026-05-15 20:40:25 +00:00
|
|
|
r.Get("/tablos/{id}/etapes/new", EtapeNewFormHandler(etapeDeps))
|
|
|
|
|
r.Get("/tablos/{id}/etapes/cancel-new", EtapeCancelNewHandler(etapeDeps))
|
|
|
|
|
r.Post("/tablos/{id}/etapes", EtapeCreateHandler(etapeDeps))
|
2026-05-15 20:44:50 +00:00
|
|
|
r.Post("/tablos/{id}/etapes/reorder", EtapeReorderHandler(etapeDeps))
|
|
|
|
|
r.Get("/tablos/{id}/etapes/{etape_id}/edit", EtapeEditFormHandler(etapeDeps))
|
|
|
|
|
r.Post("/tablos/{id}/etapes/{etape_id}", EtapeUpdateHandler(etapeDeps))
|
|
|
|
|
r.Get("/tablos/{id}/etapes/{etape_id}/delete-confirm", EtapeDeleteConfirmHandler(etapeDeps))
|
|
|
|
|
r.Post("/tablos/{id}/etapes/{etape_id}/delete", EtapeDeleteHandler(etapeDeps))
|
2026-05-15 22:27:58 +00:00
|
|
|
// Events tab and event routes — static segments BEFORE future parametric routes.
|
|
|
|
|
r.Get("/tablos/{id}/events", TabloEventsTabHandler(eventDeps))
|
|
|
|
|
r.Get("/tablos/{id}/events/new", EventNewFormHandler(eventDeps))
|
|
|
|
|
r.Get("/tablos/{id}/events/cancel-new", EventCancelNewHandler(eventDeps))
|
|
|
|
|
r.Post("/tablos/{id}/events", EventCreateHandler(eventDeps))
|
2026-05-15 22:32:55 +00:00
|
|
|
r.Get("/tablos/{id}/events/{event_id}/edit", EventEditFormHandler(eventDeps))
|
|
|
|
|
r.Post("/tablos/{id}/events/{event_id}", EventUpdateHandler(eventDeps))
|
|
|
|
|
r.Get("/tablos/{id}/events/{event_id}/delete-confirm", EventDeleteConfirmHandler(eventDeps))
|
|
|
|
|
r.Post("/tablos/{id}/events/{event_id}/delete", EventDeleteHandler(eventDeps))
|
2026-05-16 08:11:14 +00:00
|
|
|
// Discussion tab and message routes — static discussion segment before later parametric child routes.
|
|
|
|
|
r.Get("/tablos/{id}/discussion", TabloDiscussionTabHandler(discussionDeps))
|
|
|
|
|
r.Post("/tablos/{id}/discussion/messages", DiscussionMessageCreateHandler(discussionDeps))
|
2026-05-15 07:31:59 +00:00
|
|
|
// Parametric task routes — must come after static task segments.
|
|
|
|
|
r.Get("/tablos/{id}/tasks/{task_id}/show", TaskShowHandler(taskDeps))
|
|
|
|
|
r.Get("/tablos/{id}/tasks/{task_id}/edit", TaskEditHandler(taskDeps))
|
|
|
|
|
r.Post("/tablos/{id}/tasks/{task_id}", TaskUpdateHandler(taskDeps))
|
|
|
|
|
r.Get("/tablos/{id}/tasks/{task_id}/delete-confirm", TaskDeleteConfirmHandler(taskDeps))
|
|
|
|
|
r.Post("/tablos/{id}/tasks/{task_id}/delete", TaskDeleteHandler(taskDeps))
|
2026-05-15 10:28:33 +00:00
|
|
|
// File routes — static segments BEFORE parametric (Pitfall 6 in RESEARCH).
|
|
|
|
|
r.Get("/tablos/{id}/files", TabloFilesTabHandler(fileDeps))
|
|
|
|
|
r.Post("/tablos/{id}/files", FileUploadHandler(fileDeps))
|
|
|
|
|
// Parametric file routes — AFTER static file segment.
|
|
|
|
|
r.Get("/tablos/{id}/files/{file_id}/download", FileDownloadHandler(fileDeps))
|
|
|
|
|
r.Get("/tablos/{id}/files/{file_id}/delete-confirm", FileDeleteConfirmHandler(fileDeps))
|
|
|
|
|
r.Post("/tablos/{id}/files/{file_id}/delete", FileDeleteHandler(fileDeps))
|
2026-05-14 20:40:10 +00:00
|
|
|
})
|
|
|
|
|
|
2026-05-15 16:14:33 +00:00
|
|
|
// Liveness probe (D-12): always 200, no DB contact.
|
|
|
|
|
r.Get("/healthz", HealthzHandler())
|
|
|
|
|
// Readiness probe (D-13): probes DB; 200 when ready, 503 when degraded.
|
|
|
|
|
r.Get("/readyz", ReadyzHandler(pinger))
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
r.Get("/demo/time", DemoTimeHandler(func() time.Time { return time.Now() }))
|
|
|
|
|
|
2026-05-15 16:14:33 +00:00
|
|
|
// Serve embedded static assets. Sub to strip the "static/" prefix from the
|
|
|
|
|
// embedded path so /static/tailwind.css maps to static/tailwind.css inside
|
|
|
|
|
// the FS. The "fs" local name is avoided to prevent shadowing the "io/fs" import.
|
|
|
|
|
sub, err := fs.Sub(staticFS, "static")
|
|
|
|
|
if err != nil {
|
2026-05-15 16:54:49 +00:00
|
|
|
return nil, fmt.Errorf("router: failed to sub static FS: %w", err)
|
2026-05-15 16:14:33 +00:00
|
|
|
}
|
|
|
|
|
fileHandler := http.StripPrefix("/static/", http.FileServer(http.FS(sub)))
|
|
|
|
|
r.Get("/static/*", fileHandler.ServeHTTP)
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
|
2026-05-15 16:54:49 +00:00
|
|
|
return r, nil
|
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
(max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
/static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
+ Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
path traversal blocked by http.Dir)
All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 17:25:43 +00:00
|
|
|
}
|