From b2db89fc98b99e7792362688b59efa5280b694a2 Mon Sep 17 00:00:00 2001 From: Gregor Best <gbe@unobtanium.de> Date: Tue, 20 Apr 2021 23:10:23 +0200 Subject: [PATCH] Add comments --- handler-details.go | 14 +++++++++++ migrations/0002-comments.sql | 5 ++++ templates/details.tpl | 19 ++++++++++++--- vino.go | 45 +++++++++++++++++++++++++++++++++--- 4 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 migrations/0002-comments.sql diff --git a/handler-details.go b/handler-details.go index a19f0f7..634627a 100644 --- a/handler-details.go +++ b/handler-details.go @@ -9,6 +9,7 @@ import ( "log" "net/http" "strconv" + "strings" ) var detailsTemplate = template.Must(template.ParseFS(templateFS, "templates/base.tpl", "templates/details.tpl")) @@ -102,6 +103,19 @@ func (h Handler) detailsPost(w http.ResponseWriter, r *http.Request) { v.Name = name v.Rating = rating + comment := strings.TrimSpace(r.FormValue("comment")) + if comment != "" { + if len(comment) > 1024 { + comment = comment[:1024] + "..." + } + + err = v.StoreComment(r.Context(), h.db, comment) + if err != nil { + httpError(w, "can't add comment", err, http.StatusInternalServerError) + return + } + } + err = r.ParseMultipartForm(16 * 1024 * 1024) if err != nil { httpError(w, "can't parse multipart form data", err, http.StatusInternalServerError) diff --git a/migrations/0002-comments.sql b/migrations/0002-comments.sql new file mode 100644 index 0000000..14a5e59 --- /dev/null +++ b/migrations/0002-comments.sql @@ -0,0 +1,5 @@ +CREATE TABLE comments ( + content TEXT, + wine INTEGER, + FOREIGN KEY (wine) REFERENCES wines(rowid) +); \ No newline at end of file diff --git a/templates/details.tpl b/templates/details.tpl index a59053c..a16e783 100644 --- a/templates/details.tpl +++ b/templates/details.tpl @@ -35,14 +35,27 @@ <input id="form-new-picture" type="file" name="picture" accept="image/png, image/jpeg"> <!-- TODO: add a button to remove the image --> </div> + + <div class="pure-control-group"> + <label for="form-new-comment">Add comment</label> + <textarea id="form-new-comment" name="comment" placeholder="Etaoin Shrdlu"> + </textarea> + </div> </fieldset> - <details> - Etaoin Shrdlu, here be metadata + {{ if .Wine.Comments }} + <details open> + <summary>Comments</summary> + <ul> + {{ range .Wine.Comments }} + <li>{{ .Content }}</li> + {{ end }} + </ul> </details> + {{ end }} {{ if .Wine.HasPicture }} - <p>And here's a picture:<br/> + <p>Here's a picture:<br/> <img class="foto pure-img" src="/details/img?id={{ .Wine.ID }}"/> {{ end }} diff --git a/vino.go b/vino.go index 4d84c31..8922127 100644 --- a/vino.go +++ b/vino.go @@ -26,12 +26,17 @@ const ( Update ) +type Comment struct { + Content string `db:"content"` +} + type Vino struct { ID int `db:"rowid"` Name string `db:"name"` Rating int `db:"rating"` Picture image.Image `db:"-"` HasPicture bool `db:"has_picture"` // Set to true if there's picture data for this vino + Comments []Comment `db:"-"` } func DeleteVino(ctx context.Context, db *sqlx.DB, id int) error { @@ -51,7 +56,10 @@ func DeleteVino(ctx context.Context, db *sqlx.DB, id int) error { } func LoadVino(ctx context.Context, db *sqlx.DB, id int) (Vino, error) { - cols := []string{"rowid", "name", "rating", "case when picture is not null then true else false end as has_picture"} + cols := []string{ + "rowid", "name", "rating", + "CASE WHEN picture IS NOT NULL THEN true ELSE false END AS has_picture", + } query, args, err := squirrel.Select(cols...). From("wines"). @@ -61,8 +69,28 @@ func LoadVino(ctx context.Context, db *sqlx.DB, id int) (Vino, error) { return Vino{}, err } + tx, err := db.Beginx() + if err != nil { + return Vino{}, err + } + defer tx.Rollback() // The tx is readonly anyway, no need to commit anything + var v Vino - err = db.GetContext(ctx, &v, query, args...) + err = tx.GetContext(ctx, &v, query, args...) + if err != nil { + return v, err + } + + query, args, err = squirrel.Select("content"). + From("comments"). + Where(squirrel.Eq{"wine": id}). + OrderBy("rowid ASC"). + ToSql() + if err != nil { + return v, err + } + + err = tx.SelectContext(ctx, &v.Comments, query, args...) if err != nil { return v, err } @@ -85,7 +113,7 @@ func LoadPictureData(ctx context.Context, db *sqlx.DB, id int) ([]byte, error) { // AddPicture loads picture data (PNG or JPEG) from fh and sets v's picture to it. // If something goes wrong during loading, or the image is neither PNG nor JPEG, an error // is returned. If contentType is not the empty string, it is validated to be either -// image/png or image/jpeg +// image/png or image/jpeg. func (v *Vino) AddPicture(fh io.Reader, contentType string) error { switch contentType { case "", "image/jpeg", "image/png": @@ -196,6 +224,17 @@ func (v *Vino) Store(ctx context.Context, db *sqlx.DB, op storeOperation) (err e return nil } +func (v *Vino) StoreComment(ctx context.Context, db *sqlx.DB, comment string) error { + query := `INSERT INTO comments (content, wine) VALUES (?, ?)` + + _, err := db.ExecContext(ctx, query, comment, v.ID) + if err != nil { + return err + } + + return nil +} + func ListWines(ctx context.Context, db *sqlx.DB) ([]Vino, error) { query := `SELECT rowid, name, rating FROM wines ORDER BY rating DESC;` -- GitLab