From 7d59a3020c9a98bb8af7240f584ac4c7813a46f6 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 15 Jul 2026 09:41:03 +0000 Subject: refactor(store): extract meal methods from sqlite.go into meals.go Pure move: SaveMeals/GetMeals/GetMealsByDateRange and their test (plus its setupTestStoreWithMeals helper, used only by that test) out of the 1400+ line sqlite.go into their own file. No behavior change. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD --- internal/store/meals.go | 82 +++++++++++++++++++++++++++++++++++++++++++ internal/store/meals_test.go | 76 +++++++++++++++++++++++++++++++++++++++ internal/store/sqlite.go | 76 --------------------------------------- internal/store/sqlite_test.go | 65 ---------------------------------- 4 files changed, 158 insertions(+), 141 deletions(-) create mode 100644 internal/store/meals.go create mode 100644 internal/store/meals_test.go (limited to 'internal/store') diff --git a/internal/store/meals.go b/internal/store/meals.go new file mode 100644 index 0000000..5f4024b --- /dev/null +++ b/internal/store/meals.go @@ -0,0 +1,82 @@ +package store + +import ( + "time" + + "task-dashboard/internal/models" +) + +func (s *Store) SaveMeals(meals []models.Meal) error { + tx, err := s.db.Begin() + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + + stmt, err := tx.Prepare(` + INSERT OR REPLACE INTO meals + (id, recipe_name, date, meal_type, recipe_url, updated_at) + VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + `) + if err != nil { + return err + } + defer func() { _ = stmt.Close() }() + + for _, meal := range meals { + _, err := stmt.Exec( + meal.ID, + meal.RecipeName, + meal.Date, + meal.MealType, + meal.RecipeURL, + ) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// GetMeals retrieves meals from the database +func (s *Store) GetMeals(startDate, endDate time.Time) ([]models.Meal, error) { + rows, err := s.db.Query(` + SELECT id, recipe_name, date, meal_type, recipe_url + FROM meals + WHERE date BETWEEN ? AND ? + ORDER BY date ASC, + CASE meal_type + WHEN 'breakfast' THEN 1 + WHEN 'lunch' THEN 2 + WHEN 'dinner' THEN 3 + ELSE 4 + END + `, startDate, endDate) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var meals []models.Meal + for rows.Next() { + var meal models.Meal + err := rows.Scan( + &meal.ID, + &meal.RecipeName, + &meal.Date, + &meal.MealType, + &meal.RecipeURL, + ) + if err != nil { + return nil, err + } + meals = append(meals, meal) + } + + return meals, rows.Err() +} + +func (s *Store) GetMealsByDateRange(start, end time.Time) ([]models.Meal, error) { + return s.GetMeals(start, end) +} diff --git a/internal/store/meals_test.go b/internal/store/meals_test.go new file mode 100644 index 0000000..b72796e --- /dev/null +++ b/internal/store/meals_test.go @@ -0,0 +1,76 @@ +package store + +import ( + "database/sql" + "path/filepath" + "testing" + "time" + + _ "github.com/mattn/go-sqlite3" + "task-dashboard/internal/models" +) + +// setupTestStoreWithMeals creates a test store with meals table +func setupTestStoreWithMeals(t *testing.T) *Store { + t.Helper() + + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "test.db") + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + t.Fatalf("Failed to open test database: %v", err) + } + + db.SetMaxOpenConns(1) + + store := &Store{db: db} + + schema := ` + CREATE TABLE IF NOT EXISTS meals ( + id TEXT PRIMARY KEY, + recipe_name TEXT NOT NULL, + date DATETIME, + meal_type TEXT, + recipe_url TEXT, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + ` + if _, err := db.Exec(schema); err != nil { + t.Fatalf("Failed to create schema: %v", err) + } + + return store +} + +func TestGetMealsByDateRange(t *testing.T) { + store := setupTestStoreWithMeals(t) + defer func() { _ = store.Close() }() + + now := time.Now() + tomorrow := now.Add(24 * time.Hour) + + meals := []models.Meal{ + {ID: "1", RecipeName: "Meal 1", Date: now, MealType: "lunch"}, + {ID: "2", RecipeName: "Meal 2", Date: tomorrow, MealType: "dinner"}, + } + + if err := store.SaveMeals(meals); err != nil { + t.Fatalf("Failed to save meals: %v", err) + } + + start := now.Add(-1 * time.Hour) + end := now.Add(1 * time.Hour) + + results, err := store.GetMealsByDateRange(start, end) + if err != nil { + t.Fatalf("GetMealsByDateRange failed: %v", err) + } + + if len(results) != 1 { + t.Errorf("Expected 1 meal, got %d", len(results)) + } + if results[0].ID != "1" { + t.Errorf("Expected meal 1, got %s", results[0].ID) + } +} diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go index ad88166..e8c9eac 100644 --- a/internal/store/sqlite.go +++ b/internal/store/sqlite.go @@ -140,77 +140,6 @@ func (s *Store) runMigrations() error { // Meals operations // SaveMeals saves multiple meals to the database -func (s *Store) SaveMeals(meals []models.Meal) error { - tx, err := s.db.Begin() - if err != nil { - return err - } - defer func() { _ = tx.Rollback() }() - - stmt, err := tx.Prepare(` - INSERT OR REPLACE INTO meals - (id, recipe_name, date, meal_type, recipe_url, updated_at) - VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) - `) - if err != nil { - return err - } - defer func() { _ = stmt.Close() }() - - for _, meal := range meals { - _, err := stmt.Exec( - meal.ID, - meal.RecipeName, - meal.Date, - meal.MealType, - meal.RecipeURL, - ) - if err != nil { - return err - } - } - - return tx.Commit() -} - -// GetMeals retrieves meals from the database -func (s *Store) GetMeals(startDate, endDate time.Time) ([]models.Meal, error) { - rows, err := s.db.Query(` - SELECT id, recipe_name, date, meal_type, recipe_url - FROM meals - WHERE date BETWEEN ? AND ? - ORDER BY date ASC, - CASE meal_type - WHEN 'breakfast' THEN 1 - WHEN 'lunch' THEN 2 - WHEN 'dinner' THEN 3 - ELSE 4 - END - `, startDate, endDate) - if err != nil { - return nil, err - } - defer func() { _ = rows.Close() }() - - var meals []models.Meal - for rows.Next() { - var meal models.Meal - err := rows.Scan( - &meal.ID, - &meal.RecipeName, - &meal.Date, - &meal.MealType, - &meal.RecipeURL, - ) - if err != nil { - return nil, err - } - meals = append(meals, meal) - } - - return meals, rows.Err() -} - // Cache metadata operations // GetCacheMetadata retrieves cache metadata for a key @@ -536,11 +465,6 @@ func (s *Store) GetShoppingItemChecks(source string) (map[string]bool, error) { return checks, rows.Err() } -// GetMealsByDateRange retrieves meals within a specific date range -func (s *Store) GetMealsByDateRange(start, end time.Time) ([]models.Meal, error) { - return s.GetMeals(start, end) -} - // GetCardsByDateRange retrieves cards due within a specific date range, // including overdue cards (due before start) so they appear in the timeline. func (s *Store) GetCardsByDateRange(start, end time.Time) ([]models.Card, error) { diff --git a/internal/store/sqlite_test.go b/internal/store/sqlite_test.go index e8af436..55e1510 100644 --- a/internal/store/sqlite_test.go +++ b/internal/store/sqlite_test.go @@ -340,39 +340,6 @@ func TestGetGoogleTasksByDateRange_IncludesOverdue(t *testing.T) { } } -// setupTestStoreWithMeals creates a test store with meals table -func setupTestStoreWithMeals(t *testing.T) *Store { - t.Helper() - - tempDir := t.TempDir() - dbPath := filepath.Join(tempDir, "test.db") - - db, err := sql.Open("sqlite3", dbPath) - if err != nil { - t.Fatalf("Failed to open test database: %v", err) - } - - db.SetMaxOpenConns(1) - - store := &Store{db: db} - - schema := ` - CREATE TABLE IF NOT EXISTS meals ( - id TEXT PRIMARY KEY, - recipe_name TEXT NOT NULL, - date DATETIME, - meal_type TEXT, - recipe_url TEXT, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - ` - if _, err := db.Exec(schema); err != nil { - t.Fatalf("Failed to create schema: %v", err) - } - - return store -} - // TestDeleteCard verifies that DeleteCard removes a card from the cache func TestDeleteCard(t *testing.T) { store := setupTestStoreWithCards(t) @@ -615,38 +582,6 @@ func TestSaveAndGetBoards_ManyBoards(t *testing.T) { } } -func TestGetMealsByDateRange(t *testing.T) { - store := setupTestStoreWithMeals(t) - defer func() { _ = store.Close() }() - - now := time.Now() - tomorrow := now.Add(24 * time.Hour) - - meals := []models.Meal{ - {ID: "1", RecipeName: "Meal 1", Date: now, MealType: "lunch"}, - {ID: "2", RecipeName: "Meal 2", Date: tomorrow, MealType: "dinner"}, - } - - if err := store.SaveMeals(meals); err != nil { - t.Fatalf("Failed to save meals: %v", err) - } - - start := now.Add(-1 * time.Hour) - end := now.Add(1 * time.Hour) - - results, err := store.GetMealsByDateRange(start, end) - if err != nil { - t.Fatalf("GetMealsByDateRange failed: %v", err) - } - - if len(results) != 1 { - t.Errorf("Expected 1 meal, got %d", len(results)) - } - if results[0].ID != "1" { - t.Errorf("Expected meal 1, got %s", results[0].ID) - } -} - func TestGetCardsByDateRange(t *testing.T) { store := setupTestStoreWithCards(t) defer func() { _ = store.Close() }() -- cgit v1.2.3