From 1bb8389bccc1b9cc51e3c8a661ae32d42662832e Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Wed, 8 Jul 2026 09:53:33 -0400 Subject: [PATCH] fix featured --- apps/rooms/src/rooms-db.ts | 54 +++++++++++++++++++++ apps/rooms/src/rooms.app.ts | 8 +++ apps/rooms/src/test/integration/api.test.ts | 19 ++++++++ 3 files changed, 81 insertions(+) diff --git a/apps/rooms/src/rooms-db.ts b/apps/rooms/src/rooms-db.ts index 03f0d1b..e2e3160 100644 --- a/apps/rooms/src/rooms-db.ts +++ b/apps/rooms/src/rooms-db.ts @@ -533,6 +533,60 @@ export async function getRecommendedRooms( .slice(skip, skip + take) } +/** Compact room projection carried by a featured-room group. */ +export interface FeaturedRoom { + RoomId: number + RoomName: string + ImageName: string + IsRecRoomApproved: boolean + ExcludeFromLists: boolean + ExcludeFromSearch: boolean +} + +/** A time-boxed group of featured rooms, as returned by `/featuredrooms/current`. */ +export interface FeaturedRoomGroup { + FeaturedRoomGroupId: number + name: string + StartAt: string + EndAt: string + Rooms: FeaturedRoom[] +} + +/** + * Featured rooms group: public, non-dorm rooms not excluded from lists, in random + * order. There's no editorial curation behind this yet, so "featured" is just a + * random shuffle of the eligible rooms wrapped in a single always-active group. + * Small dataset, so done in memory. + */ +export async function getFeaturedRooms(db: D1Database): Promise { + const { results } = await db.prepare('SELECT data FROM rooms').all() + const rooms = parseAll(results).filter( + (r) => r.IsDorm !== true && r.Accessibility === 1 && r.ExcludeFromLists !== true + ) + // Fisher–Yates shuffle so the feed varies between requests. + for (let i = rooms.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[rooms[i], rooms[j]] = [rooms[j], rooms[i]] + } + + const str = (v: unknown): string => (typeof v === 'string' ? v : '') + const num = (v: unknown): number => (typeof v === 'number' ? v : 0) + return { + FeaturedRoomGroupId: 1, + name: 'Featured Rooms', + StartAt: '2025-12-01T11:01:00Z', + EndAt: '9999-12-08T11:00:00Z', + Rooms: rooms.map((r) => ({ + RoomId: num(r.RoomId), + RoomName: str(r.Name), + ImageName: str(r.ImageName), + IsRecRoomApproved: r.IsRecRoomApproved === true, + ExcludeFromLists: r.ExcludeFromLists === true, + ExcludeFromSearch: r.ExcludeFromSearch === true, + })), + } +} + /** * 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 diff --git a/apps/rooms/src/rooms.app.ts b/apps/rooms/src/rooms.app.ts index 7130d3a..0e8318d 100644 --- a/apps/rooms/src/rooms.app.ts +++ b/apps/rooms/src/rooms.app.ts @@ -10,6 +10,7 @@ import { findSubRoom, getBaseRooms, getFavoritedRooms, + getFeaturedRooms, getHotRooms, getInteraction, getPublicRoomsByCreator, @@ -292,6 +293,13 @@ const app = new Hono() return c.json(await getRecommendedRooms(c.env.DB, skip, take)) }) + // Featured rooms — a single always-active group whose `Rooms` are a randomly + // ordered set of public, non-dorm rooms. No real curation yet, so `current` + // just returns a shuffled list of eligible rooms in the featured-group shape. + .get('/featuredrooms/current', async (c) => { + return c.json(await getFeaturedRooms(c.env.DB)) + }) + // 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. diff --git a/apps/rooms/src/test/integration/api.test.ts b/apps/rooms/src/test/integration/api.test.ts index 034ff5d..f3cffd5 100644 --- a/apps/rooms/src/test/integration/api.test.ts +++ b/apps/rooms/src/test/integration/api.test.ts @@ -340,6 +340,25 @@ describe('rooms endpoints', () => { expect(body.length).toBeLessThanOrEqual(3) }) + it('GET /featuredrooms/current returns a featured-room group of public rooms', async () => { + const res = await SELF.fetch(`${ORIGIN}/featuredrooms/current`) + expect(res.status).toBe(200) + const body = (await res.json()) as { + FeaturedRoomGroupId: number + name: string + StartAt: string + EndAt: string + Rooms: Array<{ RoomId: number; RoomName: string; ImageName: string }> + } + expect(body.FeaturedRoomGroupId).toBe(1) + expect(body.name).toBe('Featured Rooms') + expect(body.Rooms.length).toBeGreaterThan(0) + // Compact projection carries name + image, not the full room blob. + expect(body.Rooms.every((r) => typeof r.RoomName === 'string')).toBe(true) + // The dorm (RoomId 1) is non-public, so it's never featured. + expect(body.Rooms.some((r) => r.RoomId === 1)).toBe(false) + }) + 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)