// Package notifyapi ist CFG-05: stellt Core CFG-04s // internal/notifyprefs.EnqueueIfAllowed als HTTP-Endpunkt fuer andere, // physisch getrennte Module (Archive, DMS, Mail) bereit — gleiches Muster // wie RBAC-06 (internal/policyapi): reiner Wrapper, kein zweiter // Entscheidungspfad, service-token-authentifiziert ueber // internal/policyapi.RequireServiceToken. package notifyapi import ( "encoding/json" "net/http" "gitea.perlbach24.de/scripte/nexarch/internal/notify" "gitea.perlbach24.de/scripte/nexarch/internal/notifyprefs" "gitea.perlbach24.de/scripte/nexarch/internal/policyapi" ) // Mount registriert POST /notify/enqueue hinter dem Service-Token-Check. func Mount(mux *http.ServeMux, prefs *notifyprefs.Store, dispatcher *notify.Dispatcher, serviceToken string) { mux.HandleFunc("POST /notify/enqueue", policyapi.RequireServiceToken(serviceToken, enqueueHandler(prefs, dispatcher))) } 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"` } type enqueueResponse struct { JobID string `json:"job_id"` Skipped bool `json:"skipped"` } // enqueueHandler ruft AUSSCHLIESSLICH notifyprefs.EnqueueIfAllowed auf — // dieselbe Funktion, die auch core-interne Aufrufer nutzen. Der // Praeferenz-Filter (Akzeptanzkriterium 3 aus RET-07, "je Ereignistyp // ein-/ausschaltbar") ist dadurch strukturell identisch mit dem // direkten Aufruf, nicht nur zufaellig getestet. func enqueueHandler(prefs *notifyprefs.Store, dispatcher *notify.Dispatcher) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req enqueueRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "ungültiger request-body: "+err.Error(), http.StatusBadRequest) return } if req.TenantSlug == "" || req.UserID == "" || req.EventType == "" || req.Channel == "" || req.Recipient == "" { http.Error(w, "tenant_slug, user_id, event_type, channel und recipient sind pflichtfelder", http.StatusBadRequest) return } jobID, skipped, err := notifyprefs.EnqueueIfAllowed( r.Context(), prefs, dispatcher, req.TenantSlug, req.UserID, req.EventType, req.Channel, req.Recipient, req.Payload, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(enqueueResponse{JobID: jobID, Skipped: skipped}) } }