Files
opencrm/backend/prisma/rechte-report.ts
T
duffyduckandClaude Opus 5 9f879759d8 CLI-Skripte: DATABASE_URL wieder aufbaubar machen
Die drei neuen Wartungsskripte bauten sich einen eigenen PrismaClient und
gingen damit an src/lib/prisma.ts vorbei. Dort wird DATABASE_URL aus den
DB_*-Teilen zusammengesetzt, falls sie fehlt - und genau das ist bei
`docker compose exec <dienst> npx tsx prisma/<skript>.ts` der Fall: Der
Entrypoint exportiert sie nur in den Serverprozess, eine neue Shell erbt
sie nicht. Ergebnis war "Environment variable not found: DATABASE_URL".

Der Kommentar in src/lib/prisma.ts beschreibt genau diesen Fall; die
Loesung war da, ich habe sie nur umgangen.

Betroffen war auch rolle-zuweisen.ts. Das ist der Weg, den die Startwache
im Klartext nennt, wenn kein Konto mehr die Pflichtrechte haelt - er
haette in dem Moment versagt, fuer den er gebaut ist.

Geprueft mit entzogener DATABASE_URL und nur DB_*-Teilen in der
Umgebung: rechte-report, rolle-zuweisen --liste und sync-roles laufen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 14:23:58 +02:00

163 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Bestandsaufnahme des Rechtesystems. Liest nur, aendert nichts.
*
* npx tsx prisma/rechte-report.ts → Bericht auf stdout
* npx tsx prisma/rechte-report.ts > vorher.txt
*
* Zweck: Vor und nach dem Rechte-Umbau denselben Bericht erzeugen und
* vergleichen. Die Zusage lautet "niemand verliert Zugriff" - und eine
* Zusage, die niemand nachrechnen kann, ist keine.
*
* Der Bericht ist bewusst zeilenweise und sortiert, damit `diff` darauf
* etwas Lesbares ausgibt.
*/
// Ueber src/lib/prisma.js, NICHT ueber einen eigenen PrismaClient: Dort wird
// DATABASE_URL aus den DB_*-Teilen zusammengesetzt, falls sie fehlt. Bei
// `docker compose exec <dienst> npx tsx prisma/<skript>.ts` ist genau das der
// Fall - der Entrypoint exportiert sie nur in den Serverprozess, eine neue
// Shell erbt sie nicht. Ein eigener Client scheitert dort mit
// "Environment variable not found: DATABASE_URL".
import prisma from '../src/lib/prisma.js';
import { ALLE_RECHTE, SYSTEMROLLEN_NAMEN } from '../src/config/rechte-katalog.js';
/**
* Liest isSystem/isHidden, sofern die Spalten existieren.
*
* Der Bericht muss VOR und NACH der Migration laufen - das ist sein Zweck.
* Vorher gibt es die Spalten noch nicht, und ein Absturz an dieser Stelle
* haette genau die Baseline verhindert, gegen die spaeter verglichen wird.
*/
async function leseKennzeichen(): Promise<Map<number, { isSystem: boolean; isHidden: boolean }>> {
const karte = new Map<number, { isSystem: boolean; isHidden: boolean }>();
try {
const zeilen = await prisma.$queryRawUnsafe<
Array<{ id: number; isSystem: number | boolean; isHidden: number | boolean }>
>('SELECT id, isSystem, isHidden FROM `Role`');
for (const z of zeilen) {
karte.set(Number(z.id), { isSystem: Boolean(z.isSystem), isHidden: Boolean(z.isHidden) });
}
} catch {
// Spalten noch nicht vorhanden - das ist der Zustand vor der Migration.
}
return karte;
}
async function main(): Promise<void> {
const kennzeichen = await leseKennzeichen();
const rollen = await prisma.role.findMany({
orderBy: { name: 'asc' },
select: {
id: true,
name: true,
permissions: { include: { permission: true } },
_count: { select: { users: true } },
},
});
console.log('=== ROLLEN ===');
for (const r of rollen) {
const rechte = r.permissions
.map((rp) => `${rp.permission.resource}:${rp.permission.action}`)
.sort();
const k = kennzeichen.get(r.id);
const etikett = [k?.isSystem ? 'System' : null, k?.isHidden ? 'versteckt' : null]
.filter(Boolean)
.join(', ');
console.log(
`\n[${r.name}] #${r.id}` +
(etikett ? ` (${etikett})` : '') +
` ${r._count.users} Konten, ${rechte.length} Rechte`,
);
for (const recht of rechte) console.log(` ${recht}`);
}
console.log('\n=== KONTEN (aktiv) ===');
const konten = await prisma.user.findMany({
where: { isActive: true },
orderBy: { email: 'asc' },
select: {
email: true,
roles: {
select: {
role: {
// Ausdrueckliche Spaltenauswahl, damit der Bericht auch auf einer
// noch nicht migrierten Datenbank laeuft.
select: {
name: true,
permissions: { include: { permission: true } },
},
},
},
},
},
});
for (const k of konten) {
const rechte = new Set<string>();
for (const ur of k.roles) {
for (const rp of ur.role.permissions) {
rechte.add(`${rp.permission.resource}:${rp.permission.action}`);
}
}
const rollenNamen = k.roles.map((ur) => ur.role.name).sort().join(', ') || '(keine)';
console.log(`\n[${k.email}] Rollen: ${rollenNamen} ${rechte.size} Rechte`);
for (const recht of [...rechte].sort()) console.log(` ${recht}`);
}
// --- Auffaelligkeiten -----------------------------------------------------
// Nicht als Alarm gemeint, sondern als das, was man vor einem Deploy
// wissen will. Eine umbenannte Systemrolle etwa wird vom Backfill der
// Migration nicht getroffen.
console.log('\n=== HINWEISE ===');
const hinweise: string[] = [];
const vorhandeneNamen = new Set(rollen.map((r) => r.name));
for (const erwartet of SYSTEMROLLEN_NAMEN) {
if (!vorhandeneNamen.has(erwartet)) {
hinweise.push(`Systemrolle "${erwartet}" fehlt in der Datenbank.`);
}
}
for (const r of rollen) {
const k = kennzeichen.get(r.id);
// Vor der Migration gibt es die Spalte nicht - dann ist "nicht
// gekennzeichnet" kein Befund, sondern der erwartete Zustand.
if (kennzeichen.size === 0) continue;
if (SYSTEMROLLEN_NAMEN.includes(r.name) && !k?.isSystem) {
hinweise.push(`Rolle "${r.name}" ist eine Systemrolle, aber isSystem ist nicht gesetzt.`);
}
if (!SYSTEMROLLEN_NAMEN.includes(r.name) && k?.isSystem) {
hinweise.push(`Rolle "${r.name}" traegt isSystem, gehoert aber nicht zum Katalog.`);
}
}
const alleRechteDb = await prisma.permission.findMany();
const imKatalog = new Set(ALLE_RECHTE);
for (const p of alleRechteDb) {
const s = `${p.resource}:${p.action}`;
if (!imKatalog.has(s)) hinweise.push(`Recht "${s}" steht in der Datenbank, aber nicht im Katalog.`);
}
const inDb = new Set(alleRechteDb.map((p) => `${p.resource}:${p.action}`));
for (const s of ALLE_RECHTE) {
if (!inDb.has(s)) hinweise.push(`Recht "${s}" steht im Katalog, aber nicht in der Datenbank.`);
}
// Konten ohne jede Rolle koennen sich anmelden und sehen nichts - eine
// haeufige Ursache fuer "bei mir ist alles leer".
for (const k of konten) {
if (k.roles.length === 0) hinweise.push(`Konto "${k.email}" hat keine Rolle.`);
}
if (hinweise.length === 0) console.log(' keine');
else for (const h of hinweise) console.log(` ! ${h}`);
}
main()
.catch((e) => {
console.error('[rechte-report] Fehler:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});