Built via two parallel agent workflows reading the real Prisma schema, then integrated + verified. Public: /shop (categories + storefront), /community hub, /rankings (top by credits), /staff, /photos (camera_web gallery), /help (categories + rules), /help/tickets (auth-gated user tickets + create), /settings (auth-gated, update motto via RCON). Admin: /admin/catalog (+[id] items), /admin/radio (shouts/apps/schedules + delete shout), /admin/teams (CRUD), /admin/permissions (read), /admin/wordfilter (CRUD + RCON push), /admin/ip (whitelist/blacklist CRUD), /admin/applications (list + dismiss), /admin/logs (chat/command/alert read), /admin/achievements (read). Server actions all staff-gated. Header + admin nav extended. Verified: tsc exit 0, vitest 48/48, next build exit 0 (33 routes); curl-probed every route — public 200/<main>, auth+admin correctly 307-redirect when unauthorized.
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
// NOTE: The emulator's standalone `permissions` table is not present in the
|
|
// Prisma schema (there is no `prisma.permissions` accessor). The real,
|
|
// queryable rank-permission table in this schema is `website_housekeeping_permissions`
|
|
// (model WebsiteHousekeepingPermissions), which gates each permission by the
|
|
// minimum rank that holds it — exactly the rank-permission data this page lists.
|
|
type PermissionRow = {
|
|
id: bigint;
|
|
permission: string;
|
|
minRank: number;
|
|
description: string | null;
|
|
};
|
|
|
|
export default async function AdminPermissions() {
|
|
let permissions: PermissionRow[] = [];
|
|
try {
|
|
permissions = await prisma.websiteHousekeepingPermissions.findMany({
|
|
select: { id: true, permission: true, minRank: true, description: true },
|
|
orderBy: [{ minRank: "asc" }, { permission: "asc" }],
|
|
});
|
|
} catch {
|
|
permissions = [];
|
|
}
|
|
|
|
return (
|
|
<main>
|
|
<h1>Permissions</h1>
|
|
<p className="muted">
|
|
Read-only view of rank permissions. Each entry is the minimum rank that
|
|
grants the permission.
|
|
</p>
|
|
|
|
{permissions.length === 0 ? (
|
|
<p className="muted">No permissions found.</p>
|
|
) : (
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Permission</th>
|
|
<th>Min rank</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{permissions.map((p) => (
|
|
<tr key={String(p.id)}>
|
|
<td>{String(p.id)}</td>
|
|
<td>{p.permission}</td>
|
|
<td>{p.minRank}</td>
|
|
<td className="muted">{p.description ?? "—"}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</main>
|
|
);
|
|
}
|