Die Testläufe hatten dort 70 erfundene Rollen, 112 Nutzer und 40 Akten hinterlassen. Die Rollenverwaltung zeigte deshalb "Gast" siebzehnmal, echte Rollen doppelt und in willkürlicher Reihenfolge. Aufräumen allein reicht nicht: CaseEntry, CaseEntryVersion und AuditLog lassen sich nach harter Regel 2 nicht löschen. Was ein Test dort anlegt, bleibt für immer stehen — in Phase 2 hätte das die Fallliste unbrauchbar gemacht. Tests laufen deshalb gegen <datenbank>_test auf demselben Server. pnpm test legt sie an, migriert und seedet sie vorher. Über TEST_DATABASE_URL lässt sich ein anderes Ziel setzen. Dazu die Abschnitte der Rollenverwaltung in der Reihenfolge, in der die Abteilung ihre Rollen aufzählt: Ränge, Zugehörigkeit, Zugang. Innerhalb eines Abschnitts entscheidet die Position auf dem Discord-Server, oben die höchste. Damit steht die Hierarchie an einer Stelle, die ohnehin gepflegt wird. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
79 lines
2.9 KiB
TypeScript
79 lines
2.9 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import { db } from '@/lib/db'
|
|
import { requirePermission } from '@/lib/auth/current'
|
|
import { de } from '@/lib/i18n/de'
|
|
import { RoleRow } from './RoleRow'
|
|
import { ClearancePreview } from './ClearancePreview'
|
|
|
|
export const metadata: Metadata = { title: de.roleAdmin.heading }
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
/**
|
|
* Reihenfolge der Abschnitte: erst die Ränge, dann die Zugehörigkeit, dann die
|
|
* Zugangsrollen — so, wie die Abteilung sie selbst aufzählt. Innerhalb eines
|
|
* Abschnitts entscheidet die Position der Rolle auf dem Discord-Server, oben
|
|
* die höchste. Damit steht die Hierarchie an einer Stelle, die ohnehin gepflegt
|
|
* wird, statt in einer zweiten Sortierung in dieser Anwendung.
|
|
*/
|
|
const ORDER = ['RANK', 'MEMBERSHIP', 'CLEARANCE', 'UNSORTED'] as const
|
|
|
|
export default async function RoleAdminPage() {
|
|
// Ohne roles.manage gibt es 404, nicht 403.
|
|
await requirePermission('roles.manage')
|
|
|
|
const [mappings, levels] = await Promise.all([
|
|
db.roleMapping.findMany({ orderBy: [{ sortOrder: 'desc' }, { label: 'asc' }] }),
|
|
db.clearanceLevelConfig.findMany({ orderBy: { level: 'asc' } }),
|
|
])
|
|
|
|
return (
|
|
<main className="mx-auto max-w-5xl px-6 py-10">
|
|
<h1 className="font-condensed">{de.roleAdmin.heading}</h1>
|
|
<p className="mt-2 max-w-(--container-measure) text-text-dim">{de.roleAdmin.intro}</p>
|
|
|
|
{mappings.length === 0 ? (
|
|
<p className="mt-8 text-text-dim">{de.roleAdmin.empty}</p>
|
|
) : (
|
|
<>
|
|
<ClearancePreview
|
|
levels={levels}
|
|
mappings={mappings.map((m) => ({
|
|
label: m.label,
|
|
clearanceLevel: m.clearanceLevel,
|
|
}))}
|
|
/>
|
|
|
|
{ORDER.map((category) => {
|
|
const rows = mappings.filter((m) => m.category === category)
|
|
if (rows.length === 0) return null
|
|
return (
|
|
<section key={category} className="mt-10">
|
|
<h2 className="font-condensed text-text-dim">
|
|
{de.roleAdmin.categories[category]}
|
|
</h2>
|
|
<div className="mt-2 border-t border-line">
|
|
{rows.map((role) => (
|
|
<RoleRow
|
|
key={role.discordRoleId}
|
|
role={{
|
|
discordRoleId: role.discordRoleId,
|
|
label: role.label,
|
|
clearanceLevel: role.clearanceLevel,
|
|
grantsMembership: role.grantsMembership,
|
|
category: role.category,
|
|
permissions: role.permissions,
|
|
isPresent: role.isPresent,
|
|
}}
|
|
levels={levels.map((l) => ({ level: l.level, name: l.name }))}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)
|
|
})}
|
|
</>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|