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"
|
||||
@@ -0,0 +1,63 @@
|
||||
// Datenmodell von ProxDeploy. Namensabbildung zur Vorgabe: siehe CLAUDE.md.
|
||||
// Modelle kommen mit dem Issue dazu, das sie braucht – der Plan in docs/architektur.md
|
||||
// zeigt das Zielbild.
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
enum Rolle {
|
||||
ADMIN
|
||||
OPERATOR
|
||||
}
|
||||
|
||||
enum Quelle {
|
||||
WEB
|
||||
CLI
|
||||
API
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
model Benutzer {
|
||||
id String @id @default(cuid())
|
||||
benutzername String @unique
|
||||
anzeigename String
|
||||
email String?
|
||||
/// argon2id; leer bei Benutzern, die sich nur per OIDC anmelden.
|
||||
passwortHash String?
|
||||
oidcSubjekt String? @unique
|
||||
rolle Rolle @default(OPERATOR)
|
||||
aktiv Boolean @default(true)
|
||||
/// Persönliche Standardwerte im Assistenten.
|
||||
standards Json?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @updatedAt
|
||||
protokoll Protokoll[]
|
||||
}
|
||||
|
||||
/// Audit-Log. Nur anhängen – ein Trigger verweigert UPDATE und DELETE.
|
||||
model Protokoll {
|
||||
id BigInt @id @default(autoincrement())
|
||||
zeit DateTime @default(now())
|
||||
benutzerId String?
|
||||
benutzer Benutzer? @relation(fields: [benutzerId], references: [id], onDelete: SetNull)
|
||||
quelle Quelle
|
||||
aktion String
|
||||
ziel String?
|
||||
details Json?
|
||||
ip String?
|
||||
|
||||
@@index([zeit])
|
||||
@@index([benutzerId, zeit])
|
||||
}
|
||||
|
||||
model Einstellung {
|
||||
schluessel String @id
|
||||
wert Json
|
||||
geaendertAm DateTime @updatedAt
|
||||
}
|
||||
Reference in New Issue
Block a user