Files
florian.wildeandClaude Opus 5.5 871bb3a77c 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>
2026-10-12 09:23:41 +02:00

86 lines
2.7 KiB
PL/PgSQL
Raw Permalink 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.
-- CreateEnum
CREATE TYPE "Rolle" AS ENUM ('ADMIN', 'OPERATOR');
-- CreateEnum
CREATE TYPE "Quelle" AS ENUM ('WEB', 'CLI', 'API', 'SYSTEM');
-- CreateTable
CREATE TABLE "Benutzer" (
"id" TEXT NOT NULL,
"benutzername" TEXT NOT NULL,
"anzeigename" TEXT NOT NULL,
"email" TEXT,
"passwortHash" TEXT,
"oidcSubjekt" TEXT,
"rolle" "Rolle" NOT NULL DEFAULT 'OPERATOR',
"aktiv" BOOLEAN NOT NULL DEFAULT true,
"standards" JSONB,
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"geaendertAm" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Benutzer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Protokoll" (
"id" BIGSERIAL NOT NULL,
"zeit" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"benutzerId" TEXT,
"quelle" "Quelle" NOT NULL,
"aktion" TEXT NOT NULL,
"ziel" TEXT,
"details" JSONB,
"ip" TEXT,
CONSTRAINT "Protokoll_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Einstellung" (
"schluessel" TEXT NOT NULL,
"wert" JSONB NOT NULL,
"geaendertAm" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Einstellung_pkey" PRIMARY KEY ("schluessel")
);
-- CreateIndex
CREATE UNIQUE INDEX "Benutzer_benutzername_key" ON "Benutzer"("benutzername");
-- CreateIndex
CREATE UNIQUE INDEX "Benutzer_oidcSubjekt_key" ON "Benutzer"("oidcSubjekt");
-- CreateIndex
CREATE INDEX "Protokoll_zeit_idx" ON "Protokoll"("zeit");
-- CreateIndex
CREATE INDEX "Protokoll_benutzerId_zeit_idx" ON "Protokoll"("benutzerId", "zeit");
-- AddForeignKey
ALTER TABLE "Protokoll" ADD CONSTRAINT "Protokoll_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "Benutzer"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- Regeln, die auch gelten, wenn jemand an der Service-Schicht vorbei schreibt.
-- Das Audit-Log ist nur anhängbar. Eine Ausnahme braucht es nicht: auch das Löschen
-- eines Benutzers setzt benutzerId per FK auf NULL, und das ist ein UPDATE – das
-- erlaubt der Trigger nur für genau diese Spalte.
CREATE FUNCTION "protokoll_nur_anhaengen"() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'UPDATE'
AND NEW."benutzerId" IS NULL
AND (NEW.id, NEW.zeit, NEW.quelle, NEW.aktion, NEW.ziel, NEW.details, NEW.ip)
IS NOT DISTINCT FROM (OLD.id, OLD.zeit, OLD.quelle, OLD.aktion, OLD.ziel, OLD.details, OLD.ip) THEN
RETURN NEW;
END IF;
RAISE EXCEPTION 'Das Protokoll ist nur anhängbar (%).', TG_OP USING ERRCODE = 'P0001';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "protokoll_nur_anhaengen"
BEFORE UPDATE OR DELETE ON "Protokoll"
FOR EACH ROW EXECUTE FUNCTION "protokoll_nur_anhaengen"();
ALTER TABLE "Benutzer"
ADD CONSTRAINT "benutzer_benutzername_format"
CHECK ("benutzername" ~ '^[a-z0-9][a-z0-9._-]{1,62}$');