diff --git a/src/pantry/core/sqlite.go b/src/pantry/core/sqlite.go index ae8de54..70dec73 100644 --- a/src/pantry/core/sqlite.go +++ b/src/pantry/core/sqlite.go @@ -8,12 +8,14 @@ import ( _ "modernc.org/sqlite" ) +// InitDB open or create sqlite db at db path func InitDB(path string) (*sql.DB, error) { db, err := sql.Open("sqlite", path) if err != nil { return nil, err } + // defines schema - one row per item + expiry entry schema := ` CREATE TABLE IF NOT EXISTS inventory ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -25,6 +27,7 @@ func InitDB(path string) (*sql.DB, error) { ); ` + // ensure table exists before continuing _, err = db.Exec(schema) if err != nil { return nil, fmt.Errorf("failed to apply schema %w", err) @@ -34,17 +37,25 @@ func InitDB(path string) (*sql.DB, error) { } func ListItems(db *sql.DB) ([]InventoryItem, error) { + // query all items from inventory table rows, err := db.Query("SELECT name, quantity, unit, expiry, added_at FROM inventory ORDER BY name") if err != nil { return nil, fmt.Errorf("failed to list items: %w", err) } - defer rows.Close() + // ensure rows are closed cleanly after iteration + defer func(rows *sql.Rows) { + err := rows.Close() + if err != nil { + + } + }(rows) var items []InventoryItem for rows.Next() { var item InventoryItem - var expiry sql.NullString + // read nullable expiry and parse added_at + var expiry sql.NullTime var addedAtStr string err = rows.Scan(&item.Name, &item.Quantity, &item.Unit, &expiry, &addedAtStr) @@ -52,15 +63,14 @@ func ListItems(db *sql.DB) ([]InventoryItem, error) { return nil, fmt.Errorf("failed to scan row: %w", err) } + // only assign expiry if it was non-null in db if expiry.Valid { - parsed, err := time.Parse("2006-01-02", expiry.String) - if err != nil { - item.Expiry = &parsed - } + item.Expiry = &expiry.Time } + // parse added_at from string format parsedTime, err := time.Parse("2006-01-02", addedAtStr) - if err != nil { + if err == nil { item.AddedAt = parsedTime } @@ -69,3 +79,55 @@ func ListItems(db *sql.DB) ([]InventoryItem, error) { return items, nil } + +func AddItem(db *sql.DB, item InventoryItem) error { + var existingID int + var existingQty float64 + + // check if item with same name + expiry combination exists already + // expiry compared with IS ? OR = ? to support null cases + query := ` + SELECT id, quantity FROM Inventory + where name = ? AND (expiry IS ? OR expiry = ?)` + + row := db.QueryRow(query, item.Name, item.Quantity, item.Expiry, item.Expiry) + err := row.Scan(&existingID, &existingQty) + + // convert expiry into str or nil for sql insert + var expiryVal interface{} + if item.Expiry != nil { + expiryVal = item.Expiry.Format("2006-01-02") + } else { + expiryVal = nil + } + addedAt := item.AddedAt.Format("2006-01-02") + + switch { + // no match found - insert new item + case err == sql.ErrNoRows: + _, err = db.Exec(` + INSERT INTO inventory (name, quantity, unit, expiry, added_at) + VALUES (?, ?, ?, ?, ?)`, + item.Name, item.Quantity, item.Unit, expiryVal, addedAt) + if err != nil { + return fmt.Errorf("failed to add item: %w", err) + } + + fmt.Printf("added %s, quantity: %.2f %s\n", item.Name, item.Quantity, item.Unit) + return nil + + // error during lookup (let's hope not) + case err != nil: + return fmt.Errorf("failed to add item: %w", err) + + // db match found, update quantity + default: + newQty := existingQty + item.Quantity + _, err := db.Exec(`UPDATE Inventory SET quantity = ? WHERE id = ?`, newQty, existingID) + if err != nil { + return fmt.Errorf("failed to add item: %w", err) + } + fmt.Printf("updated %s, quantity: %.2f %s\n", item.Name, item.Quantity, item.Unit) + return nil + } +} diff --git a/src/pantry/main.go b/src/pantry/main.go index ef47940..eb1a8ce 100644 --- a/src/pantry/main.go +++ b/src/pantry/main.go @@ -146,6 +146,45 @@ func main() { fmt.Printf("- %s: %.2f %s (%s)\n", item.Name, item.Quantity, item.Unit, util.DaysUntilExpiry(item.Expiry)) } + case "add-db": + if len(os.Args) < 5 { + fmt.Println("Usage: inventory add [name] [quantity] [unit] [expiry:YYYY-MM-DD]") + return + } + + name := os.Args[2] + + quantity, err := strconv.ParseFloat(os.Args[3], 64) + if err != nil { + fmt.Println("Invalid quantity") + return + } + + unit := os.Args[4] + + var expiry *time.Time + if len(os.Args) >= 6 { + parsed, err := time.Parse("2006-01-02", os.Args[5]) + if err != nil { + fmt.Println("Invalid expiry date format. Expected YYYY-MM-DD") + return + } + expiry = &parsed + } + + item := core.InventoryItem{ + Name: name, + Quantity: quantity, + Unit: unit, + Expiry: expiry, + AddedAt: time.Now(), + } + + err = core.AddItem(db, item) + if err != nil { + fmt.Println("Failed to add item:", err) + } + default: fmt.Println("Unknown command:", command) }