hub 0.165.0 source: a household's mail list is never overwritten by a box default, a box reads it back (R-1199 reopened); host_swap_short only when swap is really short (R-1216, D28 = A); host-swap-rule gate with 4 decoys; 03/05/08 updated
gates / gates (push) Successful in 15m54s

This commit is contained in:
admin committed 2026-10-22 12:23:07 +02:00
1 parent a92837410a
commit 5ae5984cf9
18 files changed
+653 -24

No files matched your search

+20 -2
View File
@@ -392,6 +392,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleNotify(w, r)
case r.Method == http.MethodPost && path == "/preferences":
h.handleSavePreferences(w, r)
case r.Method == http.MethodGet && path == "/preferences":
h.handleGetPreferences(w, r) // R-1199 reopened: a box with no local list reads the household's
case r.Method == http.MethodGet && path == "/customers":
h.handleCustomers(w, r)
case r.Method == http.MethodGet && strings.HasPrefix(path, "/customers/"):
@@ -2770,6 +2772,10 @@ func (h *Handler) handleSavePreferences(w http.ResponseWriter, r *http.Request)
// household deliberately cleared its address on the dashboard. Omitted (false) on every other push —
// including every push from a controller older than the one that emits it.
EmailCleared bool `json:"email_cleared"`
// Source (R-1199 reopened, controller ≥ 0.339.0): "household" — the household saved this list on the box
// (the settings page, or the re-push of a list saved there); "default" — the box's own default, sent because
// nothing was saved and the hub holds no household list. Absent = an older controller (prefsPushIsHousehold).
Source string `json:"source"`
}
if err := json.Unmarshal(body, &payload); err != nil || payload.CustomerID == "" {
http.Error(w, "Invalid payload: customer_id required", http.StatusBadRequest)
@@ -2791,6 +2797,18 @@ func (h *Handler) handleSavePreferences(w http.ResponseWriter, r *http.Request)
// as its own flag. A flag beside a NON-empty address is an ordinary update (the address wins). Pinned by
// TestSavePreferences_DeliberateClearDeletesAddress, TestSavePreferences_EmptyEmailCannotClobber and
// TestSavePreferences_ClearFlagWithAddressIsAnUpdate.
// R-1199 reopened: a push that is not the household's own save never overwrites a list the household saved.
household := prefsPushIsHousehold(payload.Source, payload.Email, payload.EmailCleared)
if !household {
if existing, err := h.store.GetNotificationPrefs(payload.CustomerID); err == nil && existing != nil && existing.HouseholdSaved {
h.logger.Printf("[INFO] Notification prefs push for %s (source=%q) not applied — the household saved its own list (R-1199)", payload.CustomerID, payload.Source)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"kept","household_saved":true}`))
return
}
}
saveEmail := payload.Email
if saveEmail == "" && payload.EmailCleared {
h.logger.Printf("[INFO] Notification prefs push for %s: household cleared its mail address — deleted", payload.CustomerID)
@@ -2801,13 +2819,13 @@ func (h *Handler) handleSavePreferences(w http.ResponseWriter, r *http.Request)
}
}
if err := h.store.SaveNotificationPrefs(payload.CustomerID, saveEmail, payload.EnabledEvents, payload.CooldownHours); err != nil {
if err := h.store.SaveNotificationPrefsAs(payload.CustomerID, saveEmail, payload.EnabledEvents, payload.CooldownHours, household); err != nil {
h.logger.Printf("[ERROR] Failed to save notification prefs for %s: %v", payload.CustomerID, err)
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
h.logger.Printf("[INFO] Notification preferences updated for %s: address set=%t, events=%v", payload.CustomerID, saveEmail != "", payload.EnabledEvents) // never the address (R-922)
h.logger.Printf("[INFO] Notification preferences updated for %s: address set=%t, household=%t, events=%v", payload.CustomerID, saveEmail != "", household, payload.EnabledEvents) // never the address (R-922)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
}