package notifyprefs import ( "encoding/json" "net/http" "gitea.perlbach24.de/scripte/nexarch/internal/auth" ) // Handler stellt Benachrichtigungspraeferenzen als HTTP-Endpunkte bereit // (CFG-04). Duenner Client von Store — keine eigene Zustelllogik. type Handler struct { prefs *Store } func NewHandler(prefs *Store) *Handler { return &Handler{prefs: prefs} } type setPreferenceRequest struct { EventType string `json:"event_type"` Channel string `json:"channel"` Enabled bool `json:"enabled"` } // SetPreference setzt die eigene Praeferenz des angemeldeten Benutzers // (Akzeptanzkriterium 1). Muss hinter auth.RequireAuth haengen. func (h *Handler) SetPreference(w http.ResponseWriter, r *http.Request) { claims, ok := auth.ClaimsFromContext(r.Context()) if !ok { http.Error(w, "nicht angemeldet", http.StatusUnauthorized) return } var req setPreferenceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "ungueltige Anfrage", http.StatusBadRequest) return } if err := h.prefs.Set(r.Context(), claims.TenantSlug, claims.UserID, req.EventType, req.Channel, req.Enabled); err != nil { http.Error(w, "Einstellung konnte nicht gespeichert werden.", http.StatusBadRequest) return } w.WriteHeader(http.StatusOK) } // ListMine liefert die eigenen Praeferenzen (Akzeptanzkriterium 1) — nur die // EIGENEN, niemals die eines anderen Benutzers (Pruefung 2). claims.UserID // kommt ausschliesslich aus dem verifizierten Session-Token, ist also nicht // vom Client manipulierbar. func (h *Handler) ListMine(w http.ResponseWriter, r *http.Request) { claims, ok := auth.ClaimsFromContext(r.Context()) if !ok { http.Error(w, "nicht angemeldet", http.StatusUnauthorized) return } prefs, err := h.prefs.ListForUser(r.Context(), claims.TenantSlug, claims.UserID) if err != nil { http.Error(w, "Einstellungen konnten nicht geladen werden.", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(prefs) } // ListTenantOverview liefert die Praeferenzen ALLER Benutzer des eigenen // Mandanten (Akzeptanzkriterium 3: Tenant-Admin-Übersicht). Die Zugriffs- // beschraenkung "nur Tenant-Admin" ist Aufgabe von RBAC-02 (Policy- // Enforcement-Schicht) vor diesem Handler — hier wird bewusst keine eigene // Rechtelogik dupliziert (Ticket-Produkt-DNA: duenner Client). func (h *Handler) ListTenantOverview(w http.ResponseWriter, r *http.Request) { claims, ok := auth.ClaimsFromContext(r.Context()) if !ok { http.Error(w, "nicht angemeldet", http.StatusUnauthorized) return } prefs, err := h.prefs.ListForTenant(r.Context(), claims.TenantSlug) if err != nil { http.Error(w, "Übersicht konnte nicht geladen werden.", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(prefs) }