Files
Snip/backend/internal/httpx/frontend/server.go
T
2026-06-15 21:25:57 +07:00

60 lines
1.4 KiB
Go

// Package frontend serves the built SPA. It owns exactly the app routes
// (/, /login, /dashboard) plus static assets; everything else is 404 here and
// handled by the redirect command in production.
package frontend
import (
"net/http"
"os"
"path"
"path/filepath"
)
type Server struct {
dist string
fs http.Handler
spa map[string]bool
indexAbs string
}
func New(dist string) *Server {
return &Server{
dist: dist,
fs: http.FileServer(http.Dir(dist)),
spa: map[string]bool{"/": true, "/login": true, "/dashboard": true},
indexAbs: filepath.Join(dist, "index.html"),
}
}
func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"ok"}`))
})
mux.HandleFunc("/", s.serve)
return mux
}
func (s *Server) serve(w http.ResponseWriter, r *http.Request) {
clean := path.Clean(r.URL.Path)
// App routes always render the SPA shell.
if s.spa[clean] {
http.ServeFile(w, r, s.indexAbs)
return
}
// Real static file (assets, favicon, …)?
if clean != "/" {
full := filepath.Join(s.dist, filepath.FromSlash(clean))
if st, err := os.Stat(full); err == nil && !st.IsDir() {
s.fs.ServeHTTP(w, r)
return
}
}
// Unknown path: not this command's concern.
http.NotFound(w, r)
}