package server import ( "embed" "fmt" "io/fs" "net/http" "path" "strings" ) //go:embed ui-dist/* var uiDist embed.FS type uiRenderer struct { fsys fs.FS } // newUIRenderer loads the embedded UI bundle using the default dist directory. func newUIRenderer() *uiRenderer { return newUIRendererFromFS(uiDist, "ui-dist") } // newUIRendererFromFS builds a renderer from an arbitrary filesystem root. func newUIRendererFromFS(root fs.FS, dir string) *uiRenderer { sub, err := fs.Sub(root, dir) if err != nil { return &uiRenderer{} } return &uiRenderer{fsys: sub} } // ServeIndex writes the SPA entrypoint so deep links can hydrate client-side routes. func (u *uiRenderer) ServeIndex(w http.ResponseWriter, _ *http.Request) error { if u.fsys == nil { return fmt.Errorf("UI assets are not available") } body, err := fs.ReadFile(u.fsys, "index.html") if err != nil { return fmt.Errorf("read index: %w", err) } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Cache-Control", "no-store") _, _ = w.Write(body) return nil } // ServeAsset serves hashed static assets when the request path matches a built file. func (u *uiRenderer) ServeAsset(w http.ResponseWriter, r *http.Request) bool { if u.fsys == nil { return false } if r.Method != http.MethodGet && r.Method != http.MethodHead { return false } requestPath := strings.TrimPrefix(r.URL.Path, "/") if requestPath == "" || requestPath == "." { return false } if strings.HasPrefix(requestPath, "v1/") { return false } cleaned := path.Clean(requestPath) if cleaned == "." || strings.HasPrefix(cleaned, "../") { return false } if strings.HasSuffix(requestPath, "/") || strings.HasSuffix(cleaned, "/") { return false } if _, err := fs.Stat(u.fsys, cleaned); err != nil { return false } http.FileServer(http.FS(u.fsys)).ServeHTTP(w, r) return true }