diff --git a/handler-details.go b/handler-details.go
index 023380a9dd745c0532e39596e1d49046d19f3081..23399dd62718ea85b99e23fe6d24d80bce193416 100644
--- a/handler-details.go
+++ b/handler-details.go
@@ -174,7 +174,7 @@ func (h Handler) detailsPost(w http.ResponseWriter, r *http.Request) {
 			},
 			Country: sql.NullString{
 				String: country.String(),
-				Valid:  true,
+				Valid:  country.Valid(),
 			},
 		}
 
diff --git a/storage/query/query.sql b/storage/query/query.sql
index 9258a34dd4ed73485005e415ce40c69091759731..672c3f29d900be6f2a373301700e37cad634be64 100644
--- a/storage/query/query.sql
+++ b/storage/query/query.sql
@@ -65,7 +65,7 @@ select wine_id, name, rating, country, cast(picture is not null as bool) as has_
  where wine_id = @wine_id;
 
 -- name: ListWines :many
-select wine_id, name, country, cast(picture is not null as bool) as has_picture from wines;
+select wine_id, name, rating, country, cast(picture is not null as bool) as has_picture from wines;
 
 -- name: InsertWine :execresult
 insert into wines (name, rating, country) values (@name, @rating, @country);
diff --git a/storage/query/query.sql.go b/storage/query/query.sql.go
index 349c20fdf2bf45d9339d91cd1b0e3441f9ff1f24..d721394cdcb723839511bb1f0b28724222651ac4 100644
--- a/storage/query/query.sql.go
+++ b/storage/query/query.sql.go
@@ -286,12 +286,13 @@ func (q *Queries) ListUsers(ctx context.Context) ([]string, error) {
 }
 
 const listWines = `-- name: ListWines :many
-select wine_id, name, country, cast(picture is not null as bool) as has_picture from wines
+select wine_id, name, rating, country, cast(picture is not null as bool) as has_picture from wines
 `
 
 type ListWinesRow struct {
 	WineID     int32
 	Name       string
+	Rating     sql.NullInt32
 	Country    sql.NullString
 	HasPicture bool
 }
@@ -308,6 +309,7 @@ func (q *Queries) ListWines(ctx context.Context) ([]ListWinesRow, error) {
 		if err := rows.Scan(
 			&i.WineID,
 			&i.Name,
+			&i.Rating,
 			&i.Country,
 			&i.HasPicture,
 		); err != nil {
diff --git a/storage/storage.go b/storage/storage.go
index 10c8672c47ee7e0f73a1bcfc900ed40cb34409cb..babf9eefeb28921cc003ed7cbcf6ead68d1f9c7c 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -5,7 +5,6 @@ import (
 	"context"
 	"database/sql"
 	"embed"
-	"errors"
 	"fmt"
 	"image"
 	"image/png"
@@ -15,106 +14,11 @@ import (
 
 	"git.c3pb.de/gbe/invinoveritas/log"
 	"git.c3pb.de/gbe/invinoveritas/storage/query"
-	"git.c3pb.de/gbe/invinoveritas/vino"
 	kitlog "github.com/go-kit/kit/log"
 	"github.com/go-kit/kit/log/level"
-	"github.com/jmoiron/sqlx"
 	"golang.org/x/image/draw"
 )
 
-var errNotFound = errors.New("not found")
-
-type Backend struct {
-	DB *sqlx.DB
-}
-
-func (b Backend) DeleteVino(ctx context.Context, id int) (err error) {
-	tx, err := b.DB.Begin()
-	if err != nil {
-		return err
-	}
-
-	defer func() {
-		if err != nil {
-			rerr := tx.Rollback()
-			if rerr != nil {
-				level.Error(log.GetContext(ctx)).
-					Log("error", rerr,
-						"msg", "can't roll back transaction")
-			}
-
-			return
-		}
-
-		err = tx.Commit()
-	}()
-
-	_, err = tx.ExecContext(ctx, `DELETE FROM comments WHERE wine = ?1`, id)
-	if err != nil {
-		return fmt.Errorf("deleting comments for %d: %w", id, err)
-	}
-
-	_, err = tx.ExecContext(ctx, `DELETE FROM wines WHERE id() = ?1`, id)
-	if err != nil {
-		return fmt.Errorf("deleting wine %d: %w", id, err)
-	}
-
-	return nil
-}
-
-func loadComments(ctx context.Context, v *vino.Vino, tx *sqlx.Tx) error {
-	err := tx.SelectContext(ctx, &v.Comments, `
-		SELECT id() as id, content
-		FROM comments
-		WHERE wine = ?1`, v.ID)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// loadVino uses the given read-only bolt transaction to load the data for the wine with the given id. When
-// there are no wines at all, or there is no wine with the given ID, loadVino returns errNotFound.
-func loadVino(ctx context.Context, tx *sqlx.Tx, id int) (vino.Vino, error) {
-	var v vino.Vino
-
-	err := tx.GetContext(ctx, &v, `
-		SELECT id() as id, name, rating, country, picture IS NOT NULL AS has_picture
-		FROM wines
-		WHERE id() = ?1`, id)
-	if err != nil {
-		return v, err
-	}
-
-	err = loadComments(ctx, &v, tx)
-	if err != nil {
-		return v, err
-	}
-
-	return v, nil
-}
-
-func (b Backend) LoadPictureData(ctx context.Context, id int) ([]byte, error) {
-	var data []byte
-	err := b.DB.GetContext(ctx, &data, `SELECT picture FROM wines WHERE id() = ?1`, id)
-	if errors.Is(err, sql.ErrNoRows) {
-		return nil, errNotFound
-	}
-
-	if err != nil {
-		return nil, err
-	}
-
-	if len(data) == 0 {
-		level.Error(log.GetContext(ctx)).
-			Log("msg", "zero length image data")
-		return nil, errNotFound
-	}
-
-	return data, nil
-}
-
 // AddPicture loads picture data (PNG or JPEG) from fh and persists a scaled down version in q.
 // 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
@@ -171,8 +75,8 @@ func AddPicture(ctx context.Context, q *query.Queries, wineID int, fh io.Reader,
 //go:embed migrations/*.sql
 var migrationFS embed.FS
 
-func Open(ctx context.Context, path string, logger kitlog.Logger) (*sqlx.DB, error) {
-	db, err := sqlx.Open("sqlite", path)
+func Open(ctx context.Context, path string, logger kitlog.Logger) (*sql.DB, error) {
+	db, err := sql.Open("sqlite", path)
 	if err != nil {
 		return nil, err
 	}
diff --git a/templates/details.tpl b/templates/details.tpl
index d9ebcb53eb6407f7e43ea33099c7fb414457c2c5..9beb897cfac8a13001b68e1bc6dcd2f2ad9f5932 100644
--- a/templates/details.tpl
+++ b/templates/details.tpl
@@ -38,7 +38,7 @@
 						Country (<a href="https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes#Current_ISO_3166_country_codes" target="_blank">ISO 3166 Alpha-2</a>)
 					</label>
 					{{ if .User.CanWrite }}
-						<input id="form-country" type="text" name="country" value="{{ .Wine.Country.String }}" placeholder="XX" pattern="[A-Z]{2}">
+						<input id="form-country" type="text" name="country" value="{{ .Wine.Country.String }}" placeholder="XX" pattern="[A-Za-z]{2}">
 					{{ else }}
 						<span id="form-country">{{ .Wine.Country.String }}</span>
 					{{ end }}
diff --git a/templates/index.tpl b/templates/index.tpl
index 029c8d8b1a8d86542e08cee21e9950883194bc90..2d7f6898f5ecad95ba952a10001a9793a5faff7b 100644
--- a/templates/index.tpl
+++ b/templates/index.tpl
@@ -23,12 +23,11 @@
 			<tbody>
 			{{ range .Wines }}
 				<tr>
-					<td><a href="/details?id={{ .ID }}">{{ .Name }}</a></td>
-					<td>{{ .Rating }}</td>
-					<td>{{ if eq .Country.String "XX" }}
-						{{ .Country }}
-					{{ else }}
-						<img src="/static/flags/{{ .Country }}.png"> ({{ .Country }})
+					<td><a href="/details?id={{ .WineID }}">{{ .Name }}</a></td>
+					<td>{{ .Rating.Int32 }}</td>
+					<td>{{ if .Country.Valid }}
+						<img src="/static/flags/{{ .Country.String }}.png">
+						({{ .Country.String }})
 					{{ end }}</td>
 				</tr>
 			{{ end }}
diff --git a/vino/vino.go b/vino/vino.go
index 9e07f3b673bbd55d1b103f5143ac70d5c9558844..89ad7b37e63323251cd25519c39b143f2f4f32e5 100644
--- a/vino/vino.go
+++ b/vino/vino.go
@@ -4,8 +4,7 @@ import (
 	"database/sql/driver"
 	"errors"
 	"fmt"
-	"image"
-	"io"
+	"strings"
 
 	// Imported for side effects to register format handlers
 	_ "image/jpeg"
@@ -30,6 +29,8 @@ func ISO2CountryCodeFromString(s string) (ISO2CountryCode, error) {
 		return UnknownCountry, errors.New("invalid length")
 	}
 
+	s = strings.ToUpper(s)
+
 	return ISO2CountryCode{s[0], s[1]}, nil
 }
 
@@ -41,6 +42,10 @@ func (i ISO2CountryCode) String() string {
 	return fmt.Sprintf("%c%c", i[0], i[1])
 }
 
+func (i ISO2CountryCode) Valid() bool {
+	return i.String() != "XX"
+}
+
 func (i *ISO2CountryCode) UnmarshalBinary(d []byte) error {
 	if len(d) == 0 {
 		*i = UnknownCountry
@@ -82,38 +87,3 @@ func (i *ISO2CountryCode) Scan(data interface{}) error {
 
 	return nil
 }
-
-type Vino struct {
-	ID         int             `db:"id"`
-	Name       string          `db:"name"`
-	Rating     int             `db:"rating"`
-	Country    ISO2CountryCode `db:"country"`
-	Picture    image.Image     `db:"-"`
-	HasPicture bool            `db:"has_picture"` // Set to true if there's picture data for this vino
-	Comments   []Comment       `db:"-"`
-}
-
-// 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.
-func (v *Vino) AddPicture(fh io.Reader, contentType string) error {
-	switch contentType {
-	case "", "image/jpeg", "image/png":
-	default:
-		return fmt.Errorf("unexpected content type for image: %q", contentType)
-	}
-
-	img, _, err := image.Decode(fh)
-	if err != nil {
-		return err
-	}
-
-	v.Picture = img
-
-	return nil
-}
-
-func (v Vino) String() string {
-	return fmt.Sprintf("{Name: %q, Rating: %d}", v.Name, v.Rating)
-}