- archive/internal/notifyclient: HTTP-Client fuer Core CFG-05 (gleiches Muster wie rbacclient/RET-08 fuer RBAC-06) - archive/internal/retentionnotify.Run: ermittelt faellige Objekte ueber dieselbe Funktion wie RET-02-Job/RET-06-API-Preview, filtert je Klasse nach Vorlauf+Ein-Aus-Schalter, Postgres-persistente Dedupe (retention_notifications), Fehlschlag wird protokolliert statt verworfen (kein Eintrag -> Retry beim naechsten Durchlauf) - archive/cmd/retention-notify-job: systemd-Timer-CLI, analog scrub-cli - Abweichung vom urspruenglichen Ticket-Text dokumentiert: CFG-05 statt direktem CFG-02-Import (Modul-Trennung), konfigurierte zustaendige Rolle statt Objekt-Owner (RET-01 fuehrt keinen) - real deployed auf 131 (timer taeglich 07:00 UTC), end-zu-ende bewiesen: echte notification_jobs-Zeile in Core-DB, zweiter Dienststart ohne Doppelversand Pruefungen siehe archive/docs/RET-07-PRUEFPROTOKOLL.md
85 lines
2.9 KiB
Go
85 lines
2.9 KiB
Go
// Package notifyclient ist ein schlanker HTTP-Client für Core CFG-05
|
|
// (internal/notifyapi, POST /notify/enqueue). Archive ist ein physisch
|
|
// getrenntes Go-Modul und kann Core CFG-02/CFG-04 daher nicht direkt
|
|
// importieren — nur über die HTTP-Schnittstelle von CFG-05 aufrufen.
|
|
// Gleiches Muster wie archive/internal/rbacclient (RET-08) für RBAC-06.
|
|
package notifyclient
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
type Client struct {
|
|
BaseURL string
|
|
ServiceToken string
|
|
HTTPClient *http.Client
|
|
}
|
|
|
|
func New(baseURL, serviceToken string) *Client {
|
|
return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: http.DefaultClient}
|
|
}
|
|
|
|
type enqueueRequest struct {
|
|
TenantSlug string `json:"tenant_slug"`
|
|
UserID string `json:"user_id"`
|
|
EventType string `json:"event_type"`
|
|
Channel string `json:"channel"`
|
|
Recipient string `json:"recipient"`
|
|
Payload map[string]any `json:"payload"`
|
|
}
|
|
|
|
// EnqueueResult spiegelt CFG-05s enqueueResponse.
|
|
type EnqueueResult struct {
|
|
JobID string
|
|
Skipped bool
|
|
}
|
|
|
|
// Enqueue löst ein Benachrichtigungs-Ereignis über CFG-05 aus. Jeder
|
|
// Fehler (Transport, Timeout, unerwarteter Status) wird als Fehler
|
|
// zurückgegeben — der Aufrufer entscheidet, ob/wie er das protokolliert
|
|
// (Pflichtprüfung RET-07: fehlgeschlagener Versand wird protokolliert,
|
|
// nicht stillschweigend verworfen).
|
|
func (c *Client) Enqueue(ctx context.Context, tenantSlug, userID, eventType, channel, recipient string, payload map[string]any) (EnqueueResult, error) {
|
|
body, err := json.Marshal(enqueueRequest{
|
|
TenantSlug: tenantSlug, UserID: userID, EventType: eventType, Channel: channel, Recipient: recipient, Payload: payload,
|
|
})
|
|
if err != nil {
|
|
return EnqueueResult{}, fmt.Errorf("notifyclient: request kodieren: %w", err)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/notify/enqueue", bytes.NewReader(body))
|
|
if err != nil {
|
|
return EnqueueResult{}, fmt.Errorf("notifyclient: request bauen: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-Service-Token", c.ServiceToken)
|
|
|
|
resp, err := c.httpClient().Do(req)
|
|
if err != nil {
|
|
return EnqueueResult{}, fmt.Errorf("notifyclient: aufruf fehlgeschlagen: %w", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return EnqueueResult{}, fmt.Errorf("notifyclient: unerwarteter status %d", resp.StatusCode)
|
|
}
|
|
var out struct {
|
|
JobID string `json:"job_id"`
|
|
Skipped bool `json:"skipped"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
return EnqueueResult{}, fmt.Errorf("notifyclient: antwort dekodieren: %w", err)
|
|
}
|
|
return EnqueueResult{JobID: out.JobID, Skipped: out.Skipped}, nil
|
|
}
|
|
|
|
func (c *Client) httpClient() *http.Client {
|
|
if c.HTTPClient != nil {
|
|
return c.HTTPClient
|
|
}
|
|
return http.DefaultClient
|
|
}
|