docs for api

This commit is contained in:
Devin Zuczek
2026-07-21 21:42:14 -04:00
parent ec324558a0
commit df618af435
15 changed files with 2620 additions and 589 deletions
+129 -50
View File
@@ -1,42 +1,101 @@
import { Hono } from 'hono'
import { describeRoute } from 'hono-openapi'
import { getRoomById } from '@repo/domain'
import { authedId } from '../http'
import {
AUTHED,
BareBoolean,
form,
json,
JsonArray,
QuickPlayResponse,
TagFilters,
VerifyRoleRequest,
} from '../openapi'
import type { App } from '../context'
// ---- Room keys / quick play / rooms ----------------------------------------
export const roomRoutes = new Hono<App>({ strict: false })
.get('/api/roomkeys/v1/mine', (c) => c.json([]))
.get('/api/roomkeys/v1/room', (c) => c.json([]))
.get('/api/quickPlay/v1/getandclear', (c) =>
c.json({ RoomName: null, ActionCode: null, TargetPlayerId: null })
.get(
'/api/roomkeys/v1/mine',
describeRoute({
tags: ['Rooms'],
summary: 'The callers room keys',
description: 'Nothing issues room keys yet, so this is an empty list.',
responses: { 200: json(JsonArray, 'An empty list') },
}),
(c) => c.json([])
)
.get(
'/api/roomkeys/v1/room',
describeRoute({
tags: ['Rooms'],
summary: 'A rooms keys',
description: 'Nothing issues room keys yet, so this is an empty list.',
responses: { 200: json(JsonArray, 'An empty list') },
}),
(c) => c.json([])
)
.get(
'/api/quickPlay/v1/getandclear',
describeRoute({
tags: ['Rooms'],
summary: 'Take the pending quick-play action',
description:
'A read-and-clear of whatever quick-play action is queued for the caller (joining ' +
'a friend, an invite deep link). Nothing queues one yet, so all three fields are ' +
'null — which the client reads as “nothing to do”.',
responses: { 200: json(QuickPlayResponse, 'All null — no pending action') },
}),
(c) => c.json({ RoomName: null, ActionCode: null, TargetPlayerId: null })
)
// Room search filters. The client deserializes this into an object (not an
// array) — shape from the 2025 reference.
.get('/api/rooms/v1/filters', (c) =>
c.json({
PinnedFilters: [
'recroomoriginal',
'community',
'featured',
'quest',
'pvp',
'hangout',
'game',
'art',
'store',
'tutorial',
'fandom',
'performance',
'action',
'horror',
],
PopularFilters: ['pvp', 'quest', 'game', 'hangout', 'art'],
TrendingFilters: ['roleplay', 'nomp', 'rp', 'casual', 'fun', 'action', 'military', 'sports'],
})
.get(
'/api/rooms/v1/filters',
describeRoute({
tags: ['Rooms'],
summary: 'Room browse filter chips',
description:
'The filter chips on the room browse screen. Static, taken from the 2025 ' +
'reference. The client deserializes this as an OBJECT, not an array — and unlike ' +
'the invention/event filters, `TrendingFilters` here is a real list.',
responses: { 200: json(TagFilters, 'The filter chips') },
}),
(c) =>
c.json({
PinnedFilters: [
'recroomoriginal',
'community',
'featured',
'quest',
'pvp',
'hangout',
'game',
'art',
'store',
'tutorial',
'fandom',
'performance',
'action',
'horror',
],
PopularFilters: ['pvp', 'quest', 'game', 'hangout', 'art'],
TrendingFilters: [
'roleplay',
'nomp',
'rp',
'casual',
'fun',
'action',
'military',
'sports',
],
})
)
// Verify the caller holds at least `role` in a room. Params come from the form
@@ -44,29 +103,49 @@ export const roomRoutes = new Hono<App>({ strict: false })
// room creator always passes; otherwise the caller needs a Roles entry with
// `Role >= role`. Any failure (no token, unknown room, insufficient role) is
// `false`. The `context` field (e.g. MakerPen) is accepted and ignored.
.post('/api/rooms/v1/verifyRole', async (c) => {
const body = (await c.req.parseBody().catch(() => ({}))) as Record<string, unknown>
const param = (name: string): string => {
const form = body[name]
if (typeof form === 'string' && form !== '') return form
return c.req.query(name) ?? ''
.post(
'/api/rooms/v1/verifyRole',
describeRoute({
tags: ['Rooms'],
summary: 'Verify the callers role in a room',
description:
'Whether the caller holds at least `role` in the room — the gate the client checks ' +
'before letting someone into the Maker Pen. The rooms creator always passes; ' +
'anyone else needs a `Roles` entry at that level or higher.\n\n' +
'Answers a bare `true`/`false`, and every failure is `false` rather than an error ' +
'status: no token, an unknown room, and an insufficient role are indistinguishable ' +
'to the client. Params are read from the form body, falling back to the query ' +
'string. Room data is read from the shared rooms database (owned by the `rooms` ' +
'worker).',
security: AUTHED,
requestBody: form(VerifyRoleRequest, 'The room and the role level to check'),
responses: { 200: json(BareBoolean, 'Whether the caller holds the role') },
}),
async (c) => {
const body = (await c.req.parseBody().catch(() => ({}))) as Record<string, unknown>
const param = (name: string): string => {
// (named `fromBody` rather than `form` — the openapi helper owns that name here)
const fromBody = body[name]
if (typeof fromBody === 'string' && fromBody !== '') return fromBody
return c.req.query(name) ?? ''
}
const roomId = Number.parseInt(param('roomId'), 10)
const role = Number.parseInt(param('role'), 10)
const accountId = await authedId(c)
if (accountId === null || Number.isNaN(roomId)) return c.json(false)
const room = await getRoomById(c.env.DB, roomId)
if (!room) return c.json(false)
// The creator always passes.
if (room.CreatorAccountId === accountId) return c.json(true)
// Otherwise the caller needs a room role at least as high as requested.
const roles = Array.isArray(room.Roles) ? (room.Roles as Array<Record<string, unknown>>) : []
const hasRole = roles.some(
(r) => r.AccountId === accountId && typeof r.Role === 'number' && r.Role >= (role || 0)
)
return c.json(hasRole)
}
const roomId = Number.parseInt(param('roomId'), 10)
const role = Number.parseInt(param('role'), 10)
const accountId = await authedId(c)
if (accountId === null || Number.isNaN(roomId)) return c.json(false)
const room = await getRoomById(c.env.DB, roomId)
if (!room) return c.json(false)
// The creator always passes.
if (room.CreatorAccountId === accountId) return c.json(true)
// Otherwise the caller needs a room role at least as high as requested.
const roles = Array.isArray(room.Roles) ? (room.Roles as Array<Record<string, unknown>>) : []
const hasRole = roles.some(
(r) => r.AccountId === accountId && typeof r.Role === 'number' && r.Role >= (role || 0)
)
return c.json(hasRole)
})
)