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:
@@ -799,6 +799,63 @@ export async function deleteClub(db: D1Database, clubId: number): Promise<boolea
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* One row of the "most active clubhouses right now" search: a club, the room its
|
||||
* clubhouse is, and how many players are standing in that room this second.
|
||||
*/
|
||||
export interface ActiveClubhouse {
|
||||
RoomId: number
|
||||
ClubId: number
|
||||
PlayerCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* The most a single "most active now" answer carries. It fills a carousel, not a
|
||||
* directory — nobody scrolls past the busiest few clubhouses — and the query behind it
|
||||
* scans live presence, so it stays bounded rather than growing with the club table.
|
||||
*/
|
||||
export const MOST_ACTIVE_CLUBHOUSE_LIMIT = 50
|
||||
|
||||
/**
|
||||
* Clubhouses with players in them right now, busiest first — what
|
||||
* `match`'s `/clubhousesearch/mostactivenow` serves.
|
||||
*
|
||||
* Active means someone is THERE: a club whose clubhouse is empty is absent from the
|
||||
* result rather than listed with a `PlayerCount` of 0, and a club with no clubhouse at
|
||||
* all can never appear (nothing joins to a null room). So a quiet server answers `[]`.
|
||||
*
|
||||
* Same eligibility as {@link searchClubs}, since this is a search too: public,
|
||||
* non-subscription clubs only. Ties break on ClubId so equally busy clubhouses hold a
|
||||
* stable order between calls. Counts unexpired presence only, and ignores lobby presence
|
||||
* (no instance) the way every other head-count here does.
|
||||
*/
|
||||
export async function getMostActiveClubhouses(
|
||||
db: D1Database,
|
||||
limit = MOST_ACTIVE_CLUBHOUSE_LIMIT,
|
||||
now = Math.floor(Date.now() / 1000)
|
||||
): Promise<ActiveClubhouse[]> {
|
||||
// One grouped join rather than a count per club: the club blobs never cross the wire,
|
||||
// only the clubhouse id `json_extract` pulls out of each.
|
||||
const { results } = await db
|
||||
.prepare(
|
||||
`SELECT c.club_id AS ClubId,
|
||||
json_extract(c.data, '$.ClubhouseRoomId') AS RoomId,
|
||||
COUNT(*) AS PlayerCount
|
||||
FROM club c
|
||||
JOIN presence p ON p.room_id = json_extract(c.data, '$.ClubhouseRoomId')
|
||||
WHERE c.visibility = ?1
|
||||
AND json_extract(c.data, '$.ClubType') != ?2
|
||||
AND p.expires_at > ?3
|
||||
AND p.room_instance_id IS NOT NULL
|
||||
GROUP BY c.club_id
|
||||
ORDER BY PlayerCount DESC, c.club_id
|
||||
LIMIT ?4`
|
||||
)
|
||||
.bind(ClubVisibility.Public, SUBSCRIPTION_CLUB_TYPE, now, limit)
|
||||
.all<ActiveClubhouse>()
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscription clubs (`ClubType` 1) are a creator's paid-subscriber club, not a
|
||||
* club you browse or list among your own — they're excluded from the "my clubs"
|
||||
|
||||
@@ -2464,6 +2464,47 @@ export async function getRecommendedRooms(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Trending ("rising") rooms — the listable rooms someone is standing in RIGHT NOW, busiest
|
||||
* first. What the `rising` carousel is filled from.
|
||||
*
|
||||
* This is the one feed where live presence FILTERS rather than merely ranks: the hot feed
|
||||
* sorts by head-count but still lists the empty rooms underneath it, and a carousel of
|
||||
* rooms nobody is in is not trending. So a quiet server serves an EMPTY carousel rather
|
||||
* than falling back to stored engagement — a room with no one in it has not risen.
|
||||
*
|
||||
* Ties break the way the hot feed's do (stored engagement, then RoomId), so equally busy
|
||||
* rooms page stably.
|
||||
*/
|
||||
export async function getTrendingRooms(
|
||||
db: D1Database,
|
||||
skip: number,
|
||||
take: number
|
||||
): Promise<{ Results: Room[]; TotalResults: number }> {
|
||||
const players = await countPlayersByRoom(db)
|
||||
// Nobody anywhere: nothing can be trending, and the room table needn't be read at all.
|
||||
if (players.size === 0) return { Results: [], TotalResults: 0 }
|
||||
|
||||
const { results } = await db
|
||||
.prepare(`SELECT ${ROOM_COLUMNS} FROM room WHERE ${LISTABLE_WHERE}`)
|
||||
.all<RoomRow>()
|
||||
const stats = await getRoomStats(db)
|
||||
const playerCount = (r: Room): number => players.get(roomIdOf(r)) ?? 0
|
||||
const rooms = parseAll(results)
|
||||
.filter((r) => isListable(r) && playerCount(r) > 0)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
playerCount(b) - playerCount(a) ||
|
||||
hotScore(b, stats) - hotScore(a, stats) ||
|
||||
roomIdOf(a) - roomIdOf(b)
|
||||
)
|
||||
|
||||
return {
|
||||
Results: await hydrateRooms(db, rooms.slice(skip, skip + take), stats),
|
||||
TotalResults: rooms.length,
|
||||
}
|
||||
}
|
||||
|
||||
/** Compact room projection carried by a featured-room group. */
|
||||
export interface FeaturedRoom {
|
||||
RoomId: number
|
||||
|
||||
Reference in New Issue
Block a user