Harden DB-reading pages with graceful fallback (no 500 without DB)

/news and six admin pages issued raw prisma reads with no try/catch, so
a DB outage rendered a 500 instead of an empty state. Wrap each read
(try/catch or .catch(() => fallback), preserving select() row types and
notFound() on the user-detail page). Matches the fail-soft pattern used
across the rest of the app.

Verified: tsc 0, next build 0, all public pages 200 against a dead DB.
This commit is contained in:
Simo committed 2026-06-28 16:14:40 +02:00
1 parent 3e8fb794d9
commit d5efbba9f6
7 files changed
+60 -29

No files matched your search

+9 -5
View File
@@ -4,11 +4,15 @@ import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export default async function AdminArticles() { export default async function AdminArticles() {
const articles = await prisma.websiteArticles.findMany({ // .catch(() => []) keeps the precise selected-row type while falling back to
orderBy: { createdAt: "desc" }, // an empty list when the DB is unavailable (instead of a 500).
select: { id: true, title: true, slug: true, createdAt: true }, const articles = await prisma.websiteArticles
take: 50, .findMany({
}); orderBy: { createdAt: "desc" },
select: { id: true, title: true, slug: true, createdAt: true },
take: 50,
})
.catch(() => []);
return ( return (
<main> <main>
+10 -5
View File
@@ -5,11 +5,16 @@ export const dynamic = "force-dynamic";
export default async function AdminBans() { export default async function AdminBans() {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const bans = await prisma.ban.findMany({ let bans: Awaited<ReturnType<typeof prisma.ban.findMany>> = [];
where: { banExpire: { gt: now } }, try {
orderBy: { timestamp: "desc" }, bans = await prisma.ban.findMany({
take: 100, where: { banExpire: { gt: now } },
}); orderBy: { timestamp: "desc" },
take: 100,
});
} catch {
bans = [];
}
return ( return (
<main> <main>
+12 -5
View File
@@ -3,11 +3,18 @@ import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export default async function AdminDashboard() { export default async function AdminDashboard() {
const [users, online, articles] = await Promise.all([ let users = 0;
prisma.user.count(), let online = 0;
prisma.user.count({ where: { online: "1" } }), let articles = 0;
prisma.websiteArticles.count(), try {
]); [users, online, articles] = await Promise.all([
prisma.user.count(),
prisma.user.count({ where: { online: "1" } }),
prisma.websiteArticles.count(),
]);
} catch {
// DB unavailable — show zeroed stats instead of a 500.
}
const stats = [ const stats = [
{ label: "Users", value: users }, { label: "Users", value: users },
+6 -1
View File
@@ -4,7 +4,12 @@ import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export default async function AdminSettings() { export default async function AdminSettings() {
const settings = await prisma.websiteSetting.findMany({ orderBy: { key: "asc" } }); let settings: Awaited<ReturnType<typeof prisma.websiteSetting.findMany>> = [];
try {
settings = await prisma.websiteSetting.findMany({ orderBy: { key: "asc" } });
} catch {
settings = [];
}
return ( return (
<main> <main>
+6 -4
View File
@@ -18,10 +18,12 @@ export default async function AdminUserDetail({
}) { }) {
const { id } = await params; const { id } = await params;
const userId = Number(id); const userId = Number(id);
const user = await prisma.user.findUnique({ const user = await prisma.user
where: { id: userId }, .findUnique({
select: { id: true, username: true, motto: true, rank: true, credits: true, look: true, online: true }, where: { id: userId },
}); select: { id: true, username: true, motto: true, rank: true, credits: true, look: true, online: true },
})
.catch(() => null);
if (!user) notFound(); if (!user) notFound();
return ( return (
+7 -5
View File
@@ -15,16 +15,18 @@ export default async function AdminUsers({
const page = Math.max(1, Number(sp.page ?? "1") || 1); const page = Math.max(1, Number(sp.page ?? "1") || 1);
const where = q ? { username: { contains: q } } : {}; const where = q ? { username: { contains: q } } : {};
const [users, total] = await Promise.all([ // .catch(...) keeps the precise selected-row type while falling back to an
prisma.user.findMany({ // empty page when the DB is unavailable (instead of a 500).
const users = await prisma.user
.findMany({
where, where,
select: { id: true, username: true, rank: true, credits: true, online: true }, select: { id: true, username: true, rank: true, credits: true, online: true },
orderBy: { id: "desc" }, orderBy: { id: "desc" },
skip: (page - 1) * PER_PAGE, skip: (page - 1) * PER_PAGE,
take: PER_PAGE, take: PER_PAGE,
}), })
prisma.user.count({ where }), .catch(() => []);
]); const total = await prisma.user.count({ where }).catch(() => 0);
const pages = Math.max(1, Math.ceil(total / PER_PAGE)); const pages = Math.max(1, Math.ceil(total / PER_PAGE));
return ( return (
+10 -4
View File
@@ -9,10 +9,16 @@ function formatDate(d: Date | null): string {
} }
export default async function NewsPage() { export default async function NewsPage() {
const articles = await prisma.websiteArticles.findMany({ let articles: Awaited<ReturnType<typeof prisma.websiteArticles.findMany>> = [];
orderBy: { createdAt: "desc" }, try {
take: 30, articles = await prisma.websiteArticles.findMany({
}); orderBy: { createdAt: "desc" },
take: 30,
});
} catch {
// DB unavailable — render the empty state rather than a 500.
articles = [];
}
return ( return (
<main> <main>