package boot import ( "io/fs" "net/http" "atlas9.dev/c/core/iam" ) // FrontendRoutes registers handlers that serve the embedded frontend files. // Each SPA gets its own URL prefix; requests to the prefix root serve the // HTML entry point so that client-side routing works. func FrontendRoutes(mux *http.ServeMux, files fs.FS) { fileServer := http.FileServerFS(files) // requireLogin redirects to /login if there is no authenticated principal. requireLogin := func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if iam.GetPrincipal(r.Context()).Subject == "" { http.Redirect(w, r, "/login", http.StatusFound) return } next(w, r) } } // serveHTML serves a specific HTML file for SPA entry points. serveHTML := func(name string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { r.URL.Path = name fileServer.ServeHTTP(w, r) } } // spaHandler serves static files if they exist, otherwise falls back // to the SPA entry point HTML for client-side routing. spaHandler := func(html string, prefix string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Strip the prefix to get the relative file path path := r.URL.Path[len(prefix):] if path != "" { // Check if the file exists in the embedded FS if f, err := files.Open(path); err == nil { f.Close() http.StripPrefix(prefix, fileServer).ServeHTTP(w, r) return } } // Fall back to SPA entry point r.URL.Path = html fileServer.ServeHTTP(w, r) } } // Root redirects to login mux.Handle("GET /", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if iam.GetPrincipal(ctx).Subject == "" { http.Redirect(w, r, "/login", http.StatusFound) return } http.Redirect(w, r, "/dashboard/profile", http.StatusFound) })) // Auth pages: /login, /register, /verify, /reset-password all serve auth.html // TODO visiting /login should clear the current session. mux.Handle("GET /login", serveHTML("auth.html")) mux.Handle("GET /register", serveHTML("auth.html")) mux.Handle("GET /forgot-password", serveHTML("auth.html")) mux.Handle("GET /verify", serveHTML("auth.html")) mux.Handle("GET /reset-password", serveHTML("auth.html")) mux.Handle("GET /auth/", http.StripPrefix("/auth/", fileServer)) // Admin SPA mux.Handle("GET /admin", requireLogin(serveHTML("admin.html"))) mux.Handle("GET /admin/", requireLogin(spaHandler("admin.html", "/admin/"))) // Dashboard SPA mux.Handle("GET /dashboard", requireLogin(serveHTML("dashboard.html"))) mux.Handle("GET /dashboard/", requireLogin(spaHandler("dashboard.html", "/dashboard/"))) }