package handler import ( "io/fs" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/wotra/wotra/internal/service" "github.com/wotra/wotra/internal/store" ) // NewRouter builds the full HTTP router. func NewRouter( authToken string, entrySvc *service.EntryService, daySvc *service.DayService, settingsSvc *service.SettingsService, weekSvc *service.WeekService, syncStore *store.SyncStore, staticFiles fs.FS, ) http.Handler { r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) // Unauthenticated r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) }) // Authenticated API r.Route("/api", func(r chi.Router) { r.Use(AuthMiddleware(authToken)) entryH := NewEntryHandler(entrySvc) entryH.Routes(r) dayH := NewDayHandler(daySvc) dayH.Routes(r) settingsH := NewSettingsHandler(settingsSvc) settingsH.Routes(r) weekH := NewWeekHandler(weekSvc) weekH.Routes(r) syncH := NewSyncHandler(syncStore) syncH.Routes(r) exportH := NewExportHandler(entrySvc, daySvc, weekSvc) exportH.Routes(r) }) // Serve embedded SPA if available (production build) if staticFiles != nil { fileServer := http.FileServer(http.FS(staticFiles)) r.Get("/*", func(w http.ResponseWriter, r *http.Request) { // Try to open the requested path; fall back to index.html for SPA routing path := r.URL.Path if path == "/" || path == "" { path = "index.html" } else { path = path[1:] // strip leading slash } if _, err := staticFiles.Open(path); err != nil { r.URL.Path = "/" } fileServer.ServeHTTP(w, r) }) } return r }