167 lines
4.7 KiB
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
|
|
}
|