support for 202507 endpoints (#37)

* [auth][api] accept the 20250424.01 client

* [2025] unstable

* 20250718.0

* correct one this time

* stubs

* more stubs

* more stubs

* [lists] add worker

* [ai] route stubs

* [api] player photo setting

* [econ] add roomEconConfig route

* [infra] update worker generators

* [worker] add cards/moderation/platformnotification workers

* [lists] updates to some endpoints

* [clubs] stub out announcement endpoint, for now

* [econ] stub out season endpoints for now

* [chat] apps/chat stub out party endpoint not sure the shape yet

* [api] stub out statsig and lockeditems

* [doc] new services

* [lists] stub the bulk endpoint

* [datacollection] add placeholder service until we can kill it

* [api] set gifting to lvl5

* update lock

* [cdn] enable cache

* [match] matchmake v2

* [lists] stub some lists

* [ai] stubs

* [rooms] new subroom save endpoint

* [econ] add bulk purchase endpoint

* [discovery] update featured creator to 1 for fun

* [api] add photo settings flag

* [chat] fixup chat permissions (sorta)

* [auth] restrictions endpoint

* [rooms] contributed endpoint

* [api] fix outfit endpoint

* [discovery] attempt to fix store

* [chat] privacy endpoints

* [api] cheered images

* [rooms] add xp endpoint (disbaled)

* [rooms] add xp endpoint (disabled)

* update images-db for cheers

* [rooms] add autocomplete endpoint

* [cdn/img] increase cache ttl for statics

* [api] bulk route for images

* [accounts] add banner image

* [api] add misc missing endpoints

* [discovery] remove AI tab

* [platformnotifications] stub some endpoints

* [lists] add some more lists

* [rooms] additional endpoints

* [chat] stub a few privacy endpoints

* [econ] stub some endpoints

* misc db fixes

* [api] tweak shape for images v6

* [rooms] dont show trending RROs
This commit is contained in:
devin
2026-08-18 23:07:24 -04:00
committed by Devin Zuczek
parent 66c09806f9
commit 178d3b5b0e
162 changed files with 114930 additions and 469 deletions
+101
View File
@@ -0,0 +1,101 @@
import { resolver } from 'hono-openapi'
import { z } from 'zod'
import type { OpenAPIV3_1 } from 'openapi-types'
/**
* OpenAPI schemas for the discovery worker.
*
* IMPORTANT: these are DESCRIPTIVE ONLY. They are passed to `describeRoute` to generate
* the spec and are never wired into `hono-openapi`'s `validator()`. Same rationale as
* the other workers: a reverse-engineered protocol, lenient handlers, no runtime
* validation.
*
* Do NOT add `.meta({ id })` to these schemas — with this hono-openapi + zod v4 setup a
* meta'd schema used in a response emits a `$ref` the framework doesn't always hoist into
* `components.schemas`, leaving a dangling reference. Leaving meta off makes every schema
* inline, which renders correctly in any tool.
*/
/** Emit a zod schema as an `application/json` response body. */
export function json(schema: z.ZodType, description: string) {
return { description, content: { 'application/json': { schema: resolver(schema) } } }
}
/** The `{type}` path parameter, which is the filename in `static/`. */
export const PAGE_SOURCE_PARAM: OpenAPIV3_1.ParameterObject = {
name: 'type',
in: 'path',
required: true,
description: [
'The page source — `WatchHome`, `PlayHighlight`, `CommunityBoard`, `PlayMenuTabs`,',
'`PlayCategories`, `StoreCategories`, `StoreFeatured`, `StoreClothing`,',
'`StoreConsumables`, `bulk` at the time of writing. It names a file in `static/`',
'(`<type>.json`) and is matched exactly, case included, so the set is whatever is',
'published rather than anything this worker enumerates.',
].join(' '),
// Deliberately not an `enum`: the accepted values are the published files, and a spec
// that froze today's list would be wrong the moment one is added.
schema: { type: 'string', example: 'WatchHome' },
}
// ---- Response schemas ------------------------------------------------------
/** `GET /` — the liveness probe body. */
export const ServiceStatus = z.object({
service: z.literal('discovery'),
status: z.literal('ok'),
})
/**
* How one carousel on a discovery page is filled and drawn. `source`/`sourceMetadata`
* name a feed (`Hot`, `Recent`, `PlaylistById` + an id, `CarouselEndpoint` + a slug, …)
* that the client resolves against the `rooms`/`api` workers itself — this worker only
* says WHICH carousels a page has and in what order, never their contents.
*/
export const DiscoverySection = z.object({
id: z.string().describe('Unique id of this section on this page, e.g. `Rooms_RRO_WatchHome`'),
sectionType: z
.int()
.describe(
'What the section lists: 0 RoomsSection · 1 AccountsSection · 2 InventionsSection · ' +
'3 ClubsSection · 4 StoreItemsSection · 5 EventsSection · 6 RoomBanner · 7 Top5Section · ' +
'8 CustomAvatarItemsSection · 9 AdsSection · 10 SkusSection · 11 RoomCategorySection · ' +
'12 RoomCategoryListSection · 13 DiscoverySection'
),
sectionSubType: z.string().describe('The sections kind, shared across pages, e.g. `Rooms_RRO`'),
source: z.string().describe('The feed that fills the section'),
sourceMetadata: z
.string()
.nullable()
.describe('Argument to `source` — a carousel slug, a playlist id, … `null` when it takes none'),
displayMetadata: z
.string()
.nullable()
.describe(
'How the section is drawn (`DisplayTitle`, `numRows`, `backgroundColor`, …), as an ' +
'embedded JSON *string* the client parses itself — not an object. Its booleans and ' +
'numbers are quoted in most sections and bare in some; both forms are live in the ' +
'captures, so the client evidently takes either. `null` where a section is drawn ' +
'however its type says (several store and play-highlight sections).'
),
})
/**
* `GET /sections/pagesource/{type}` — a page's sections, in the order they are drawn.
*
* The DTO accepts anything (its validator is a no-op), but the STORE page builder is much
* stricter and drops a section it doesn't like SILENTLY — no error reaches the client, the
* carousel simply isn't there. A section it keeps must have:
*
* - a non-empty `displayMetadata` that parses, and whose gating (platform, junior account,
* account age) says it's supported;
* - `sectionType` 4 (StoreItemsSection → the product carousel) or 13 (DiscoverySection).
* Anything else logs "Unhandled SectionType {0}, skipping";
* - for 13 only, a `source` of exactly `CuratedList` or `PageSource`, with `sourceMetadata`
* carrying the argument — a curated-list id, or another page source to recurse into.
*
* So a store section that renders nothing is worth checking against these before assuming
* the feed behind it is empty.
*/
export const DiscoverySections = DiscoverySection.array()