diff --git a/handler-details.go b/handler-details.go index 91286ab335890f3a0677053bce679468891f4b44..d486644744b477d376043fb25f24741b40e52212 100644 --- a/handler-details.go +++ b/handler-details.go @@ -3,6 +3,7 @@ package main import ( "bytes" "errors" + "fmt" "html/template" "io" "log" @@ -52,7 +53,66 @@ func (h Handler) img(w http.ResponseWriter, r *http.Request) { log.Printf("wrote image for %d: %d bytes", id, n) } +func (h Handler) detailsPost(w http.ResponseWriter, r *http.Request) { + log.Println("handling", r.Method, r.URL, "from", r.RemoteAddr) + + action := r.FormValue("action") + switch action { + case "save", "delete": + default: + httpError(w, "invalid parameters", fmt.Errorf("action missing or unknown: %q", action), http.StatusBadRequest) + return + } + + id, err := strconv.Atoi(r.FormValue("id")) + if err != nil { + httpError(w, "can't parse id", err, http.StatusBadRequest) + return + } + + if action == "delete" { + err = DeleteVino(r.Context(), h.db, id) + if err != nil { + httpError(w, "can't delete wine", err, http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + + name := r.FormValue("name") + if name == "" { + httpError(w, "name can't be empty", nil, http.StatusBadRequest) + return + } + + v, err := LoadVino(r.Context(), h.db, id) + if err != nil { + // Only log error here, just create a new entry. + log.Println("can't load vino with id", id, ":", err) + } + + v.Name = name + + err = v.Store(r.Context(), h.db, Update) + if err != nil { + httpError(w, "can't store wine", err, http.StatusInternalServerError) + return + } + + // TODO: Redirect to newly created/updated vino + + http.Redirect(w, r, "/details?id="+r.FormValue("id"), http.StatusSeeOther) + return +} + func (h Handler) details(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + h.detailsPost(w, r) + return + } + log.Println("handling", r.Method, r.URL, "from", r.RemoteAddr) if r.Method != "GET" { diff --git a/handler-index.go b/handler-index.go index a1ac263b1ed4e23f4d8501b7bc7dc0fc42c16485..aec3cd562bbb9b1305245f512f7f97b530c79416 100644 --- a/handler-index.go +++ b/handler-index.go @@ -46,11 +46,6 @@ func (h Handler) index(w http.ResponseWriter, r *http.Request) { } name := r.FormValue("name") - if name == "" { - httpError(w, "bad name", errors.New("empty or missing"), http.StatusBadRequest) - return - } - if len(name) > 80 { httpError(w, "bad name", errors.New("name too long, max length is 80"), http.StatusBadRequest) return @@ -112,7 +107,7 @@ func (h Handler) index(w http.ResponseWriter, r *http.Request) { vino.Picture = img } - err = vino.Store(r.Context(), h.db) + err = vino.Store(r.Context(), h.db, Add) if err != nil { httpError(w, fmt.Sprintf("can't store wine %q", vino), err, http.StatusInternalServerError) return diff --git a/main.go b/main.go index b9a914c3bcaa013b16236193330e1b9da0343000..0ab495736f39a9fca9f9b43580c59c969190970d 100644 --- a/main.go +++ b/main.go @@ -20,8 +20,13 @@ type Handler struct { } func httpError(w http.ResponseWriter, msg string, err error, status int) { - log.Println(msg+":", err) - http.Error(w, msg+": "+err.Error(), status) + if err != nil { + msg += ": " + err.Error() + } + + log.Println(msg) + + http.Error(w, msg, status) } func main() { diff --git a/migrations/0001-initial.sql b/migrations/0001-initial.sql index 9e4127566ce56efc754f411d1ca0ed00b32f9773..29d7eafe50b2b6ef25ba3e4c69c86fb7d0a69934 100644 --- a/migrations/0001-initial.sql +++ b/migrations/0001-initial.sql @@ -1,6 +1,5 @@ CREATE TABLE wines ( name TEXT, - rating INT, -- number of stars - picture BINARY, -- jpeg/png image of the label on the bottle - UNIQUE(name) + rating INT, -- number of stars + picture BINARY -- jpeg/png image of the label on the bottle ); \ No newline at end of file diff --git a/templates/details.tpl b/templates/details.tpl index 73609da04e952df0af03f38a97ac3fded5d3b89c..9e2d3a403177ef4bd609cd788a6ea59679532b06 100644 --- a/templates/details.tpl +++ b/templates/details.tpl @@ -33,9 +33,12 @@ <footer class="small pure-u-1"> <div class="maxwidth box"> - <!-- TODO: give these buttons values so that the Go code can decide what to do --> - <button type="submit" class="pure-button pure-button-primary">Save Changes</button> - <button type="submit" class="right pure-button button-warning">Delete</button> + <button type="submit" name="action" value="save" class="pure-button pure-button-primary"> + Save Changes + </button> + <button type="submit" name="action" value="delete" class="right pure-button button-warning"> + Delete + </button> </div> </footer> </form> diff --git a/templates/index.tpl b/templates/index.tpl index d7009d3fde1ef7b33c39fc8284aa55454f7d3b1d..d81ad1563f45df8abe09d182c0ea5ef582d20a25 100644 --- a/templates/index.tpl +++ b/templates/index.tpl @@ -10,7 +10,7 @@ <tbody> {{ range .Wines }} <tr> - <td>{{ .ID }}</td> + <td><a href="/details?id={{ .ID }}">{{ .ID }}</a></td> <td>{{ .Rating }}</td> <td><a href="/details?id={{ .ID }}">{{ .Name }}</a></td> </tr> @@ -27,7 +27,7 @@ <div class="pure-g"> <div class="pure-u-3-4"> <div class="pad-bottom pad-right"> - <input class="name" type="text" name="name" placeholder="Name" required> + <input class="name" type="text" name="name" placeholder="Name"> </div> <div class="upload-btn-wrapper"> <button class="pure-button">Select Picture</button> diff --git a/vino.go b/vino.go index af960eb4ade2b2f512192640ba6c77a683c404ef..eb4f2484e721310cf4a3d33b8a7bbcbddf487b3a 100644 --- a/vino.go +++ b/vino.go @@ -13,6 +13,13 @@ import ( "golang.org/x/image/draw" ) +type storeOperation int + +const ( + Add storeOperation = iota + Update +) + type Vino struct { ID int `db:"rowid"` Name string `db:"name"` @@ -20,6 +27,22 @@ type Vino struct { Picture image.Image `db:"-"` } +func DeleteVino(ctx context.Context, db *sqlx.DB, id int) error { + query, args, err := squirrel.Delete("wines"). + Where(squirrel.Eq{"rowid": id}). + ToSql() + if err != nil { + return err + } + + _, err = db.ExecContext(ctx, query, args...) + if err != nil { + return err + } + + return nil +} + func LoadVino(ctx context.Context, db *sqlx.DB, id int) (Vino, error) { cols := []string{"rowid", "name", "rating"} @@ -56,12 +79,14 @@ func (v Vino) String() string { return fmt.Sprintf("{Name: %q, Rating: %d}", v.Name, v.Rating) } -func (v Vino) Store(ctx context.Context, db *sqlx.DB) (err error) { +func (v Vino) Store(ctx context.Context, db *sqlx.DB, op storeOperation) (err error) { values := map[string]interface{}{ "name": v.Name, "rating": v.Rating, } + log.Println("image data:", values) + if v.Picture != nil { // Scale image down and encode as PNG // Get aspect ratio of incoming picture @@ -107,20 +132,21 @@ func (v Vino) Store(ctx context.Context, db *sqlx.DB) (err error) { } }() - _, err = squirrel.Insert("wines"). - SetMap(values). - RunWith(tx). - ExecContext(ctx) - - if err != nil { + if op == Update { // Looks like we need to do an update instead _, err = squirrel.Update("wines"). - Where("name = ?", v.Name). + Where("rowid = ?", v.ID). SetMap(values). RunWith(tx). ExecContext(ctx) + return err } + _, err = squirrel.Insert("wines"). + SetMap(values). + RunWith(tx). + ExecContext(ctx) + return err }