-- 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}$');