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
+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")
}