refactor(api): collapse /admin/account and /player/account onto /auth/me/account #166

Merged
whitlocktech merged 1 commits from refactor/collapse-account-surfaces into edge 2026-08-29 05:54:28 +00:00
Member

Engagement Phase 1a — design of record: docs/website/ENGAGEMENT.md Phase 1a (companion PR). Lands ahead of Phase 1b.

Why

Self-service account security had three URL surfaces onto one controller. All three mounted the same admin/account.controller.js handlers, and each of the three router files carried a header comment apologising for the arrangement — player/account.router.js:8: "Three URL surfaces, one implementation; this file must not grow a fourth copy of the logic."

Phase 1b adds a self-service field (email), Phase 3 adds another (channel preferences). Each would otherwise be written three times.

Which one to keep

/auth/me/account was already a strict superset — that settled it:

Surface Routes Gate
/admin/account 6 noindex, isLoggedIn, staffOnly
/player/account 8 noindex, requireAuth
/auth/me/account 10 noindex, requireAuth

The split was already leaking. Recovery codes exist only on /auth/me, so client.js called /auth/me/account/recovery-codes/* for two operations on a screen it otherwise served from /admin/account — in the same file. We are not collapsing three equal surfaces; we are deleting two partial ones.

Gating is equivalent where it overlapped: /player and /auth/me apply byte-identical noindex, requireAuth, and staffOnly on /admin/account was strictly narrower while buying nothing, since every handler is self-scoped to req.user.id. There is no CSRF layer to differ.

What changed

  • 14 routes deleted, 0 added, no handler changed.
  • account.controller.js moved router/v1/admin/router/v1/auth/, beside the one router that still reaches it.
  • Web client: 14 call sites moved onto a root-level api.myAccount / api.changeUsername / … group, matching the /auth/me methods already sitting there.
  • Android app: no change. MeApi.kt was already 100% /auth/me/account/*.
  • Swagger tags Admin · Account and Player were declared only by the deleted routes and go with them. The orphaned AccountStatus schema goes too; PlayerAccount is re-described as the any-role /auth/me/account shape (name kept so existing $refs resolve).

Breaking change, accepted deliberately

This removes 14 paths from the published OpenAPI surface. Both consumers are in this org. Deprecate-then-delete was considered and rejected: it would leave Phase 1b deciding whether to add its email routes to surfaces already marked for removal.

Verification

  • routes.manifest.json: exactly 14 deletions, 0 additions.
  • OpenAPI spec: same 14 paths removed, 0 surviving path definitions changed, components otherwise untouched. Its large textual diff is pure reordering — removing the first-mounted router shifts every later path. Verified by set-comparison against the committed spec, not by reading the diff.
  • 1203 server tests, 288 client tests, 53 bot tests — all green.
  • check:modules, check:hosts, routes:manifest --check all pass.

Note for anyone regenerating locally: website/modules/uo must be excluded (MODULES_DIR=<empty dir>) or the manifest picks up 33 module routes.

Reviewer notes

  • /admin/account and /account as browser paths are untouched — the SPA pages still live there. Only the API paths moved.
  • Pre-existing and not touched here: four swagger tags (Admin · Modules, Admin · Teams, Player · Teams, Public · Teams) are used by routes but never declared. Unrelated drift.

AI disclosure

Written with Claude Code (Opus 5).

Engagement **Phase 1a** — design of record: [`docs/website/ENGAGEMENT.md` Phase 1a](https://gitea.whitlocktech.com/RunicGateway/docs) (companion PR). Lands ahead of Phase 1b. ## Why Self-service account security had **three URL surfaces onto one controller**. All three mounted the same `admin/account.controller.js` handlers, and each of the three router files carried a header comment apologising for the arrangement — `player/account.router.js:8`: *"Three URL surfaces, one implementation; this file must not grow a fourth copy of the logic."* Phase 1b adds a self-service field (email), Phase 3 adds another (channel preferences). Each would otherwise be written three times. ## Which one to keep `/auth/me/account` was already a **strict superset** — that settled it: | Surface | Routes | Gate | |---|---|---| | `/admin/account` | 6 | `noindex, isLoggedIn, staffOnly` | | `/player/account` | 8 | `noindex, requireAuth` | | **`/auth/me/account`** | **10** | `noindex, requireAuth` | **The split was already leaking.** Recovery codes exist only on `/auth/me`, so `client.js` called `/auth/me/account/recovery-codes/*` for two operations on a screen it otherwise served from `/admin/account` — in the same file. We are not collapsing three equal surfaces; we are deleting two partial ones. Gating is equivalent where it overlapped: `/player` and `/auth/me` apply byte-identical `noindex, requireAuth`, and `staffOnly` on `/admin/account` was strictly narrower while buying nothing, since every handler is self-scoped to `req.user.id`. There is no CSRF layer to differ. ## What changed - **14 routes deleted, 0 added, no handler changed.** - `account.controller.js` moved `router/v1/admin/` → `router/v1/auth/`, beside the one router that still reaches it. - Web client: 14 call sites moved onto a root-level `api.myAccount` / `api.changeUsername` / … group, matching the `/auth/me` methods already sitting there. - **Android app: no change.** `MeApi.kt` was already 100% `/auth/me/account/*`. - Swagger tags `Admin · Account` and `Player` were declared *only* by the deleted routes and go with them. The orphaned `AccountStatus` schema goes too; `PlayerAccount` is re-described as the any-role `/auth/me/account` shape (name kept so existing `$ref`s resolve). ## Breaking change, accepted deliberately This removes 14 paths from the published OpenAPI surface. Both consumers are in this org. Deprecate-then-delete was considered and rejected: it would leave Phase 1b deciding whether to add its email routes to surfaces already marked for removal. ## Verification - `routes.manifest.json`: **exactly 14 deletions, 0 additions.** - OpenAPI spec: same 14 paths removed, **0 surviving path definitions changed**, `components` otherwise untouched. Its large textual diff is **pure reordering** — removing the first-mounted router shifts every later path. Verified by set-comparison against the committed spec, not by reading the diff. - **1203** server tests, **288** client tests, **53** bot tests — all green. - `check:modules`, `check:hosts`, `routes:manifest --check` all pass. > Note for anyone regenerating locally: `website/modules/uo` must be excluded (`MODULES_DIR=<empty dir>`) or the manifest picks up 33 module routes. ## Reviewer notes - `/admin/account` and `/account` as **browser** paths are untouched — the SPA pages still live there. Only the API paths moved. - Pre-existing and *not* touched here: four swagger tags (`Admin · Modules`, `Admin · Teams`, `Player · Teams`, `Public · Teams`) are used by routes but never declared. Unrelated drift. ## AI disclosure Written with Claude Code (Opus 5).
wtclaude added 1 commit 2026-08-29 05:50:22 +00:00
refactor(api): collapse /admin/account and /player/account onto /auth/me/account
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 26s
PR Checks / client-build (pull_request) Successful in 27s
PR Checks / server-tests (pull_request) Successful in 10m32s
6e61146678
Self-service account security had three URL surfaces onto one controller. All
three mounted the same `admin/account.controller.js` handlers; each of the three
router files carried a header comment apologising for the arrangement.

`/auth/me/account` was already a strict superset, which settles which to keep:

  /admin/account   6 routes  noindex, isLoggedIn, staffOnly
  /player/account  8 routes  noindex, requireAuth
  /auth/me/account 10 routes noindex, requireAuth

Neither of the deleted surfaces carried recovery codes, and /admin/account
carried no username or password change at all — so client.js already called
/auth/me/account/recovery-codes/* for two operations on a screen it otherwise
served from /admin/account. The split was leaking before this change.

Gating is equivalent where it overlapped: /player and /auth/me apply identical
`noindex, requireAuth`, and `staffOnly` on /admin/account was strictly narrower
while buying nothing, since every handler is self-scoped to req.user.id. There
is no CSRF layer to differ.

  - 14 routes deleted, 0 added, no handler changed.
  - account.controller.js moves router/v1/admin/ -> router/v1/auth/, beside the
    one router that still reaches it.
  - Web client: 14 call sites move onto a root-level api.myAccount /
    api.changeUsername / ... group, matching the /auth/me methods already there.
  - Android app: no change. MeApi.kt was already 100% /auth/me/account/*.
  - Two swagger tags, `Admin · Account` and `Player`, were declared only by the
    deleted routes and go with them. The orphaned `AccountStatus` schema goes
    too; `PlayerAccount` is re-described as the any-role /auth/me/account shape
    (the name is kept so existing $refs resolve).

Breaking to the published OpenAPI surface, accepted deliberately: both consumers
are in this org, and deprecate-then-delete would leave the next phase deciding
whether to add routes to surfaces already marked for removal.

Verification: routes.manifest.json shows exactly 14 deletions and 0 additions.
The OpenAPI spec loses the same 14 paths with zero surviving path definitions
changed; its large textual diff is pure reordering, because removing the
first-mounted router shifts every later path. 1203 server tests, 288 client
tests, 53 bot tests green; check:modules, check:hosts and routes:manifest
--check all pass.

Design of record: docs/website/ENGAGEMENT.md Phase 1a. This lands ahead of
engagement Phase 1b, which adds a self-service email field — written once here
rather than three times.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech approved these changes 2026-08-29 05:54:21 +00:00
whitlocktech merged commit c2e4df5b3d into edge 2026-08-29 05:54:28 +00:00
whitlocktech deleted branch refactor/collapse-account-surfaces 2026-08-29 05:54:29 +00:00
Sign in to join this conversation.
No description provided.