RET-07: fristablauf-benachrichtigungen
- 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
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user