- 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
65 lines
2.6 KiB
Go
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})
|
|
}
|
|
}
|