From 35abf612771c4b695065cd8920fa61595d9d9674 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Sat, 29 Aug 2026 00:49:40 -0500 Subject: [PATCH] =?UTF-8?q?docs(website):=20one=20self-service=20surface?= =?UTF-8?q?=20=E2=80=94=20/auth/me/account?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to RunicGateway/website's collapse of /admin/account (6 routes) and /player/account (8 routes) onto /auth/me/account, which was already a strict superset of both. - BACKEND_DESIGN.md: the two router-tree entries go; the /auth/me prose is rewritten from "additive, the older routes stay for web back-compat" to the single surface it now is, recording why /auth/me was the one to keep and that gating was equivalent. account.controller.js moved to router/v1/auth/. The /player-group paragraph loses account.router.js from its mount list. - ENGAGEMENT.md: new Phase 1a records the collapse as built, and §0.6 finding 4 is corrected — it named router/v1/player/account.router.js, which is gone. Phase 1b's change-and-verify flow now lands on /auth/me/account and nowhere else, which was the reason to do this first: a self-service field would otherwise have been written three times, in 1b and again in Phase 3. - android/PLAN.md §6.4/§8: the "routes stay for web back-compat" note is now false. The app needed no change — MeApi.kt was already 100% /auth/me/*. - API_V2_PLAN.md: a forward pointer only. Its router inventories are a record of the domain split as it landed and are deliberately left as written. api-route-inventory.json is regenerated wholesale, not partially updated. It is a generated mirror of server/routes.manifest.json with no CI gate, and it had drifted on its own: 168 entries against the manifest's 203, missing every Teams route and still listing the two Gmail connect routes Phase 1 deleted. It is now byte-identical to the manifest. A gate for it is flagged in ENGAGEMENT.md Phase 1a as later work — a mirror nothing checks will drift again. PROJECT_TREE.md is deliberately untouched: it is auto-generated by the sync-project-tree workflow in the website repo, which regenerates it from tracked files on main. Co-Authored-By: Claude --- android/PLAN.md | 30 ++-- website/API_V2_PLAN.md | 8 + website/BACKEND_DESIGN.md | 40 +++-- website/ENGAGEMENT.md | 57 ++++++- website/api-route-inventory.json | 268 +++++++++++++++++++++++-------- 5 files changed, 308 insertions(+), 95 deletions(-) diff --git a/android/PLAN.md b/android/PLAN.md index 3397b62..bbae2c2 100644 --- a/android/PLAN.md +++ b/android/PLAN.md @@ -659,7 +659,7 @@ not rank). | **Rules / Leaderboards / Market** | everyone, *if the shard publishes them* | `/public/shard/{ruleset,points,market}` (M11) | | **Atlas** (bestiary) | everyone, *if the shard publishes it* | `/public/atlas/*` (M11) | | Contact | everyone | `/public/contact` | -| **My Account** | signed-in | `/player/account/*` (or `/admin/account/*` for staff — see §6.4) | +| **My Account** | signed-in | `/auth/me/account/*` — one surface for every role (see §6.4) | | **My Characters / Vendors / Houses** | `player` (linked) | `/player/shard/*` | | Sign in / Sign out | toggles on session | `/auth/mobile/*` | @@ -707,8 +707,10 @@ Guidelines: and `GET /public/atlas/*` are the new reads. Full contract and traps in §9 M11. ### 6.3 Player self-service & game data (bearer) -- **Account** — `GET /player/account`; `PATCH /player/account/username`; - `PATCH /player/account/password`; TOTP `setup`/`enable`/`disable`; identities `GET` / `DELETE`. +- **Account** — `GET /auth/me/account`; `PATCH /auth/me/account/username`; + `PATCH /auth/me/account/password`; TOTP `setup`/`enable`/`disable`; identities `GET` / `DELETE`. + Role-agnostic — see §6.4. (These were `/player/account/*` when this section was written; that + surface was deleted on 2026-08-29. The app was already calling `/auth/me/*` and needed no change.) - **Game account linking** — `POST /player/shard/link` (one-time `[link` code), `POST /player/shard/account` (hybrid signup, when enabled), `GET /player/shard/accounts`. - **My game data** — `GET /player/shard/roster/:account`, `/char/:serial`, `/vendors/:account`, @@ -724,13 +726,16 @@ Guidelines: art/asset work on the platform side) and is explicitly out of the first release. ### 6.4 Self-service is role-agnostic under `/auth/**` (decided) -Player self-service is under `/player/account/*` (gated to `role='player'`) and staff use the *same* -handlers under `/admin/account/*`. Rather than have the app branch by role (and touch `/admin`), we -**add a role-agnostic self surface under `/auth/**`** — the canonical "me" endpoints for every role. -The app calls these regardless of role. This is an **additive v1** -change (see §8): the existing `/player/account/*` and `/admin/account/*` routes stay for web -back-compat; `/auth/me/*` reuses the same `account.controller` handlers behind `requireAuth` (any -authenticated role), so there's no logic duplication. +Self-service account security lives under `/auth/me/account/*` — the canonical "me" endpoints for +every role, behind `requireAuth` (any authenticated role). The app calls these regardless of role and +never touches `/admin`. + +> **Update (2026-08-29).** When this was decided it was an *additive* change: `/player/account/*` +> (gated to `role='player'`) and `/admin/account/*` served the same `account.controller` handlers and +> stayed for web back-compat. **Those 14 routes have since been deleted** — `/auth/me/account` was +> already a strict superset of both, so the web client moved onto it and `/auth/me/*` is now the only +> self-service surface. See `website/ENGAGEMENT.md` Phase 1a. **The app required no change**: `MeApi.kt` +> was already 100% `/auth/me/account/*`. > **M10 update (2026-07-21):** *self-service* stays role-agnostic under `/auth/me/*` as above. Separately, > the **operational** admin subset (§1, §10 — moderation, support queue, dashboard/site-mode, content) @@ -784,8 +789,9 @@ maintenance cost. Reserve v2 for a real breaking re-shape if one ever arises. 1. **Role-agnostic self-service under `/auth/**` (§6.4, decided).** ✅ **DONE (2026-07-19, RunicGateway/website#76 (+ this docs PR)).** A `me.routes.js` sub-router mounts the existing `account.controller` self handlers behind `requireAuth` (any role) at - `/auth/me/*`, so the app has one self surface and never touches `/admin`. The old - `/player/account/*` + `/admin/account/*` routes stay for web back-compat. Shipped routes: + `/auth/me/*`, so the app has one self surface and never touches `/admin`. (The old + `/player/account/*` + `/admin/account/*` routes stayed for web back-compat at the time; they were + deleted on 2026-08-29 — see §6.4.) Shipped routes: - `GET /auth/me` — current `{ id, username, role }` (already existed; the app's role source). - `GET /auth/me/account` — full self account. - `PATCH /auth/me/account/username`, `PATCH /auth/me/account/password`. diff --git a/website/API_V2_PLAN.md b/website/API_V2_PLAN.md index a95f987..b0a5dca 100644 --- a/website/API_V2_PLAN.md +++ b/website/API_V2_PLAN.md @@ -14,6 +14,14 @@ Target repo: `website/` · Docs owner: this file + `BACKEND_DESIGN.md` > 2. **The domain split** — `admin.routes.js` (1552 lines, 110 routes) broken into one router file per > business capability, **in place, with every URL unchanged**. This is the actual driver. +> **Later change, recorded here so the tables below are not read as current (2026-08-29).** The router +> inventories in this file are a **record of the split as it landed**, and are deliberately left as +> written. Since then, `admin/account.router.js` (6 routes) and `player/account.router.js` (8 routes) +> were **deleted**: `/auth/me/account/*` was already a strict superset of both, and is now the single +> self-service surface. `account.controller.js` moved to `router/v1/auth/`. See +> [ENGAGEMENT.md](./ENGAGEMENT.md) Phase 1a and [BACKEND_DESIGN.md](./BACKEND_DESIGN.md) §4. The +> authoritative URL list is, as this file argues throughout, the generated manifest — never a table. + --- ## Why the auth merge is out diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index 6e3d892..61ca5f4 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -87,7 +87,15 @@ server/ /me sub-routers below also match the bare /me and supply its noindex header me.routes.js (23) /auth/me/account*, sessions, trusted - devices — router-level requireAuth + devices — router-level requireAuth. + The ONLY self-service account surface + (see below); account.controller.js + sits beside it and is reached from + nowhere else + account.controller.js the self-service handlers: username, + password, TOTP, identities, device + sessions, trusted devices, recovery + codes notifications.routes.js (3) /auth/me/devices*, notifications/* mobile.routes.js + /auth/mobile/* — native bearer login mobileSso.routes.js (5) @@ -121,9 +129,6 @@ server/ player/ index.js owns the shared `noindex, requireAuth` gate (authenticated, ANY role — staff are a superset of players) and the mount table - account.router.js (8) /player/account — credentials, TOTP, - linked identities; handlers shared - with /admin/account and /auth/me appeals.router.js (4) /player/appeals appeals.controller.js (/player/shard is module-uo's) @@ -145,7 +150,6 @@ server/ own prefixes; owns the shared `noindex, isLoggedIn, staffOnly` gate and declares no routes itself - account.router.js (6) /admin/account — self-service, no adminOnly users.router.js (9) /admin/users — adminOnly. The six /users/:id/shard/* routes are a MODULE's, reached through the @@ -786,21 +790,29 @@ their own router level, and `/sso/:provider/link` carries `requireAuth` per rout | GET · PUT | `/me/notifications/subscriptions` | cookie / bearer | `{streams:[id]}` on PUT | get / replace own opted-in streams (unknown ids dropped) | | GET · PUT | `/me/notifications/teams` | cookie / bearer | `{teams:[{teamId,muted,emailMode}]}` on PUT | get / replace own **per-Team** preferences (phase 6, [`TEAMS.md`](TEAMS.md) §6.3). One entry per Team the caller could be notified about — active membership or an active forum grant — plus any Team they already hold a preference for; server-side defaults applied. An entry naming a Team the caller has no access to is **dropped, not refused**: a Team left between loading the screen and saving it is a race, not a client bug. The array is required even when empty (`../android/PLAN.md` §11) | -**Role-agnostic self-service (`/auth/me/*`).** The canonical "me" surface for **every** authenticated -role. It reuses the exact `account.controller` handlers as `/player/account/*` and `/admin/account/*` -(no logic duplication) behind `requireAuth` **only** — any active account, never a specific role. This -lets a client (the Android app) manage its own account through one surface without ever touching -`/admin` (docs/android/PLAN.md §6.4). The older `/player/account/*` + `/admin/account/*` routes stay -for web back-compat. +**Role-agnostic self-service (`/auth/me/*`).** The **only** self-service account surface, for every +authenticated role, behind `requireAuth` **only** — any active account, never a specific role. A +client (the Android app) manages its own account through it without ever touching `/admin` +(docs/android/PLAN.md §6.4). + +It used to be the third of three URL surfaces onto `account.controller`, beside `/player/account/*` +and `/admin/account/*`. **Those 14 routes were deleted.** Both were strictly *smaller* than this one — +neither carried recovery codes, and `/admin/account` carried no username or password change — so the +web client already reached in here for part of a single screen. Gating was equivalent where it +overlapped (`/player` and `/auth/me` are byte-identical `noindex, requireAuth`; `staffOnly` on +`/admin/account` was strictly narrower and bought nothing, since every handler is self-scoped to +`req.user.id`). The controller moved to `router/v1/auth/account.controller.js` beside its one +remaining router. **New self-service fields go here and only here.** **The `/player/*` group is self-service, not player-only.** Staff are a **superset** of players — every -player ability plus their staff tools on top — so the whole group (`account.router.js`, -`appeals.router.js`, mounted by `player/index.js`, plus whatever a module mounts here) sits behind +player ability plus their staff tools on top — so the whole group (`appeals.router.js`, mounted by +`player/index.js`, plus whatever a module mounts here) sits behind the shared `noindex, requireAuth` gate **only**, never `requireRole('player')`. Every handler is self-scoped to the caller by `req.user.id`, so an admin/editor/ moderator using it sees only their **own** linked accounts and characters (with the pre-existing `isAdmin` bypass still letting a genuine admin read *any* character). `module-uo` inherits the rule and relies on it: its `/player/shard/*` handlers are the identical self-scoped ones it also serves -under `/admin/shard/*`, so the two are interchangeable. This is why a staff account with linked game characters gets its "My characters" and +under `/admin/shard/*`, so the two are interchangeable. (Core no longer does this for account +security — see `/auth/me/*` above — but the rule the module depends on is unchanged.) This is why a staff account with linked game characters gets its "My characters" and personal notification streams on the mobile client — the group no longer 403s a non-`player` role. `teams.router.js` joins the group in Teams phase 2, and relies on exactly that rule: a moderator is in diff --git a/website/ENGAGEMENT.md b/website/ENGAGEMENT.md index df78384..4060d12 100644 --- a/website/ENGAGEMENT.md +++ b/website/ENGAGEMENT.md @@ -182,10 +182,16 @@ duplicates Phase 1b must clean up, and it makes `email_verified` too weak a sign duplicate wins (which is why §7.1 Q1's answer is oldest-wins, not verified-wins). **4. There is no self-serve email flow at all.** No route lets a user set or change their own address -after signup — `router/v1/player/account.router.js` has none, and `users.model.js:72`'s `update()` is -reached only by admin user management. An address is captured once, at registration or SSO provisioning, -and is thereafter unchangeable by its owner. A verification gate presupposes a change-and-verify flow, -so Phase 1b builds one; it is not an add-on to an existing screen. +after signup — `router/v1/auth/me.routes.js`, the self-service surface, has none, and +`users.model.js:72`'s `update()` is reached only by admin user management. An address is captured once, +at registration or SSO provisioning, and is thereafter unchangeable by its owner. A verification gate +presupposes a change-and-verify flow, so Phase 1b builds one; it is not an add-on to an existing +screen. + +> Read out of the tree on 2026-08-28, when self-service had *three* URL surfaces onto one controller +> (`/auth/me/account/*`, `/player/account/*`, `/admin/account/*`), so a new field meant writing it +> three times. **The two smaller ones were deleted on 2026-08-29** — see Phase 1a below. Phase 1b's +> change-and-verify routes are written once, on `/auth/me/account`. **One consequence for the error surface.** A unique constraint needs a user-facing failure, and the obvious wording (*"that email is already registered"*) makes account existence queryable — a step back @@ -1204,6 +1210,7 @@ change is not complete until `docs/` reflects it" — is the floor; this table i | Phase | `docs/` | Other repos | | --- | --- | --- | | **1** Remove Gmail OAuth2, SMTP | `website/BACKEND_DESIGN.md` §7 **rewritten** (not amended — it documents Gmail OAuth2 as *the* mechanism); route tables lose `/admin/email/connect/*` | `website/README.md` + `.env.example` wherever they point at Connect Gmail · **`runicgateway.com`**: `notifications-and-email.mdx` (its "There is no SMTP option" aside is now false), `configuration.mdx:62`, `troubleshooting.mdx:101`, `system-architecture.mdx:117` · a release note | +| **1a** One self surface | `website/BACKEND_DESIGN.md` — the `/auth/me` prose and the `/player`+`/admin` router trees · `api-route-inventory.json` regenerated · `website/ENGAGEMENT.md` this phase | `android/PLAN.md` §6.4/§8 — the "routes stay for web back-compat" note is now false · `website/API_V2_PLAN.md` historical tables get a pointer | | **1b** Unique email | `website/BACKEND_DESIGN.md` — the `users` table (the "not unique" note is now false), the new change/verify routes, and the de-dupe migration as an operator-visible upgrade step | `website/README.md` upgrade notes · a release note naming the admin report and the verification-gate default | | **2** Trigger registry | `website/MODULE_API.md` §1.1 (**1.7.0** + correct the stale "1.6.0 has only ever been on `edge`" paragraph), §2.3 (`ctx.events`, `ctx.inbox`), §2.4 (`registerEventTriggers`), §7.3's dormant-rule note · `website/ENGAGEMENT.md` §4.3 kept true | `Integration-kit`: `ci/core-ref.json` re-pinned (the equality check goes red **on purpose**) + chapter 2 gains a "registering a trigger" section · **`runicgateway.com`**: `platform.json.moduleApi` → 1.7.0 | | **3** Channel preferences | `website/BACKEND_DESIGN.md` route table · `android/PLAN.md` §11 | — | @@ -1299,6 +1306,45 @@ of a 500 that takes the contact form with it. --- +### Phase 1a — One self-service surface: `/auth/me/account` ✅ + +**Not in the original plan.** It was added on 2026-08-29, ahead of Phase 1b, when mapping Phase 1b's +ground truth turned up that self-service account security had **three URL surfaces onto one +controller** — `/auth/me/account/*`, `/player/account/*` and `/admin/account/*`, all mounting the same +`account.controller` handlers. Phase 1b adds a self-service field (email), Phase 3 adds another +(channel preferences), and each would otherwise have been written three times. + +**`/auth/me/account` was already a strict superset**, which is what settled which one to keep: it was +the only surface carrying recovery codes, and `/admin/account` carried no username or password change +at all. The web client already reached into `/auth/me` for two calls on a screen it otherwise served +from `/admin/account` — the split was leaking before anyone touched it. Gating was 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`. + +**14 routes deleted, 0 added, no handler changed.** `account.controller.js` moved from +`router/v1/admin/` to `router/v1/auth/`, beside the one router that still reaches it. The web client's +14 call sites moved onto the root-level `api.myAccount` / `api.changeUsername` / … group. The Android +app needed **nothing** — `MeApi.kt` was already 100% `/auth/me/account/*`. + +It is a **breaking change to the published OpenAPI surface**, accepted deliberately: both consumers are +in this org, and deprecating-then-deleting would have meant Phase 1b deciding whether to add its email +routes to surfaces already marked for removal. + +**As built:** `routes.manifest.json` shows exactly 14 deletions and 0 additions; the OpenAPI spec loses +the same 14 paths with **zero surviving path definitions changed** and identical `components` (its large +textual diff is pure reordering — removing the first-mounted router shifts every later path). Two +swagger tags — `Admin · Account` and `Player` — were declared *only* by the deleted routes and went with +them. 1203 server tests and 288 client tests green. + +**One thing this phase did not fix, on purpose:** `docs/website/api-route-inventory.json`, the docs +mirror of the route manifest, had drifted badly on its own (168 entries against the manifest's 203 — +missing every Teams route, and still listing the two Gmail `connect` routes Phase 1 deleted). It is a +generated mirror with no CI gate, so it was regenerated wholesale here rather than partially updated. +**A mirror nothing checks will drift again** — a gate for it belongs in a later phase. + +--- + ### Phase 1b — Unique, changeable, verifiable email addresses *(decision 6)* **Lands alone, between 1 and 2, and before any engagement mail exists.** It touches registration, SSO @@ -1323,7 +1369,8 @@ uniqueness *after* a send log and a suppression list hold rows is strictly worse `foo@x.com` are one mailbox everywhere that matters, and folding must happen in the index rather than in application code that can be bypassed. 3. **A self-serve change-and-verify flow**, which does not exist today (§0.6 finding 4). Set/change - address, a signed time-boxed verification link, `email_verified` set only on link use. SSO's + address, a signed time-boxed verification link, `email_verified` set only on link use. It lands on + **`/auth/me/account` and nowhere else** — Phase 1a made that the single self-service surface. SSO's `emailVerified: Boolean(profile.email)` is corrected at the same time to honour the IdP's actual `email_verified` claim rather than the mere presence of an address. 4. **The verification gate as an admin setting** — `on` for fresh installs, `off` for upgrades, so the diff --git a/website/api-route-inventory.json b/website/api-route-inventory.json index 3860ae1..ed7f851 100644 --- a/website/api-route-inventory.json +++ b/website/api-route-inventory.json @@ -17,30 +17,6 @@ "method": "GET", "path": "/api/health" }, - { - "method": "GET", - "path": "/api/v1/admin/account" - }, - { - "method": "GET", - "path": "/api/v1/admin/account/identities" - }, - { - "method": "DELETE", - "path": "/api/v1/admin/account/identities/:provider" - }, - { - "method": "POST", - "path": "/api/v1/admin/account/totp/disable" - }, - { - "method": "POST", - "path": "/api/v1/admin/account/totp/enable" - }, - { - "method": "POST", - "path": "/api/v1/admin/account/totp/setup" - }, { "method": "GET", "path": "/api/v1/admin/activity" @@ -89,14 +65,6 @@ "method": "PUT", "path": "/api/v1/admin/email/config" }, - { - "method": "GET", - "path": "/api/v1/admin/email/connect/callback" - }, - { - "method": "GET", - "path": "/api/v1/admin/email/connect/start" - }, { "method": "POST", "path": "/api/v1/admin/email/disconnect" @@ -145,6 +113,14 @@ "method": "GET", "path": "/api/v1/admin/moderation/recent" }, + { + "method": "GET", + "path": "/api/v1/admin/moderation/reports" + }, + { + "method": "POST", + "path": "/api/v1/admin/moderation/reports/:id/handle" + }, { "method": "GET", "path": "/api/v1/admin/moderation/search" @@ -293,6 +269,98 @@ "method": "PUT", "path": "/api/v1/admin/site-mode" }, + { + "method": "GET", + "path": "/api/v1/admin/teams" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/:id" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/:id/archive" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/:id/display-name" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/:id/forum/moderation" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/:id/grants" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/:id/hide" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/:id/leader-override" + }, + { + "method": "DELETE", + "path": "/api/v1/admin/teams/:id/leader-override/:memberKey" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/:id/unhide" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/forum/settings" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/forum/uploads" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/integrations" + }, + { + "method": "PUT", + "path": "/api/v1/admin/teams/integrations" + }, + { + "method": "DELETE", + "path": "/api/v1/admin/teams/integrations/:teamId" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/requests" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/requests/:id/decide" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/resync" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/review" + }, + { + "method": "GET", + "path": "/api/v1/admin/teams/voice" + }, + { + "method": "PUT", + "path": "/api/v1/admin/teams/voice" + }, + { + "method": "DELETE", + "path": "/api/v1/admin/teams/voice/:teamId" + }, + { + "method": "POST", + "path": "/api/v1/admin/teams/voice/sync" + }, { "method": "POST", "path": "/api/v1/admin/uploads" @@ -477,6 +545,14 @@ "method": "PUT", "path": "/api/v1/auth/me/notifications/subscriptions" }, + { + "method": "GET", + "path": "/api/v1/auth/me/notifications/teams" + }, + { + "method": "PUT", + "path": "/api/v1/auth/me/notifications/teams" + }, { "method": "GET", "path": "/api/v1/auth/me/sessions" @@ -557,38 +633,6 @@ "method": "POST", "path": "/api/v1/auth/sso/totp" }, - { - "method": "GET", - "path": "/api/v1/player/account" - }, - { - "method": "GET", - "path": "/api/v1/player/account/identities" - }, - { - "method": "DELETE", - "path": "/api/v1/player/account/identities/:provider" - }, - { - "method": "PATCH", - "path": "/api/v1/player/account/password" - }, - { - "method": "POST", - "path": "/api/v1/player/account/totp/disable" - }, - { - "method": "POST", - "path": "/api/v1/player/account/totp/enable" - }, - { - "method": "POST", - "path": "/api/v1/player/account/totp/setup" - }, - { - "method": "PATCH", - "path": "/api/v1/player/account/username" - }, { "method": "GET", "path": "/api/v1/player/appeals" @@ -605,6 +649,66 @@ "method": "GET", "path": "/api/v1/player/appeals/eligible" }, + { + "method": "GET", + "path": "/api/v1/player/teams" + }, + { + "method": "GET", + "path": "/api/v1/player/teams/:slug/access" + }, + { + "method": "PATCH", + "path": "/api/v1/player/teams/:slug/forum/posts/:id" + }, + { + "method": "POST", + "path": "/api/v1/player/teams/:slug/forum/posts/:id/moderate" + }, + { + "method": "POST", + "path": "/api/v1/player/teams/:slug/forum/report" + }, + { + "method": "GET", + "path": "/api/v1/player/teams/:slug/forum/threads" + }, + { + "method": "POST", + "path": "/api/v1/player/teams/:slug/forum/threads" + }, + { + "method": "GET", + "path": "/api/v1/player/teams/:slug/forum/threads/:id" + }, + { + "method": "POST", + "path": "/api/v1/player/teams/:slug/forum/threads/:id/moderate" + }, + { + "method": "POST", + "path": "/api/v1/player/teams/:slug/forum/threads/:id/posts" + }, + { + "method": "POST", + "path": "/api/v1/player/teams/:slug/forum/uploads" + }, + { + "method": "DELETE", + "path": "/api/v1/player/teams/:slug/forum/uploads/:id" + }, + { + "method": "GET", + "path": "/api/v1/player/teams/:slug/grants" + }, + { + "method": "POST", + "path": "/api/v1/player/teams/:slug/grants" + }, + { + "method": "DELETE", + "path": "/api/v1/player/teams/:slug/grants/:userId" + }, { "method": "POST", "path": "/api/v1/public/contact" @@ -637,6 +741,34 @@ "method": "GET", "path": "/api/v1/public/status" }, + { + "method": "GET", + "path": "/api/v1/public/teams" + }, + { + "method": "GET", + "path": "/api/v1/public/teams/:slug" + }, + { + "method": "GET", + "path": "/api/v1/public/teams/:slug/activity" + }, + { + "method": "GET", + "path": "/api/v1/public/teams/:slug/members" + }, + { + "method": "GET", + "path": "/api/v1/public/teams/by-external/:moduleId/:externalId" + }, + { + "method": "GET", + "path": "/api/v1/public/teams/unsubscribe/:token" + }, + { + "method": "POST", + "path": "/api/v1/public/teams/unsubscribe/:token" + }, { "method": "GET", "path": "/api/v1/public/version" @@ -674,6 +806,14 @@ { "method": "GET", "path": "/internal/bot-config" + }, + { + "method": "GET", + "path": "/internal/commands" + }, + { + "method": "POST", + "path": "/internal/commands/dispatch" } ] }