// 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[] auftraege Auftrag[] } /// 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 } /// Ein Proxmox-VE-Einzelhost oder -Cluster. Geheimnisse liegen AES-256-GCM-verschlüsselt (v1.…). model Server { id String @id @default(cuid()) /// Kurzname, auch für die CLI (`--server rz1`). name String @unique /// Anzeigename, z. B. „Rechenzentrum 1“. titel String /// https://host:8006 url String /// user@realm!tokenid tokenId String tokenGeheimnis String /// Aus: nur ein gepinnter Fingerprint zählt. An: Systemvertrauen (+ optional Pin). tlsPruefen Boolean @default(false) /// SHA-256 des Serverzertifikats, Großbuchstaben mit Doppelpunkten. fingerabdruck String? sshAktiv Boolean @default(false) sshBenutzer String @default("root") sshPort Int @default(22) sshPrivat String? sshOeffentlich String? /// Gepinnte Host-Keys je Node: { "pve01": "SHA256:…" }. sshHostSchluessel Json? templateVmidVon Int @default(9000) templateVmidBis Int @default(9999) gastVmidVon Int @default(100) /// Statische Adresse für die Template-Vorbereitung ohne SSH: { bridge, vlan, ip, gateway }. bauNetz Json? /// Standard-Node, -Storage, -Bridge für den Assistenten. standards Json? /// Bericht des letzten Verbindungstests inkl. Fähigkeiten (VerbindungsBericht, #003). letzterTest Json? erreichbar Boolean? pveVersion String? geprueftAm DateTime? erstelltAm DateTime @default(now()) geaendertAm DateTime @updatedAt auftraege Auftrag[] templates Template[] } enum Herkunft { KATALOG EIGEN } /// Spiegel von catalog/distros/*.yaml. Die Datei ist maßgeblich; nur `aktiv` gehört der Datenbank. model Distribution { id String @id name String familie String /// Validierter Inhalt der YAML-Datei. definition Json quelltext String dateiHash String aktiv Boolean @default(true) /// Gesetzt, wenn die Datei aus dem Katalog verschwunden ist – die Zeile bleibt für den Verlauf. entferntAm DateTime? synchronisiertAm DateTime geaendertVonId String? } /// Profile aus catalog/profiles/*.yaml (KATALOG, schreibgeschützt) und eigene aus der GUI (EIGEN). model Profil { id String @id name String beschreibung String? familien String[] herkunft Herkunft definition Json /// YAML wie eingegeben – Kommentare bleiben erhalten. quelltext String dateiHash String? aktiv Boolean @default(true) entferntAm DateTime? synchronisiertAm DateTime? geaendertVonId String? geaendertAm DateTime @updatedAt } enum GastTyp { VM LXC } enum AuftragsArt { AUSROLLEN TEMPLATE_BAUEN AUFRAEUMEN } enum AuftragsStatus { WARTEND LAEUFT ERFOLGREICH FEHLGESCHLAGEN ABGEBROCHEN AUFGERAEUMT } enum PhasenStatus { OFFEN LAEUFT ERLEDIGT FEHLER UEBERSPRUNGEN } /// Ein Hintergrundjob: Ausrollen, Template bauen oder Aufräumen. Läuft im worker. model Auftrag { id String @id @default(cuid()) /// Kurz und tippbar für CLI und URLs: „d-7f3k2“. kurzId String @unique art AuftragsArt status AuftragsStatus @default(WARTEND) serverId String server Server @relation(fields: [serverId], references: [id], onDelete: Restrict) knoten String? gastTyp GastTyp? vmid Int? name String? distributionId String? profile String[] /// Validierte Anfrage – Grundlage für „Wiederholen“. anfrage Json /// Plan zum Zeitpunkt des Starts (Phasen und geplante API-Aufrufe). plan Json? /// Kopfzeile wie in der CLI: „Server rz1 · Node pve01 · Debian 13 (Template 9013)“ – von der Engine gesetzt. kopf String? /// IP, SSH-Befehl, Dauer … ergebnis Json? /// Was ProxDeploy angelegt hat – Grundlage fürs Aufräumen. angelegt Json? /// { phase, ursache, hinweis } fehler Json? quelle Quelle erstelltVonId String erstelltVon Benutzer @relation(fields: [erstelltVonId], references: [id], onDelete: Restrict) wiederholtVonId String? /// Bei AUFRAEUMEN: der Auftrag, dessen Reste entfernt werden. bezugId String? abbrechen Boolean @default(false) erstelltAm DateTime @default(now()) gestartetAm DateTime? beendetAm DateTime? phasen Phase[] log Logeintrag[] @@index([serverId, erstelltAm]) @@index([status]) @@index([erstelltAm]) } model Phase { id String @id @default(cuid()) auftragId String auftrag Auftrag @relation(fields: [auftragId], references: [id], onDelete: Cascade) reihenfolge Int schluessel String titel String status PhasenStatus @default(OFFEN) /// Der graue Detailtext hinter dem Phasennamen – in Web und CLI derselbe. detail String? ursache String? hinweis String? gestartetAm DateTime? beendetAm DateTime? @@unique([auftragId, schluessel]) @@index([auftragId, reihenfolge]) } model Logeintrag { /// Zugleich die Event-ID im SSE-Strom (Last-Event-ID). id BigInt @id @default(autoincrement()) auftragId String auftrag Auftrag @relation(fields: [auftragId], references: [id], onDelete: Cascade) zeit DateTime @default(now()) stufe String phase String? nachricht String daten Json? @@index([auftragId, id]) } /// Vergebene VMIDs, solange ein Auftrag läuft – verhindert Doppelvergabe zwischen parallelen Aufträgen. model VmidReservierung { serverId String vmid Int auftragId String ablaufAm DateTime @@id([serverId, vmid]) @@index([auftragId]) } /// Von ProxDeploy gebaute Templates. Maßgeblich ist Proxmox (Tag proxdeploy-template); hier steht, woraus gebaut wurde. model Template { id String @id @default(cuid()) serverId String server Server @relation(fields: [serverId], references: [id], onDelete: Cascade) knoten String vmid Int distributionId String storage String imageDatei String pruefsumme String gebautVonAuftragId String? gebautAm DateTime @default(now()) @@unique([serverId, vmid]) @@index([serverId, distributionId]) }