From 0eb2ae488d7e7472217532ab1f3a6c4cd307605f Mon Sep 17 00:00:00 2001 From: Gregor Best <gbe@unobtanium.de> Date: Sun, 11 Apr 2021 23:45:41 +0200 Subject: [PATCH] Add template for detailed view --- handler-details.go | 50 ++++++++++++++++++++++ handler-index.go | 98 +++++++++++++++++++++++++++++++++++++++++++ main.go | 6 ++- templates/base.tpl | 18 ++++++++ templates/details.tpl | 9 ++++ templates/index.tpl | 2 +- vino.go | 23 ++++++++++ 7 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 handler-details.go create mode 100644 handler-index.go create mode 100644 templates/base.tpl create mode 100644 templates/details.tpl diff --git a/handler-details.go b/handler-details.go new file mode 100644 index 0000000..b2bc880 --- /dev/null +++ b/handler-details.go @@ -0,0 +1,50 @@ +package main + +import ( + "errors" + "html/template" + "log" + "net/http" + "strconv" +) + +var detailsTemplate = template.Must(template.ParseFS(templateFS, "templates/base.tpl", "templates/details.tpl")) + +func (h Handler) details(w http.ResponseWriter, r *http.Request) { + log.Println("handling", r.Method, r.URL, "from", r.RemoteAddr) + + if r.Method != "GET" { + httpError(w, "bad method", errors.New(r.Method), http.StatusMethodNotAllowed) + return + } + + args := r.URL.Query() + log.Println("args", args) + + if args.Get("id") == "" { + httpError(w, "bad request", errors.New("empty id"), http.StatusBadRequest) + return + } + + id, err := strconv.Atoi(args.Get("id")) + if err != nil { + httpError(w, "can't parse id", err, http.StatusBadRequest) + return + } + + v, err := LoadVino(r.Context(), h.db, id, false) + if err != nil { + httpError(w, "can't load wine data", err, http.StatusInternalServerError) + return + } + + data := struct { + Wine Vino + }{v} + + err = detailsTemplate.ExecuteTemplate(w, "details.tpl", data) + if err != nil { + log.Println("can't execute details template:", err) + return + } +} diff --git a/handler-index.go b/handler-index.go new file mode 100644 index 0000000..92b9a07 --- /dev/null +++ b/handler-index.go @@ -0,0 +1,98 @@ +package main + +import ( + "errors" + "fmt" + "html/template" + "io/ioutil" + "log" + "net/http" + "strconv" +) + +var indexTemplate = template.Must(template.ParseFS(templateFS, "templates/base.tpl", "templates/index.tpl")) + +func (h Handler) index(w http.ResponseWriter, r *http.Request) { + log.Println("handling", r.Method, r.URL, "from", r.RemoteAddr) + + if r.Method == "GET" { + wines, err := ListWines(r.Context(), h.db) + if err != nil { + httpError(w, "can't list wines", err, http.StatusInternalServerError) + return + } + + w.Header().Add("content-type", "text/html") + + data := struct { + Wines []Vino + }{wines} + + err = indexTemplate.ExecuteTemplate(w, "index.tpl", data) + if err != nil { + log.Println("can't execute index template:", err) + } + + return + } + + if r.Method != "POST" { + httpError(w, "invalid method", errors.New(r.Method), http.StatusMethodNotAllowed) + return + } + + 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 + } + + ratingVal := r.FormValue("rating") + + var ( + rating int + err error + ) + + if ratingVal != "" { + rating, err = strconv.Atoi(ratingVal) + if err != nil { + httpError(w, "can't convert rating", err, http.StatusBadRequest) + return + } + } + + picFile, picHdr, err := r.FormFile("picture") + if err != nil { + httpError(w, "can't open picture file", err, http.StatusInternalServerError) + return + } + defer picFile.Close() + + log.Println("picture", picHdr.Size, picHdr.Header.Get("Content-Type")) + + picData, err := ioutil.ReadAll(picFile) + if err != nil { + httpError(w, "can't read picture file", err, http.StatusInternalServerError) + return + } + + vino := Vino{ + Name: name, + Rating: rating, + Picture: picData, + } + + err = vino.Store(r.Context(), h.db) + if err != nil { + httpError(w, fmt.Sprintf("can't store wine %q", vino), err, http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusSeeOther) // TODO: Is this the correct status? +} diff --git a/main.go b/main.go index 6c983c6..046a765 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "embed" - "html/template" "log" "net/http" @@ -12,11 +11,14 @@ import ( //go:embed templates/*.tpl var templateFS embed.FS -var templates = template.Must(template.ParseFS(templateFS, "templates/base.tpl", "templates/index.tpl")) //go:embed static/* var staticFS embed.FS +type Handler struct { + db *sqlx.DB +} + func httpError(w http.ResponseWriter, msg string, err error, status int) { log.Println(msg+":", err) http.Error(w, msg+": "+err.Error(), status) diff --git a/templates/base.tpl b/templates/base.tpl new file mode 100644 index 0000000..e518942 --- /dev/null +++ b/templates/base.tpl @@ -0,0 +1,18 @@ +{{ define "base" }} +<!doctype html> +<html> +<head> + <style type="text/css"> + span.todo { + background: red; + } + </style> + <title>In Vino Veritas {{ block "title" . }}{{ end }}</title> +</head> +<body> +{{ block "content" . }} +TODO +{{ end }} +</body> +</html> +{{ end }} \ No newline at end of file diff --git a/templates/details.tpl b/templates/details.tpl new file mode 100644 index 0000000..cf4d4e2 --- /dev/null +++ b/templates/details.tpl @@ -0,0 +1,9 @@ +{{ template "base" . }} + +{{ define "title" }}Details for {{ .Wine.Name }}{{ end }} + +{{ define "content" }} + {{ with .Wine }} + {{ .ID }} - {{ .Name }} - {{ .Rating }} + {{ end }} +{{ end }} \ No newline at end of file diff --git a/templates/index.tpl b/templates/index.tpl index 3996bd4..d087e16 100644 --- a/templates/index.tpl +++ b/templates/index.tpl @@ -19,7 +19,7 @@ {{ range .Wines }} <tr> <td>{{ .ID }}</td> - <td><a href="/details/{{ .ID }}">{{ .Name }}</a></td> + <td><a href="/details?id={{ .ID }}">{{ .Name }}</a></td> <td>{{ .Rating }}</td> </tr> {{ end }} diff --git a/vino.go b/vino.go index 0a4cb6c..90c34f7 100644 --- a/vino.go +++ b/vino.go @@ -16,6 +16,29 @@ type Vino struct { Picture []byte `db:"picture"` } +func LoadVino(ctx context.Context, db *sqlx.DB, id int, withImage bool) (Vino, error) { + cols := []string{"rowid", "name", "rating"} + if withImage { + cols = append(cols, "picture") + } + + query, args, err := squirrel.Select(cols...). + From("wines"). + Where(squirrel.Eq{"rowid": id}). + ToSql() + if err != nil { + return Vino{}, err + } + + var v Vino + err = db.GetContext(ctx, &v, query, args...) + if err != nil { + return v, err + } + + return v, nil +} + func (v Vino) String() string { return fmt.Sprintf("{Name: %q, Rating: %d}", v.Name, v.Rating) } -- GitLab