/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.
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
import Link from "next/link";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function AdminArticles() {
|
|
// .catch(() => []) keeps the precise selected-row type while falling back to
|
|
// an empty list when the DB is unavailable (instead of a 500).
|
|
const articles = await prisma.websiteArticles
|
|
.findMany({
|
|
orderBy: { createdAt: "desc" },
|
|
select: { id: true, title: true, slug: true, createdAt: true },
|
|
take: 50,
|
|
})
|
|
.catch(() => []);
|
|
|
|
return (
|
|
<main>
|
|
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
|
<h1 style={{ flex: 1 }}>Articles</h1>
|
|
<Link href="/admin/articles/new">+ New article</Link>
|
|
</div>
|
|
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
|
<thead>
|
|
<tr style={{ textAlign: "left", color: "var(--muted)" }}>
|
|
<th>Title</th>
|
|
<th>Slug</th>
|
|
<th>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{articles.map((a) => (
|
|
<tr key={String(a.id)} style={{ borderTop: "1px solid var(--border)" }}>
|
|
<td>
|
|
<Link href={`/admin/articles/${a.id}`}>{a.title}</Link>
|
|
</td>
|
|
<td className="muted">{a.slug}</td>
|
|
<td className="muted">{a.createdAt ? a.createdAt.toISOString().slice(0, 10) : ""}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</main>
|
|
);
|
|
}
|