feat(quick-261016-0fm): per-admin theme and locale preferences

- GET and PUT /account/preferences (own row only, interactive sessions,
  CSRF header on cookie writes) store admin_theme and admin_locale in
  backend_users.metadata, keeping every other key
- GET /auth/me and the My account answers carry preferences
- phrasebook.Translator.Locales lists the locales defining a prefix
- route tables, conformance cases, OpenAPI document, TS types and docs
This commit is contained in:
Jakub Zych
2026-10-16 09:26:09 +02:00
parent 1a03399357
commit 047858ae84
21 changed files with 729 additions and 12 deletions

View File

@@ -713,6 +713,17 @@ func conformCases() []conformCase {
}
return rec
}, into[cabana.Envelope[cabana.AdminProfile]](), nil},
{"PUT /account/preferences", 200, "cabana.Envelope-cabana_AdminPreferences", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
rec := e.send(t, http.MethodPut, "/account/preferences", map[string]any{"theme": "dark"}, true)
var body cabana.Envelope[cabana.AdminPreferences]
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil || body.Data.Theme != "dark" {
t.Fatalf("preferences body = %s (%v)", rec.Body.String(), err)
}
return rec
}, into[cabana.Envelope[cabana.AdminPreferences]](), nil},
{"GET /account/preferences", 200, "cabana.Envelope-cabana_AdminPreferences", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
return e.send(t, http.MethodGet, "/account/preferences", nil, true)
}, into[cabana.Envelope[cabana.AdminPreferences]](), nil},
{"POST /account/avatar", 200, "cabana.Envelope-cabana_AdminProfile", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)