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:
@@ -0,0 +1,85 @@
|
||||
-- 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}$');
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
Reference in New Issue
Block a user