mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 22:51:30 -07:00
more image endpoints, rooms
This commit is contained in:
@@ -366,6 +366,27 @@ export async function getHotRooms(
|
||||
return { Results: rooms.slice(skip, skip + take), TotalResults: rooms.length }
|
||||
}
|
||||
|
||||
/**
|
||||
* Recommended rooms feed: public, non-dorm rooms not excluded from lists, ranked
|
||||
* by engagement (same score as the hot feed). Unlike the hot feed this returns a
|
||||
* bare array — the client's recommendation room-source loader expects a plain
|
||||
* list, like the other `*by/me`/base sources. The `splitTest*` A/B params the
|
||||
* client passes don't change the result. Paginated via skip/take; the dataset is
|
||||
* small, so this filters/sorts in memory rather than in SQL.
|
||||
*/
|
||||
export async function getRecommendedRooms(
|
||||
db: D1Database,
|
||||
skip: number,
|
||||
take: number
|
||||
): Promise<Room[]> {
|
||||
const { results } = await db.prepare('SELECT data FROM rooms').all<RoomRow>()
|
||||
const roomId = (r: Room): number => (typeof r.RoomId === 'number' ? r.RoomId : 0)
|
||||
return parseAll(results)
|
||||
.filter((r) => r.IsDorm !== true && r.Accessibility === 1 && r.ExcludeFromLists !== true)
|
||||
.sort((a, b) => hotScore(b) - hotScore(a) || roomId(a) - roomId(b))
|
||||
.slice(skip, skip + take)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rooms similar to a target room: public, non-dorm rooms (excluding the target)
|
||||
* that share at least one tag with it, ranked by shared-tag count then
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getHotRooms,
|
||||
getInteraction,
|
||||
getPublicRoomsByCreator,
|
||||
getRecommendedRooms,
|
||||
getRoomById,
|
||||
getRoomByName,
|
||||
getRoomsByCreator,
|
||||
@@ -185,6 +186,16 @@ const app = new Hono<App>()
|
||||
return c.json(await getBaseRooms(c.env.DB, skip, take))
|
||||
})
|
||||
|
||||
// Recommended rooms feed — public, non-dorm rooms ranked by engagement, returned
|
||||
// as a bare array (the client's recommendation room-source expects a plain list).
|
||||
// The `splitTestId`/`splitTestValue` A/B params are accepted and ignored.
|
||||
// Paginated via skip/take (take defaults to 100).
|
||||
.get('/rooms/recommendations', 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 getRecommendedRooms(c.env.DB, skip, take))
|
||||
})
|
||||
|
||||
// Bulk room lookup by `id` or `name` — returns an array of matched rooms (the
|
||||
// client calls this bare on the rooms host). Rooms not in D1 are simply absent
|
||||
// from the result; the client treats an empty result as NoSuchRoom.
|
||||
|
||||
@@ -316,6 +316,30 @@ describe('rooms endpoints', () => {
|
||||
expect(body.length).toBeLessThanOrEqual(5)
|
||||
})
|
||||
|
||||
it('GET /rooms/recommendations returns a bare array of public rooms (split-test params ignored)', async () => {
|
||||
const res = await SELF.fetch(
|
||||
`${ORIGIN}/rooms/recommendations?splitTestId=1&splitTestValue=5`
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
const body = (await res.json()) as Array<{ RoomId: number; IsDorm?: boolean }>
|
||||
expect(Array.isArray(body)).toBe(true)
|
||||
expect(body.length).toBeGreaterThan(0)
|
||||
// The dorm (RoomId 1) is non-public, so it's never recommended.
|
||||
expect(body.some((r) => r.RoomId === 1 || r.IsDorm === true)).toBe(false)
|
||||
|
||||
// The split-test params don't change the result.
|
||||
const plain = (await (
|
||||
await SELF.fetch(`${ORIGIN}/rooms/recommendations`)
|
||||
).json()) as Array<{ RoomId: number }>
|
||||
expect(plain.map((r) => r.RoomId)).toEqual(body.map((r) => r.RoomId))
|
||||
})
|
||||
|
||||
it('GET /rooms/recommendations respects take pagination', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/recommendations?skip=0&take=3`)
|
||||
const body = (await res.json()) as unknown[]
|
||||
expect(body.length).toBeLessThanOrEqual(3)
|
||||
})
|
||||
|
||||
it('GET /rooms/:id/similar returns { Results, TotalResults } of tag-sharing rooms (excluding self)', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/2/similar`)
|
||||
expect(res.status).toBe(200)
|
||||
|
||||
Reference in New Issue
Block a user