Files
Epicnabbo-Catalogus-Updated…/src/app/api/radio/points/leaderboard/route.ts
T

56 lines
1.6 KiB
TypeScript

import { inArray, sum } from "drizzle-orm";
import { apiJson } from "@/lib/api";
import { db, RadioListenerPoints, User } from "@/lib/db";
// Radio listener-points leaderboard: the top 20 users by total points, summed
// across radio_listener_points and joined to users for username/look. Public
// (no auth) — mirrors the AtomCMS radio leaderboard widget.
export const dynamic = "force-dynamic";
export async function GET(_req: Request) {
try {
// Sum points per user. Sort/slice in JS so we stay adapter-agnostic about
// aggregate ordering, then resolve the top 20 to usernames/looks.
const grouped = await db
.select({
userId: RadioListenerPoints.userId,
points: sum(RadioListenerPoints.points),
})
.from(RadioListenerPoints)
.groupBy(RadioListenerPoints.userId);
const ranked = grouped
.map((g) => ({
userId: Number(g.userId),
points: Number(g.points ?? 0),
}))
.sort((a, b) => b.points - a.points)
.slice(0, 20);
if (ranked.length === 0) {
return apiJson({ data: [] });
}
const userIds = ranked.map((r) => r.userId);
const users = await db
.select({ id: User.id, username: User.username, look: User.look })
.from(User)
.where(inArray(User.id, userIds));
const userById = new Map(users.map((u) => [u.id, u]));
const data = ranked.map((r) => {
const u = userById.get(r.userId);
return {
username: u?.username ?? null,
look: u?.look ?? null,
points: r.points,
};
});
return apiJson({ data });
} catch {
// DB unavailable — serve an empty leaderboard rather than a 500.
return apiJson({ data: [] }, { status: 200 });
}
}