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:
puttaalu committed 2026-10-20 18:26:18 +02:00
1 parent f1e946228a
commit bb8c7109fa
11 files changed
+251 -17

No files matched your search

+10 -1
View File
@@ -100,7 +100,16 @@ func (s *Server) Listen() error {
mux.HandleFunc("POST /api/edit", s.guard(s.handleEdit))
mux.HandleFunc("POST /api/delete", s.guard(s.handleDelete))
mux.HandleFunc("POST /api/tags", s.guard(s.handleBulkTags))
mux.HandleFunc("GET /api/thumb/{slug}", s.guard(s.handleThumb))
// Thumbnails and the tag list — deliberately NOT guarded. Both serve the
// same public data as the static site (thumbs under /thumbs/, the tag list
// at /tags.json), so there is nothing to protect. Keeping them off the auth
// path means they can never return 401 and so can never feed an nginx
// auth-fail jail (fail2ban), which would otherwise ban the app over a
// per-row thumbnail load or an autocomplete poll. They stay under /api/
// only so nginx proxies them to the app, reaching non-browser clients past
// a front-door botwall on `location /`.
mux.HandleFunc("GET /api/thumb/{slug}", s.handleThumb)
mux.HandleFunc("GET /api/tags.json", s.handleTagsJSON)
mux.HandleFunc("GET /api/trash", s.guard(s.handleListTrash))
mux.HandleFunc("POST /api/restore", s.guard(s.handleRestore))
mux.HandleFunc("POST /api/trash/empty", s.guard(s.handleEmptyTrash))