package storage import ( "bytes" "context" "encoding/json" "fmt" "net/http" ) // UsageMetric ist der Metrikname, unter dem Core (internal/usage, // LIC-05) den Speicherverbrauch je Mandant führt — muss exakt // internal/usage.StorageBytesMetric aus dem NEXARCH-Core-Modul // entsprechen (Mail kann Core nicht importieren, daher hier gespiegelt // — identisches Muster wie DMS FDN-03). const UsageMetric = "storage_bytes" // UsageReporter meldet Speicherverbrauchsänderungen an Core // (Akzeptanzkriterium 4). Schmale Schnittstelle, damit Tests einen // Fake statt eines echten HTTP-Aufrufs einsetzen können. type UsageReporter interface { Report(ctx context.Context, tenantSlug, metric string, delta int64) error } // usageDeltaDTO entspricht Core internal/resync.usageDeltaDTO // (JSON-Vertrag: tenant_slug/metric/delta), über den API-11 // (resync-api) real erreichbar ist. type usageDeltaDTO struct { TenantSlug string `json:"tenant_slug"` Metric string `json:"metric"` Delta int64 `json:"delta"` } // HTTPUsageReporter meldet über Core API-11 (resync-api, // internal/resync.Handler.UsageHandler), authentifiziert über // dasselbe Service-Credential-Verfahren wie jeder andere Modul-Core- // Aufruf (API-02). type HTTPUsageReporter struct { endpointURL string clientID string clientSecret string httpClient *http.Client } func NewHTTPUsageReporter(endpointURL, clientID, clientSecret string, httpClient *http.Client) *HTTPUsageReporter { if httpClient == nil { httpClient = http.DefaultClient } return &HTTPUsageReporter{endpointURL: endpointURL, clientID: clientID, clientSecret: clientSecret, httpClient: httpClient} } func (r *HTTPUsageReporter) Report(ctx context.Context, tenantSlug, metric string, delta int64) error { body, err := json.Marshal([]usageDeltaDTO{{TenantSlug: tenantSlug, Metric: metric, Delta: delta}}) if err != nil { return fmt.Errorf("storage: nutzungsmeldung serialisieren: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.endpointURL, bytes.NewReader(body)) if err != nil { return fmt.Errorf("storage: nutzungsmeldungs-anfrage aufbauen: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Nexarch-Client-Id", r.clientID) req.Header.Set("X-Nexarch-Client-Secret", r.clientSecret) resp, err := r.httpClient.Do(req) if err != nil { return fmt.Errorf("storage: nutzungsmeldung senden: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("storage: nutzungsmeldung von core abgelehnt: status %d", resp.StatusCode) } return nil }