diff options
author | Alan Pearce | 2024-05-08 00:15:52 +0200 |
---|---|---|
committer | Alan Pearce | 2024-05-08 00:16:03 +0200 |
commit | 973345ad50f9b237714fcb364cf7f665b3909f9d (patch) | |
tree | 15225430bd5895b5140df0e301b0e6c3fb5758a8 /internal | |
parent | f459e84ecf7307fe2eeb7fbaa5b0c50613ec04f4 (diff) | |
download | searchix-973345ad50f9b237714fcb364cf7f665b3909f9d.tar.lz searchix-973345ad50f9b237714fcb364cf7f665b3909f9d.tar.zst searchix-973345ad50f9b237714fcb364cf7f665b3909f9d.zip |
feat: paginate search results
Diffstat (limited to 'internal')
-rw-r--r-- | internal/search/search.go | 14 | ||||
-rw-r--r-- | internal/server/server.go | 57 |
2 files changed, 60 insertions, 11 deletions
diff --git a/internal/search/search.go b/internal/search/search.go index b449512..83a8365 100644 --- a/internal/search/search.go +++ b/internal/search/search.go @@ -15,7 +15,7 @@ import ( "github.com/pkg/errors" ) -const maxResults = 10 +const ResultsPerPage = 20 type Result[T options.NixOption] struct { *bleve.SearchResult @@ -93,9 +93,14 @@ func New[T options.NixOption](kind string) (*Index[T], error) { }, nil } -func (index *Index[T]) Search(ctx context.Context, keyword string) (*Result[T], error) { +func (index *Index[T]) Search(ctx context.Context, keyword string, from uint64) (*Result[T], error) { query := bleve.NewMatchQuery(keyword) search := bleve.NewSearchRequest(query) + search.Size = ResultsPerPage + + if from != 0 { + search.From = int(from) + } bleveResult, err := index.index.SearchInContext(ctx, search) select { @@ -106,13 +111,10 @@ func (index *Index[T]) Search(ctx context.Context, keyword string) (*Result[T], return nil, errors.WithMessage(err, "failed to execute search query") } - results := make([]T, min(maxResults, bleveResult.Total)) + results := make([]T, min(ResultsPerPage, bleveResult.Total)) for i, result := range bleveResult.Hits { doc, _ := index.docs.Load(result.ID) results[i] = doc.(T) - if i > maxResults { - break - } } return &Result[T]{ diff --git a/internal/server/server.go b/internal/server/server.go index 9649ad2..db40e6f 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -9,9 +9,11 @@ import ( "log/slog" "net" "net/http" + "net/url" "os" "path" "slices" + "strconv" "time" cfg "searchix/internal/config" @@ -63,8 +65,11 @@ type TemplateData struct { type ResultData[T options.NixOption] struct { TemplateData - Query string - Results *search.Result[T] + Query string + ResultsPerPage int + Results *search.Result[T] + Prev string + Next string } func applyDevModeOverrides(config *cfg.Config) { @@ -158,7 +163,16 @@ func New(runtimeConfig *Config) (*Server, error) { return } - results, err := index[source].Search(ctx, r.URL.Query().Get("query")) + qs := r.URL.Query().Get("query") + pg := r.URL.Query().Get("page") + var page uint64 = 1 + if pg != "" { + page, err = strconv.ParseUint(pg, 10, 64) + if err != nil || page == 0 { + http.Error(w, "Bad query string", http.StatusBadRequest) + } + } + results, err := index[source].Search(ctx, qs, (page-1)*search.ResultsPerPage) if err != nil { if err == context.DeadlineExceeded { http.Error(w, "Search timed out", http.StatusInternalServerError) @@ -173,9 +187,42 @@ func New(runtimeConfig *Config) (*Server, error) { LiveReload: jsSnippet, Source: source, }, - Query: r.URL.Query().Get("query"), - Results: results, + ResultsPerPage: search.ResultsPerPage, + Query: qs, + Results: results, } + + hits := uint64(len(results.Hits)) + if results.Total > hits { + q, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + http.Error(w, "Query string error", http.StatusBadRequest) + + return + } + + if page*search.ResultsPerPage > results.Total { + http.Error(w, "Not found", http.StatusNotFound) + + return + } + + if page*search.ResultsPerPage < results.Total { + q.Set("page", strconv.FormatUint(page+1, 10)) + tdata.Next = "results?" + q.Encode() + } + + if page > 1 { + p := page - 1 + if p == 1 { + q.Del("page") + } else { + q.Set("page", strconv.FormatUint(p, 10)) + } + tdata.Prev = "results?" + q.Encode() + } + } + if r.Header.Get("Fetch") == "true" { w.Header().Add("Content-Type", "text/html; charset=utf-8") err = templates["options"].ExecuteTemplate(w, "options.gotmpl", tdata) |