Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/app/api/leaderboard/refresh/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
}
18 changes: 11 additions & 7 deletions src/lib/leaderboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<LeaderboardPayload> {
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 = {}
Expand All @@ -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<LeaderboardPayload>(getLeaderboardCacheKey(period));
return stale ?? null;
}
Expand Down
Loading