From 07db227557f0cb29e87c90fdc3d1c433ca14188f Mon Sep 17 00:00:00 2001
From: Gregor Best <gbe@unobtanium.de>
Date: Sun, 16 May 2021 19:34:45 +0200
Subject: [PATCH] Store ISO3 country codes

---
 handler-details.go    | 13 +++++++++---
 templates/details.tpl | 10 ++++-----
 templates/index.tpl   | 10 ++++++---
 vino.go               | 48 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 70 insertions(+), 11 deletions(-)

diff --git a/handler-details.go b/handler-details.go
index b048d7d..e7f6434 100644
--- a/handler-details.go
+++ b/handler-details.go
@@ -84,6 +84,12 @@ func (h Handler) detailsPost(w http.ResponseWriter, r *http.Request) {
 		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)
+	}
+
 	name := r.FormValue("name")
 	if name == "" {
 		httpError(w, "name can't be empty", nil, http.StatusBadRequest)
@@ -96,14 +102,15 @@ func (h Handler) detailsPost(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	v, err := LoadVino(r.Context(), h.db, id)
+	country, err := ISO3CountryFromString(r.FormValue("country"))
 	if err != nil {
-		// Only log error here, just create a new entry.
-		log.Println("can't load vino with id", id, ":", err)
+		httpError(w, "can't parse country", nil, http.StatusBadRequest)
+		return
 	}
 
 	v.Name = name
 	v.Rating = rating
+	v.Country = country
 
 	comment := strings.TrimSpace(r.FormValue("comment"))
 	if comment != "" {
diff --git a/templates/details.tpl b/templates/details.tpl
index 6f58e94..353041a 100644
--- a/templates/details.tpl
+++ b/templates/details.tpl
@@ -15,11 +15,6 @@
 			<input type="hidden" name="id" value="{{ .Wine.ID }}">
 
 			<fieldset>
-				<div class="pure-control-group">
-					<label for="form-id">ID</label>
-					<input type="text" id="form-id" value="{{ .Wine.ID }}" disabled>
-				</div>
-
 				<div  class="pure-control-group">
 					<label for="form-name">Name</label>
 					<input id="form-name" type="text" name="name" value="{{ .Wine.Name }}" placeholder="Name">
@@ -30,6 +25,11 @@
 					<input id="form-rating" type="number" name="rating" value="{{ .Wine.Rating }}" placeholder="Rating">
 				</div>
 
+				<div class="pure-control-group">
+					<label for="form-country">Country (ISO3)</label>
+					<input id="form-country" type="text" name="country" value="{{ .Wine.Country }}" placeholder="XXX" pattern="[A-Z]{3}">
+				</div>
+
 				<div class="pure-control-group">
 					<label for="form-new-picture">New picture</label>
 					<input id="form-new-picture" type="file" name="picture" accept="image/png, image/jpeg">
diff --git a/templates/index.tpl b/templates/index.tpl
index 2660329..3c82e49 100644
--- a/templates/index.tpl
+++ b/templates/index.tpl
@@ -8,13 +8,17 @@
 <div class="content withfooter pure-u-1">
 	<div class="maxwidth box">
 		<table class="pure-table">
-			<thead><tr><th>#</th><th>Rating</th><th class="fill">Name</th></tr></thead>
+			<thead><tr>
+				<th class="fill">Name</th>
+				<th>Rating</th>
+				<th>Country</th>
+			</tr></thead>
 			<tbody>
 			{{ range .Wines }}
 				<tr>
-					<td><a href="/details?id={{ .ID }}">{{ .ID }}</a></td>
-					<td>{{ .Rating }}</td>
 					<td><a href="/details?id={{ .ID }}">{{ .Name }}</a></td>
+					<td>{{ .Rating }}</td>
+					<td>{{ .Country }}</td>
 				</tr>
 			{{ end }}
 			</tbody>
diff --git a/vino.go b/vino.go
index ad69328..f4dfb4c 100644
--- a/vino.go
+++ b/vino.go
@@ -28,10 +28,46 @@ type Comment struct {
 	Content string
 }
 
+type ISO3CountryCode [3]byte
+
+var UnknownCountry = ISO3CountryCode{'X', 'X', 'X'}
+
+func ISO3CountryFromString(s string) (ISO3CountryCode, error) {
+	if len(s) == 0 {
+		return UnknownCountry, nil
+	}
+
+	if len(s) != 3 {
+		return UnknownCountry, errors.New("unexpected length")
+	}
+
+	return ISO3CountryCode{s[0], s[1], s[2]}, nil
+}
+
+func (i ISO3CountryCode) String() string {
+	return fmt.Sprintf("%c%c%c", i[0], i[1], i[2])
+}
+
+func (i *ISO3CountryCode) UnmarshalBinary(d []byte) error {
+	if len(d) == 0 {
+		*i = [3]byte{'X', 'X', 'X'}
+		return nil
+	}
+
+	if len(d) != 3 {
+		return errors.New("invalid length")
+	}
+
+	*i = [3]byte{d[0], d[1], d[2]}
+
+	return nil
+}
+
 type Vino struct {
 	ID         uuid.UUID
 	Name       string
 	Rating     int
+	Country    ISO3CountryCode
 	Picture    image.Image
 	HasPicture bool // Set to true if there's picture data for this vino
 	Comments   []Comment
@@ -74,6 +110,11 @@ func loadVino(tx *bolt.Tx, id uuid.UUID) (Vino, error) {
 		return v, err
 	}
 
+	err = v.Country.UnmarshalBinary(data.Get([]byte("country")))
+	if err != nil {
+		return v, err
+	}
+
 	comments := data.Bucket([]byte("comments"))
 	if comments != nil {
 		err := comments.ForEach(func(k, txt []byte) error {
@@ -283,6 +324,13 @@ func (v *Vino) Store(ctx context.Context, db *bolt.DB) error {
 			}
 		}
 
+		if v.Country != UnknownCountry {
+			err = wine.Put([]byte("country"), v.Country[:])
+			if err != nil {
+				return err
+			}
+		}
+
 		return nil
 	})
 }
-- 
GitLab