/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.
77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
import Link from "next/link";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const PER_PAGE = 25;
|
|
|
|
export default async function AdminUsers({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ q?: string; page?: string }>;
|
|
}) {
|
|
const sp = await searchParams;
|
|
const q = (sp.q ?? "").trim();
|
|
const page = Math.max(1, Number(sp.page ?? "1") || 1);
|
|
const where = q ? { username: { contains: q } } : {};
|
|
|
|
// .catch(...) keeps the precise selected-row type while falling back to an
|
|
// empty page when the DB is unavailable (instead of a 500).
|
|
const users = await prisma.user
|
|
.findMany({
|
|
where,
|
|
select: { id: true, username: true, rank: true, credits: true, online: true },
|
|
orderBy: { id: "desc" },
|
|
skip: (page - 1) * PER_PAGE,
|
|
take: PER_PAGE,
|
|
})
|
|
.catch(() => []);
|
|
const total = await prisma.user.count({ where }).catch(() => 0);
|
|
const pages = Math.max(1, Math.ceil(total / PER_PAGE));
|
|
|
|
return (
|
|
<main>
|
|
<h1>Users</h1>
|
|
<form style={{ display: "flex", gap: "0.5rem", marginBottom: "1rem" }}>
|
|
<input name="q" defaultValue={q} placeholder="Search username" />
|
|
<button type="submit">Search</button>
|
|
</form>
|
|
|
|
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
|
<thead>
|
|
<tr style={{ textAlign: "left", color: "var(--muted)" }}>
|
|
<th>ID</th>
|
|
<th>Username</th>
|
|
<th>Rank</th>
|
|
<th>Credits</th>
|
|
<th>Online</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{users.map((u) => (
|
|
<tr key={u.id} style={{ borderTop: "1px solid var(--border)" }}>
|
|
<td>{u.id}</td>
|
|
<td>
|
|
<Link href={`/admin/users/${u.id}`}>{u.username}</Link>
|
|
</td>
|
|
<td>{u.rank}</td>
|
|
<td>{u.credits}</td>
|
|
<td>{u.online === "1" ? "●" : "○"}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
|
|
<p className="muted">
|
|
Page {page} / {pages} · {total} users
|
|
{page < pages ? (
|
|
<>
|
|
{" · "}
|
|
<Link href={`/admin/users?q=${encodeURIComponent(q)}&page=${page + 1}`}>Next →</Link>
|
|
</>
|
|
) : null}
|
|
</p>
|
|
</main>
|
|
);
|
|
}
|