package api import ( "io/fs" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "git.georgsen.dk/hwlab/internal/api/handlers" ) // spaHandler serves static files and falls back to index.html for unknown paths, // enabling client-side routing in the React SPA. type spaHandler struct { staticFS fs.FS } func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Try to open the requested path in the embedded FS. f, err := h.staticFS.Open(r.URL.Path) if err != nil { // File not found — serve index.html so the SPA router handles it. r2 := r.Clone(r.Context()) r2.URL.Path = "/" http.FileServer(http.FS(h.staticFS)).ServeHTTP(w, r2) return } f.Close() http.FileServer(http.FS(h.staticFS)).ServeHTTP(w, r) } // NewRouter creates the chi router. staticFiles is the fs.FS rooted at web/dist, // passed from main.go where the go:embed directive lives. // intakeHandler handles POST /api/intake (multipart photo upload). func NewRouter(staticFiles fs.FS, intakeHandler http.Handler) http.Handler { r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(middleware.RealIP) r.Route("/api", func(r chi.Router) { r.Get("/health", handlers.Health) r.Post("/intake", intakeHandler.ServeHTTP) }) // SPA fallback — serve static files; unknown paths fall back to index.html. r.Handle("/*", spaHandler{staticFS: staticFiles}) return r }