From b96e1e19dfe39f42c2e3b6cd2e28056d4c418de2 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Thu, 1 Oct 2026 13:59:36 -0400 Subject: [PATCH] ddd --- main.go | 18 +++++++++++++ public/app.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ sqlite.go | 2 +- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 743af45..19e64c0 100644 --- a/main.go +++ b/main.go @@ -131,6 +131,24 @@ func main() { } writeJSON(w, http.StatusOK, map[string][]flashcardSummary{"flashcards": summaries}) }))) + mux.Handle("/api/v1/flashcards/search", withAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"}) + return + } + query := r.URL.Query().Get("q") + if query == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "missing query parameter 'q'"}) + return + } + results, err := searchEngine.Search(r.Context(), query, 50) + if err != nil { + logger.Error("search flashcards", zap.Error(err)) + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"}) + return + } + writeJSON(w, http.StatusOK, map[string]any{"results": results, "query": query}) + }))) mux.Handle("/api/v1/flashcards/", withAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"}) diff --git a/public/app.js b/public/app.js index 6c5879c..f8ee93b 100644 --- a/public/app.js +++ b/public/app.js @@ -1,6 +1,7 @@ import m from "https://esm.sh/mithril@2.3.8"; const API = "/api/v1/flashcards"; +const SEARCH_API = "/api/v1/flashcards/search"; const AUTH = "/api/v1/auth"; const USERS = "/api/v1/users"; const SESSIONS = "/api/v1/sessions"; @@ -221,6 +222,8 @@ const List = { oninit: async function (v) { v.state.cards = []; v.state.loading = true; + v.state.query = ""; + v.state.searching = false; const res = await fetch(API); const data = await res.json(); v.state.cards = data.flashcards || []; @@ -235,10 +238,78 @@ const List = { m("p.cyber-text-muted.cyber-mt-md", "loading\u2026"), ]); } + + const doSearch = async () => { + const q = v.state.query.trim(); + if (!q) { + v.state.searching = true; + const res = await fetch(API); + const data = await res.json(); + v.state.cards = data.flashcards || []; + v.state.searching = false; + v.state.page = 1; + m.redraw(); + return; + } + v.state.searching = true; + m.redraw(); + const res = await fetch(SEARCH_API + "?q=" + encodeURIComponent(q)); + const data = await res.json(); + v.state.cards = (data.results || []).map((r) => ({ + id: r.id, + version: r.version, + note: r.note, + tags: r.tags, + host: r.host, + user: r.user, + score: r.score, + })); + v.state.searching = false; + v.state.page = 1; + m.redraw(); + }; + const totalPages = Math.max(1, Math.ceil(v.state.cards.length / PAGE_SIZE)); const page = Math.min(v.state.page, totalPages); const slice = v.state.cards.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); return m(".cyber-p-lg", { style: "max-width:720px;margin:0 auto" }, [ + m( + "form.cyber-flex.cyber-gap-sm.cyber-mb-md", + { + onsubmit: (e) => { + e.preventDefault(); + doSearch(); + }, + }, + [ + m("input.cyber-input", { + style: "flex:1", + placeholder: "Search flashcards\u2026", + value: v.state.query, + oninput: (e) => { + v.state.query = e.target.value; + }, + }), + m( + "button.cyber-btn.cyber-btn--primary", + { type: "submit", disabled: v.state.searching }, + v.state.searching ? "..." : "Search", + ), + v.state.query + ? m( + "button.cyber-btn.cyber-btn--ghost", + { + type: "button", + onclick: () => { + v.state.query = ""; + doSearch(); + }, + }, + "Clear", + ) + : null, + ], + ), m(Pager, { page, total: totalPages, diff --git a/sqlite.go b/sqlite.go index 89c2a03..a2a24ba 100644 --- a/sqlite.go +++ b/sqlite.go @@ -216,7 +216,7 @@ func (s *sqliteStore) listAll(ctx context.Context) ([]flashcardSummary, error) { WHERE f.version = (SELECT MAX(f2.version) FROM flashcard f2 WHERE f2.public_id = f.public_id) AND f.delete_timestamp IS NULL AND f.public_id != 0 - ORDER BY f.public_id`, + ORDER BY f.created DESC`, ) if err != nil { return nil, fmt.Errorf("list: %w", err)