feat: enable React Compiler, add Redis caching for API routes, update README with requirements and install guide
Local Build and Deploy / deploy (push) Failing after 57s
Local Build and Deploy / deploy (push) Failing after 57s
This commit is contained in:
1 parent
f53d25e0a5
commit
ccd4994028
5 files changed
+248
-267
No files matched your search
+26
-24
@@ -1,5 +1,6 @@
|
||||
import { apiJson } from "@/lib/api";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { redisCache, apiCacheKey } from "@/lib/redis-cache";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -10,31 +11,32 @@ export const dynamic = "force-dynamic";
|
||||
* used by the public pages and the admin dashboard.
|
||||
*/
|
||||
export async function GET(_req: Request) {
|
||||
let articles: unknown[] = [];
|
||||
let online = 0;
|
||||
let hotelName = "Atom";
|
||||
|
||||
try {
|
||||
[articles, online, hotelName] = await Promise.all([
|
||||
prisma.websiteArticles.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
shortStory: true,
|
||||
image: true,
|
||||
createdAt: true,
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 4,
|
||||
}),
|
||||
prisma.user.count({ where: { online: "1" } }),
|
||||
siteSettings.get("hotel_name", "Atom").then((v) => v ?? "Atom"),
|
||||
]);
|
||||
|
||||
return apiJson({ articles, online, hotelName });
|
||||
const data = await redisCache(
|
||||
apiCacheKey("home"),
|
||||
15,
|
||||
async () => {
|
||||
const [articles, online, hotelName] = await Promise.all([
|
||||
prisma.websiteArticles.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
shortStory: true,
|
||||
image: true,
|
||||
createdAt: true,
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 4,
|
||||
}),
|
||||
prisma.user.count({ where: { online: "1" } }),
|
||||
siteSettings.get("hotel_name", "Atom").then((v) => v ?? "Atom"),
|
||||
]);
|
||||
return { articles, online, hotelName };
|
||||
},
|
||||
);
|
||||
return apiJson(data);
|
||||
} catch {
|
||||
// DB unavailable — return an empty payload instead of a 500.
|
||||
return apiJson({ articles: [], online: 0, hotelName }, { status: 200 });
|
||||
return apiJson({ articles: [], online: 0, hotelName: "Atom" }, { status: 200 });
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiJson } from "@/lib/api";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { redisCache, apiCacheKey } from "@/lib/redis-cache";
|
||||
|
||||
// Radio player config: the subset of radio_* website_settings the front-end
|
||||
// player needs (stream URL, name, autoplay, enabled, widget visibility) as a
|
||||
@@ -27,19 +28,23 @@ const CONFIG_KEYS = new Set([
|
||||
|
||||
export async function GET(_req: Request) {
|
||||
try {
|
||||
const rows = await prisma.websiteSetting.findMany({
|
||||
where: { key: { in: Array.from(CONFIG_KEYS) } },
|
||||
select: { key: true, value: true },
|
||||
});
|
||||
|
||||
const config: Record<string, string> = {};
|
||||
for (const row of rows) {
|
||||
config[row.key] = row.value;
|
||||
}
|
||||
|
||||
const config = await redisCache(
|
||||
apiCacheKey("radio:config"),
|
||||
30,
|
||||
async () => {
|
||||
const rows = await prisma.websiteSetting.findMany({
|
||||
where: { key: { in: Array.from(CONFIG_KEYS) } },
|
||||
select: { key: true, value: true },
|
||||
});
|
||||
const result: Record<string, string> = {};
|
||||
for (const row of rows) {
|
||||
result[row.key] = row.value;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
);
|
||||
return apiJson(config);
|
||||
} catch {
|
||||
// DB unavailable — serve an empty config rather than a 500.
|
||||
return apiJson({}, { status: 200 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import "server-only";
|
||||
|
||||
import { redis } from "@/lib/redis";
|
||||
|
||||
/**
|
||||
* Cache the result of a fetch function in Redis.
|
||||
* Falls back to the fresh fetch if Redis is unavailable.
|
||||
*/
|
||||
export async function redisCache<T>(
|
||||
key: string,
|
||||
ttlSeconds: number,
|
||||
fetch: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
if (!redis) return fetch();
|
||||
|
||||
try {
|
||||
const cached = await redis.get(key);
|
||||
if (cached !== null) {
|
||||
return JSON.parse(cached) as T;
|
||||
}
|
||||
} catch {
|
||||
// cache miss or error — fall through to fresh fetch
|
||||
}
|
||||
|
||||
const fresh = await fetch();
|
||||
|
||||
try {
|
||||
await redis.setex(key, ttlSeconds, JSON.stringify(fresh));
|
||||
} catch {
|
||||
// ignore write errors
|
||||
}
|
||||
|
||||
return fresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate a cached key. No-op if Redis is unavailable.
|
||||
*/
|
||||
export async function invalidateCache(key: string): Promise<void> {
|
||||
if (!redis) return;
|
||||
try {
|
||||
await redis.del(key);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a namespaced cache key for API routes.
|
||||
*/
|
||||
export function apiCacheKey(path: string): string {
|
||||
return `api:${path}`;
|
||||
}
|
||||
Reference in new issue
Block a user