// Package notifyclient implementiert die Mail-seitige Kopplung an Core // CFG-02/CFG-05 (INT-05): ein zentraler Dispatcher übernimmt Warteschlange, // Wiederholungslogik, Kanal-Abstraktion UND — laut CFG-05s eigener // Beschreibung ("internal/notifyprefs.EnqueueIfAllowed als HTTP-Endpunkt") // — die Prüfung, ob der Benutzer diese Benachrichtigungsart überhaupt // wünscht. Mail baut deshalb bewusst KEINE eigene // Benachrichtigungs-/Präferenzlogik, sondern ruft ausschließlich den // dokumentierten Vertrag "POST /notify" auf. // // Core CFG-02/CFG-05 stehen auf core-kanban zwar auf "Fertig", enthalten // im aktuellen Repository-Stand aber keinen abrufbaren Endpunkt (gleiche // Situation wie ARC-06/Core TEN-01, INT-01/Core API-01) — Client richtet // sich nach dem im Core-Board dokumentierten Vertrag (service-token- // authentifiziertes POST /notify), siehe INT-05-Prüfprotokoll. package notifyclient import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" ) // Event ist eine einzelne Benachrichtigung an den Core-Dispatcher. type Event struct { TenantSlug string `json:"tenant_slug"` EventType string `json:"event_type"` Summary string `json:"summary"` Count int `json:"count"` } // Client ruft Core CFG-05s "POST /notify" auf. type Client struct { baseURL string serviceToken string httpClient *http.Client } // NewClient erstellt einen Client. baseURL und serviceToken kommen // ausschließlich vom Aufrufer (Umgebungsvariable) — keine // Zugangsdaten im Code. func NewClient(baseURL, serviceToken string) *Client { return &Client{ baseURL: baseURL, serviceToken: serviceToken, httpClient: &http.Client{Timeout: 5 * time.Second}, } } // Notify sendet EIN Ereignis. Ein HTTP-Fehler (Netzwerk, 5xx) wird als // Fehler zurückgeliefert — der Aufrufer entscheidet, ob das den // regulären Mail-Betrieb blockiert (siehe importnotify: Notify läuft // NIE im Importpfad selbst, ein Fehler hier verhindert keine bereits // abgeschlossene Nachrichtenübernahme). Ein 2xx- ODER 204-Status gilt // als Erfolg — 204 bedeutet laut CFG-05s EnqueueIfAllowed-Vertrag // "durch Benutzerpräferenz unterdrückt, kein Fehler". func (c *Client) Notify(ctx context.Context, ev Event) error { body, err := json.Marshal(ev) if err != nil { return fmt.Errorf("notifyclient: ereignis serialisieren: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/notify", bytes.NewReader(body)) if err != nil { return fmt.Errorf("notifyclient: anfrage bauen: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+c.serviceToken) resp, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("notifyclient: /notify aufrufen: %w", err) } defer func() { _ = resp.Body.Close() }() respBody, _ := io.ReadAll(resp.Body) // 2xx (inkl. 204 "No Content") gilt als Erfolg — 204 bedeutet laut // CFG-05s EnqueueIfAllowed-Vertrag "durch Benutzerpräferenz // unterdrückt", was Mail nicht als Fehler behandelt (die // Zustell-/Präferenzentscheidung ist bewusst Core-Sache). if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } return fmt.Errorf("notifyclient: /notify status %d: %s", resp.StatusCode, string(respBody)) }