mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
[rooms,match] add a couple missing endpoints
This commit is contained in:
@@ -815,6 +815,15 @@ export const RoomExperiencePlayer = z
|
||||
.array(z.unknown())
|
||||
.describe('Always empty — no per-room experience is tracked')
|
||||
|
||||
/**
|
||||
* `GET /rooms/curated_playlists` — the curated room playlists the discovery pages'
|
||||
* playlist sections draw from. Nothing curates one on this server, so the list is always
|
||||
* empty and the element shape is unknown until something fills it.
|
||||
*/
|
||||
export const CuratedPlaylists = z
|
||||
.array(z.unknown())
|
||||
.describe('Always empty — nothing curates a room playlist yet')
|
||||
|
||||
/**
|
||||
* `GET /publishState/configs` — the limits the client enforces on republishing a room:
|
||||
* how many updates are allowed in the rolling window, and the cooldown/expiry around
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
getSubRoomPermissions,
|
||||
getSubRoomSaveById,
|
||||
getSubRoomSaves,
|
||||
getTrendingRooms,
|
||||
getVisitedRooms,
|
||||
isPlayerBannedFromRoom,
|
||||
modifySubRoom,
|
||||
@@ -76,6 +77,7 @@ import {
|
||||
CloneRoomRequest,
|
||||
CloningRequest,
|
||||
CreateSubRoomRequest,
|
||||
CuratedPlaylists,
|
||||
DescriptionRequest,
|
||||
DormRoomId,
|
||||
FeaturedRoomGroupDto,
|
||||
@@ -864,6 +866,58 @@ const app = new Hono<App>()
|
||||
}
|
||||
)
|
||||
|
||||
// Curated room playlists — the editorially grouped room lists the discovery pages'
|
||||
// `PlaylistById` sections draw from. Nothing curates one yet, so this is an empty array:
|
||||
// the client reads that as "no playlists" and simply draws no playlist rows, where a 404
|
||||
// leaves it retrying a feed that isn't coming.
|
||||
.get(
|
||||
'/rooms/curated_playlists',
|
||||
describeRoute({
|
||||
tags: ['Discovery'],
|
||||
summary: 'Curated room playlists',
|
||||
description: [
|
||||
'The curated room playlists the discovery pages’ playlist sections draw from. There',
|
||||
'is no editorial curation on this server yet, so this is always an empty array —',
|
||||
'which the client reads as “no playlists” and draws nothing, rather than the 404 it',
|
||||
'would keep retrying.',
|
||||
].join(' '),
|
||||
responses: { 200: json(CuratedPlaylists, 'Always an empty list') },
|
||||
}),
|
||||
(c) => c.json([])
|
||||
)
|
||||
|
||||
// The `rising` carousel — the discovery pages fill a `CarouselEndpoint` section by
|
||||
// slug, and this is the one the client asks for by name. Trending means someone is IN
|
||||
// the room right now: unlike the hot feed, which ranks by head-count but still lists
|
||||
// the empty rooms underneath, this one FILTERS on live presence, so a quiet server
|
||||
// serves an empty carousel rather than a stale one.
|
||||
//
|
||||
// Paged like the hot feed (`skip`/`take`, take defaults to 100) and answers the same
|
||||
// `{ Results, TotalResults }` envelope its sibling feeds do. Only `rising` is served —
|
||||
// the other slugs in the discovery catalogue (`foryou`, `staffpicks`, the
|
||||
// `*_algoendpoint` rows) keep 404ing until each is given a feed of its own.
|
||||
.get(
|
||||
'/rooms/carousel/rising',
|
||||
describeRoute({
|
||||
tags: ['Discovery'],
|
||||
summary: 'The “rising” rooms carousel',
|
||||
description: [
|
||||
'The rooms players are in RIGHT NOW, busiest first — the trending carousel. Live',
|
||||
'presence is a filter here, not just a sort: a room nobody is standing in is absent',
|
||||
'entirely, so this is empty when the server is quiet rather than falling back to',
|
||||
'stored engagement the way `/rooms/hot` does. Ties break on engagement and then',
|
||||
'RoomId, so equally busy rooms page stably. Public, non-dorm, listable rooms only.',
|
||||
].join(' '),
|
||||
parameters: pageParams(100),
|
||||
responses: { 200: json(PagedRooms, 'The carousel page') },
|
||||
}),
|
||||
async (c) => {
|
||||
const skip = Number.parseInt(c.req.query('skip') ?? '0', 10) || 0
|
||||
const take = Number.parseInt(c.req.query('take') ?? '100', 10) || 100
|
||||
return c.json(await getTrendingRooms(c.env.DB, skip, take))
|
||||
}
|
||||
)
|
||||
|
||||
// "Base" rooms — template rooms (tagged `base`) the client offers when creating
|
||||
// a room. Returned regardless of accessibility. Paginated via skip/take (take
|
||||
// defaults to 100). Returns a bare array.
|
||||
|
||||
@@ -890,6 +890,68 @@ describe('rooms endpoints', () => {
|
||||
).run()
|
||||
})
|
||||
|
||||
it('GET /rooms/curated_playlists is an empty list, not a 404', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/curated_playlists`)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual([])
|
||||
})
|
||||
|
||||
it('GET /rooms/carousel/rising serves only rooms players are in, busiest first', async () => {
|
||||
const rising = async (qs = '?skip=0&take=100') =>
|
||||
(await (await SELF.fetch(`${ORIGIN}/rooms/carousel/rising${qs}`)).json()) as {
|
||||
Results: Array<{ RoomId: number; IsDorm?: boolean }>
|
||||
TotalResults: number
|
||||
}
|
||||
|
||||
// Nobody is anywhere in the fixture, and an empty carousel is the honest answer —
|
||||
// this feed does NOT fall back to engagement the way /rooms/hot does.
|
||||
expect(await rising()).toEqual({ Results: [], TotalResults: 0 })
|
||||
|
||||
// Pick from the tail of the hot feed so the order below can only come from presence.
|
||||
const hot = (
|
||||
(await (await SELF.fetch(`${ORIGIN}/rooms/hot?skip=0&take=100`)).json()) as {
|
||||
Results: Array<{ RoomId: number }>
|
||||
}
|
||||
).Results.map((r) => r.RoomId)
|
||||
const busiest = hot[hot.length - 1]
|
||||
const quieter = hot[hot.length - 2]
|
||||
|
||||
const expiresAt = Math.floor(Date.now() / 1000) + 900
|
||||
const seed = env.DB.prepare('INSERT OR REPLACE INTO presence (data) VALUES (?1)')
|
||||
await env.DB.batch(
|
||||
[
|
||||
{ accountId: 90101, roomInstance: { roomInstanceId: 1001001, roomId: busiest } },
|
||||
{ accountId: 90102, roomInstance: { roomInstanceId: 1001002, roomId: busiest } },
|
||||
{ accountId: 90103, roomInstance: { roomInstanceId: 1001003, roomId: quieter } },
|
||||
// The dorm nobody may list, and a lobby presence in no room at all: neither
|
||||
// puts a room in the carousel.
|
||||
{ accountId: 90104, roomInstance: { roomInstanceId: 1001004, roomId: 1 } },
|
||||
{ accountId: 90105, roomInstance: null },
|
||||
].map((p) => seed.bind(JSON.stringify({ ...p, expiresAt })))
|
||||
)
|
||||
|
||||
const busy = await rising()
|
||||
expect(busy.Results.map((r) => r.RoomId)).toEqual([busiest, quieter])
|
||||
expect(busy.TotalResults).toBe(2)
|
||||
expect(busy.Results.some((r) => r.RoomId === 1 || r.IsDorm === true)).toBe(false)
|
||||
|
||||
// Paged like its sibling feeds: TotalResults stays the full count.
|
||||
expect(await rising('?skip=0&take=1')).toMatchObject({ TotalResults: 2 })
|
||||
expect((await rising('?skip=0&take=1')).Results.map((r) => r.RoomId)).toEqual([busiest])
|
||||
expect((await rising('?skip=1&take=100')).Results.map((r) => r.RoomId)).toEqual([quieter])
|
||||
|
||||
// Presence that has expired is nobody standing there.
|
||||
await env.DB.prepare(
|
||||
`UPDATE presence SET data = json_set(data, '$.expiresAt', ?1)
|
||||
WHERE account_id BETWEEN 90101 AND 90105`
|
||||
)
|
||||
.bind(Math.floor(Date.now() / 1000) - 1)
|
||||
.run()
|
||||
expect(await rising()).toEqual({ Results: [], TotalResults: 0 })
|
||||
|
||||
await env.DB.prepare('DELETE FROM presence WHERE account_id BETWEEN 90101 AND 90105').run()
|
||||
})
|
||||
|
||||
it('GET /rooms/hot aliases #recroomoriginal to the rro tag', async () => {
|
||||
const aliased = (await (
|
||||
await SELF.fetch(`${ORIGIN}/rooms/hot?tag=recroomoriginal`)
|
||||
@@ -3746,8 +3808,10 @@ describe('rooms endpoints', () => {
|
||||
'GET /rooms/autocomplete_search',
|
||||
'GET /rooms/base',
|
||||
'GET /rooms/bulk',
|
||||
'GET /rooms/carousel/rising',
|
||||
'GET /rooms/contributedby/me',
|
||||
'GET /rooms/createdby/me',
|
||||
'GET /rooms/curated_playlists',
|
||||
'GET /rooms/favoritedby/me',
|
||||
'GET /rooms/hot',
|
||||
'GET /rooms/ownedby/me',
|
||||
|
||||
Reference in New Issue
Block a user