package store // import ( // "context" // "encoding/json" // "fmt" // "strings" // "time" // "github.com/google/uuid" // "git.warky.dev/wdevs/amcs/internal/generatedmodels" // ext "git.warky.dev/wdevs/amcs/internal/types" // ) // func (db *DB) AddRecipe(ctx context.Context, r ext.Recipe) (ext.Recipe, error) { // ingredients, err := json.Marshal(r.Ingredients) // if err != nil { // return ext.Recipe{}, fmt.Errorf("marshal ingredients: %w", err) // } // instructions, err := json.Marshal(r.Instructions) // if err != nil { // return ext.Recipe{}, fmt.Errorf("marshal instructions: %w", err) // } // if r.Tags == nil { // r.Tags = []string{} // } // row := db.pool.QueryRow(ctx, ` // insert into recipes (name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags, rating, notes) // values ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8, $9, $10) // returning id, created_at, updated_at // `, r.Name, nullStr(r.Cuisine), r.PrepTimeMinutes, r.CookTimeMinutes, r.Servings, // ingredients, instructions, r.Tags, r.Rating, nullStr(r.Notes)) // created := r // var model generatedmodels.ModelPublicRecipes // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.Recipe{}, fmt.Errorf("insert recipe: %w", err) // } // created.ID = model.ID.UUID() // created.CreatedAt = model.CreatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time() // return created, nil // } // func (db *DB) SearchRecipes(ctx context.Context, query, cuisine string, tags []string, ingredient string) ([]ext.Recipe, error) { // args := []any{} // conditions := []string{} // if q := strings.TrimSpace(query); q != "" { // args = append(args, "%"+q+"%") // conditions = append(conditions, fmt.Sprintf("name ILIKE $%d", len(args))) // } // if c := strings.TrimSpace(cuisine); c != "" { // args = append(args, c) // conditions = append(conditions, fmt.Sprintf("cuisine = $%d", len(args))) // } // if len(tags) > 0 { // args = append(args, tags) // conditions = append(conditions, fmt.Sprintf("tags @> $%d", len(args))) // } // if ing := strings.TrimSpace(ingredient); ing != "" { // args = append(args, "%"+ing+"%") // conditions = append(conditions, fmt.Sprintf("ingredients::text ILIKE $%d", len(args))) // } // q := `select id, name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags::text[], rating, notes, created_at, updated_at from recipes` // if len(conditions) > 0 { // q += " where " + strings.Join(conditions, " and ") // } // q += " order by name" // rows, err := db.pool.Query(ctx, q, args...) // if err != nil { // return nil, fmt.Errorf("search recipes: %w", err) // } // defer rows.Close() // var recipes []ext.Recipe // for rows.Next() { // r, err := scanRecipeRow(rows) // if err != nil { // return nil, err // } // recipes = append(recipes, r) // } // return recipes, rows.Err() // } // func (db *DB) GetRecipe(ctx context.Context, id uuid.UUID) (ext.Recipe, error) { // row := db.pool.QueryRow(ctx, ` // select id, name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags::text[], rating, notes, created_at, updated_at // from recipes where id = $1 // `, id) // var model generatedmodels.ModelPublicRecipes // var tags []string // if err := row.Scan(&model.ID, &model.Name, &model.Cuisine, &model.PrepTimeMinutes, &model.CookTimeMinutes, &model.Servings, // &model.Ingredients, &model.Instructions, &tags, &model.Rating, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.Recipe{}, fmt.Errorf("get recipe: %w", err) // } // if tags == nil { // tags = []string{} // } // return recipeFromModel(model, tags), nil // } // func (db *DB) UpdateRecipe(ctx context.Context, id uuid.UUID, r ext.Recipe) (ext.Recipe, error) { // ingredients, err := json.Marshal(r.Ingredients) // if err != nil { // return ext.Recipe{}, fmt.Errorf("marshal ingredients: %w", err) // } // instructions, err := json.Marshal(r.Instructions) // if err != nil { // return ext.Recipe{}, fmt.Errorf("marshal instructions: %w", err) // } // if r.Tags == nil { // r.Tags = []string{} // } // _, err = db.pool.Exec(ctx, ` // update recipes set // name = $2, cuisine = $3, prep_time_minutes = $4, cook_time_minutes = $5, // servings = $6, ingredients = $7::jsonb, instructions = $8::jsonb, // tags = $9, rating = $10, notes = $11 // where id = $1 // `, id, r.Name, nullStr(r.Cuisine), r.PrepTimeMinutes, r.CookTimeMinutes, r.Servings, // ingredients, instructions, r.Tags, r.Rating, nullStr(r.Notes)) // if err != nil { // return ext.Recipe{}, fmt.Errorf("update recipe: %w", err) // } // return db.GetRecipe(ctx, id) // } // func (db *DB) CreateMealPlan(ctx context.Context, weekStart time.Time, entries []ext.MealPlanInput) ([]ext.MealPlanEntry, error) { // if _, err := db.pool.Exec(ctx, `delete from meal_plans where week_start = $1`, weekStart); err != nil { // return nil, fmt.Errorf("clear meal plan: %w", err) // } // var results []ext.MealPlanEntry // for _, e := range entries { // row := db.pool.QueryRow(ctx, ` // insert into meal_plans (week_start, day_of_week, meal_type, recipe_id, custom_meal, servings, notes) // values ($1, $2, $3, $4, $5, $6, $7) // returning id, created_at // `, weekStart, e.DayOfWeek, e.MealType, e.RecipeID, nullStr(e.CustomMeal), e.Servings, nullStr(e.Notes)) // entry := ext.MealPlanEntry{ // WeekStart: weekStart, // DayOfWeek: e.DayOfWeek, // MealType: e.MealType, // RecipeID: e.RecipeID, // CustomMeal: e.CustomMeal, // Servings: e.Servings, // Notes: e.Notes, // } // var model generatedmodels.ModelPublicMealPlans // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // return nil, fmt.Errorf("insert meal plan entry: %w", err) // } // entry.ID = model.ID.UUID() // entry.CreatedAt = model.CreatedAt.Time() // results = append(results, entry) // } // return results, nil // } // func (db *DB) GetMealPlan(ctx context.Context, weekStart time.Time) ([]ext.MealPlanEntry, error) { // rows, err := db.pool.Query(ctx, ` // select mp.id, mp.week_start, mp.day_of_week, mp.meal_type, mp.recipe_id, r.name, mp.custom_meal, mp.servings, mp.notes, mp.created_at // from meal_plans mp // left join recipes r on r.id = mp.recipe_id // where mp.week_start = $1 // order by // case mp.day_of_week // when 'monday' then 1 when 'tuesday' then 2 when 'wednesday' then 3 // when 'thursday' then 4 when 'friday' then 5 when 'saturday' then 6 // when 'sunday' then 7 else 8 // end, // case mp.meal_type // when 'breakfast' then 1 when 'lunch' then 2 when 'dinner' then 3 // when 'snack' then 4 else 5 // end // `, weekStart) // if err != nil { // return nil, fmt.Errorf("get meal plan: %w", err) // } // defer rows.Close() // var entries []ext.MealPlanEntry // for rows.Next() { // var model generatedmodels.ModelPublicMealPlans // var recipeName *string // if err := rows.Scan(&model.ID, &model.WeekStart, &model.DayOfWeek, &model.MealType, &model.RecipeID, &recipeName, &model.CustomMeal, &model.Servings, &model.Notes, &model.CreatedAt); err != nil { // return nil, fmt.Errorf("scan meal plan entry: %w", err) // } // entries = append(entries, mealPlanEntryFromModel(model, strVal(recipeName))) // } // return entries, rows.Err() // } // func (db *DB) GenerateShoppingList(ctx context.Context, weekStart time.Time) (ext.ShoppingList, error) { // entries, err := db.GetMealPlan(ctx, weekStart) // if err != nil { // return ext.ShoppingList{}, err // } // recipeIDs := map[uuid.UUID]bool{} // for _, e := range entries { // if e.RecipeID != nil { // recipeIDs[*e.RecipeID] = true // } // } // aggregated := map[string]*ext.ShoppingItem{} // for id := range recipeIDs { // recipe, err := db.GetRecipe(ctx, id) // if err != nil { // continue // } // for _, ing := range recipe.Ingredients { // key := strings.ToLower(ing.Name) // if existing, ok := aggregated[key]; ok { // if ing.Quantity != "" { // existing.Quantity += "+" + ing.Quantity // } // } else { // recipeIDCopy := id // aggregated[key] = &ext.ShoppingItem{ // Name: ing.Name, // Quantity: ing.Quantity, // Unit: ing.Unit, // Purchased: false, // RecipeID: &recipeIDCopy, // } // } // } // } // items := make([]ext.ShoppingItem, 0, len(aggregated)) // for _, item := range aggregated { // items = append(items, *item) // } // itemsJSON, err := json.Marshal(items) // if err != nil { // return ext.ShoppingList{}, fmt.Errorf("marshal shopping items: %w", err) // } // row := db.pool.QueryRow(ctx, ` // insert into shopping_lists (week_start, items) // values ($1, $2::jsonb) // on conflict (week_start) do update set items = excluded.items, updated_at = now() // returning id, created_at, updated_at // `, weekStart, itemsJSON) // var model generatedmodels.ModelPublicShoppingLists // list := ext.ShoppingList{WeekStart: weekStart, Items: items} // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.ShoppingList{}, fmt.Errorf("upsert shopping list: %w", err) // } // list.ID = model.ID.UUID() // list.CreatedAt = model.CreatedAt.Time() // list.UpdatedAt = model.UpdatedAt.Time() // return list, nil // } // func scanRecipeRow(rows interface{ Scan(...any) error }) (ext.Recipe, error) { // var model generatedmodels.ModelPublicRecipes // var tags []string // if err := rows.Scan(&model.ID, &model.Name, &model.Cuisine, &model.PrepTimeMinutes, &model.CookTimeMinutes, &model.Servings, // &model.Ingredients, &model.Instructions, &tags, &model.Rating, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // return ext.Recipe{}, fmt.Errorf("scan recipe: %w", err) // } // if tags == nil { // tags = []string{} // } // return recipeFromModel(model, tags), nil // }