diff options
Diffstat (limited to 'internal/store/shopping.go')
| -rw-r--r-- | internal/store/shopping.go | 69 |
1 files changed, 65 insertions, 4 deletions
diff --git a/internal/store/shopping.go b/internal/store/shopping.go index 40571ac..448a2be 100644 --- a/internal/store/shopping.go +++ b/internal/store/shopping.go @@ -1,24 +1,85 @@ package store -import "time" +import ( + "database/sql" + "errors" + "fmt" + "strings" + "time" +) type UserShoppingItem struct { ID int64 Name string Store string + Section string Checked bool CreatedAt time.Time } // SaveUserShoppingItem saves a new user shopping item func (s *Store) SaveUserShoppingItem(name, store string) error { - _, err := s.db.Exec(`INSERT INTO user_shopping_items (name, store) VALUES (?, ?)`, name, store) + return s.SaveUserShoppingItemInSection(name, store, "") +} + +// SaveUserShoppingItemInSection saves a new item filed under a section. +func (s *Store) SaveUserShoppingItemInSection(name, store, section string) error { + _, err := s.db.Exec( + `INSERT INTO user_shopping_items (name, store, section) VALUES (?, ?, ?)`, + strings.TrimSpace(name), strings.TrimSpace(store), strings.TrimSpace(section)) return err } +// UpdateUserShoppingItem renames an item and/or moves it between stores and +// sections. Everything an edit can change goes through here, so a partial +// update cannot leave an item half-moved. +func (s *Store) UpdateUserShoppingItem(id int64, name, store, section string) error { + name = strings.TrimSpace(name) + store = strings.TrimSpace(store) + section = strings.TrimSpace(section) + if name == "" { + return fmt.Errorf("item name is required") + } + if store == "" { + return fmt.Errorf("store is required") + } + + res, err := s.db.Exec( + `UPDATE user_shopping_items SET name = ?, store = ?, section = ? WHERE id = ?`, + name, store, section, id) + if err != nil { + return err + } + // An UPDATE that matches nothing succeeds at the SQL level, so a stale or + // wrong id would otherwise look like a successful edit. + if n, err := res.RowsAffected(); err == nil && n == 0 { + return ErrItemNotFound + } + return nil +} + +// GetUserShoppingItem returns a single item, or ErrItemNotFound. +func (s *Store) GetUserShoppingItem(id int64) (*UserShoppingItem, error) { + var item UserShoppingItem + err := s.db.QueryRow( + `SELECT id, name, store, COALESCE(section, ''), checked, created_at + FROM user_shopping_items WHERE id = ?`, id). + Scan(&item.ID, &item.Name, &item.Store, &item.Section, &item.Checked, &item.CreatedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrItemNotFound + } + if err != nil { + return nil, err + } + return &item, nil +} + +// ErrItemNotFound is returned when an item id matches nothing. +var ErrItemNotFound = errors.New("shopping item not found") + // GetUserShoppingItems retrieves all user shopping items func (s *Store) GetUserShoppingItems() ([]UserShoppingItem, error) { - rows, err := s.db.Query(`SELECT id, name, store, checked, created_at FROM user_shopping_items ORDER BY store, created_at DESC`) + rows, err := s.db.Query(`SELECT id, name, store, COALESCE(section, ''), checked, created_at FROM user_shopping_items ORDER BY store, created_at DESC`) if err != nil { return nil, err } @@ -27,7 +88,7 @@ func (s *Store) GetUserShoppingItems() ([]UserShoppingItem, error) { var items []UserShoppingItem for rows.Next() { var item UserShoppingItem - if err := rows.Scan(&item.ID, &item.Name, &item.Store, &item.Checked, &item.CreatedAt); err != nil { + if err := rows.Scan(&item.ID, &item.Name, &item.Store, &item.Section, &item.Checked, &item.CreatedAt); err != nil { return nil, err } items = append(items, item) |
