-- --------------------------------------------------------------------------- -- HANDGESCHRIEBEN. Prisma erzeugt hier zusaetzlich Loeschbefehle fuer die -- zusammengesetzten Fremdschluessel (Mandantengrenze), die Trigramm-Indizes -- (Archivsuche), die partiellen eindeutigen Indizes und alle Trigger: es kennt -- sie nicht, weil sie sich im Schema nicht ausdruecken lassen. Diese -- Loeschbefehle sind bewusst gestrichen. -- -- tests/datenbankregeln.test.ts prueft, dass alle diese Objekte vorhanden sind – -- ein versehentlich stehengelassener Loeschbefehl faellt dort sofort auf. -- --------------------------------------------------------------------------- -- Benachrichtigungen per Mail und Webhook (#034). CREATE TYPE "Zustellweg" AS ENUM ('MAIL', 'WEBHOOK'); CREATE TYPE "Zustellzustand" AS ENUM ('OFFEN', 'GESENDET', 'FEHLGESCHLAGEN', 'AUFGEGEBEN'); CREATE TYPE "Meldeanlass" AS ENUM ( 'LAUF_ZUGEWIESEN', 'LAUF_UEBERFAELLIG', 'LAUF_ABGESCHLOSSEN', 'ABGLEICH_FEHLGESCHLAGEN' ); -- Je Empfaenger abschaltbar. ALTER TABLE "Benutzer" ADD COLUMN "meldungenAktiv" BOOLEAN NOT NULL DEFAULT true; -- SMTP und Webhook gehoeren zur einen Einstellungszeile. ALTER TABLE "Einstellung" ADD COLUMN "smtpHost" TEXT, ADD COLUMN "smtpPort" INTEGER, ADD COLUMN "smtpTls" BOOLEAN NOT NULL DEFAULT true, ADD COLUMN "smtpBenutzer" TEXT, ADD COLUMN "smtpPasswortVerschluesselt" TEXT, ADD COLUMN "smtpAbsender" TEXT, ADD COLUMN "webhookUrl" TEXT, ADD COLUMN "webhookGeheimnisVerschluesselt" TEXT; CREATE TABLE "Zustellung" ( "id" TEXT NOT NULL, "anlass" "Meldeanlass" NOT NULL, "weg" "Zustellweg" NOT NULL, "empfaenger" TEXT NOT NULL, "betreff" TEXT NOT NULL, "rumpf" TEXT NOT NULL, "zustand" "Zustellzustand" NOT NULL DEFAULT 'OFFEN', "versuche" INTEGER NOT NULL DEFAULT 0, "naechsterVersuchAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "letzterFehler" TEXT, "gesendetAm" TIMESTAMP(3), "erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "geaendertAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "benutzerId" TEXT, "laufId" TEXT, "verzeichnisquelleId" TEXT, CONSTRAINT "Zustellung_pkey" PRIMARY KEY ("id") ); -- Der Index, auf dem der Zeitplan arbeitet: er fragt jede Minute nach offenen -- und faelligen Zeilen. Ohne ihn waere das ein vollstaendiger Durchlauf der -- Tabelle, die mit der Zeit nur waechst. CREATE INDEX "Zustellung_zustand_naechsterVersuchAm_idx" ON "Zustellung"("zustand", "naechsterVersuchAm"); CREATE INDEX "Zustellung_erstelltAm_idx" ON "Zustellung"("erstelltAm"); CREATE INDEX "Zustellung_laufId_idx" ON "Zustellung"("laufId"); CREATE INDEX "Zustellung_benutzerId_idx" ON "Zustellung"("benutzerId"); ALTER TABLE "Zustellung" ADD CONSTRAINT "Zustellung_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "Benutzer"("id") ON DELETE SET NULL ON UPDATE CASCADE, ADD CONSTRAINT "Zustellung_laufId_fkey" FOREIGN KEY ("laufId") REFERENCES "Lauf"("id") ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT "Zustellung_verzeichnisquelleId_fkey" FOREIGN KEY ("verzeichnisquelleId") REFERENCES "Verzeichnisquelle"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- --------------------------------------------------------------------------- -- Regeln, die auch dann gelten, wenn jemand an der Service-Schicht vorbei -- schreibt. -- --------------------------------------------------------------------------- -- Ein Empfaenger ohne Inhalt ist eine Zeile, die nie ankommen kann. ALTER TABLE "Zustellung" ADD CONSTRAINT "Zustellung_empfaenger_nicht_leer" CHECK (length(btrim("empfaenger")) > 0); ALTER TABLE "Zustellung" ADD CONSTRAINT "Zustellung_versuche_nicht_negativ" CHECK ("versuche" >= 0); -- „Gesendet“ und „wann“ gehoeren zusammen, in beide Richtungen. Dieselbe Form -- wie bei `Lauf_abgeschlossen_mit_zeitpunkt`: ein Zustand ohne Zeitpunkt macht -- jede spaetere Auswertung zur Schaetzung. ALTER TABLE "Zustellung" ADD CONSTRAINT "Zustellung_gesendet_mit_zeitpunkt" CHECK (("zustand" = 'GESENDET') = ("gesendetAm" IS NOT NULL)); -- Eine Mail geht an eine Person, ein Webhook nicht. Ohne das koennte eine -- Webhook-Zeile an einem Benutzer haengen und in dessen Mailverlauf auftauchen. ALTER TABLE "Zustellung" ADD CONSTRAINT "Zustellung_webhook_ohne_benutzer" CHECK ("weg" <> 'WEBHOOK' OR "benutzerId" IS NULL); -- Jeder Anlass haengt an dem, worum es geht: die drei Lauf-Anlaesse an einem -- Lauf, der Abgleich an einer Quelle. Eine Meldung ohne Bezug waere ein Satz -- ohne Gegenstand — und der Link in der Mail zeigte ins Leere. ALTER TABLE "Zustellung" ADD CONSTRAINT "Zustellung_bezug_passt_zum_anlass" CHECK ( ("anlass" = 'ABGLEICH_FEHLGESCHLAGEN' AND "verzeichnisquelleId" IS NOT NULL AND "laufId" IS NULL) OR ("anlass" <> 'ABGLEICH_FEHLGESCHLAGEN' AND "laufId" IS NOT NULL AND "verzeichnisquelleId" IS NULL) ); -- Verschluesselt, nicht im Klartext – dieselbe Pruefung wie beim -- LDAP-Bind-Passwort. `v.` ist das Praefix aus `src/lib/geheimnisse.ts`. ALTER TABLE "Einstellung" ADD CONSTRAINT "Einstellung_smtp_passwort_verschluesselt" CHECK ("smtpPasswortVerschluesselt" ~ '^v[0-9]+\.'); ALTER TABLE "Einstellung" ADD CONSTRAINT "Einstellung_webhook_geheimnis_verschluesselt" CHECK ("webhookGeheimnisVerschluesselt" ~ '^v[0-9]+\.'); ALTER TABLE "Einstellung" ADD CONSTRAINT "Einstellung_smtp_port_bereich" CHECK ("smtpPort" IS NULL OR ("smtpPort" BETWEEN 1 AND 65535)); -- --------------------------------------------------------------------------- -- Und die Regel, die der Zeitplan braucht. -- --------------------------------------------------------------------------- /* * **Ueberfaellig wird je Lauf und Empfaenger genau einmal gemeldet.** * * Der Zeitplan sieht jede Minute nach. Ohne diesen Index bekaeme der * Zustaendige fuer einen Lauf, der ueber das Wochenende ueberfaellig steht, * 2880 Mails — und zwar ohne dass irgendein Test rot waere, denn jede einzelne * davon ist korrekt. Der Dienst prueft dasselbe noch einmal, nur mit besserer * Meldung. * * Partiell, weil es nur fuer diesen einen Anlass gilt: „abgeschlossen“ darf nach * einem Zurueckholen und erneuten Abschliessen ein zweites Mal hinausgehen, und * „zugewiesen“ bei jeder neuen Zuweisung. */ CREATE UNIQUE INDEX "Zustellung_ueberfaellig_einmal" ON "Zustellung"("laufId", "empfaenger") WHERE "anlass" = 'LAUF_UEBERFAELLIG';