go-recipe-book/pkg/db/recipe.go

167 lines
4.7 KiB
Go

package db
import (
"database/sql/driver"
"fmt"
"strings"
)
type RecipeID uint64
type RecipeInstructions []string
// Join the instructions with 0x1F, the "Unit Separator" ASCII character
func (ri RecipeInstructions) Value() (driver.Value, error) {
return strings.Join(ri, "\x1F"), nil
}
// Split the stored string by "Unit Separator" characters
func (ri *RecipeInstructions) Scan(src interface{}) error {
val, is_ok := src.(string)
if !is_ok {
return fmt.Errorf("incompatible type for RecipeInstructions list: %#v", src)
}
*ri = RecipeInstructions(strings.Split(val, "\x1F"))
return nil
}
type Recipe struct {
ID RecipeID `db:"rowid"`
Name string `db:"name"`
Blurb string `db:"blurb"`
Instructions RecipeInstructions `db:"instructions"`
Ingredients []Ingredient
ComputedFoodID FoodID `db:"computed_food_id"`
}
// Save the recipe. New recipes will have their ID back-filled from the DB.
//
// Automatically updates the computed food, creating one if it's a new recipe and back-filling the
// `computed_food_id` foreign key to it.
func (db *DB) SaveRecipe(r *Recipe) {
if r.ID == RecipeID(0) {
// Do create
// Create the computed food
computed_food := Food{Name: r.Name}
db.SaveFood(&computed_food)
r.ComputedFoodID = computed_food.ID
// Create the recipe
result, err := db.DB.NamedExec(`
insert into recipes (name, blurb, instructions, computed_food_id)
values (:name, :blurb, :instructions, :computed_food_id)
`, r)
if err != nil {
panic(err)
}
// Update the ID
id, err := result.LastInsertId()
if err != nil {
panic(err)
}
r.ID = RecipeID(id)
} else {
// Do update
result, err := db.DB.NamedExec(`
update recipes set name=:name, blurb=:blurb, instructions=:instructions where rowid = :rowid
`, r)
if err != nil {
panic(err)
}
count, err := result.RowsAffected()
if err != nil {
panic(err)
}
if count != 1 {
panic(fmt.Errorf("Got recipe with ID (%d), so attempted update, but it doesn't exist", r.ID))
}
}
for i := range r.Ingredients {
r.Ingredients[i].InRecipeID = r.ID
if r.Ingredients[i].ListOrder == 0 {
r.Ingredients[i].ListOrder = int64(i)
}
db.SaveIngredient(&r.Ingredients[i])
}
// Update the computed food
computed_food := r.ComputeFood()
db.SaveFood(&computed_food)
}
func (db *DB) GetRecipeByID(id RecipeID) (ret Recipe, err error) {
err = db.DB.Get(&ret, `
select rowid, name, blurb, instructions, computed_food_id
from recipes
where rowid = ?
`, id)
if err != nil {
return Recipe{}, fmt.Errorf("fetching recipe with ID %d: %w", id, err)
}
// Load the ingredients
err = db.DB.Select(&ret.Ingredients, `
select rowid, ifnull(food_id, 0) food_id, ifnull(recipe_id, 0) recipe_id, quantity, units,
in_recipe_id, list_order, is_hidden
from ingredients
where in_recipe_id = ?
order by list_order asc
`, id)
if err != nil {
panic(err)
}
for i := range ret.Ingredients {
if ret.Ingredients[i].FoodID != FoodID(0) {
// ingredient is a food
ret.Ingredients[i].Food, err = db.GetFoodByID(ret.Ingredients[i].FoodID)
} else {
// ingredient is a food; i.Food is the ComputedFood of the Recipe
var computed_food_id FoodID
err = db.DB.Get(&computed_food_id, `select computed_food_id from recipes where rowid = ?`, ret.Ingredients[i].RecipeID)
if err != nil {
panic(err)
}
ret.Ingredients[i].Food, err = db.GetFoodByID(computed_food_id)
}
if err != nil {
panic(err)
}
}
return
}
func (db *DB) GetAllRecipes() []Recipe {
var ret []Recipe
err := db.DB.Select(&ret, `select rowid, name, blurb, instructions, computed_food_id from recipes`)
if err != nil {
panic(err)
}
return ret
}
func (r Recipe) ComputeFood() Food {
// If r.ComputedFoodID is 0, so should be the ID of returned Food
ret := Food{ID: r.ComputedFoodID, Name: r.Name}
for _, ingr := range r.Ingredients {
ret.Cals += ingr.Quantity * ingr.Food.Cals
ret.Carbs += ingr.Quantity * ingr.Food.Carbs
ret.Protein += ingr.Quantity * ingr.Food.Protein
ret.Fat += ingr.Quantity * ingr.Food.Fat
ret.Sugar += ingr.Quantity * ingr.Food.Sugar
ret.Alcohol += ingr.Quantity * ingr.Food.Alcohol
ret.Water += ingr.Quantity * ingr.Food.Water
ret.Potassium += ingr.Quantity * ingr.Food.Potassium
ret.Calcium += ingr.Quantity * ingr.Food.Calcium
ret.Sodium += ingr.Quantity * ingr.Food.Sodium
ret.Magnesium += ingr.Quantity * ingr.Food.Magnesium
ret.Phosphorus += ingr.Quantity * ingr.Food.Phosphorus
ret.Iron += ingr.Quantity * ingr.Food.Iron
ret.Zinc += ingr.Quantity * ingr.Food.Zinc
ret.Mass += ingr.Quantity * ingr.Food.Mass
ret.Price += ingr.Quantity * ingr.Food.Price
}
return ret
}