Add remaining public + admin pages (parallel build, 26 files)
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.
This commit is contained in:
1 parent
9a79acebe7
commit
e96b606e1e
27 files changed
+2400
No files matched your search
@@ -0,0 +1,62 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user