Add tag autocomplete to the Android app
Both tag fields (post and edit) now suggest existing tags as you type,
matching the web upload/manage forms. Suggestions come from a new
GET /api/tags.json that serves the generated public/tags.json. It lives under
/api/ so nginx proxies it to the app, reaching non-browser clients even when a
front-door botwall gates the static site (the same approach as /api/thumb/).
/api/tags.json and /api/thumb/{slug} are both left unguarded: they serve the
same public data as the static site (/tags.json and /thumbs/), so there is
nothing to protect, and keeping them off the auth path means they can never
return 401 — otherwise an nginx auth-fail jail (fail2ban) would ban the app
over an autocomplete poll or a per-row thumbnail load.
A reusable TagField composable filters on the token after the last comma and
completes it on tap; fetchTags() returns an empty list on any failure, so the
field degrades to a plain tags input when the endpoint is absent.
This commit is contained in:
1 parent
f1e946228a
commit
bb8c7109fa
11 files changed
+251
-17
No files matched your search
@@ -59,11 +59,14 @@ func (s *Server) handleSession(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, map[string]bool{"authenticated": false})
|
||||
}
|
||||
|
||||
// handleThumb serves a photo's thumbnail from the generated output, behind the
|
||||
// same Basic auth as the rest of /api/. Non-browser clients (the Android app)
|
||||
// use this instead of the static /thumbs/<slug>.jpg path, because a front-door
|
||||
// botwall on `location /` can block a non-browser client from the static site
|
||||
// while `/api/` is proxied straight to this server.
|
||||
// handleThumb serves a photo's thumbnail from the generated output. Non-browser
|
||||
// clients (the Android app) use this instead of the static /thumbs/<slug>.jpg
|
||||
// path, because a front-door botwall on `location /` can block a non-browser
|
||||
// client from the static site while `/api/` is proxied straight to this server.
|
||||
// It is registered UNGUARDED (see server.go): thumbnails are the same public
|
||||
// data as the static /thumbs/, so there is nothing to protect, and staying off
|
||||
// the auth path means it can never 401 and so can never trip an auth-fail jail
|
||||
// that would ban the app over a per-row thumbnail load.
|
||||
func (s *Server) handleThumb(w http.ResponseWriter, r *http.Request) {
|
||||
slug := r.PathValue("slug")
|
||||
if slug == "" || slug != filepath.Base(slug) || strings.HasPrefix(slug, ".") {
|
||||
@@ -85,6 +88,30 @@ func (s *Server) handleThumb(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeContent(w, r, slug+".jpg", fi.ModTime(), f)
|
||||
}
|
||||
|
||||
// handleTagsJSON serves the generated tags.json under /api/ so the Android app
|
||||
// can offer tag autocomplete. It is registered UNGUARDED (see server.go): the
|
||||
// tag list is the same public data as the static /tags.json, so there is
|
||||
// nothing to protect, and staying off the auth path means it can never return
|
||||
// 401 and so can never trip an nginx auth-fail jail that would ban the app
|
||||
// over an autocomplete poll. It lives under /api/ only so nginx proxies it to
|
||||
// the app, reaching non-browser clients past a front-door botwall on
|
||||
// `location /` (the same botwall reason /api/thumb/ exists).
|
||||
func (s *Server) handleTagsJSON(w http.ResponseWriter, r *http.Request) {
|
||||
f, err := os.Open(filepath.Join(s.outputDir, "tags.json"))
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
http.ServeContent(w, r, "tags.json", fi.ModTime(), f)
|
||||
}
|
||||
|
||||
// handleManagePage serves the (auth-gated) edit/delete UI.
|
||||
func (s *Server) handleManagePage(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := assets.ReadFile("templates/manage.html")
|
||||
|
||||
Reference in new issue
Block a user