feat: Grundgerüst mit Anmeldung, Layout und Design-Tokens

Next.js 16, Prisma 7, Tailwind 4 und Vitest/Playwright, Konfiguration aus
Laufzettel übernommen, weil dort derselbe Stack erprobt ist.

Die Farben der Branding-Vorgabe stehen als Tokens in globals.css, das
helle Theme setzt dieselben Namen neu. Amber und Rot sind dort
abgedunkelt, sonst reicht der Kontrast nicht – ein Test prüft beide
Themes gegen 4,5:1.

Anmeldung mit lokalem Benutzer, Rolle je Anfrage aus der Datenbank,
Audit-Log per Trigger nur anhängbar. Geprüft im gebauten Stand auf
1440 px und 390 px.

Closes #001

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 09:23:41 +02:00
co-authored by Claude Opus 5.5
parent c3211d0fe2
commit 871bb3a77c
75 changed files with 10155 additions and 15 deletions
+35
View File
@@ -0,0 +1,35 @@
import { beforeEach, describe, expect, it } from "vitest";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
/**
* Regeln, die in der Datenbank selbst stehen – sie gelten auch, wenn jemand an der
* Service-Schicht vorbei schreibt. Jede ist hier namentlich aufgeführt.
*/
describe.runIf(datenbankVerfuegbar)("Datenbankregeln", () => {
beforeEach(datenbankLeeren);
it("protokoll_nur_anhaengen: Einträge lassen sich weder ändern noch löschen", async () => {
const eintrag = await testPrisma.protokoll.create({ data: { quelle: "SYSTEM", aktion: "Test" } });
await expect(
testPrisma.protokoll.update({ where: { id: eintrag.id }, data: { aktion: "Gefälscht" } }),
).rejects.toThrow(/nur anhängbar/);
await expect(testPrisma.protokoll.delete({ where: { id: eintrag.id } })).rejects.toThrow(/nur anhängbar/);
});
it("protokoll_nur_anhaengen: das Löschen eines Benutzers lässt seine Einträge stehen", async () => {
const benutzer = await testPrisma.benutzer.create({ data: { benutzername: "weg", anzeigename: "Weg" } });
await testPrisma.protokoll.create({
data: { quelle: "WEB", aktion: "Angemeldet", benutzerId: benutzer.id },
});
await testPrisma.benutzer.delete({ where: { id: benutzer.id } });
const eintraege = await testPrisma.protokoll.findMany();
expect(eintraege).toHaveLength(1);
expect(eintraege[0]?.benutzerId).toBeNull();
});
it("benutzer_benutzername_format: nur Kleinbuchstaben, Ziffern, . _ -", async () => {
await expect(
testPrisma.benutzer.create({ data: { benutzername: "Florian Wilde", anzeigename: "x" } }),
).rejects.toThrow(/benutzer_benutzername_format/);
});
});