feat: Datenmodell und Umgebung fuer Benachrichtigungen

Drei Tabellen: die Einstellung je Benutzer, angemeldete Push-Geraete und
ein Nachweis je verschickter Meldung. Der Nachweis ist das Gedaechtnis
gegen Wiederholungen — ohne ihn kaeme dieselbe Sache bei jedem Durchlauf
erneut.

Kalenderkonten bekommen einen Zaehler fuer Fehlversuche in Folge, damit
ein Abgleichfehler erst nach mehreren Versuchen gemeldet wird.

VAPID- und SMTP-Werte sind freiwillig: fehlen sie, laeuft der Planer
ohne den jeweiligen Weg weiter und sagt es in den Einstellungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-11 08:58:59 +02:00
co-authored by Claude Opus 5
parent ef79b24a96
commit 24f613d372
7 changed files with 338 additions and 8 deletions
@@ -0,0 +1,66 @@
-- CreateTable
CREATE TABLE "benachrichtigungseinstellung" (
"id" TEXT NOT NULL,
"benutzerId" TEXT NOT NULL,
"anlaesse" TEXT NOT NULL DEFAULT 'tagesueberblick,faelligkeit',
"perPush" BOOLEAN NOT NULL DEFAULT false,
"perMail" BOOLEAN NOT NULL DEFAULT false,
"ueberblickUm" INTEGER NOT NULL DEFAULT 420,
"ruheVon" INTEGER NOT NULL DEFAULT 1200,
"ruheBis" INTEGER NOT NULL DEFAULT 420,
"ruheAmWochenende" BOOLEAN NOT NULL DEFAULT false,
"zeitblockVorlaufMinuten" INTEGER NOT NULL DEFAULT 10,
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"aktualisiertAm" TIMESTAMP(3) NOT NULL,
CONSTRAINT "benachrichtigungseinstellung_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "push_geraet" (
"id" TEXT NOT NULL,
"benutzerId" TEXT NOT NULL,
"endpunkt" TEXT NOT NULL,
"p256dh" TEXT NOT NULL,
"auth" TEXT NOT NULL,
"bezeichnung" TEXT,
"erstelltAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"zuletztBenutzt" TIMESTAMP(3),
"fehlversuche" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "push_geraet_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "meldungsnachweis" (
"id" TEXT NOT NULL,
"benutzerId" TEXT NOT NULL,
"schluessel" TEXT NOT NULL,
"gesendetAm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "meldungsnachweis_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "benachrichtigungseinstellung_benutzerId_key" ON "benachrichtigungseinstellung"("benutzerId");
-- CreateIndex
CREATE UNIQUE INDEX "push_geraet_endpunkt_key" ON "push_geraet"("endpunkt");
-- CreateIndex
CREATE INDEX "push_geraet_benutzerId_idx" ON "push_geraet"("benutzerId");
-- CreateIndex
CREATE INDEX "meldungsnachweis_gesendetAm_idx" ON "meldungsnachweis"("gesendetAm");
-- CreateIndex
CREATE UNIQUE INDEX "meldungsnachweis_benutzerId_schluessel_key" ON "meldungsnachweis"("benutzerId", "schluessel");
-- AddForeignKey
ALTER TABLE "benachrichtigungseinstellung" ADD CONSTRAINT "benachrichtigungseinstellung_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "benutzer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "push_geraet" ADD CONSTRAINT "push_geraet_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "benutzer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "meldungsnachweis" ADD CONSTRAINT "meldungsnachweis_benutzerId_fkey" FOREIGN KEY ("benutzerId") REFERENCES "benutzer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "kalenderkonto" ADD COLUMN "fehlversuche" INTEGER NOT NULL DEFAULT 0;
+74 -4
View File
@@ -31,10 +31,13 @@ model Benutzer {
erstelltAm DateTime @default(now())
aktualisiertAm DateTime @updatedAt
konten Konto[]
passkeys Passkey[]
audit AuditEintrag[]
kalenderkonten Kalenderkonto[]
konten Konto[]
passkeys Passkey[]
audit AuditEintrag[]
kalenderkonten Kalenderkonto[]
benachrichtigung Benachrichtigungseinstellung?
pushGeraete PushGeraet[]
meldungen Meldungsnachweis[]
@@index([aktiv])
@@map("benutzer")
@@ -300,6 +303,9 @@ model Kalenderkonto {
letzterAbgleich DateTime?
/// Klartextmeldung des letzten Fehlschlags, ohne Tokens.
letzterFehler String?
/// Wie oft der Abgleich hintereinander scheiterte. Ein Aussetzer ist keine
/// Nachricht wert; gemeldet wird erst ab mehreren.
fehlversuche Int @default(0)
aktiv Boolean @default(true)
@@ -378,3 +384,67 @@ model Zeitblock {
@@index([aufgabeId])
@@map("zeitblock")
}
/// Wie und worueber ein Benutzer benachrichtigt werden will.
model Benachrichtigungseinstellung {
id String @id @default(cuid())
benutzerId String @unique
/// Kommagetrennte Anlaesse, siehe src/lib/benachrichtigung.ts.
anlaesse String @default("tagesueberblick,faelligkeit")
perPush Boolean @default(false)
perMail Boolean @default(false)
/// Alles als Minuten seit Mitternacht, Europe/Berlin.
ueberblickUm Int @default(420)
ruheVon Int @default(1200)
ruheBis Int @default(420)
ruheAmWochenende Boolean @default(false)
zeitblockVorlaufMinuten Int @default(10)
erstelltAm DateTime @default(now())
aktualisiertAm DateTime @updatedAt
benutzer Benutzer @relation(fields: [benutzerId], references: [id], onDelete: Cascade)
@@map("benachrichtigungseinstellung")
}
/// Ein Browser, der Push-Nachrichten empfangen darf.
model PushGeraet {
id String @id @default(cuid())
benutzerId String
/// Vom Browser vergebene Adresse; eindeutig je Geraet.
endpunkt String @unique
/// Schluessel aus der Push-Anmeldung, verschluesselt abgelegt.
p256dh String
auth String
bezeichnung String?
erstelltAm DateTime @default(now())
zuletztBenutzt DateTime?
/// Wie oft der Versand hintereinander scheiterte; ab einer Grenze weg.
fehlversuche Int @default(0)
benutzer Benutzer @relation(fields: [benutzerId], references: [id], onDelete: Cascade)
@@index([benutzerId])
@@map("push_geraet")
}
/// Nachweis, dass eine Meldung schon hinausging – verhindert Wiederholungen.
model Meldungsnachweis {
id String @id @default(cuid())
benutzerId String
/// anlass:sache:tag, siehe meldungsSchluessel().
schluessel String
gesendetAm DateTime @default(now())
benutzer Benutzer @relation(fields: [benutzerId], references: [id], onDelete: Cascade)
@@unique([benutzerId, schluessel])
@@index([gesendetAm])
@@map("meldungsnachweis")
}