summaryrefslogtreecommitdiff
path: root/internal/store/shopping.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/shopping.go')
-rw-r--r--internal/store/shopping.go69
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)