77c720e2df
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 50s
CI / test-go-edu-search (push) Successful in 45s
CI / test-python-klausur (push) Failing after 3m50s
CI / test-python-agent-core (push) Successful in 36s
CI / test-nodejs-website (push) Successful in 49s
- CLAUDE.md gets a new section summarising the two feature strands, pitfalls (Timefold name, JSX quotes, LOC budget), the auth/messaging outsourcing, and pointers to the three memory files for next session. - docs-src/services/schulkalender/ — 5 MkDocs pages mirroring the stundenplan structure: index, architecture, holidays, parent-flow, notifications. Each with DB tables, endpoints, and the dispatch payload contract for the colleague's Matrix/Email services. - mkdocs.yml gains the Schulkalender nav entry under Services. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.4 KiB
Markdown
97 lines
3.4 KiB
Markdown
# Notifications
|
||
|
||
## Pipeline
|
||
|
||
```
|
||
06:00 Uhr (Berlin-Zeit, Container TZ=Europe/Berlin)
|
||
│
|
||
▼
|
||
NotificationService.RunForDate(today)
|
||
│
|
||
▼
|
||
dueEvents() findet cal_school_event mit
|
||
(start_date - today) ∈ notification_lead_days
|
||
│
|
||
▼
|
||
Pro Event: fuer jede Audience (parents/students) und jeden Channel
|
||
(matrix für alle, email zusaetzlich nur fuer parents):
|
||
│
|
||
▼
|
||
dispatchOne()
|
||
1. Idempotenz-Check (UNIQUE notification_log)
|
||
2. recipientsFor() — JOIN parent_account+parent_child fuer
|
||
betroffene Klassen, gibt Email-Liste + bevorzugte Sprache zurueck
|
||
3. Render-Template (templates.go, 8 Sprachen)
|
||
4. POST {MATRIX,EMAIL}_SERVICE_URL mit DispatchPayload
|
||
5. notification_log writeLog (sent/failed/skipped)
|
||
```
|
||
|
||
## Cron-Mechanik
|
||
|
||
`main.go` startet einen Goroutine-Ticker mit 1h-Intervall. Sobald `time.Now().Hour() == 6` wird `RunForDate` aufgerufen. Idempotent — die UNIQUE auf notification_log filtert Doppel-Calls am selben Tag.
|
||
|
||
Bei Container-Restart vor 06:00 läuft trotzdem alles korrekt: der naechste 06-Tick fired bis spaetestens 06:59:59. Bei Restart nach 06:00: erste Notification erst am Folgetag (acceptable trade-off gegen einen 1-Min-Ticker).
|
||
|
||
## Manueller Trigger
|
||
|
||
```bash
|
||
# Heute jetzt scannen
|
||
curl -X POST http://localhost:8084/api/v1/school/calendar/notifications/run-now
|
||
|
||
# Backfill (z.B. nach langem Container-Down)
|
||
curl -X POST 'http://localhost:8084/api/v1/school/calendar/notifications/run-now?date=2026-05-20'
|
||
```
|
||
|
||
Antwort: `{"date":"2026-05-22","sent":N,"failed":N,"skipped":N,"already_logged":N}`.
|
||
|
||
## Template-Engine
|
||
|
||
Datei: `school-service/internal/notifications/templates.go`. Schema:
|
||
|
||
```
|
||
templates[lang][event_type][audience][bucket] → {Subject, Body}
|
||
```
|
||
|
||
- `lang` ∈ de/en/tr/ar/uk/ru/pl/fr (Fallback `de`)
|
||
- `event_type` ∈ fortbildung/schulfeier/klassenfahrt/projekttag/eltern_info/andere (Fallback `andere`)
|
||
- `audience` ∈ parents/students (Fallback `parents`)
|
||
- `bucket` ∈ today/tomorrow/days (Fallback `days`)
|
||
|
||
Placeholders: `{{title}}`, `{{date}}`, `{{date_pretty}}`, `{{class_name}}`, `{{class_suffix}}`, `{{teacher_name}}`, `{{lead}}`.
|
||
|
||
Beispiel-Render (TR / schulfeier / parents / 1-Tag-Vorlauf):
|
||
```
|
||
Subject: Yarın: Sommerfest (5a)
|
||
Body: Sayın veliler, yarın (15.06.2026) Sommerfest gerçekleşiyor (5a).
|
||
```
|
||
|
||
## DispatchPayload (Endpoint-Vertrag mit Matrix/Email Service)
|
||
|
||
```json
|
||
{
|
||
"channel": "matrix",
|
||
"recipient": "mama@example.de",
|
||
"language": "tr",
|
||
"subject": "Yarın: Sommerfest",
|
||
"body": "Sayın veliler, ...",
|
||
"event_id": "uuid-…",
|
||
"lead_days": 1
|
||
}
|
||
```
|
||
|
||
Erwartete Antwort vom Upstream: HTTP 2xx = sent. 4xx/5xx = failed. Wir leiten **keine** Empfaenger-Identifier-Aufloesung weiter ans Upstream — die Matrix-Bridge mapt Email → Matrix-Handle in der eigenen Logik.
|
||
|
||
Bei `MATRIX_SERVICE_URL` oder `EMAIL_SERVICE_URL` leer: status='skipped', kein Versandversuch. Erlaubt lokales Testen ohne Upstream.
|
||
|
||
## Status-Anzeige im Lehrer-UI
|
||
|
||
`DayDetail` mountet `NotificationStatus` fuer jedes Event mit `notify_parents` oder `notify_students`. Lädt `GET /api/v1/school/calendar/events/:id/notifications` und zeigt Badges:
|
||
|
||
- ✓ gruen = sent
|
||
- ✗ rot = failed (Hover zeigt error_message)
|
||
- ⏱ amber = skipped (Upstream noch nicht konfiguriert)
|
||
|
||
## Privacy
|
||
|
||
`notification_log` ist nur über JOIN cal_school_event sichtbar — Lehrer sieht nur Logs seiner eigenen Events. Eltern haben gar keine UI fuer Logs.
|