package database import ( "database/sql" "fmt" "mealprep/models" "time" _ "github.com/mattn/go-sqlite3" ) var DB *sql.DB // InitDB initializes the database and creates tables func InitDB(dbPath string) error { var err error DB, err = sql.Open("sqlite3", dbPath) if err != nil { return fmt.Errorf("failed to open database: %w", err) } // Test connection if err = DB.Ping(); err != nil { return fmt.Errorf("failed to ping database: %w", err) } // Create tables if err = createTables(); err != nil { return fmt.Errorf("failed to create tables: %w", err) } // Run migrations if err = runMigrations(); err != nil { return fmt.Errorf("failed to run migrations: %w", err) } return nil } func createTables() error { schema := ` CREATE TABLE IF NOT EXISTS ingredients ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, unit TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS meals ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT, meal_type TEXT NOT NULL DEFAULT 'lunch' ); CREATE TABLE IF NOT EXISTS meal_ingredients ( meal_id INTEGER NOT NULL, ingredient_id INTEGER NOT NULL, quantity REAL NOT NULL, PRIMARY KEY (meal_id, ingredient_id), FOREIGN KEY (meal_id) REFERENCES meals(id) ON DELETE CASCADE, FOREIGN KEY (ingredient_id) REFERENCES ingredients(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS week_plan ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, meal_id INTEGER NOT NULL, FOREIGN KEY (meal_id) REFERENCES meals(id) ON DELETE CASCADE ); ` _, err := DB.Exec(schema) return err } func runMigrations() error { // Check if meal_type column exists var count int err := DB.QueryRow("SELECT COUNT(*) FROM pragma_table_info('meals') WHERE name='meal_type'").Scan(&count) if err != nil { return err } // Add meal_type column if it doesn't exist if count == 0 { _, err = DB.Exec("ALTER TABLE meals ADD COLUMN meal_type TEXT NOT NULL DEFAULT 'lunch'") if err != nil { return err } } return nil } // Ingredient operations func GetAllIngredients() ([]models.Ingredient, error) { rows, err := DB.Query("SELECT id, name, unit FROM ingredients ORDER BY name") if err != nil { return nil, err } defer rows.Close() var ingredients []models.Ingredient for rows.Next() { var ing models.Ingredient if err := rows.Scan(&ing.ID, &ing.Name, &ing.Unit); err != nil { return nil, err } ingredients = append(ingredients, ing) } return ingredients, nil } func AddIngredient(name, unit string) (int64, error) { result, err := DB.Exec("INSERT INTO ingredients (name, unit) VALUES (?, ?)", name, unit) if err != nil { return 0, err } return result.LastInsertId() } func DeleteIngredient(id int) error { _, err := DB.Exec("DELETE FROM ingredients WHERE id = ?", id) return err } // Meal operations func GetAllMeals() ([]models.Meal, error) { rows, err := DB.Query("SELECT id, name, description, meal_type FROM meals ORDER BY name") if err != nil { return nil, err } defer rows.Close() var meals []models.Meal for rows.Next() { var meal models.Meal if err := rows.Scan(&meal.ID, &meal.Name, &meal.Description, &meal.MealType); err != nil { return nil, err } meals = append(meals, meal) } return meals, nil } func GetMealByID(id int) (*models.Meal, error) { var meal models.Meal err := DB.QueryRow("SELECT id, name, description, meal_type FROM meals WHERE id = ?", id). Scan(&meal.ID, &meal.Name, &meal.Description, &meal.MealType) if err != nil { return nil, err } return &meal, nil } func AddMeal(name, description, mealType string) (int64, error) { result, err := DB.Exec("INSERT INTO meals (name, description, meal_type) VALUES (?, ?, ?)", name, description, mealType) if err != nil { return 0, err } return result.LastInsertId() } func DeleteMeal(id int) error { _, err := DB.Exec("DELETE FROM meals WHERE id = ?", id) return err } // Meal Ingredients operations func GetMealIngredients(mealID int) ([]models.MealIngredient, error) { query := ` SELECT mi.meal_id, mi.ingredient_id, mi.quantity, i.name, i.unit FROM meal_ingredients mi JOIN ingredients i ON mi.ingredient_id = i.id WHERE mi.meal_id = ? ORDER BY i.name ` rows, err := DB.Query(query, mealID) if err != nil { return nil, err } defer rows.Close() var ingredients []models.MealIngredient for rows.Next() { var mi models.MealIngredient if err := rows.Scan(&mi.MealID, &mi.IngredientID, &mi.Quantity, &mi.IngredientName, &mi.Unit); err != nil { return nil, err } ingredients = append(ingredients, mi) } return ingredients, nil } func AddMealIngredient(mealID, ingredientID int, quantity float64) error { _, err := DB.Exec( "INSERT OR REPLACE INTO meal_ingredients (meal_id, ingredient_id, quantity) VALUES (?, ?, ?)", mealID, ingredientID, quantity, ) return err } func DeleteMealIngredient(mealID, ingredientID int) error { _, err := DB.Exec("DELETE FROM meal_ingredients WHERE meal_id = ? AND ingredient_id = ?", mealID, ingredientID) return err } // Week Plan operations func GetWeekPlan() ([]models.WeekPlanEntry, error) { query := ` SELECT wp.id, wp.date, wp.meal_id, m.name, m.meal_type FROM week_plan wp JOIN meals m ON wp.meal_id = m.id ORDER BY wp.date, m.meal_type ` rows, err := DB.Query(query) if err != nil { return nil, err } defer rows.Close() var entries []models.WeekPlanEntry for rows.Next() { var entry models.WeekPlanEntry var dateStr string if err := rows.Scan(&entry.ID, &dateStr, &entry.MealID, &entry.MealName, &entry.MealType); err != nil { return nil, err } entry.Date, _ = time.Parse("2006-01-02", dateStr) entries = append(entries, entry) } return entries, nil } func AddWeekPlanEntry(date time.Time, mealID int) error { dateStr := date.Format("2006-01-02") _, err := DB.Exec("INSERT INTO week_plan (date, meal_id) VALUES (?, ?)", dateStr, mealID) return err } func DeleteWeekPlanEntry(id int) error { _, err := DB.Exec("DELETE FROM week_plan WHERE id = ?", id) return err } // Grocery List operations func GetGroceryList() ([]models.GroceryItem, error) { query := ` SELECT i.name, SUM(mi.quantity) as total_quantity, i.unit FROM week_plan wp JOIN meal_ingredients mi ON wp.meal_id = mi.meal_id JOIN ingredients i ON mi.ingredient_id = i.id GROUP BY i.id, i.name, i.unit ORDER BY i.name ` rows, err := DB.Query(query) if err != nil { return nil, err } defer rows.Close() var items []models.GroceryItem for rows.Next() { var item models.GroceryItem if err := rows.Scan(&item.IngredientName, &item.TotalQuantity, &item.Unit); err != nil { return nil, err } items = append(items, item) } return items, nil }