feat: seed script, forgot-password, hover cards, shortcuts, OpenAPI docs, runbook (phase 12)
- scripts/seed.go: idempotent demo data per §11.11 (make seed) - forgot/reset password: SMTP-gated, one-shot TTL tokens, uniform responses against enumeration, sessions revoked on reset; login page link + pages - profile hover cards on [data-user-card] elements (§11.13) - keyboard shortcuts: g b/m/t/h navigation, / focuses search (§10) - bulk archive endpoint (§11.9) - hand-written OpenAPI 3.1 covering §6, served at /api/docs + yaml download - make backup / make restore (mongodump archive via the mongo container) - README: quick start, demo data, runbook, breaker/job operations, working Caddy + nginx reverse-proxy samples (WS block, client_max_body_size), documented later-stubs (§11.24) - smoke.sh now exercises register → logout → login → me → board → pages Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,14 @@ package httpx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"bountyboard/api"
|
||||
"bountyboard/internal/domain"
|
||||
"bountyboard/web"
|
||||
)
|
||||
@@ -155,6 +157,17 @@ func (s *Server) routesWeb(mux *http.ServeMux) {
|
||||
})
|
||||
})
|
||||
|
||||
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,
|
||||
@@ -223,6 +236,24 @@ func (s *Server) routesWeb(mux *http.ServeMux) {
|
||||
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(`<!doctype html><html lang="en"><head><meta charset="utf-8">
|
||||
<title>Bounty Board API</title><link rel="stylesheet" href="/static/css/app.css">
|
||||
<script src="/static/js/theme.js"></script></head>
|
||||
<body><main class="container"><h1>Bounty Board API</h1>
|
||||
<p><a class="btn" href="/api/openapi.yaml" download>Download openapi.yaml</a></p>
|
||||
<pre style="white-space:pre-wrap;background:var(--surface);border:1px solid var(--border);padding:16px;border-radius:var(--radius)">` +
|
||||
html.EscapeString(string(api.OpenAPI)) + `</pre></main></body></html>`))
|
||||
})
|
||||
}
|
||||
|
||||
func loginErrorMessage(code string) string {
|
||||
switch code {
|
||||
case "":
|
||||
|
||||
Reference in New Issue
Block a user