internal/tenantsettings: pro-Tenant-Einstellungen (Anzeigename, Logo, Farbschema, Zeitzone, Sprache) in der Registry-DB, alle Spalten nullable — fehlender Wert bedeutet immer "Systemvoreinstellung verwenden" (Defaults(): color_scheme=system, timezone=UTC, language=de), niemals ein Fehler (Akzeptanzkriterium 2). Store.Update schreibt aktuellen Stand + Historieneintrag atomar in einer Transaktion mit FOR-UPDATE-Lock auf der aktuellen Zeile (Akzeptanzkriterium 3, Race-sicher bei nebenlaeufigen Updates desselben Tenants). Patch-Typ mit *string-Feldern erlaubt Teil-Updates ohne unbeteiligte Felder zu beruehren. Schlanker Handler (Get/Update ueber tenant_id-Query-Parameter) als Vorbereitung der REST-Schnittstelle — echte Auth/Versionierung kommt erst mit API-01/IAM-02/RBAC-01. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Aenderung eines Tenants wirkt sich nicht auf einen anderen aus — TestUpdate_IsolatedBetweenTenants: Tenant A geaendert, Tenant B bleibt nachweislich bei Defaults(). PASS. 2. Fehlende Werte liefern Defaults statt Fehler — TestGet_UnsetTenantReturnsDefaults (Tenant ganz ohne Datensatz) und TestUpdate_PartialPatchKeepsOtherFieldsAtDefault (nur ein Feld gesetzt, Rest bleibt Default). PASS. 3. API-Schema von zweiter Person gegen Dokumentation geprueft — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Zusaetzlich automatisiert getestet (Akzeptanzkriterium 3): TestUpdate_HistoryTracksVersions — 3 aufeinanderfolgende Aenderungen, Historie liefert alle 3 in korrekter Reihenfolge, nicht angefasste Felder bleiben aus dem vorherigen Update erhalten. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
75 lines
2.0 KiB
Go
75 lines
2.0 KiB
Go
package tenantsettings
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
)
|
|
|
|
// Handler stellt Lesen/Schreiben der Tenant-Einstellungen als schlanke
|
|
// Vorbereitung der Schnittstelle bereit (API-01/TEN-05 sind nicht Teil
|
|
// dieser Kachel). tenantID wird hier als Pfad-/Query-Parameter erwartet,
|
|
// eine echte Auth-/Berechtigungspruefung kommt erst mit IAM-02/RBAC-01.
|
|
type Handler struct {
|
|
store *Store
|
|
}
|
|
|
|
func NewHandler(store *Store) *Handler {
|
|
return &Handler{store: store}
|
|
}
|
|
|
|
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|
tenantID := r.URL.Query().Get("tenant_id")
|
|
if tenantID == "" {
|
|
http.Error(w, "tenant_id fehlt", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
settings, err := h.store.Get(r.Context(), tenantID)
|
|
if err != nil {
|
|
http.Error(w, "einstellungen konnten nicht geladen werden", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, settings)
|
|
}
|
|
|
|
type updateRequest struct {
|
|
DisplayName *string `json:"display_name"`
|
|
LogoURL *string `json:"logo_url"`
|
|
ColorScheme *string `json:"color_scheme"`
|
|
Timezone *string `json:"timezone"`
|
|
Language *string `json:"language"`
|
|
}
|
|
|
|
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|
tenantID := r.URL.Query().Get("tenant_id")
|
|
if tenantID == "" {
|
|
http.Error(w, "tenant_id fehlt", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req updateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "ungueltige Anfrage", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
settings, err := h.store.Update(r.Context(), tenantID, Patch{
|
|
DisplayName: req.DisplayName,
|
|
LogoURL: req.LogoURL,
|
|
ColorScheme: req.ColorScheme,
|
|
Timezone: req.Timezone,
|
|
Language: req.Language,
|
|
})
|
|
if err != nil {
|
|
http.Error(w, "einstellungen konnten nicht gespeichert werden", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, settings)
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|