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
+23
View File
@@ -41,6 +41,22 @@ const schema = z.object({
GOOGLE_CLIENT_ID: z.string().trim().default(''),
GOOGLE_CLIENT_SECRET: z.string().trim().default(''),
// --- Benachrichtigungen (M5) ---
// VAPID-Schluessel fuer Web-Push. Erzeugen mit:
// npx web-push generate-vapid-keys
VAPID_OEFFENTLICH: z.string().trim().default(''),
VAPID_PRIVAT: z.string().trim().default(''),
/// Kontaktadresse fuer den Push-Dienst, als mailto:.
VAPID_KONTAKT: z.string().trim().default(''),
SMTP_HOST: z.string().trim().default(''),
SMTP_PORT: z.coerce.number().int().min(1).max(65535).default(587),
SMTP_BENUTZER: z.string().trim().default(''),
SMTP_PASSWORT: z.string().default(''),
SMTP_ABSENDER: z.string().trim().default(''),
/// Implizites TLS (Port 465). Sonst wird STARTTLS versucht.
SMTP_TLS: jaNein.default(false),
DEMO_MODUS: jaNein.default(false),
})
@@ -51,6 +67,9 @@ export type Umgebung = z.infer<typeof schema> & {
/** Graph laesst sich verbinden, sobald Kennung und Geheimnis stehen. */
graphAktiv: boolean
googleAktiv: boolean
/** Web-Push ist erst mit vollstaendigem VAPID-Satz moeglich. */
pushAktiv: boolean
mailAktiv: boolean
}
let zwischengespeichert: Umgebung | null = null
@@ -72,6 +91,10 @@ function lesen(): Umgebung {
),
graphAktiv: Boolean(werte.GRAPH_CLIENT_ID && werte.GRAPH_CLIENT_SECRET),
googleAktiv: Boolean(werte.GOOGLE_CLIENT_ID && werte.GOOGLE_CLIENT_SECRET),
pushAktiv: Boolean(
werte.VAPID_OEFFENTLICH && werte.VAPID_PRIVAT && werte.VAPID_KONTAKT,
),
mailAktiv: Boolean(werte.SMTP_HOST && werte.SMTP_ABSENDER),
}
}