mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 23:21:30 -07:00
stubs
This commit is contained in:
@@ -675,3 +675,11 @@ export const PhotonAccessTokenDto = z.object({
|
||||
export const PlayerDataDto = z.object({
|
||||
Data: z.string().describe('Always empty — no per-room player data is stored'),
|
||||
})
|
||||
|
||||
/**
|
||||
* `GET /rooms/{roomId}/experience/player` — the caller's per-room experience/progression
|
||||
* entries. Stubbed empty; the element shape is unknown until something stores one.
|
||||
*/
|
||||
export const RoomExperiencePlayer = z
|
||||
.array(z.unknown())
|
||||
.describe('Always empty — no per-room experience is tracked')
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getFeaturedRooms,
|
||||
getHotRooms,
|
||||
getInteraction,
|
||||
getOrCreateDormRoom,
|
||||
getPresence,
|
||||
getPublicRoomsByCreator,
|
||||
getRecommendedRooms,
|
||||
@@ -96,6 +97,7 @@ import {
|
||||
RoomBanEnvelope,
|
||||
RoomDto,
|
||||
RoomEnvelope,
|
||||
RoomExperiencePlayer,
|
||||
roomIdParam,
|
||||
RoomLookup,
|
||||
RoomResultEnvelope,
|
||||
@@ -845,6 +847,31 @@ const app = new Hono<App>()
|
||||
ownedRooms
|
||||
)
|
||||
|
||||
// The caller's own dorm, in the same shape `GET /rooms/{roomId}` serves — the client
|
||||
// renders it with the same code path. Gets-or-creates, exactly as entering a dorm
|
||||
// does (`match`), so a player who has never been to their dorm gets one here rather
|
||||
// than a 404; the id is stable from then on.
|
||||
.get(
|
||||
'/dormroom/me',
|
||||
describeRoute({
|
||||
tags: ['My rooms'],
|
||||
summary: 'The caller’s dorm',
|
||||
description: [
|
||||
'The caller’s personal dorm room, as `GET /rooms/{roomId}` would serve it —',
|
||||
'`SubRooms` re-attached, same DTO. The dorm is provisioned on first access (cloned',
|
||||
'from the seeded template dorm), so this returns a room for any authed caller and',
|
||||
'never 404s; calling it repeatedly returns the same dorm.',
|
||||
].join(' '),
|
||||
security: AUTHED,
|
||||
responses: { 200: json(RoomDto, 'The caller’s dorm'), 401: UNAUTHORIZED_RESPONSE },
|
||||
}),
|
||||
async (c) => {
|
||||
const accountId = await authedAccountId(c)
|
||||
if (accountId === null) return unauthorized(c)
|
||||
return c.json(await getOrCreateDormRoom(c.env.DB, accountId))
|
||||
}
|
||||
)
|
||||
|
||||
// Public: the rooms a given account owns that are publicly viewable. No auth —
|
||||
// returns a bare array (empty when the account owns no public rooms).
|
||||
.get(
|
||||
@@ -2611,6 +2638,24 @@ const app = new Hono<App>()
|
||||
(c) => c.json({ Data: '' })
|
||||
)
|
||||
|
||||
// The caller's per-room experience/progression. Stub → empty list.
|
||||
.get(
|
||||
'/rooms/:roomId{[0-9]+}/experience/player',
|
||||
describeRoute({
|
||||
tags: ['Rooms'],
|
||||
summary: 'The caller’s per-room experience',
|
||||
description: [
|
||||
'Per-room experience/progression for the calling player. Nothing tracks any yet, so',
|
||||
'this is an empty list — which the client reads as “no progress in this room”, where',
|
||||
'a 404 would stall the room load. No auth, matching `playerdata/me`: the answer is',
|
||||
'the same for every caller until something writes here.',
|
||||
].join(' '),
|
||||
parameters: [roomIdParam],
|
||||
responses: { 200: json(RoomExperiencePlayer, 'An empty list') },
|
||||
}),
|
||||
(c) => c.json([])
|
||||
)
|
||||
|
||||
// Single room by id. 404 when the room isn't in D1. Ignores the
|
||||
// include/unityAsset* query params.
|
||||
.get(
|
||||
|
||||
@@ -89,6 +89,18 @@ beforeAll(async () => {
|
||||
// Seed each room and split its subrooms into the subroom table (mirrors 0007's backfill).
|
||||
for (const r of importRooms) await seedRoomWithSubRooms(env.DB, r as Record<string, unknown>)
|
||||
|
||||
// Accounts table (owned by the auth worker) — provisioning a dorm reads the username
|
||||
// to name the room. Seed the player `dormroom/me` provisions a fresh dorm for.
|
||||
await env.DB.prepare(
|
||||
`CREATE TABLE IF NOT EXISTS account (
|
||||
data TEXT NOT NULL,
|
||||
account_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.accountId')) VIRTUAL
|
||||
)`
|
||||
).run()
|
||||
await env.DB.prepare('INSERT OR IGNORE INTO account (data) VALUES (?1)')
|
||||
.bind(JSON.stringify({ accountId: 999, username: 'Dormer' }))
|
||||
.run()
|
||||
|
||||
// Relationship table (owned by the api worker) — `visitedby/:playerId` reads it to
|
||||
// check the caller is a friend of the player whose history they're asking for.
|
||||
await env.DB.prepare(
|
||||
@@ -129,6 +141,14 @@ describe('rooms endpoints', () => {
|
||||
expect(body.SubRooms[0].UnitySceneId).toBe('76d98498-60a1-430c-ab76-b54a29b7a163')
|
||||
})
|
||||
|
||||
// Stub. Registered (not 404) matters more than the body: the client asks for this on
|
||||
// room entry, and an unregistered path stalls the load rather than erroring visibly.
|
||||
it('GET /rooms/:id/experience/player returns [] for any room', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/92/experience/player`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
it('GET /rooms/:id 404s for a room not in D1', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/99999`)
|
||||
expect(res.status).toBe(404)
|
||||
@@ -181,6 +201,41 @@ describe('rooms endpoints', () => {
|
||||
expect(other).toEqual([])
|
||||
})
|
||||
|
||||
it('GET /dormroom/me serves the caller’s own dorm in the room shape', async () => {
|
||||
// No token → 401. Without this the endpoint would hand out (and provision) a dorm
|
||||
// for whichever account a fallback picked.
|
||||
const noAuth = await SELF.fetch(`${ORIGIN}/dormroom/me`)
|
||||
expect(noAuth.status).toBe(401)
|
||||
|
||||
// Account 1 owns the seeded dorm (RoomId 1), served exactly as GET /rooms/1 does —
|
||||
// same DTO, SubRooms re-attached.
|
||||
const res = await SELF.fetch(`${ORIGIN}/dormroom/me`, { headers: await bearer('1') })
|
||||
expect(res.status).toBe(200)
|
||||
const dorm = (await res.json()) as {
|
||||
RoomId: number
|
||||
IsDorm: boolean
|
||||
CreatorAccountId: number
|
||||
SubRooms: Array<{ UnitySceneId: string }>
|
||||
}
|
||||
expect(dorm).toMatchObject({ RoomId: 1, IsDorm: true, CreatorAccountId: 1 })
|
||||
expect(dorm.SubRooms[0].UnitySceneId).toBe('76d98498-60a1-430c-ab76-b54a29b7a163')
|
||||
expect(dorm).toEqual(await (await SELF.fetch(`${ORIGIN}/rooms/1`)).json())
|
||||
|
||||
// A player who has never entered their dorm gets one provisioned rather than a
|
||||
// 404, and it belongs to THEM — not the template dorm they were cloned from.
|
||||
const fresh = (await (
|
||||
await SELF.fetch(`${ORIGIN}/dormroom/me`, { headers: await bearer('999') })
|
||||
).json()) as { RoomId: number; IsDorm: boolean; CreatorAccountId: number }
|
||||
expect(fresh).toMatchObject({ IsDorm: true, CreatorAccountId: 999 })
|
||||
expect(fresh.RoomId).not.toBe(1)
|
||||
|
||||
// Idempotent: the second call is the same dorm, not a second one.
|
||||
const again = (await (
|
||||
await SELF.fetch(`${ORIGIN}/dormroom/me`, { headers: await bearer('999') })
|
||||
).json()) as { RoomId: number }
|
||||
expect(again.RoomId).toBe(fresh.RoomId)
|
||||
})
|
||||
|
||||
// The website's "My rooms" list is a browser calling this worker from another origin,
|
||||
// so a response without CORS headers is one the browser throws away — and the page
|
||||
// can't tell that apart from the server being down. Pinned on the preflight too: the
|
||||
@@ -580,9 +635,7 @@ describe('rooms endpoints', () => {
|
||||
it('GET /rooms/hot?tag=community serves rooms the Coach account did not create', async () => {
|
||||
type Feed = { Results: Array<{ Name: string }>; TotalResults: number }
|
||||
const feed = async (): Promise<Feed> =>
|
||||
(await (
|
||||
await SELF.fetch(`${ORIGIN}/rooms/hot?tag=community&skip=0&take=100`)
|
||||
).json()) as Feed
|
||||
(await (await SELF.fetch(`${ORIGIN}/rooms/hot?tag=community&skip=0&take=100`)).json()) as Feed
|
||||
const names = async (): Promise<string[]> => (await feed()).Results.map((r) => r.Name)
|
||||
|
||||
// No room carries a `community` tag, and every seeded room belongs to Coach
|
||||
@@ -2911,6 +2964,7 @@ describe('rooms endpoints', () => {
|
||||
'DELETE /rooms/{roomId}/subrooms/{subRoomId}',
|
||||
'GET /',
|
||||
'GET /XXXfeaturedrooms/current',
|
||||
'GET /dormroom/me',
|
||||
'GET /photon_access_token',
|
||||
'GET /rooms',
|
||||
'GET /rooms/base',
|
||||
@@ -2926,6 +2980,7 @@ describe('rooms endpoints', () => {
|
||||
'GET /rooms/visitedby/{playerId}',
|
||||
'GET /rooms/{roomId}',
|
||||
'GET /rooms/{roomId}/bans',
|
||||
'GET /rooms/{roomId}/experience/player',
|
||||
'GET /rooms/{roomId}/interactionby/me',
|
||||
'GET /rooms/{roomId}/playerdata/me',
|
||||
'GET /rooms/{roomId}/similar',
|
||||
|
||||
Reference in New Issue
Block a user