feat: add 5-min in-memory cache to dashboard stats endpoint
This commit is contained in:
+53
-1
@@ -10,6 +10,51 @@ import {
|
|||||||
import { eq, count, sql, gte, and, desc } from "drizzle-orm";
|
import { eq, count, sql, gte, and, desc } from "drizzle-orm";
|
||||||
import { requireContributorOrAdmin } from "@/lib/auth/guard";
|
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<string, number>;
|
||||||
|
gamesBySource: Record<string, number>;
|
||||||
|
playabilityDistribution: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CacheEntry<T> {
|
||||||
|
data: T;
|
||||||
|
expiresAt: number;
|
||||||
|
}
|
||||||
|
const statsCache = new Map<string, CacheEntry<unknown>>();
|
||||||
|
const STATS_CACHE_TTL = 300_000; // 5 minutes
|
||||||
|
|
||||||
|
function getCached<T>(key: string): T | null {
|
||||||
|
const entry = statsCache.get(key) as CacheEntry<T> | undefined;
|
||||||
|
if (!entry) return null;
|
||||||
|
if (Date.now() > entry.expiresAt) {
|
||||||
|
statsCache.delete(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entry.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCached<T>(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(
|
export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags: ["Dashboard"] } }).get(
|
||||||
"/stats",
|
"/stats",
|
||||||
async ({ request, set }) => {
|
async ({ request, set }) => {
|
||||||
@@ -19,6 +64,10 @@ export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags
|
|||||||
return { error: guard.error };
|
return { error: guard.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check cache
|
||||||
|
const cached = getCached<DashboardStatsResult>("dashboard_stats");
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
const thirtyDaysAgo = new Date();
|
const thirtyDaysAgo = new Date();
|
||||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||||
|
|
||||||
@@ -120,7 +169,7 @@ export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags
|
|||||||
.groupBy(games.playabilityStatus),
|
.groupBy(games.playabilityStatus),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
overview: {
|
overview: {
|
||||||
totalGames: totalGames[0]?.count ?? 0,
|
totalGames: totalGames[0]?.count ?? 0,
|
||||||
totalBenchmarks: totalBenchmarks[0]?.count ?? 0,
|
totalBenchmarks: totalBenchmarks[0]?.count ?? 0,
|
||||||
@@ -159,5 +208,8 @@ export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags
|
|||||||
{} as Record<string, number>,
|
{} as Record<string, number>,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setCached("dashboard_stats", result);
|
||||||
|
return result;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Reference in New Issue
Block a user