feat(domains): Modul Domains mit AutoDNS-Zugang, Verbindungstest und Einstellungen
- Migration mit vier Tabellen (Einstellungen, Kunden, Kontaktzuordnung, Bestellungen), Zeilenschutz je Organisation - AutoDNS-Client mit festen Demo-/Live-Adressen, Takt-Begrenzer, 20 s Zeitlimit, ohne Wiederholung - Zugang je System verschluesselt gespeichert, Live-Wechsel nur mit Bestaetigung, Verbindungstest - Modulseite mit Systemkennzeichnung und Reiter Einstellungen (nur Verwalten) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
-- 261008-dts — Modul "Domains" (AutoDNS / InterNetX Domainrobot).
|
||||
--
|
||||
-- Zweck: vier neue Tabellen und zwei Aufzaehlungen.
|
||||
-- * "DomainsConfig": Einstellungen der Organisation (Singleton), getrennte
|
||||
-- Zugaenge fuer das Demo- und das Live-System, Passwoerter AES-verschluesselt
|
||||
-- (CryptoService), Standard-Nameserver.
|
||||
-- * "DomainsCustomer": Kunden, denen AutoDNS-Kontakte zugeordnet werden;
|
||||
-- eine Zeile darf als "Eigene Firma" markiert sein (Pruefung im Dienst).
|
||||
-- * "DomainsContactAssignment": Zuordnung AutoDNS-Kontakt -> Kunde. Die
|
||||
-- Umgebung (DEMO/LIVE) gehoert zum Schluessel, weil Kontakt-Ids beider
|
||||
-- Systeme kollidieren. Kontakte und Domains selbst werden NICHT gespiegelt,
|
||||
-- AutoDNS bleibt die Quelle der Wahrheit.
|
||||
-- * "DomainsOrder": Domain-Bestellungen (Geldsicherheit). "openKey" traegt den
|
||||
-- Domainnamen, solange die Bestellung offen ist (DRAFT, SUBMITTING,
|
||||
-- SUBMITTED, UNKNOWN, SUCCESS) und ist bei FAILED/CANCELED NULL. Die
|
||||
-- Unique-Regel (tenantId, environment, openKey) verhindert zwei offene
|
||||
-- Bestellungen derselben Domain; NULLs wiederholen sich in Postgres
|
||||
-- beliebig. SUCCESS behaelt den Schluessel mit Absicht: kurz nach der
|
||||
-- Registrierung kann ein verzoegerter WHOIS noch "frei" melden.
|
||||
--
|
||||
-- AutoDNS-Kontakt- und Job-Ids stehen als TEXT (opake Dezimalzahlen).
|
||||
--
|
||||
-- Von Hand geschrieben (Vorbild 20261002130000_handelsware_datev); der
|
||||
-- DDL-Teil stammt aus `prisma migrate diff`, damit der Stand ohne Abweichung
|
||||
-- zum Schema passt.
|
||||
--
|
||||
-- Zeilenschutz (Pflicht — sonst schlaegt rls-coverage.spec.ts fehl): alle
|
||||
-- Tabellen tragen `tenantId` und `tenant_isolation_policy` OHNE
|
||||
-- Benutzerdimension (`USING ("tenantId" = current_tenant_id())`) — das sind
|
||||
-- Verwaltungsdaten der Organisation, keine persoenlichen Daten eines
|
||||
-- Benutzers. Keine `system_read_policy`: es gibt keinen Hintergrunddienst, der
|
||||
-- diese Tabellen ueber alle Organisationen liest (Auftragsstatus wird beim
|
||||
-- Oeffnen der Seite abgefragt, nicht per Zeitplan).
|
||||
--
|
||||
-- Rechte fuer die Anwendungsrolle tessera_app kommen automatisch ueber
|
||||
-- ALTER DEFAULT PRIVILEGES aus 20260909130000_rls_app_role — hier nichts zu
|
||||
-- tun.
|
||||
--
|
||||
-- WICHTIG: wie alle bisherigen RLS-Migrationen wirken diese Regeln erst,
|
||||
-- wenn die Anwendung als Rolle ohne Umgehungsrecht verbindet (Schalter
|
||||
-- heute AUS, siehe docs/mandantentrennung-datenbankrolle.md).
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AutodnsEnvironment" AS ENUM ('DEMO', 'LIVE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DomainOrderStatus" AS ENUM ('DRAFT', 'SUBMITTING', 'SUBMITTED', 'SUCCESS', 'FAILED', 'UNKNOWN', 'CANCELED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DomainsConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"environment" "AutodnsEnvironment" NOT NULL DEFAULT 'DEMO',
|
||||
"demoUser" TEXT,
|
||||
"demoEncryptedPassword" TEXT,
|
||||
"demoContext" INTEGER,
|
||||
"liveUser" TEXT,
|
||||
"liveEncryptedPassword" TEXT,
|
||||
"liveContext" INTEGER,
|
||||
"defaultNameServers" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DomainsConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DomainsCustomer" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"isOwnCompany" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DomainsCustomer_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DomainsContactAssignment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"environment" "AutodnsEnvironment" NOT NULL,
|
||||
"autodnsContactId" TEXT NOT NULL,
|
||||
"customerId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DomainsContactAssignment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DomainsOrder" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"environment" "AutodnsEnvironment" NOT NULL,
|
||||
"domainName" TEXT NOT NULL,
|
||||
"openKey" TEXT,
|
||||
"status" "DomainOrderStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"payload" JSONB NOT NULL,
|
||||
"jobId" TEXT,
|
||||
"jobStatus" TEXT,
|
||||
"errorText" TEXT,
|
||||
"createdByUserId" TEXT NOT NULL,
|
||||
"confirmedByUserId" TEXT,
|
||||
"confirmedByUsername" TEXT,
|
||||
"confirmedAt" TIMESTAMP(3),
|
||||
"lastCheckedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DomainsOrder_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DomainsConfig_tenantId_key" ON "DomainsConfig"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DomainsConfig_tenantId_idx" ON "DomainsConfig"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DomainsCustomer_tenantId_idx" ON "DomainsCustomer"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DomainsCustomer_tenantId_name_key" ON "DomainsCustomer"("tenantId", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DomainsContactAssignment_tenantId_idx" ON "DomainsContactAssignment"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DomainsContactAssignment_customerId_idx" ON "DomainsContactAssignment"("customerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DomainsContactAssignment_tenantId_environment_autodnsContac_key" ON "DomainsContactAssignment"("tenantId", "environment", "autodnsContactId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DomainsOrder_tenantId_idx" ON "DomainsOrder"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DomainsOrder_tenantId_environment_openKey_key" ON "DomainsOrder"("tenantId", "environment", "openKey");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DomainsContactAssignment" ADD CONSTRAINT "DomainsContactAssignment_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "DomainsCustomer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- Zeilenschutz
|
||||
ALTER TABLE "DomainsConfig" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "DomainsConfig" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "DomainsConfig"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
|
||||
ALTER TABLE "DomainsCustomer" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "DomainsCustomer" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "DomainsCustomer"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
|
||||
ALTER TABLE "DomainsContactAssignment" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "DomainsContactAssignment" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "DomainsContactAssignment"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
|
||||
ALTER TABLE "DomainsOrder" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "DomainsOrder" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "DomainsOrder"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
@@ -841,6 +841,108 @@ model NextcloudAlertSubscription {
|
||||
@@index([tenantId, userId])
|
||||
}
|
||||
|
||||
// Domains (quick-261008-dts): Anbindung an AutoDNS / InterNetX (Domainrobot).
|
||||
// AutoDNS bleibt die Quelle der Wahrheit fuer Kontakte und Domains (werden live
|
||||
// gelesen, nie gespiegelt); lokal liegt nur, was AutoDNS nicht kennt (Zuordnung
|
||||
// Kontakt -> Kunde) oder was Geldsicherheit braucht (Bestellungen). AutoDNS-
|
||||
// Kontakt- und Job-Ids sind opake Dezimalzahlen und stehen als String (kein
|
||||
// Int32-Ueberlauf). Zeilenschutz nach Muster ProxmoxServer (tenantId, keine
|
||||
// Relation zu Tenant).
|
||||
enum AutodnsEnvironment {
|
||||
DEMO
|
||||
LIVE
|
||||
}
|
||||
|
||||
enum DomainOrderStatus {
|
||||
DRAFT
|
||||
SUBMITTING
|
||||
SUBMITTED
|
||||
SUCCESS
|
||||
FAILED
|
||||
UNKNOWN
|
||||
CANCELED
|
||||
}
|
||||
|
||||
// Einstellungen je Organisation (Singleton). Getrennte Zugaenge fuer das
|
||||
// Demo- und das Live-System; Passwoerter AES-verschluesselt (CryptoService),
|
||||
// nie an den Client zurueckgegeben.
|
||||
model DomainsConfig {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @unique
|
||||
environment AutodnsEnvironment @default(DEMO)
|
||||
demoUser String?
|
||||
demoEncryptedPassword String?
|
||||
demoContext Int?
|
||||
liveUser String?
|
||||
liveEncryptedPassword String?
|
||||
liveContext Int?
|
||||
defaultNameServers String[] @default([])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
// Kunden, denen AutoDNS-Kontakte zugeordnet werden. Hoechstens einer ist die
|
||||
// eigene Firma (Pruefung im Dienst). Kein Firmenname als Vorgabe.
|
||||
model DomainsCustomer {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
name String
|
||||
isOwnCompany Boolean @default(false)
|
||||
assignments DomainsContactAssignment[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([tenantId, name])
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
// Zuordnung AutoDNS-Kontakt -> Kunde. Die Umgebung ist Teil des Schluessels,
|
||||
// weil Kontakt-Ids von Demo- und Live-System zwangslaeufig kollidieren.
|
||||
model DomainsContactAssignment {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
environment AutodnsEnvironment
|
||||
autodnsContactId String
|
||||
customerId String
|
||||
customer DomainsCustomer @relation(fields: [customerId], references: [id], onDelete: Restrict)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([tenantId, environment, autodnsContactId])
|
||||
@@index([tenantId])
|
||||
@@index([customerId])
|
||||
}
|
||||
|
||||
// Domain-Bestellungen (Geldsicherheit). `openKey` traegt den Domainnamen,
|
||||
// solange die Bestellung offen ist (DRAFT, SUBMITTING, SUBMITTED, UNKNOWN,
|
||||
// SUCCESS), und ist bei FAILED/CANCELED null; die Unique-Regel darauf
|
||||
// verhindert zwei offene Bestellungen derselben Domain (NULLs wiederholen
|
||||
// sich in Postgres).
|
||||
model DomainsOrder {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
environment AutodnsEnvironment
|
||||
domainName String
|
||||
openKey String?
|
||||
status DomainOrderStatus @default(DRAFT)
|
||||
payload Json
|
||||
jobId String?
|
||||
jobStatus String?
|
||||
errorText String?
|
||||
createdByUserId String
|
||||
confirmedByUserId String?
|
||||
confirmedByUsername String?
|
||||
confirmedAt DateTime?
|
||||
lastCheckedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([tenantId, environment, openKey])
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
// Eigene Module (quick-260929-9wc): vom Administrator angelegte Seitenleisten-
|
||||
// Eintraege, die eine externe https-Seite im Rahmen zeigen. Sichtbar fuer alle
|
||||
// Benutzer des Mandanten. Zeilenschutz nach Muster ProxmoxServer (tenantId,
|
||||
|
||||
Reference in New Issue
Block a user