Skip to content

Commit 47cea17

Browse files
committed
feat(leaderboard): async cache refresh via cron endpoint
Adds refreshLeaderboardCache() to src/lib/leaderboard.ts, a new GET /api/leaderboard/refresh cron endpoint with CRON_SECRET Bearer auth, and rewrites the GET handler to cache-only flow. Closes #1799.
1 parent d9a4193 commit 47cea17

2 files changed

Lines changed: 35 additions & 7 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { NextResponse } from "next/server";
2+
import { refreshLeaderboardCache } from "@/lib/leaderboard";
3+
4+
export const dynamic = "force-dynamic";
5+
6+
export async function GET(req: Request) {
7+
const authHeader = req.headers.get("authorization");
8+
const cronSecret = process.env.CRON_SECRET;
9+
10+
if (!cronSecret) {
11+
return NextResponse.json({ error: "CRON_SECRET is not configured" }, { status: 500 });
12+
}
13+
14+
if (authHeader !== `Bearer ${cronSecret}` && process.env.NODE_ENV !== "development") {
15+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
16+
}
17+
18+
try {
19+
const payload = await refreshLeaderboardCache();
20+
return NextResponse.json({ success: true, generatedAt: payload.generatedAt });
21+
} catch (err) {
22+
return NextResponse.json({ error: "Failed to refresh leaderboard cache" }, { status: 500 });
23+
}
24+
}

src/lib/leaderboard.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -326,12 +326,17 @@ export async function buildLeaderboard(
326326
};
327327
}
328328

329-
/**
330-
* Returns cached leaderboard data or builds fresh data.
331-
* Used by both the server component (direct call) and the API route (thin wrapper).
332-
* Does NOT include thundering-herd protection — that belongs in the API route
333-
* where multiple serverless instances may race.
334-
*/
329+
export async function refreshLeaderboardCache(
330+
filters: LeaderboardFilters = {}
331+
): Promise<LeaderboardPayload> {
332+
const payload = await buildLeaderboard(filters);
333+
const period = filters.period ?? DEFAULT_PERIOD;
334+
const cacheKey = getLeaderboardCacheKey(period);
335+
await cacheSet(cacheKey, payload, CACHE_STALE_SECONDS);
336+
setMemoryCachedLeaderboard(payload, period);
337+
return payload;
338+
}
339+
335340
export async function getLeaderboardData(
336341
bypass = false,
337342
filters: LeaderboardFilters = {}
@@ -356,7 +361,6 @@ export async function getLeaderboardData(
356361
return payload;
357362
} catch (err) {
358363
console.error("[Leaderboard] Build failed:", err);
359-
// Return stale data rather than null when the fresh build fails.
360364
const stale = await cacheGet<LeaderboardPayload>(getLeaderboardCacheKey(period));
361365
return stale ?? null;
362366
}

0 commit comments

Comments
 (0)