- Add internal/config/config.go with viper-backed Config struct - Explicit BindEnv calls for reliable env var -> config mapping (mapstructure v2 compat) - Config loads from config.json + .env, env vars take precedence - Add config.json with non-secret defaults (port, timeouts, URLs) - Fix SPA fallback: spaHandler serves index.html for unknown paths (client-side routing) - All 5 tests pass: TestHealth, TestLoadDefaults, TestLoadEnvOverride, TestLoadNetBoxURL - Add Makefile with build/dev/test/clean targets
49 lines
1.3 KiB
Go
49 lines
1.3 KiB
Go
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.
|
|
func NewRouter(staticFiles fs.FS) 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)
|
|
})
|
|
|
|
// SPA fallback — serve static files; unknown paths fall back to index.html.
|
|
r.Handle("/*", spaHandler{staticFS: staticFiles})
|
|
|
|
return r
|
|
}
|