Files
summercms/modules/boardwalk
Jakub Zych 143eca5a1f feat(cabana): add the My account page with subtitle and password change
- lagoon: 202610090001_backend_users_metadata adds WinterCMS's nullable
  jsonable backend_users.metadata column (TEXT); rollback drops only it
- cabana.BackendUser.Metadata and Subtitle read metadata.subtitle leniently
  (NULL, blank, null, [], invalid JSON and non-strings read as empty)
- GET /auth/me returns subtitle; PUT /account edits the signed-in admin's
  own names, email and subtitle, and the password with current_password;
  any other key is a 422 and nothing is written
- email: required, valid, 6-255 chars, unique against other admins' email
  and login ignoring case (soft-deleted included); a unique-index race is 422
- a password change sets tokens_valid_after, revokes the presented token and
  gives a cookie session a fresh cookie
- swag annotation, regenerated admin.json and schema.d.ts; route inventories,
  CSRF walk (27) and OpenAPI conformance learn the route
- SPA: My account item in the user menu opens /account (AccountView), which
  saves through PUT /account and updates the top bar via setCurrentUser
- backend::lang.myaccount strings (en, pl), rebuilt boardwalk dist
- cabana and lagoon READMEs, users-and-permissions and admin-spa docs
2026-10-09 17:38:04 +02:00
..

boardwalk

HTTP handler that serves the embedded admin SPA build under a configurable path prefix.

import "git.golem15.com/golem15/summercms/modules/boardwalk"

Overview

boardwalk embeds the compiled admin SPA (dist/, produced by npm --prefix admin run build) into the binary and serves it. The build is path-agnostic: index.html carries a placeholder token that the handler replaces once, at construction, with the prefix the admin is mounted under, so one build works at any backend URI. cabana mounts it when it activates the admin routes. It stands in for the server-rendered backend layouts of WinterCMS, which the Go port replaces with a single-page app.

Features

  • Serves the embedded build under any prefix, rewriting relative asset URLs and the admin base meta in index.html for that prefix (boardwalk.RewriteIndex).
  • Fails at boot when index.html lacks the boardwalk.BaseToken placeholder, which catches a stale or hand-edited build.
  • Falls back to index.html for client-side routes and directories; missing files with an extension get a plain 404.
  • Hands every request whose path under the prefix is api or starts with api/ to a caller-supplied handler, so admin API misses stay JSON instead of returning the SPA.
  • Long-lived immutable caching for hashed files under assets/, no-cache for other files and no-store for index.html.
  • Security headers on every response: a restrictive Content-Security-Policy, frame denial, nosniff, a same-origin referrer policy and noindex, nofollow.
  • Explicit content types for scripts, styles, fonts, SVG and JSON, with a MIME lookup fallback.

Usage

notFoundAPI := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusNotFound)
	_, _ = w.Write([]byte(`{"error":"not found"}`))
})

spa, err := boardwalk.Handler("/backend", notFoundAPI)
if err != nil {
	return err
}
mux.Handle("/backend/", spa)

API reference

Identifier Description
boardwalk.Handler Builds the SPA handler for a prefix; the second argument answers unmatched api/ paths.
boardwalk.Dist Returns the embedded build as an fs.FS rooted at dist/.
boardwalk.RewriteIndex Rewrites raw index.html bytes for a prefix; errors when the placeholder token is missing.
boardwalk.BaseToken The placeholder in dist/index.html that is replaced by the prefix.
boardwalk.ContentType The Content-Type served for a file name, with explicit UTF-8 JavaScript and CSS types. Shared with the plugin asset route in cabana.
boardwalk.SetSecurityHeaders Sets the admin security headers (nosniff, referrer policy, no framing, the script-src 'self' CSP, noindex) on a response.

Dependencies

  • SummerCMS modules: none.
  • Third-party: none.
  • Standard library: bytes, embed, errors, fmt, html, io/fs, mime, net/http, path, strings, time.

The embedded dist/ tree is generated from the admin/ Vite project, whose build writes to modules/boardwalk/dist.

Testing

go test ./modules/boardwalk/...

The tests run the handler through net/http/httptest against the embedded build and in-memory file systems; they need no external services.