Files
recflare/apps/discovery/src/openapi.ts
T
devin 178d3b5b0e 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
2026-08-21 15:15:55 -04:00

102 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()