diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-09-20 07:47:48 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-09-20 07:47:48 +0000 |
| commit | c5ab2f9a7f6a0b1e456e13934268d1509504c55a (patch) | |
| tree | bc1e74b6b07341246aaa71ee498b6c3513e2231d /internal/handlers/shopping.go | |
| parent | b970be11ee895c34e019231022e9e5244e4aec2f (diff) | |
Drop Trello as a shopping source, importing the two groups worth keeping
The Trello "Shopping" board had become a dumping ground: of 24 cards, 13 had no
list at all (rendering as "Uncategorized"), and the rest were spread across
Costco, Hardware Store, "Perennial " (trailing space, so a separate store from
"Perennial") and online. Its items were read-only in doot — no rename, no move,
no delete, only hide — and its list names were doubling as an accidental store
registry, which is the only reason stores like Hardware Store existed at all.
Migration 032 imports the two groups asked for, Hardware Store and the unlisted
cards, into user_shopping_items. Costco, Perennial and online cards are left
behind. Nothing is destroyed: the cards remain in Trello, this only stops doot
displaying them. Imported rows become ordinary user items, so they also become
editable, which Trello-sourced items never were.
Trello remains the source for the other eight boards; this is only the Shopping
board feeding the shopping list.
Also updated the quick-action modal's Shopping tab, which posted source=trello
with a Trello list_id. Left alone it would have kept creating cards that no
longer appear anywhere. It now posts source=shopping with a store name, handled
by a new case in HandleUnifiedAdd, and its picker reads from a new
/partials/shopping-stores endpoint. The Trello-lists endpoint it used to call
is deleted along with its route.
Note for the edit feature still to come: stores have no existence independent
of items, so a store with nothing in it cannot be chosen. That was previously
masked by Trello's lists persisting. "Move between stores and sections" needs a
real stores/sections table.
Migration tested against a seeded snapshot of the real board, including the
trailing-space store name, other boards, replay safety and the empty-cache
no-op. Two existing tests asserting Trello check-state in shopping mode were
updated to plantoeat, the only remaining external source.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'internal/handlers/shopping.go')
| -rw-r--r-- | internal/handlers/shopping.go | 89 |
1 files changed, 21 insertions, 68 deletions
diff --git a/internal/handlers/shopping.go b/internal/handlers/shopping.go index 1c58126..e06cad6 100644 --- a/internal/handlers/shopping.go +++ b/internal/handlers/shopping.go @@ -16,7 +16,7 @@ import ( "task-dashboard/internal/models" ) -// HandleTabShopping renders the Shopping tab (Trello Shopping board + PlanToEat + User items) +// HandleTabShopping renders the Shopping tab (PlanToEat + user-added items) func (h *Handler) HandleTabShopping(w http.ResponseWriter, r *http.Request) { ctx := r.Context() stores := h.aggregateShoppingLists(ctx) @@ -125,7 +125,7 @@ func (h *Handler) HandleShoppingModeComplete(w http.ResponseWriter, r *http.Requ JSONError(w, http.StatusInternalServerError, "Failed to delete item", err) return } - case "trello", "plantoeat": + case "plantoeat": // Mark as checked (will be filtered out of view) if err := h.store.SetShoppingItemChecked(source, id, true); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to complete item", err) @@ -160,81 +160,34 @@ func (h *Handler) HandleShoppingModeComplete(w http.ResponseWriter, r *http.Requ }{storeName, items}) } -// HandleGetShoppingLists returns the lists from the Shopping board for quick-add -func (h *Handler) HandleGetShoppingLists(w http.ResponseWriter, r *http.Request) { - boards, err := h.store.GetBoards() - if err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to get boards", err) - return - } - - var shoppingBoardID string - for _, b := range boards { - if strings.EqualFold(b.Name, "Shopping") { - shoppingBoardID = b.ID - break - } - } - - if shoppingBoardID == "" { - JSONError(w, http.StatusNotFound, "Shopping board not found", nil) - return - } - - lists, err := h.trelloClient.GetLists(r.Context(), shoppingBoardID) - if err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to get lists", err) - return - } +// HandleGetShoppingStores returns the current store names as <option> elements +// for the quick-action modal's store picker. +// +// This replaces the Trello-lists endpoint the modal used to call. Note the +// store names come from the items themselves — there is no separate store +// registry yet, so a store with nothing in it does not appear here. +func (h *Handler) HandleGetShoppingStores(w http.ResponseWriter, r *http.Request) { + stores := h.aggregateShoppingLists(r.Context()) w.Header().Set("Content-Type", "text/html") - for _, list := range lists { + for _, s := range stores { _, _ = fmt.Fprintf(w, `<option value="%s">%s</option>`, - template.HTMLEscapeString(list.ID), - template.HTMLEscapeString(list.Name)) + template.HTMLEscapeString(s.Name), + template.HTMLEscapeString(s.Name)) } } -// aggregateShoppingLists combines Trello, PlanToEat, and user shopping items by store +// aggregateShoppingLists combines PlanToEat and user shopping items by store func (h *Handler) aggregateShoppingLists(ctx context.Context) []models.ShoppingStore { storeMap := make(map[string]map[string][]models.UnifiedShoppingItem) - // Fetch locally stored checked states for external sources - trelloChecks, _ := h.store.GetShoppingItemChecks("trello") + // Fetch locally stored checked states for the remaining external source. + // Trello is no longer a shopping source: its items were read-only here and + // its list names were doubling as an accidental store registry. See + // migration 032 for the one-time import that preceded removing it. plantoeatChecks, _ := h.store.GetShoppingItemChecks("plantoeat") - // 1. Fetch Trello "Shopping" board cards - boards, err := h.store.GetBoards() - if err != nil { - log.Printf("ERROR [Shopping/Trello]: %v", err) - } else { - for _, board := range boards { - if strings.EqualFold(board.Name, "Shopping") { - log.Printf("DEBUG [Shopping]: Found Shopping board with %d cards", len(board.Cards)) - for _, card := range board.Cards { - storeName := card.ListName - if storeName == "" { - storeName = "Uncategorized" - } - - if storeMap[storeName] == nil { - storeMap[storeName] = make(map[string][]models.UnifiedShoppingItem) - } - - item := models.UnifiedShoppingItem{ - ID: card.ID, - Name: card.Name, - Store: storeName, - Source: "trello", - Checked: trelloChecks[card.ID], - } - storeMap[storeName][""] = append(storeMap[storeName][""], item) - } - } - } - } - - // 2. Fetch PlanToEat shopping list + // 1. Fetch PlanToEat shopping list if h.planToEatClient != nil { items, err := h.planToEatClient.GetShoppingList(ctx) if err != nil { @@ -272,7 +225,7 @@ func (h *Handler) aggregateShoppingLists(ctx context.Context) []models.ShoppingS log.Printf("DEBUG [Shopping/PlanToEat]: Client not configured") } - // 3. Fetch user-added shopping items + // 2. Fetch user-added shopping items userItems, err := h.store.GetUserShoppingItems() if err != nil { log.Printf("ERROR [Shopping/User]: %v", err) @@ -298,7 +251,7 @@ func (h *Handler) aggregateShoppingLists(ctx context.Context) []models.ShoppingS } } - // 4. Convert map to sorted slice + // 3. Convert map to sorted slice var stores []models.ShoppingStore for storeName, categories := range storeMap { store := models.ShoppingStore{Name: storeName} |
