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 fuehrt — muss exakt // internal/usage.StorageBytesMetric aus dem NEXARCH-Core-Modul entsprechen // (Core kann von DMS als eigenem Go-Modul nicht importiert werden, daher // hier als Konstante gespiegelt statt importiert). const UsageMetric = "storage_bytes" // UsageReporter meldet Speicherverbrauchsaenderungen an Core (Akzeptanz- // kriterium 4). Schmale Schnittstelle, damit Tests einen Fake statt eines // echten HTTP-Aufrufs einsetzen koennen. 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) — dieselbe Struktur, hier // gespiegelt, da DMS Cores internal/-Pakete nicht importieren kann. type usageDeltaDTO struct { TenantSlug string `json:"tenant_slug"` Metric string `json:"metric"` Delta int64 `json:"delta"` } // HTTPUsageReporter meldet ueber Cores Resync-Nutzungs-Endpunkt // (internal/resync.Handler.UsageHandler, API-06), authentifiziert ueber // 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 }