Files
nexarch/internal/notifyapi/handler.go
T
sysops 23a3cc1a62 CFG-05: modulübergreifender-http-endpunkt-für-benachrichtigungs-ereignisse
- internal/notifyapi.Mount (POST /notify/enqueue), reiner Wrapper um
  notifyprefs.EnqueueIfAllowed (CFG-04)
- Reuse von internal/policyapi.RequireServiceToken (RBAC-06), kein
  neues Provisorium, eigener Service-Token pro Endpunkt
- Tests: 401 ohne Token, Aequivalenz HTTP vs. direkter Aufruf
  (aktiviert/deaktiviert), realer Fremd-Modul-Client mit echter
  notification_jobs-Zeile
- real deployed auf 131 (notify-api, Port 8094), Grant-Nachverfolgung
  fuer nexarch_core auf notification_preferences/notification_jobs,
  end-zu-ende per curl nachgewiesen (401, job_id+skipped:false)

Pruefungen siehe docs/CFG-05-PRUEFPROTOKOLL.md
2026-08-30 09:04:31 +02:00

65 lines
2.6 KiB
Go

// 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})
}
}