From 64b026386def98c968b6abee902838210fb485d9 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Sat, 16 May 2026 17:02:48 +0800 Subject: [PATCH] feat: add 5-min in-memory cache to dashboard stats endpoint --- lib/api/dashboard.ts | 54 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/api/dashboard.ts b/lib/api/dashboard.ts index c4b9464..17cfd3d 100644 --- a/lib/api/dashboard.ts +++ b/lib/api/dashboard.ts @@ -10,6 +10,51 @@ import { import { eq, count, sql, gte, and, desc } from "drizzle-orm"; import { requireContributorOrAdmin } from "@/lib/auth/guard"; +// In-memory cache for dashboard stats (TTL 300s) +interface DashboardStatsResult { + overview: { + totalGames: number; + totalBenchmarks: number; + totalUsers: number; + pendingReports: number; + pendingSuggestions: number; + }; + recent: { + benchmarksLast30Days: number; + gamesLast30Days: number; + }; + topContributors: Array<{ + userId: string | null; + name: string; + count: number; + }>; + syncHealth: Record; + gamesBySource: Record; + playabilityDistribution: Record; +} + +interface CacheEntry { + data: T; + expiresAt: number; +} +const statsCache = new Map>(); +const STATS_CACHE_TTL = 300_000; // 5 minutes + +function getCached(key: string): T | null { + const entry = statsCache.get(key) as CacheEntry | undefined; + if (!entry) return null; + if (Date.now() > entry.expiresAt) { + statsCache.delete(key); + return null; + } + return entry.data; +} + +function setCached(key: string, data: T): void { + statsCache.set(key, { data, expiresAt: Date.now() + STATS_CACHE_TTL }); +} + + export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags: ["Dashboard"] } }).get( "/stats", async ({ request, set }) => { @@ -19,6 +64,10 @@ export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags return { error: guard.error }; } + // Check cache + const cached = getCached("dashboard_stats"); + if (cached) return cached; + const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); @@ -120,7 +169,7 @@ export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags .groupBy(games.playabilityStatus), ]); - return { + const result = { overview: { totalGames: totalGames[0]?.count ?? 0, totalBenchmarks: totalBenchmarks[0]?.count ?? 0, @@ -159,5 +208,8 @@ export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags {} as Record, ), }; + + setCached("dashboard_stats", result); + return result; }, ); \ No newline at end of file