about summary refs log tree commit diff stats
path: root/internal/server
diff options
context:
space:
mode:
authorAlan Pearce2024-05-08 00:15:52 +0200
committerAlan Pearce2024-05-08 00:16:03 +0200
commit973345ad50f9b237714fcb364cf7f665b3909f9d (patch)
tree15225430bd5895b5140df0e301b0e6c3fb5758a8 /internal/server
parentf459e84ecf7307fe2eeb7fbaa5b0c50613ec04f4 (diff)
downloadsearchix-973345ad50f9b237714fcb364cf7f665b3909f9d.tar.lz
searchix-973345ad50f9b237714fcb364cf7f665b3909f9d.tar.zst
searchix-973345ad50f9b237714fcb364cf7f665b3909f9d.zip
feat: paginate search results
Diffstat (limited to 'internal/server')
-rw-r--r--internal/server/server.go57
1 files changed, 52 insertions, 5 deletions
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)