feat(db): sqlite support for adding items, add-db command.

This commit is contained in:
rsxri 2025-08-04 22:40:46 +01:00
parent 7678178e36
commit 8da7c2e8db
2 changed files with 108 additions and 7 deletions

View file

@ -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
}
}

View file

@ -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)
}