mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
117 lines
8.3 KiB
Markdown
117 lines
8.3 KiB
Markdown
# econ
|
|
|
|
Economy Worker served on the `econ` subdomain (`econ.recflare.net`). Hosts the
|
|
avatar/economy endpoints the game client calls on the `econ` service (distinct from the
|
|
main `api` worker, which also serves many of them — the client may call either host).
|
|
|
|
Balances, inventory, consumables, saved outfits, avatars and gift boxes are D1-backed;
|
|
storefront catalogs are static assets (`static/storefronts/sf{N}.json`) served via the
|
|
ASSETS binding. Several routes are still empty-list stubs.
|
|
|
|
## Routes
|
|
|
|
`✓` = auth-gated (validates the Bearer JWT from the `auth` worker; empty-body 401 when
|
|
missing/invalid).
|
|
|
|
| Method | Path | Auth | Description |
|
|
| -------- | ---------------------------------------------------- | ---- | --------------------------------------- |
|
|
| GET | `/api/avatar/v1/defaultunlocked` | | Default-unlocked avatar items (static) |
|
|
| GET | `/api/avatar/v1/defaultbaseavataritems` | | Default base avatar items (stub `[]`) |
|
|
| GET | `/api/avatar/v4/items` | ✓ | Owned items + the default catalog |
|
|
| GET | `/econ/customAvatarItems/v1/owned` | ✓ | Owned custom avatar items (stub) |
|
|
| GET | `/api/objectives/v1/myprogress` | | Objectives progress (static) |
|
|
| GET/POST | `/api/objectives/v1/cleargroup` | | Clear an objectives group (no-op `[]`) |
|
|
| GET | `/api/avatar/v2` | ✓ | The player's own avatar |
|
|
| POST | `/api/avatar/v2/set` | ✓ | Save the player's avatar |
|
|
| GET | `/api/checklist/v1/current` | ✓ | NUX checklist (stub `[]`) |
|
|
| GET | `/api/itemWishlists/v1/wishlist/me` | ✓ | Item wishlist (stub `[]`) |
|
|
| GET | `/api/avatar/v3/saved` | ✓ | Saved outfits |
|
|
| POST | `/api/avatar/v3/saved/set` | ✓ | Save an outfit into a slot |
|
|
| GET | `/api/avatar/v2/gifts` | ✓ | Pending (unopened) gift boxes |
|
|
| POST | `/api/avatar/v2/gifts/consume` | | Open a gift box → success envelope |
|
|
| GET | `/api/avatar/v2/:id` | | Another player's avatar (render subset) |
|
|
| GET | `/api/equipment/v2/getUnlocked` | | Unlocked equipment (stub `[]`) |
|
|
| GET | `/api/roomconsumables/v1/roomConsumable/room/:id` | | Room consumables (stub `[]`) |
|
|
| GET | `/api/roomconsumables/v1/roomConsumable/room/:id/me` | | Caller's room consumables (stub `[]`) |
|
|
| GET | `/api/roomcurrencies/v1/currencies` | | Room currencies (stub `[]`) |
|
|
| GET | `/api/roomcurrencies/v1/getAllBalances` | | Room balances (stub `[]`) |
|
|
| POST | `/api/settings/v2/set` | ✓ | Persist settings (accept-and-ack) |
|
|
| GET | `/api/consumables/v2/getUnlocked` | ✓ | Unlocked consumables |
|
|
| POST | `/api/consumables/v1/consume` | ✓ | Consume an owned consumable |
|
|
| GET | `/api/storefronts/v4/balance/:currencyType` | ✓ | Currency balance |
|
|
| GET | `/api/storefronts/v3/giftdropstore/:id` | | Gift-drop storefront catalog |
|
|
| POST | `/api/storefronts/v2/buyItem` | ✓ | Buy a storefront item |
|
|
| GET | `/api/storefronts/v1/adcarouselitems` | | Ad-carousel items (static) |
|
|
| GET | `/api/challenge/v2/getCurrent` | | Current weekly challenge (static) |
|
|
| POST | `/api/challenge/v2/updateProgress` | | Report challenge progress (stub) |
|
|
| GET | `/api/gamerewards/v1/pending` | | Pending game rewards (stub `[]`) |
|
|
| POST | `/api/gamerewards/v1/request` | | Request a game reward (stub `[]`) |
|
|
| GET | `/api/roomkeys/v1/mine` | | The player's room keys (stub `[]`) |
|
|
| GET | `/api/roomkeys/v1/room` | | Room keys for a room (stub `[]`) |
|
|
| POST | `/api/CampusCard/v1/UpdateAndGetSubscription` | | Subscription lookup (both null) |
|
|
| GET | `/openapi.json` | | Generated OpenAPI 3.1 spec (see below) |
|
|
|
|
The app runs with `strict: false`, so trailing-slash variants match (the client posts
|
|
`/gifts/consume/` with a trailing slash).
|
|
|
|
## API documentation
|
|
|
|
`GET /openapi.json` serves a spec generated from `describeRoute` blocks alongside each
|
|
handler, with the schemas in `src/openapi.ts`. **Descriptive, not enforced** — same
|
|
rationale as the `auth`/`accounts`/`match` workers. A test asserts every route appears
|
|
in the spec, so adding one without documenting it fails.
|
|
|
|
## Purchases (`buyItem`)
|
|
|
|
The core flow. The client posts the storefront/item ids, the currency, and the
|
|
`RequestedPrice` it rendered; the handler:
|
|
|
|
1. looks the item up in `static/storefronts/sf{StorefrontType}.json`;
|
|
2. rejects a stale price (`409`) — this stops a stale or tampered client buying at a
|
|
price the catalog no longer offers;
|
|
3. debits the buyer **atomically** (`400` on insufficient balance);
|
|
4. grants the drop — an avatar item into the `inventory` table (own-once), a consumable
|
|
into the `consumable` table (each buy stacks a new instance); currency/xp drops
|
|
aren't granted yet;
|
|
5. returns a **gift box** and pushes a `StorefrontBalanceUpdate` over the socket.
|
|
|
|
Two things are easy to get wrong:
|
|
|
|
- **`Balance` in the response is the _change_ applied** (the negated price), not the
|
|
resulting total. The client reads its new total from `GET /balance/:type`.
|
|
- **Ownership is persisted at purchase**, not when the box is opened. Opening a box
|
|
(`/gifts/consume`) just deletes it — the item was already granted. So the grant never
|
|
waits on the cosmetic "open it" moment.
|
|
|
|
A `Gift` block routes the item (and box) to another player, but the caller always pays.
|
|
A self-buy or anonymous gift is attributed to the "Coach" system account (id 1).
|
|
|
|
## Consume envelopes
|
|
|
|
Both consume routes (`/gifts/consume`, `/consumables/consume`) always answer HTTP 200
|
|
with `{ error: "", success: true, value: null }` — even for a missing or already-gone
|
|
target. A captured real consume returns this envelope, not an empty body: the client
|
|
parses it to finish the action, so a bare 200 reads as a failure and the item never
|
|
finishes unlocking. Deletes are scoped to the caller, so an unauthenticated or
|
|
mismatched call is a harmless no-op (opening _another_ player's box is a 403).
|
|
|
|
## Bindings
|
|
|
|
| Binding | Type | Notes |
|
|
| ---------------------------- | -------------- | -------------------------------------------------------- |
|
|
| `DB` | D1 | Shared `recflare` database — balances, inventory, etc. |
|
|
| `JWT_SECRET` | Secrets Store | Shared HS256 signing key (see the `auth` README) |
|
|
| `ASSETS` | static assets | Serves `sf{N}.json` storefront catalogs |
|
|
| `RECFLARE_NOTIFICATIONS_HUB` | Durable Object | Cross-worker RPC to the `notify` worker's hub |
|
|
| `STARTING_TOKENS` | var | Optional; new-player token grant (default in balance-db) |
|
|
|
|
Add a storefront by dropping a new `sfN.json` in `static/storefronts` — no code change.
|
|
|
|
## Known gaps
|
|
|
|
- Gifting to another player grants the item and box but does not notify the recipient.
|
|
- `buyItem` grants avatar-item and consumable drops; currency/xp drops aren't granted.
|
|
- Consumables are granted and listed but never spent by gameplay, so `Count` only grows.
|
|
- Several routes (room keys, wishlist, equipment, room consumables/currencies, game
|
|
rewards) are empty-list stubs pending their own stores.
|