internal/channels: konkrete Zustellkanaele fuer CFG-02s Dispatcher.
TemplateStore.Resolve loest Vorlagen pro Tenant auf und faellt auf
GlobalTemplateScope zurueck, wenn ein Tenant keine eigene gesetzt hat
(Akzeptanzkriterium 3). Render nutzt text/template mit
Option("missingkey=error") — ein fehlender Platzhalter bricht das Rendering
MIT FEHLER ab, statt eine unvollstaendige Nachricht zu erzeugen
(Akzeptanzkriterium 1).
EmailSender implementiert notify.Sender: rendert ZUERST die Vorlage, bevor
ueberhaupt eine SMTP-Verbindung aufgebaut wird — schlaegt das Rendering
fehl, wird nie ein Netzwerkzugriff versucht. Ein anschliessend fehl-
schlagender SMTP-Versand liefert einen Fehler, den CFG-02s bereits
getestete Wiederholungslogik verarbeitet (kein zweiter Retry-Mechanismus
hier). InAppSender persistiert In-App-Nachrichten ueber InAppStore
(Akzeptanzkriterium 2, ueber API abrufbar/als gelesen markierbar). Router
waehlt den Kanal anhand Notification.Channel — ein neuer Kanal wird per
Register() ergaenzt, ohne Dispatcher oder Router umzubauen.
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Vorlagenrendering mit fehlenden Platzhaltern bricht kontrolliert ab —
TestRender_MissingPlaceholderAborts und
TestEmailSender_AbortsBeforeSMTPWhenTemplateMissing (Fehler kommt von der
Vorlagenaufloesung, kein SMTP-Verbindungsversuch). PASS.
2. In-App-Benachrichtigung nach Markierung als gelesen korrekt gefuehrt —
TestInAppStore_MarkReadIsReflectedCorrectly. PASS.
3. E-Mail-Versand bei nicht erreichbarem SMTP-Server loest dokumentiertes
Retry-Verhalten ueber CFG-02 aus —
TestEmailSender_TriggersDispatcherRetryOnUnreachableSMTP: echter
EmailSender gegen unerreichbaren Host, ueber notify.Dispatcher
eingereiht, nach ausgeschoepften Wiederholungen status=failed mit
korrekter Versuchszahl. PASS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
42 lines
1.1 KiB
Go
42 lines
1.1 KiB
Go
package channels
|
|
|
|
import (
|
|
"context"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/notify"
|
|
)
|
|
|
|
// InAppSender implementiert notify.Sender fuer den In-App-Kanal
|
|
// (Akzeptanzkriterium 2). Nutzt eine Vorlage, falls payload["template_key"]
|
|
// gesetzt ist, sonst direkt payload["title"]/["body"].
|
|
type InAppSender struct {
|
|
store *InAppStore
|
|
templates *TemplateStore
|
|
}
|
|
|
|
func NewInAppSender(store *InAppStore, templates *TemplateStore) *InAppSender {
|
|
return &InAppSender{store: store, templates: templates}
|
|
}
|
|
|
|
func (s *InAppSender) Send(ctx context.Context, n notify.Notification) error {
|
|
tenantSlug, _ := n.Payload["tenant_slug"].(string)
|
|
|
|
var title, body string
|
|
if templateKey, ok := n.Payload["template_key"].(string); ok && templateKey != "" {
|
|
tmpl, err := s.templates.Resolve(ctx, tenantSlug, templateKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
title, body, err = Render(tmpl, n.Payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
title, _ = n.Payload["title"].(string)
|
|
body, _ = n.Payload["body"].(string)
|
|
}
|
|
|
|
_, err := s.store.Create(ctx, tenantSlug, n.Recipient, title, body)
|
|
return err
|
|
}
|