package web import ( "encoding/json" "io" "log/slog" "net/http" "pete/internal/storage" ) // maxPrefsBytes caps the stored blob. Preferences are small (disabled feeds, // a postal code, a couple toggles); this is generous headroom, not a target. const maxPrefsBytes = 64 * 1024 // handlePrefs serves GET (load) and PUT (save) of the signed-in user's // preferences blob. The blob is opaque JSON owned by the frontend. Both verbs // require a session; anonymous callers get 401 and fall back to localStorage. func (s *Server) handlePrefs(w http.ResponseWriter, r *http.Request) { if s.auth == nil { http.Error(w, "auth disabled", http.StatusNotFound) return } u := s.auth.userFromRequest(r) if u == nil { w.Header().Set("Content-Type", "application/json; charset=utf-8") http.Error(w, `{"error":"not signed in"}`, http.StatusUnauthorized) return } switch r.Method { case http.MethodGet: blob, err := storage.GetUserPrefs(u.Sub) if err != nil { slog.Error("prefs: load failed", "sub", u.Sub, "err", err) http.Error(w, "internal error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Cache-Control", "no-store") if blob == "" { _, _ = w.Write([]byte(`{"prefs":null}`)) return } // blob is already valid JSON; embed it verbatim. _, _ = w.Write([]byte(`{"prefs":`)) _, _ = io.WriteString(w, blob) _, _ = w.Write([]byte(`}`)) case http.MethodPut: body, err := io.ReadAll(io.LimitReader(r.Body, maxPrefsBytes+1)) if err != nil { http.Error(w, "read error", http.StatusBadRequest) return } if len(body) > maxPrefsBytes { http.Error(w, "preferences too large", http.StatusRequestEntityTooLarge) return } if !json.Valid(body) { http.Error(w, "invalid JSON", http.StatusBadRequest) return } if err := storage.PutUserPrefs(u.Sub, string(body), u.Name, u.Email); err != nil { slog.Error("prefs: save failed", "sub", u.Sub, "err", err) http.Error(w, "internal error", http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } }