diff --git a/src/app/api/leaderboard/refresh/route.ts b/src/app/api/leaderboard/refresh/route.ts new file mode 100644 index 000000000..85a396bde --- /dev/null +++ b/src/app/api/leaderboard/refresh/route.ts @@ -0,0 +1,24 @@ +import { NextResponse } from "next/server"; +import { refreshLeaderboardCache } from "@/lib/leaderboard"; + +export const dynamic = "force-dynamic"; + +export async function GET(req: Request) { + const authHeader = req.headers.get("authorization"); + const cronSecret = process.env.CRON_SECRET; + + if (!cronSecret) { + return NextResponse.json({ error: "CRON_SECRET is not configured" }, { status: 500 }); + } + + if (authHeader !== `Bearer ${cronSecret}` && process.env.NODE_ENV !== "development") { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + try { + const payload = await refreshLeaderboardCache(); + return NextResponse.json({ success: true, generatedAt: payload.generatedAt }); + } catch (err) { + return NextResponse.json({ error: "Failed to refresh leaderboard cache" }, { status: 500 }); + } +} diff --git a/src/lib/leaderboard.ts b/src/lib/leaderboard.ts index 5d1701870..c72b6ce2a 100644 --- a/src/lib/leaderboard.ts +++ b/src/lib/leaderboard.ts @@ -326,12 +326,17 @@ export async function buildLeaderboard( }; } -/** - * Returns cached leaderboard data or builds fresh data. - * Used by both the server component (direct call) and the API route (thin wrapper). - * Does NOT include thundering-herd protection — that belongs in the API route - * where multiple serverless instances may race. - */ +export async function refreshLeaderboardCache( + filters: LeaderboardFilters = {} +): Promise { + const payload = await buildLeaderboard(filters); + const period = filters.period ?? DEFAULT_PERIOD; + const cacheKey = getLeaderboardCacheKey(period); + await cacheSet(cacheKey, payload, CACHE_STALE_SECONDS); + setMemoryCachedLeaderboard(payload, period); + return payload; +} + export async function getLeaderboardData( bypass = false, filters: LeaderboardFilters = {} @@ -356,7 +361,6 @@ export async function getLeaderboardData( return payload; } catch (err) { console.error("[Leaderboard] Build failed:", err); - // Return stale data rather than null when the fresh build fails. const stale = await cacheGet(getLeaderboardCacheKey(period)); return stale ?? null; }