Add performance optimizations and component refactors

- Cache read-heavy public API routes via redisCache (leaderboard, values,
  shop, articles, photos, guilds, teams, staff, users, home, radio, badges)
- Add single-flight and bounded-memory cache layer with unit tests
- Parallelize independent DB queries on search, rares, shop, staff, polls
  and profile pages
- Push radio points leaderboard aggregation to SQL with a LIMIT
- Split studio-client and import-furni-client into focused modules
- Clean up next.config.ts
This commit is contained in:
openhands
2026-08-17 22:02:21 +02:00
parent 54f2bc5e0c
commit f285a7cd98
42 changed files with 1616 additions and 1205 deletions
+39 -28
View File
@@ -5,43 +5,54 @@
import { count, desc } from "drizzle-orm";
import { apiJson, pagination } from "@/lib/api";
import { db, Guilds } from "@/lib/db";
import { apiCacheKey, cacheSafe, redisCache } from "@/lib/redis-cache";
export async function GET(req: Request) {
const sp = new URL(req.url).searchParams;
const { page, perPage, skip, take } = pagination(sp);
try {
const [totalRows, rows] = await Promise.all([
db.select({ total: count() }).from(Guilds),
db
.select({
id: Guilds.id,
name: Guilds.name,
description: Guilds.description,
userId: Guilds.userId,
})
.from(Guilds)
.orderBy(desc(Guilds.id))
.limit(take)
.offset(skip),
]);
const total = totalRows[0]?.total ?? 0;
const data = await redisCache(
apiCacheKey(`guilds:${page}:${perPage}`),
120,
async () => {
const [totalRows, rows] = await Promise.all([
db.select({ total: count() }).from(Guilds),
db
.select({
id: Guilds.id,
name: Guilds.name,
description: Guilds.description,
userId: Guilds.userId,
})
.from(Guilds)
.orderBy(desc(Guilds.id))
.limit(take)
.offset(skip),
]);
// Rename userId → ownerId for the public payload.
const data = rows.map(({ userId, ...rest }) => ({
...rest,
ownerId: userId,
}));
// Rename userId → ownerId for the public payload.
const renamed = rows.map(({ userId, ...rest }) => ({
...rest,
ownerId: userId,
}));
return apiJson({
data,
meta: {
page,
perPage,
total,
lastPage: Math.max(1, Math.ceil(total / perPage)),
return cacheSafe({
data: renamed,
meta: {
page,
perPage,
total: totalRows[0]?.total ?? 0,
lastPage: Math.max(
1,
Math.ceil((totalRows[0]?.total ?? 0) / perPage),
),
},
});
},
});
);
return apiJson(data);
} catch {
// DB unreachable — never 500; return an empty, well-formed payload.
return apiJson(