-
Notifications
You must be signed in to change notification settings - Fork 2
/
recipe.go
174 lines (149 loc) · 5.37 KB
/
recipe.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package models
import (
"database/sql"
"log"
"github.com/jmoiron/sqlx"
)
// RecipeModel provides functionality to edit and retrieve recipes.
type RecipeModel struct {
*Model
}
// Recipe is the primary model class for recipe storage and retrieval
type Recipe struct {
ID int64 `json:"id" db:"id"`
Name string `json:"name" db:"name"`
ServingSize string `json:"servingSize" db:"serving_size"`
NutritionInfo string `json:"nutritionInfo" db:"nutrition_info"`
Ingredients string `json:"ingredients" db:"ingredients"`
Directions string `json:"directions" db:"directions"`
SourceURL string `json:"sourceUrl" db:"source_url"`
AvgRating float64 `json:"averageRating" db:"avg_rating"`
MainImage RecipeImage `json:"mainImage"`
Tags []string `json:"tags"`
}
// Recipes represents a collection of Recipe objects
type Recipes []Recipe
func (m *RecipeModel) migrate(tx *sqlx.Tx) error {
if m.Model.currentDbVersion >= 3 && m.Model.previousDbVersion < 3 {
recipes := new(Recipes)
if err := m.db.Select(recipes, "SELECT id FROM recipe"); err != nil {
return err
}
for _, recipe := range *recipes {
log.Printf("[migrate] Processing recipe %d", recipe.ID)
if err := m.Model.Images.migrateImages(recipe.ID, tx); err != nil {
return err
}
}
}
return nil
}
// Create stores the recipe in the database as a new record using
// a dedicated transation that is committed if there are not errors.
func (m *RecipeModel) Create(recipe *Recipe) error {
return m.tx(func(tx *sqlx.Tx) error {
return m.CreateTx(recipe, tx)
})
}
// CreateTx stores the recipe in the database as a new record using
// the specified transaction.
func (m *RecipeModel) CreateTx(recipe *Recipe, tx *sqlx.Tx) error {
stmt := "INSERT INTO recipe (name, serving_size, nutrition_info, ingredients, directions, source_url) " +
"VALUES ($1, $2, $3, $4, $5, $6) RETURNING id"
err := tx.Get(recipe, stmt,
recipe.Name, recipe.ServingSize, recipe.NutritionInfo, recipe.Ingredients, recipe.Directions, recipe.SourceURL)
if err != nil {
return err
}
for _, tag := range recipe.Tags {
err := m.Tags.CreateTx(recipe.ID, tag, tx)
if err != nil {
return err
}
}
return nil
}
// Read retrieves the information about the recipe from the database, if found.
// If no recipe exists with the specified ID, a NoRecordFound error is returned.
func (m *RecipeModel) Read(id int64) (*Recipe, error) {
stmt := "SELECT " +
"r.id, r.name, r.serving_size, r.nutrition_info, r.ingredients, r.directions, r.source_url, COALESCE((SELECT g.rating FROM recipe_rating AS g WHERE g.recipe_id = r.id), 0) AS avg_rating " +
"FROM recipe AS r WHERE r.id = $1"
recipe := new(Recipe)
err := m.db.Get(recipe, stmt, id)
if err == sql.ErrNoRows {
return nil, ErrNotFound
} else if err != nil {
return nil, err
}
tags, err := m.Tags.List(id)
if err != nil {
return nil, err
}
recipe.Tags = *tags
return recipe, nil
}
// Update stores the specified recipe in the database by updating the
// existing record with the specified id using a dedicated transation
// that is committed if there are not errors.
func (m *RecipeModel) Update(recipe *Recipe) error {
return m.tx(func(tx *sqlx.Tx) error {
return m.UpdateTx(recipe, tx)
})
}
// UpdateTx stores the specified recipe in the database by updating the
// existing record with the sepcified id using the specified transaction.
func (m *RecipeModel) UpdateTx(recipe *Recipe, tx *sqlx.Tx) error {
_, err := tx.Exec(
"UPDATE recipe "+
"SET name = $1, serving_size = $2, nutrition_info = $3, ingredients = $4, directions = $5, source_url = $6 "+
"WHERE id = $7",
recipe.Name, recipe.ServingSize, recipe.NutritionInfo, recipe.Ingredients, recipe.Directions, recipe.SourceURL, recipe.ID)
if err != nil {
return err
}
// Deleting and recreating seems inefficent. Maybe make this smarter.
err = m.Tags.DeleteAllTx(recipe.ID, tx)
if err != nil {
return err
}
for _, tag := range recipe.Tags {
err = m.Tags.CreateTx(recipe.ID, tag, tx)
if err != nil {
return err
}
}
return nil
}
// Delete removes the specified recipe from the database using a dedicated transation
// that is committed if there are not errors. Note that this method does not delete
// any attachments that we associated with the deleted recipe.
func (m *RecipeModel) Delete(id int64) error {
return m.tx(func(tx *sqlx.Tx) error {
return m.DeleteTx(id, tx)
})
}
// DeleteTx removes the specified recipe from the database using the specified transaction.
// Note that this method does not delete any attachments that we associated with the deleted recipe.
func (m *RecipeModel) DeleteTx(id int64, tx *sqlx.Tx) error {
if _, err := tx.Exec("DELETE FROM recipe WHERE id = $1", id); err != nil {
return err
}
// If we successfully deleted the recipe, delete all of it's attachments
return m.Images.DeleteAllTx(id, tx)
}
// SetRating adds or updates the rating of the specified recipe.
func (m *RecipeModel) SetRating(id int64, rating float64) error {
var count int64
err := m.db.Get(&count, "SELECT count(*) FROM recipe_rating WHERE recipe_id = $1", id)
if err == sql.ErrNoRows || count == 0 {
_, err = m.db.Exec(
"INSERT INTO recipe_rating (recipe_id, rating) VALUES ($1, $2)", id, rating)
return err
}
if err == nil {
_, err = m.db.Exec(
"UPDATE recipe_rating SET rating = $1 WHERE recipe_id = $2", rating, id)
}
return err
}