apps/cellar/handlers_public.go 3.8 K raw
1
package main
2
3
import (
4
	"html/template"
5
	"net/http"
6
	"strings"
7
	"time"
8
)
9
10
func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
11
	wines, err := getCellarWines(a.DB)
12
	if err != nil {
13
		a.Log.Error("list wines", "err", err)
14
		http.Error(w, "Server error", http.StatusInternalServerError)
15
		return
16
	}
17
	out := make([]wineWithSVG, 0, len(wines))
18
	for _, wine := range wines {
19
		svg := buildPentagonSVG(wine.Sweetness, wine.Acidity, wine.Tannin, wine.Alcohol, wine.Body, 80.0, false)
20
		out = append(out, wineWithSVG{Wine: wine, PentagonSVG: template.HTML(svg)})
21
	}
22
	a.renderPage(w, "index.html", indexPageData{Wines: out})
23
}
24
25
func (a *App) wineDetail(w http.ResponseWriter, r *http.Request) {
26
	wine, err := getWineByShortID(a.DB, r.PathValue("short_id"))
27
	if err != nil {
28
		http.Error(w, "Server error", http.StatusInternalServerError)
29
		return
30
	}
31
	if wine == nil {
32
		http.Error(w, "Wine not found", http.StatusNotFound)
33
		return
34
	}
35
	pentagon := buildPentagonSVG(wine.Sweetness, wine.Acidity, wine.Tannin, wine.Alcohol, wine.Body, 250.0, true)
36
	bars := buildBarsSVG(wine.Clarity, wine.ColorIntensity, wine.AromaIntensity, wine.NoseComplexity, 250.0)
37
	a.renderPage(w, "wine.html", wineDetailPageData{Wine: *wine, PentagonSVG: template.HTML(pentagon), BarsSVG: template.HTML(bars)})
38
}
39
40
func (a *App) wineImage(w http.ResponseWriter, r *http.Request) {
41
	bytes, mime, err := getWineImage(a.DB, r.PathValue("short_id"))
42
	if err != nil {
43
		http.Error(w, "Server error", http.StatusInternalServerError)
44
		return
45
	}
46
	if bytes == nil {
47
		http.NotFound(w, r)
48
		return
49
	}
50
	if mime == "" {
51
		mime = "application/octet-stream"
52
	}
53
	w.Header().Set("Content-Type", mime)
54
	_, _ = w.Write(bytes)
55
}
56
57
func (a *App) wishlistHandler(w http.ResponseWriter, r *http.Request) {
58
	wines, err := getWishlistWines(a.DB)
59
	if err != nil {
60
		http.Error(w, "Server error", http.StatusInternalServerError)
61
		return
62
	}
63
	a.renderPage(w, "wishlist.html", wishlistPageData{Wines: wines, IsAdmin: a.Sessions.HasValid(r)})
64
}
65
66
func xmlEscape(s string) string {
67
	r := strings.NewReplacer("&", "&amp;", "<", "&lt;", ">", "&gt;", `"`, "&quot;", "'", "&apos;")
68
	return r.Replace(s)
69
}
70
71
func toRFC2822(sqliteTS string) string {
72
	if t, err := time.Parse("2006-01-02 15:04:05", sqliteTS); err == nil {
73
		return t.UTC().Format(time.RFC1123Z)
74
	}
75
	return sqliteTS
76
}
77
78
func (a *App) rssFeed(w http.ResponseWriter, r *http.Request) {
79
	wines, err := getCellarWines(a.DB)
80
	if err != nil {
81
		http.Error(w, "Server error", http.StatusInternalServerError)
82
		return
83
	}
84
	siteURL := a.SiteURL
85
86
	var items strings.Builder
87
	for _, wine := range wines {
88
		link := siteURL + "/wines/" + xmlEscape(wine.ShortID)
89
		title := xmlEscape(wine.Name)
90
		var parts []string
91
		if wine.Origin != "" {
92
			parts = append(parts, "Origin: "+wine.Origin)
93
		}
94
		if wine.Grape != "" {
95
			parts = append(parts, "Grape: "+wine.Grape)
96
		}
97
		if wine.Notes != "" {
98
			parts = append(parts, wine.Notes)
99
		}
100
		desc := xmlEscape(strings.Join(parts, " — "))
101
		pub := toRFC2822(wine.CreatedAt)
102
		items.WriteString("    <item>\n      <title>" + title + "</title>\n      <link>" + link + "</link>\n      <guid>" + link + "</guid>\n      <description>" + desc + "</description>\n      <pubDate>" + pub + "</pubDate>\n    </item>\n")
103
	}
104
105
	lastBuild := ""
106
	if len(wines) > 0 {
107
		lastBuild = toRFC2822(wines[0].CreatedAt)
108
	}
109
110
	out := `<?xml version="1.0" encoding="UTF-8"?>
111
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
112
  <channel>
113
    <title>` + xmlEscape(a.SiteTitle) + `</title>
114
    <link>` + siteURL + `</link>
115
    <description>` + xmlEscape(a.SiteDescription) + `</description>
116
    <lastBuildDate>` + lastBuild + `</lastBuildDate>
117
    <atom:link href="` + siteURL + `/feed.xml" rel="self" type="application/rss+xml"/>
118
` + items.String() + `  </channel>
119
</rss>`
120
	w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
121
	_, _ = w.Write([]byte(out))
122
}