Bounty Board API
` + html.EscapeString(string(api.OpenAPI)) + `
package httpx import ( "crypto/sha256" "encoding/hex" "fmt" "html" "html/template" "io/fs" "net/http" "net/url" "strings" "bountyboard/api" "bountyboard/internal/domain" "bountyboard/web" ) // pageData is the payload every template receives. type pageData struct { Title string Active string // nav highlight key User *domain.User DefaultTheme string Narrow bool OIDCEnabled bool Error string HasLocalAuth bool Scripts []string Data map[string]any // page-specific extras } var templateFuncs = template.FuncMap{ "initials": func(name string) string { parts := strings.Fields(name) switch { case len(parts) >= 2: return strings.ToUpper(string([]rune(parts[0])[:1]) + string([]rune(parts[1])[:1])) case len(parts) == 1 && len(parts[0]) > 0: return strings.ToUpper(string([]rune(parts[0])[:1])) default: return "?" } }, } // parseTemplates builds one template set per page (layout + page content). func parseTemplates() (map[string]*template.Template, error) { pages, err := fs.Glob(web.FS, "templates/*.html") if err != nil { return nil, err } out := make(map[string]*template.Template) for _, page := range pages { name := strings.TrimPrefix(page, "templates/") if name == "layout.html" { continue } t, err := template.New(name).Funcs(templateFuncs). ParseFS(web.FS, "templates/layout.html", page) if err != nil { return nil, fmt.Errorf("parse %s: %w", page, err) } out[name] = t } return out, nil } func (s *Server) render(w http.ResponseWriter, r *http.Request, page string, data *pageData) { t, ok := s.templates[page] if !ok { s.internalError(w, r, "render", fmt.Errorf("unknown template %q", page)) return } if data == nil { data = &pageData{} } if data.DefaultTheme == "" { data.DefaultTheme = "light" if data.User != nil && data.User.Settings.Theme != "" { data.DefaultTheme = data.User.Settings.Theme } } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := t.ExecuteTemplate(w, "layout", data); err != nil { s.log.Error("execute template", "page", page, "err", err) } } // pageUser resolves the session for an HTML page; returns nil when not // logged in (no error response — pages redirect instead). func (s *Server) pageUser(r *http.Request) *domain.User { c, err := r.Cookie(sessionCookie) if err != nil || c.Value == "" { return nil } sess, err := s.store.SessionByToken(r.Context(), c.Value) if err != nil { return nil } u, err := s.store.UserByID(r.Context(), sess.UserID) if err != nil || u.Disabled { return nil } return u } // page wraps an HTML handler that requires login: redirects anonymous // visitors to /login?next=… and forces pending password changes through // /change-password. func (s *Server) page(h func(http.ResponseWriter, *http.Request, *domain.User)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { u := s.pageUser(r) if u == nil { http.Redirect(w, r, "/login?next="+url.QueryEscape(r.URL.RequestURI()), http.StatusFound) return } if u.MustChangePassword() && r.URL.Path != "/change-password" { http.Redirect(w, r, "/change-password", http.StatusFound) return } h(w, r, u) } } func (s *Server) routesWeb(mux *http.ServeMux) { staticFS, err := fs.Sub(web.FS, "static") if err != nil { panic(err) // embed layout is fixed at compile time } // no-cache + a build-wide strong ETag: browsers revalidate on every // load (cheap 304) and can never run a stale CSS/JS mix after a deploy etag := staticETag(staticFS) fileServer := http.StripPrefix("/static/", http.FileServerFS(staticFS)) mux.Handle("GET /static/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("If-None-Match") == etag { w.WriteHeader(http.StatusNotModified) return } w.Header().Set("Cache-Control", "no-cache") w.Header().Set("ETag", etag) fileServer.ServeHTTP(w, r) })) mux.HandleFunc("GET /{$}", s.page(func(w http.ResponseWriter, r *http.Request, u *domain.User) { s.render(w, r, "home.html", &pageData{Title: "Home", User: u}) })) mux.HandleFunc("GET /login", func(w http.ResponseWriter, r *http.Request) { if s.pageUser(r) != nil { http.Redirect(w, r, "/", http.StatusFound) return } s.render(w, r, "login.html", &pageData{ Title: "Log in", Narrow: true, OIDCEnabled: s.cfg.OIDC.Enabled(), Error: loginErrorMessage(r.URL.Query().Get("error")), Scripts: []string{"/static/js/login.js"}, }) }) mux.HandleFunc("GET /register", func(w http.ResponseWriter, r *http.Request) { if s.pageUser(r) != nil { http.Redirect(w, r, "/", http.StatusFound) return } s.render(w, r, "register.html", &pageData{ Title: "Create account", Narrow: true, Scripts: []string{"/static/js/register.js"}, }) }) for _, p := range []struct{ path, tmpl, title, script string }{ {"/forgot-password", "forgot-password.html", "Forgot password", "/static/js/forgot-password.js"}, {"/reset-password", "reset-password.html", "Reset password", "/static/js/reset-password.js"}, } { mux.HandleFunc("GET "+p.path, func(w http.ResponseWriter, r *http.Request) { s.render(w, r, p.tmpl, &pageData{ Title: p.title, Narrow: true, Scripts: []string{p.script}, }) }) } mux.HandleFunc("GET /change-password", s.page(func(w http.ResponseWriter, r *http.Request, u *domain.User) { s.render(w, r, "change-password.html", &pageData{ Title: "Change password", User: u, Narrow: true, Scripts: []string{"/static/js/change-password.js"}, }) })) mux.HandleFunc("GET /profile", s.page(func(w http.ResponseWriter, r *http.Request, u *domain.User) { s.render(w, r, "profile.html", &pageData{ Title: "Profile", User: u, HasLocalAuth: u.Auth.Local != nil, Scripts: []string{"/static/js/profile.js"}, }) })) mux.HandleFunc("GET /admin", s.page(func(w http.ResponseWriter, r *http.Request, u *domain.User) { if !u.Roles.Admin { s.render(w, r, "placeholder.html", &pageData{Title: "Not authorized", User: u}) return } s.render(w, r, "admin.html", &pageData{ Title: "Administration", Active: "admin", User: u, Scripts: []string{"/static/js/admin.js"}, }) })) mux.HandleFunc("GET /consultant/board", s.page(func(w http.ResponseWriter, r *http.Request, u *domain.User) { if !u.Roles.Consultant && !u.Roles.Admin { s.render(w, r, "placeholder.html", &pageData{Title: "Not authorized", User: u}) return } s.render(w, r, "atomization.html", &pageData{ Title: "Atomization Board", Active: "atomization", User: u, Scripts: []string{"/static/js/atomization.js"}, }) })) rolePage := func(path, tmpl, title, active, script string, check func(*domain.User) bool) { mux.HandleFunc("GET "+path, s.page(func(w http.ResponseWriter, r *http.Request, u *domain.User) { if check != nil && !check(u) && !u.Roles.Admin { s.render(w, r, "placeholder.html", &pageData{Title: "Not authorized", User: u}) return } s.render(w, r, tmpl, &pageData{ Title: title, Active: active, User: u, Scripts: []string{script}, }) })) } isDev := func(u *domain.User) bool { return u.Roles.Developer } isCons := func(u *domain.User) bool { return u.Roles.Consultant } rolePage("/board", "board.html", "Bounty Board", "board", "/static/js/board.js", isDev) rolePage("/my-tasks", "my-tasks.html", "My Tasks", "my-tasks", "/static/js/my-tasks.js", isDev) rolePage("/consultant/reviews", "reviews.html", "Review Queue", "reviews", "/static/js/reviews.js", isCons) rolePage("/consultant/pool", "pool.html", "Developer Pool", "pool", "/static/js/pool.js", isCons) rolePage("/notifications", "notifications.html", "Notifications", "", "/static/js/notifications-page.js", nil) mux.HandleFunc("GET /tasks/{id}", s.page(func(w http.ResponseWriter, r *http.Request, u *domain.User) { s.render(w, r, "task.html", &pageData{ Title: "Task", User: u, Scripts: []string{"/static/js/task.js"}, Data: map[string]any{"TaskID": r.PathValue("id")}, }) })) rolePage("/messages", "messages.html", "Messages", "messages", "/static/js/messages.js", nil) rolePage("/metrics", "metrics.html", "Metrics", "metrics", "/static/js/metrics.js", nil) } // routesDocs serves the OpenAPI document and a minimal viewer (§6). func (s *Server) routesDocs(mux *http.ServeMux) { mux.HandleFunc("GET /api/openapi.yaml", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/yaml") w.Write(api.OpenAPI) }) mux.HandleFunc("GET /api/docs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(`
` + html.EscapeString(string(api.OpenAPI)) + `