apps/library/handlers_web.go 7.2 K raw
1
package main
2
3
import (
4
	"net/http"
5
	"strconv"
6
	"strings"
7
8
	"github.com/stevedylandev/andromeda/pkg/auth"
9
	"github.com/stevedylandev/andromeda/pkg/web"
10
)
11
12
var sectionDefs = []struct {
13
	Slug   string
14
	Status string
15
}{
16
	{"reading", "reading"},
17
	{"read", "read"},
18
	{"want", "want"},
19
}
20
21
func bookToView(b Book) bookView {
22
	v := bookView{Title: b.Title, Authors: b.Authors}
23
	if b.CoverURL != nil {
24
		v.CoverURL = *b.CoverURL
25
	}
26
	if b.Notes != nil {
27
		v.Notes = *b.Notes
28
	}
29
	return v
30
}
31
32
func bookToRow(b Book) adminBookRow {
33
	r := adminBookRow{ID: b.ID, Title: b.Title, Authors: b.Authors, Status: b.Status}
34
	if b.ISBN != nil {
35
		r.ISBN = *b.ISBN
36
	}
37
	if b.CoverURL != nil {
38
		r.CoverURL = *b.CoverURL
39
	}
40
	if b.Notes != nil {
41
		r.Notes = *b.Notes
42
	}
43
	return r
44
}
45
46
func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
47
	all, _ := listBooks(a.DB, "")
48
	labels, _ := getCategoryLabels(a.DB)
49
50
	makeSection := func(status string) sectionView {
51
		sec := sectionView{Label: labelFor(labels, status)}
52
		for _, b := range all {
53
			if b.Status == status {
54
				sec.Books = append(sec.Books, bookToView(b))
55
			}
56
		}
57
		return sec
58
	}
59
60
	data := indexPageData{BaseURL: a.BaseURL, NavMode: a.DisplayMode == DisplayModeNav}
61
	if data.NavMode {
62
		selected := sectionDefs[0].Slug
63
		if q := r.URL.Query().Get("category"); q != "" {
64
			for _, s := range sectionDefs {
65
				if s.Slug == q {
66
					selected = q
67
					break
68
				}
69
			}
70
		}
71
		for _, s := range sectionDefs {
72
			data.NavCategories = append(data.NavCategories, navCategory{Slug: s.Slug, Label: labelFor(labels, s.Status), Active: s.Slug == selected})
73
		}
74
		for _, s := range sectionDefs {
75
			if s.Slug == selected {
76
				data.Sections = []sectionView{makeSection(s.Status)}
77
				break
78
			}
79
		}
80
	} else {
81
		for _, s := range sectionDefs {
82
			sec := makeSection(s.Status)
83
			if len(sec.Books) > 0 {
84
				data.Sections = append(data.Sections, sec)
85
			}
86
		}
87
	}
88
	web.Render(a.Templates, w, "index.html", data, a.Log)
89
}
90
91
func (a *App) loginGetHandler(w http.ResponseWriter, r *http.Request) {
92
	web.Render(a.Templates, w, "login.html", loginPageData{Error: r.URL.Query().Get("error")}, a.Log)
93
}
94
95
func (a *App) loginPostHandler(w http.ResponseWriter, r *http.Request) {
96
	if a.AdminPassword == "" {
97
		web.RedirectWithError(w, r, "/admin/login", "No admin password configured")
98
		return
99
	}
100
	if err := r.ParseForm(); err != nil {
101
		web.RedirectWithError(w, r, "/admin/login", "Bad request")
102
		return
103
	}
104
	if !auth.VerifyPassword(r.FormValue("password"), a.AdminPassword) {
105
		web.RedirectWithError(w, r, "/admin/login", "Invalid password")
106
		return
107
	}
108
	token, err := a.Sessions.Create()
109
	if err != nil {
110
		a.Log.Error("create session failed", "err", err)
111
		web.RedirectWithError(w, r, "/admin/login", "Session error")
112
		return
113
	}
114
	a.Sessions.PruneExpired()
115
	http.SetCookie(w, a.Sessions.SessionCookie(token))
116
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
117
}
118
119
func (a *App) logoutHandler(w http.ResponseWriter, r *http.Request) {
120
	if c, err := r.Cookie(a.Sessions.CookieName); err == nil && c.Value != "" {
121
		a.Sessions.Delete(c.Value)
122
	}
123
	http.SetCookie(w, a.Sessions.ClearCookie())
124
	http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
125
}
126
127
func (a *App) adminHandler(w http.ResponseWriter, r *http.Request) {
128
	all, _ := listBooks(a.DB, "")
129
	labels, _ := getCategoryLabels(a.DB)
130
	rows := make([]adminBookRow, 0, len(all))
131
	for _, b := range all {
132
		rows = append(rows, bookToRow(b))
133
	}
134
135
	libraryQuery := r.URL.Query().Get("q")
136
	searched := strings.TrimSpace(libraryQuery) != ""
137
	var results []adminBookRow
138
	if searched {
139
		found, _ := searchBooks(a.DB, libraryQuery)
140
		results = make([]adminBookRow, 0, len(found))
141
		for _, b := range found {
142
			results = append(results, bookToRow(b))
143
		}
144
	}
145
146
	web.Render(a.Templates, w, "admin.html", adminPageData{
147
		Success:         r.URL.Query().Get("success"),
148
		Error:           r.URL.Query().Get("error"),
149
		Books:           rows,
150
		Labels:          labels,
151
		LibraryQuery:    libraryQuery,
152
		LibraryResults:  results,
153
		LibrarySearched: searched,
154
	}, a.Log)
155
}
156
157
func (a *App) adminUpdateLabels(w http.ResponseWriter, r *http.Request) {
158
	if err := r.ParseForm(); err != nil {
159
		web.RedirectWithError(w, r, "/admin", "Bad request")
160
		return
161
	}
162
	reading := strings.TrimSpace(r.FormValue("reading"))
163
	read := strings.TrimSpace(r.FormValue("read"))
164
	want := strings.TrimSpace(r.FormValue("want"))
165
	if reading == "" || read == "" || want == "" {
166
		web.RedirectWithError(w, r, "/admin", "Labels cannot be empty")
167
		return
168
	}
169
	if err := setSetting(a.DB, "category_label.reading", reading); err != nil {
170
		web.RedirectWithError(w, r, "/admin", "Failed to save labels")
171
		return
172
	}
173
	_ = setSetting(a.DB, "category_label.read", read)
174
	_ = setSetting(a.DB, "category_label.want", want)
175
	web.RedirectWithSuccess(w, r, "/admin", "Labels updated")
176
}
177
178
func (a *App) adminSearch(w http.ResponseWriter, r *http.Request) {
179
	hits, err := googleBooksSearch(r.Context(), r.URL.Query().Get("q"), a.GoogleBooksKey)
180
	if err != nil {
181
		a.Log.Warn("google books search failed", "err", err)
182
		web.WriteError(w, http.StatusBadGateway, err.Error())
183
		return
184
	}
185
	if hits == nil {
186
		hits = []SearchHit{}
187
	}
188
	web.WriteJSON(w, http.StatusOK, hits)
189
}
190
191
func (a *App) adminAddBook(w http.ResponseWriter, r *http.Request) {
192
	if err := r.ParseForm(); err != nil {
193
		web.RedirectWithError(w, r, "/admin", "Bad request")
194
		return
195
	}
196
	status := r.FormValue("status")
197
	if !validStatus(status) {
198
		web.RedirectWithError(w, r, "/admin", "Invalid status")
199
		return
200
	}
201
	b := NewBook{Title: r.FormValue("title"), Authors: r.FormValue("authors"), Status: status}
202
	if v := strings.TrimSpace(r.FormValue("google_id")); v != "" {
203
		b.GoogleID = &v
204
	}
205
	if v := strings.TrimSpace(r.FormValue("isbn")); v != "" {
206
		b.ISBN = &v
207
	}
208
	if v := strings.TrimSpace(r.FormValue("cover_url")); v != "" {
209
		b.CoverURL = &v
210
	}
211
	if _, err := insertBook(a.DB, b); err != nil {
212
		a.Log.Error("insert book", "err", err)
213
		web.RedirectWithError(w, r, "/admin", "Failed to add book")
214
		return
215
	}
216
	web.RedirectWithSuccess(w, r, "/admin", "Book added")
217
}
218
219
func (a *App) adminUpdateStatus(w http.ResponseWriter, r *http.Request) {
220
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
221
	if err != nil {
222
		web.RedirectWithError(w, r, "/admin", "Bad id")
223
		return
224
	}
225
	if err := r.ParseForm(); err != nil {
226
		web.RedirectWithError(w, r, "/admin", "Bad request")
227
		return
228
	}
229
	status := r.FormValue("status")
230
	if !validStatus(status) {
231
		web.RedirectWithError(w, r, "/admin", "Invalid status")
232
		return
233
	}
234
	_ = updateBookStatus(a.DB, id, status)
235
	web.RedirectWithSuccess(w, r, "/admin", "Status updated")
236
}
237
238
func (a *App) adminUpdateNotes(w http.ResponseWriter, r *http.Request) {
239
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
240
	if err != nil {
241
		web.RedirectWithError(w, r, "/admin", "Bad id")
242
		return
243
	}
244
	if err := r.ParseForm(); err != nil {
245
		web.RedirectWithError(w, r, "/admin", "Bad request")
246
		return
247
	}
248
	notes := strings.TrimSpace(r.FormValue("notes"))
249
	var n *string
250
	if notes != "" {
251
		n = &notes
252
	}
253
	_ = updateBookNotes(a.DB, id, n)
254
	web.RedirectWithSuccess(w, r, "/admin", "Notes saved")
255
}
256
257
func (a *App) adminDeleteBook(w http.ResponseWriter, r *http.Request) {
258
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
259
	if err == nil {
260
		_ = deleteBook(a.DB, id)
261
	}
262
	web.RedirectWithSuccess(w, r, "/admin", "Book removed")
263
}