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

+36
View File
@@ -330,3 +330,39 @@ func TestHandleListPhotos(t *testing.T) {
t.Errorf("foo tags = %v", foo.Tags)
}
}
func TestHandleTagsJSON(t *testing.T) {
s, _, output := newTestServer(t)
rec := httptest.NewRecorder()
s.handleTagsJSON(rec, httptest.NewRequest(http.MethodGet, "/api/tags.json", nil))
if rec.Code != http.StatusOK {
t.Fatalf("tags.json: code=%d", rec.Code)
}
if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
t.Errorf("content-type = %q", ct)
}
var tags []struct {
Tag string `json:"tag"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &tags); err != nil {
t.Fatalf("decode: %v", err)
}
got := map[string]bool{}
for _, tg := range tags {
got[tg.Tag] = true
}
if !got["berlin"] || !got["street"] {
t.Errorf("tags = %v, want berlin+street", tags)
}
// Missing file → 404.
if err := os.Remove(filepath.Join(output, "tags.json")); err != nil {
t.Fatal(err)
}
rec = httptest.NewRecorder()
s.handleTagsJSON(rec, httptest.NewRequest(http.MethodGet, "/api/tags.json", nil))
if rec.Code != http.StatusNotFound {
t.Errorf("missing tags.json: code=%d, want 404", rec.Code)
}
}