feat(meldungen): Benachrichtigungen per Mail und Webhook (#54)
Ein zugewiesener Lauf fiel bisher nur auf, wenn jemand die Uebersicht oeffnete; ein ueberfaelliger ueberhaupt nicht. Vier Anlaesse gehen jetzt hinaus — zugewiesen, ueberfaellig, abgeschlossen und Verzeichnisabgleich fehlgeschlagen —, je Person abschaltbar. Niemand bekommt Post ueber die eigene Tat: das ist der haeufigste Grund, Benachrichtigungen ganz abzuschalten, und deshalb faellt der Handelnde immer aus der Empfaengerliste. Eingereiht wird im Anfrage-Pfad, gesendet wird es nicht. Ein haengender Mailserver darf kein Abhaken blockieren, also schreibt der Dienst eine Zeile in `Zustellung` und ein eigener Zeitplan schickt sie. Betreff und Rumpf stehen fertig darin: eine Meldung sagt, was *damals* war. In keiner Nachricht steht ein Schrittinhalt, und damit auch kein erzeugtes Passwort. Darueber wacht src/lib/meldungen.ts — die Funktionen dort nehmen nur Kopfdaten, der Typ laesst nichts anderes zu. Geprueft wird es zusaetzlich ueber alle Rumpftexte, die wirklich verschickt wurden, so wie #012 es fuer die Druckansicht tat. Zwei Dinge haetten ohne Datenbank nicht gehalten. Die Einmaligkeit der Ueberfaelligkeitsmeldung liegt in einem partiellen eindeutigen Index: der Zeitplan sieht jede Minute nach, und ohne ihn bekaeme der Zustaendige fuer einen Lauf, der ueber das Wochenende steht, 2880 Mails — jede fuer sich korrekt, kein Test rot. Und die Sperre der Warteschlange liegt in derselben Tabelle wie die des Abgleichs, denn hinter einem Lastverteiler laufen zwei Instanzen. Eine stille Warteschlange ist schlimmer als keine: Verwaltung → Zustellungen zeigt mit Grund, was hinausging und was nicht. Geprueft gegen einen echten SMTP-Faenger (mailpit) statt gegen eine abgefangene Funktion — und `mailpit` gehoert damit ins Tor vor dem Commit, sonst ueberspringt die Suite 3 Tests und sieht gruen aus. Closes #54 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1be73b266c
commit
a417426f43
@@ -1,10 +1,10 @@
|
||||
# #034 M21 – Benachrichtigungen per Mail und Webhook
|
||||
|
||||
**Status:** offen
|
||||
**Status:** erledigt
|
||||
**Im Tracker:** [#54](https://git.wilde-server.de/florian.wilde/Laufzettel/issues/54)
|
||||
**Meilenstein:** v0.2.0
|
||||
**Labels:** phase-2, backend, frontend, infra
|
||||
**Geschätzt:** 10 h **Tatsächlich:** –
|
||||
**Geschätzt:** 10 h **Tatsächlich:** 3 h 30 min
|
||||
|
||||
## Ziel
|
||||
|
||||
@@ -14,44 +14,93 @@ Ein zugewiesener Lauf fällt heute nur auf, wenn jemand die Übersicht öffnet.
|
||||
|
||||
## Zu entscheiden
|
||||
|
||||
- [ ] **Nur diese vier Ereignisse, je Empfänger abschaltbar** — Empfehlung. Ein allgemeiner
|
||||
- [x] **Nur diese vier Ereignisse, je Empfänger abschaltbar** — Empfehlung. Ein allgemeiner
|
||||
Benachrichtigungsbaukasten ist eine Phase für sich; vier Ereignisse decken ab, was im Betrieb
|
||||
wirklich fehlt.
|
||||
- [ ] **Mail über SMTP, keine Dienstanbindung.** Eine selbst gehostete Anwendung soll an den
|
||||
- [x] **Mail über SMTP, keine Dienstanbindung.** Eine selbst gehostete Anwendung soll an den
|
||||
Mailserver des Hauses können, und das ist SMTP. Zugangsdaten verschlüsselt wie das
|
||||
LDAP-Bind-Passwort.
|
||||
- [ ] **Zustellung in einer Warteschlange in der Datenbank**, nicht im Anfrage-Pfad. Ein hängender
|
||||
- [x] **Zustellung in einer Warteschlange in der Datenbank**, nicht im Anfrage-Pfad. Ein hängender
|
||||
Mailserver darf kein Abhaken blockieren. Dieselbe Sperre wie beim Abgleich
|
||||
(`AbgleichSperre`) verhindert, dass zwei Instanzen dasselbe zweimal schicken.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Modell `Zustellung`: Ereignis, Empfänger, Weg (MAIL | WEBHOOK), Versuche, letzter Fehler,
|
||||
- [x] Modell `Zustellung`: Ereignis, Empfänger, Weg (MAIL | WEBHOOK), Versuche, letzter Fehler,
|
||||
Zustand; mit Index auf das, was der Zeitplan abholt
|
||||
- [ ] SMTP-Einstellungen unter Admin → Einstellungen: Host, Port, TLS, Benutzer, Passwort
|
||||
- [x] SMTP-Einstellungen unter Admin → Einstellungen: Host, Port, TLS, Benutzer, Passwort
|
||||
(verschlüsselt, nie in einer Antwort), Absender, „Testmail schicken“
|
||||
- [ ] Webhook: Ziel-URL, Geheimnis, **HMAC-SHA256 über den Rumpf** im Kopf `X-Laufzettel-Signatur`,
|
||||
- [x] Webhook: Ziel-URL, Geheimnis, **HMAC-SHA256 über den Rumpf** im Kopf `X-Laufzettel-Signatur`,
|
||||
dazu ein Zeitstempel gegen Wiedereinspielen
|
||||
- [ ] Wiederholung mit wachsendem Abstand (1, 5, 30, 120 Minuten), dann aufgeben und sichtbar
|
||||
- [x] Wiederholung mit wachsendem Abstand (1, 5, 30, 120 Minuten), dann aufgeben und sichtbar
|
||||
machen. Eine stille Warteschlange ist schlimmer als keine
|
||||
- [ ] Überfälligkeit erkennt der Zeitplan, der schon läuft (`node-cron` im Prozess) — kein zweiter
|
||||
- [x] Überfälligkeit erkennt der Zeitplan, der schon läuft (`node-cron` im Prozess) — kein zweiter
|
||||
Dienst
|
||||
- [ ] Je Benutzer abschaltbar; ein Benutzer ohne Mailadresse bekommt keine Mail und keinen Fehler
|
||||
- [ ] Admin: die letzten Zustellungen mit Zustand und Grund
|
||||
- [ ] **`{{generiertes_passwort}}` kommt in keine Nachricht.** Eine Mail mit dem Lauf-Inhalt wäre
|
||||
- [x] Je Benutzer abschaltbar; ein Benutzer ohne Mailadresse bekommt keine Mail und keinen Fehler
|
||||
- [x] Admin: die letzten Zustellungen mit Zustand und Grund
|
||||
- [x] **`{{generiertes_passwort}}` kommt in keine Nachricht.** Eine Mail mit dem Lauf-Inhalt wäre
|
||||
genau die Lücke, die das Projekt an jeder anderen Stelle geschlossen hat. Die Nachricht
|
||||
enthält Titel, Status und einen Link — **keine Schrittinhalte**
|
||||
- [ ] `docs/betrieb/`: SMTP einrichten, Webhook prüfen, Signatur nachrechnen
|
||||
- [ ] Anleitung Kapitel 07
|
||||
- [x] `docs/betrieb/`: SMTP einrichten, Webhook prüfen, Signatur nachrechnen
|
||||
- [x] Anleitung Kapitel 07
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test gegen einen echten SMTP-Fänger im Container (mailpit): zugewiesen, überfällig,
|
||||
- [x] Test gegen einen echten SMTP-Fänger im Container (mailpit): zugewiesen, überfällig,
|
||||
abgeschlossen, Abgleich fehlgeschlagen — vier Mails, richtige Empfänger
|
||||
- [ ] Test gegen einen lokalen Empfänger: die Signatur lässt sich mit dem Geheimnis nachrechnen, mit
|
||||
- [x] Test gegen einen lokalen Empfänger: die Signatur lässt sich mit dem Geheimnis nachrechnen, mit
|
||||
einem falschen nicht
|
||||
- [ ] Test: eine fehlgeschlagene Zustellung wird wiederholt und ist danach sichtbar
|
||||
- [ ] Test: zwei gleichzeitige Durchgänge schicken nichts zweimal
|
||||
- [ ] Test: in **keiner** Nachricht steht ein erzeugtes Passwort — über alle Rumpftexte geprüft, wie
|
||||
- [x] Test: eine fehlgeschlagene Zustellung wird wiederholt und ist danach sichtbar
|
||||
- [x] Test: zwei gleichzeitige Durchgänge schicken nichts zweimal
|
||||
- [x] Test: in **keiner** Nachricht steht ein erzeugtes Passwort — über alle Rumpftexte geprüft, wie
|
||||
in #012
|
||||
- [ ] Test: ein Benutzer mit abgeschalteter Benachrichtigung bekommt nichts
|
||||
- [x] Test: ein Benutzer mit abgeschalteter Benachrichtigung bekommt nichts
|
||||
|
||||
## Beim Umsetzen dazugekommen
|
||||
|
||||
- **Die Sperre wurde aus dem Abgleich herausgezogen.** `mitAbgleichSperre` lag in
|
||||
`services/abgleich.ts`; die Warteschlange braucht dieselbe Mechanik, und zwei Fassungen davon
|
||||
wären die schlechteste aller Lösungen. Sie heißt jetzt `mitSperre` in `services/sperre.ts`. Die
|
||||
Tabelle behält den Namen `AbgleichSperre` — ein Umbenennen wäre eine Migration, die nichts
|
||||
verbessert, und der Schlüssel sagt ohnehin, wer sperrt.
|
||||
- **Niemand bekommt Post über die eigene Tat.** Steht nicht im Issue, ist aber der häufigste Grund,
|
||||
Benachrichtigungen ganz abzuschalten. Der Handelnde fällt immer aus der Empfängerliste.
|
||||
- **Die Mandantengrenze gilt auch für Empfänger.** Eine Meldung trägt Lauftitel und Gerätenamen;
|
||||
ohne diese Prüfung wäre die Benachrichtigung der eine Weg nach draußen, den `sichtbareKundenIds`
|
||||
nicht abdeckt. Ein Benutzer, der den Kunden des Laufs nicht sehen darf, bekommt keine Mail — mit
|
||||
eigenem Test.
|
||||
- **Die Einmaligkeit der Überfälligkeitsmeldung liegt in der Datenbank**, als partieller eindeutiger
|
||||
Index. Der Zeitplan sieht jede Minute nach; ohne ihn bekäme der Zuständige für einen Lauf, der
|
||||
über das Wochenende steht, **2880 Mails** — und jede einzelne davon wäre für sich korrekt, kein
|
||||
Test rot. Gegenprobe gefahren: ohne den Index werden zwei Tests rot.
|
||||
- **Fünf Versuche, nicht vier.** Die vier Wartezeiten aus dem Issue (1, 5, 30, 120 Minuten) liegen
|
||||
*zwischen* den Versuchen — der fünfte Fehlschlag gibt auf. Meine erste Fassung indizierte um eins
|
||||
verschoben und wartete nach dem ersten Fehlschlag fünf Minuten; der Test hat es sofort gezeigt.
|
||||
- **Ein eigener Zeitplan, nicht derselbe wie der Abgleich.** Der Mailversand darf nicht warten, weil
|
||||
gerade ein großes Verzeichnis gelesen wird. Zwei Takte sind billig.
|
||||
- **`mailpit` gehört ins Tor vor dem Commit.** Ohne den Container überspringt die Suite **3 Tests**
|
||||
(gemessen: 36 statt 33) — genau die, die den echten SMTP-Dialog prüfen. `CLAUDE.md`, das README
|
||||
und `scripts/pruefe-doku.mjs` nennen ihn jetzt, und die Prüfung ist gegengeprobt: ohne ihn im
|
||||
Block wird `pnpm pruefe:doku` rot.
|
||||
- **`it.runIf(mailpitDa)` wird beim Einlesen der Datei ausgewertet.** Das Flag stand in `beforeAll`
|
||||
und kam zu spät: der erste Lauf meldete „3 skipped“, war grün und hatte den Mailversand nie
|
||||
geprüft. Jetzt wird auf Modulebene geprüft, wie bei `datenbankVerfuegbar`.
|
||||
- **pnpm wollte eine Mindestalter-Sperre umgehen.** `pnpm add nodemailer` trug von sich aus
|
||||
`minimumReleaseAgeExclude: nodemailer@10.0.15` ein — die Version war **vom selben Tag**. Das ist
|
||||
kein Formfehler, sondern genau die Warnung, vor der die Regel schützt: ein frisches Release eines
|
||||
Mailpakets ungeprüft einzubauen. Gepinnt ist jetzt `10.0.10` vom 14. September, die Ausnahme ist
|
||||
weg, und die Konvention steht in `CLAUDE.md`.
|
||||
- **Zwei Knöpfe „Speichern“ auf einer Seite.** Beim Durchklicken aufgefallen: die Einstellungsseite
|
||||
hatte zwei gleich benannte. Der neue heißt jetzt „Benachrichtigungen speichern“ — für einen
|
||||
Menschen genauso verwirrend wie für einen Test.
|
||||
- **Die Testmail geht direkt hinaus, nicht über die Warteschlange.** Ein Knopf, der „eingereiht“
|
||||
meldet und den Fehler eine Minute später woanders zeigt, prüft nichts.
|
||||
- **Der Mail-Schalter gilt auch für das eigene Konto**, anders als „aktiv“: sich selbst zu
|
||||
deaktivieren wäre ein Eigentor, seine eigene Post abzustellen der häufigste Wunsch überhaupt. Eine
|
||||
**Profilseite**, auf der das jeder selbst könnte, fehlt noch — sie kommt mit der Selbstauskunft
|
||||
(#035). Bis dahin stellt ein Administrator es um, und so steht es in der Anleitung.
|
||||
- **Im gebauten Stand wirklich gefahren:** SMTP eingetragen, Testmail bei mailpit angekommen, ein
|
||||
überfälliger Lauf angelegt — und der Zeitplan hat die Meldung **von selbst** verschickt, ohne
|
||||
Zutun. Dazu geprüft: das Passwort steht nicht im Quelltext der Seite, Speichern ohne Passwortfeld
|
||||
behält es, die Zustellungsliste zeigt keinen Rumpf, und 390 px ohne waagerechtes Scrollen auf allen
|
||||
drei Seiten.
|
||||
|
||||
@@ -38,6 +38,6 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac
|
||||
| #031 | [M18 – Abteilungsspezifische Zusatzabschnitte](031-abteilungsabschnitte.md) | erledigt | v0.2.0 | 8 h | 1 h 35 min |
|
||||
| #032 | [M19 – Anhänge und Fotos an Schritten](032-anhaenge.md) | erledigt | v0.2.0 | 10 h | 3 h 05 min |
|
||||
| #033 | [M20 – Live-Aktualisierung per SSE statt Polling](033-sse-live.md) | erledigt | v0.2.0 | 6 h | 1 h 40 min |
|
||||
| #034 | [M21 – Benachrichtigungen per Mail und Webhook](034-benachrichtigungen.md) | offen | v0.2.0 | 10 h | – |
|
||||
| #034 | [M21 – Benachrichtigungen per Mail und Webhook](034-benachrichtigungen.md) | erledigt | v0.2.0 | 10 h | 3 h 30 min |
|
||||
| #035 | [M22 – Mitarbeiter-Selbstauskunft](035-selbstauskunft.md) | offen | v0.2.0 | 8 h | – |
|
||||
| | **Summe** | | | **229 h** | **50 h 05 min** |
|
||||
| | **Summe** | | | **229 h** | **53 h 35 min** |
|
||||
|
||||
Reference in New Issue
Block a user