accounts
Accounts Worker served on the accounts subdomain (accounts.recflare.net) — a Hono
app for account reads, profile mutations and lookups. Accounts live in the shared
recflare D1 database, whose account schema and migrations are owned by the auth
worker; this worker binds it read/write.
Routes
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | / |
Health check | |
| GET | /account/me |
✓ | The caller's own account (private self DTO) |
| GET | /account/search?name= |
Prefix-search accounts by username | |
| GET | /account/bulk?id=1&id=2,3 |
Look up many accounts by id | |
| GET | /account/:id |
A single public account | |
| GET | /account/:id/bio |
A player's bio | |
| POST | /account/create |
Create an account → { success, value } |
|
| GET | /parentalcontrol/me |
✓ | The caller's parental-control flags |
| GET | /accountprivacysettings/:id |
An account's privacy settings | |
| PUT | /account/me/displayname |
✓ | Set display name |
| PUT | /account/me/username |
✓ | Change username (unique + change remaining) |
| POST | /account/me/email |
✓ | Set email |
| POST | /account/me/phone |
✓ | Set phone number |
| PUT | /account/me/identityflags |
✓ | Set identity flags bitmask |
| PUT | /account/me/personalpronouns |
✓ | Set personal pronouns (posted as pronounFlags) |
| PUT | /account/me/bio |
✓ | Set bio |
| PUT | /account/me/profileimage |
✓ | Set avatar object key |
| GET | /openapi.json |
Generated OpenAPI 3.1 spec (see below) |
Auth-gated routes validate the Bearer JWT issued by the auth worker and return an
empty-body 401 when it's missing or invalid.
API documentation
GET /openapi.json serves a spec generated from describeRoute blocks that sit
alongside each handler, with the schemas in src/openapi.ts.
The spec is descriptive, not enforced. Nothing validates requests against it — same
rationale as the auth worker: this serves a protocol reverse-engineered from the Rec
Room client, the handlers are lenient (form fields are read as
typeof value === 'string' ? value : ''), and reads fall back to a synthesized default
account rather than 404. Read a "required" field as the client always sends it, not
the server rejects it if absent.
A test asserts that every route the worker serves appears in the spec, so adding a route without documenting it fails rather than silently shipping an incomplete spec.
Account shapes
Two DTOs, both camelCase:
- Public (
toAccountDto) — returned for any account. Excludes private fields. - Self (
toSelfAccountDto, the/account/meshape) — the public DTO plus owner-onlyemail,birthdayandavailableUsernameChanges.
Two client-deserializer quirks are load-bearing and deliberate:
juniorState/parentAccountIdare omitted entirely when unset — emittingnullmakes the client's enum parser throw.email/birthdayaren't enums, so they're kept asnull.GET /accountprivacysettings/:idnever returns a bare{}— that fails the client's deserializer ("Deserialization returned null"), so the id is echoed back with recent history reported visible. Nothing stores per-player privacy yet.
Missing rows fall back to defaults
Account reads (/account/me, /account/:id, /account/bulk) never 404 on an unknown
id — they synthesize a default account (defaultAccount) so every requested id is
present in the response. bulk in particular guarantees one entry per requested id.
Notifications
Profile mutations persist to the account row and then push through the shared
notifications hub (a single global Durable Object owned by the notify worker): the
owner receives SelfAccountUpdate + AccountUpdate, and every connected client
receives an AccountUpdate broadcast. Hub failures are logged and swallowed — the
write has already committed, so a hub hiccup must not fail the request.
This matters most for the mutations whose HTTP response carries no account body
(personalpronouns, identityflags): the client only learns the new value from the
pushed update, and since those fields are in the public DTO, every other client needs
the broadcast too. email and phone are private, so they persist without a push.
Bindings
| Binding | Type | Notes |
|---|---|---|
DB |
D1 | Shared recflare database; account schema owned by auth |
JWT_SECRET |
Secrets Store | Shared HS256 signing key (see the auth README) |
RECFLARE_NOTIFICATIONS_HUB |
Durable Object | Cross-worker RPC to the notify worker's hub |
This worker has no migrations of its own — the account table is created and migrated
by auth (apps/auth/migrations/).
Known gaps
POST /account/createparsesplatformIdbut doesn't yet persist it, and doesn't create the dorm Room/SubRoom a new account should get.