From d40c0e188a7fe1b36887f59c4a9958faa81b3d44 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Sat, 8 Jun 2024 20:31:47 +0200 Subject: feat: add detail pages for packages/options --- internal/config/importer-type.go | 11 ++++++ internal/index/search.go | 77 ++++++++++++++++++++++++++-------------- internal/nix/importable.go | 9 ++++- internal/server/mux.go | 62 ++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 27 deletions(-) (limited to 'internal') diff --git a/internal/config/importer-type.go b/internal/config/importer-type.go index 8f64d58..9bd50b6 100644 --- a/internal/config/importer-type.go +++ b/internal/config/importer-type.go @@ -25,6 +25,17 @@ func (i ImporterType) String() string { return fmt.Sprintf("Type(%d)", i) } +func (i ImporterType) Singular() string { + switch i { + case Packages: + return "package" + case Options: + return "option" + } + + return fmt.Sprintf("Type(%d)", i) +} + func ParseImporterType(name string) (ImporterType, error) { switch strcase.KebabCase(name) { case "packages": diff --git a/internal/index/search.go b/internal/index/search.go index 47f8ac1..3198afb 100644 --- a/internal/index/search.go +++ b/internal/index/search.go @@ -82,6 +82,42 @@ func setField( return q } +func (index *ReadIndex) search( + ctx context.Context, + request *bleve.SearchRequest, +) (*Result, error) { + request.Fields = []string{"_data"} + + bleveResult, err := index.index.SearchInContext(ctx, request) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err != nil { + return nil, errors.WithMessage(err, "failed to execute search query") + } + + results := make([]DocumentMatch, min(ResultsPerPage, bleveResult.Total)) + var buf bytes.Buffer + for i, result := range bleveResult.Hits { + _, err = buf.WriteString(result.Fields["_data"].(string)) + if err != nil { + return nil, errors.WithMessage(err, "error fetching result data") + } + err = gob.NewDecoder(&buf).Decode(&results[i].Data) + if err != nil { + return nil, errors.WithMessagef(err, "error decoding gob data: %s", buf.String()) + } + buf.Reset() + } + + return &Result{ + SearchResult: bleveResult, + Hits: results, + }, nil + } +} + func (index *ReadIndex) Search( ctx context.Context, source *config.Source, @@ -127,38 +163,27 @@ func (index *ReadIndex) Search( search := bleve.NewSearchRequest(query) search.Size = ResultsPerPage - search.Fields = []string{"_data"} if from != 0 { search.From = int(from) } - bleveResult, err := index.index.SearchInContext(ctx, search) - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - if err != nil { - return nil, errors.WithMessage(err, "failed to execute search query") - } + return index.search(ctx, search) +} - results := make([]DocumentMatch, min(ResultsPerPage, bleveResult.Total)) - var buf bytes.Buffer - for i, result := range bleveResult.Hits { - _, err = buf.WriteString(result.Fields["_data"].(string)) - if err != nil { - return nil, errors.WithMessage(err, "error fetching result data") - } - err = gob.NewDecoder(&buf).Decode(&results[i].Data) - if err != nil { - return nil, errors.WithMessagef(err, "error decoding gob data: %s", buf.String()) - } - buf.Reset() - } +func (index *ReadIndex) GetDocument( + ctx context.Context, + source *config.Source, + id string, +) (*nix.Importable, error) { + key := nix.MakeKey(source, id) + query := bleve.NewDocIDQuery([]string{key}) + search := bleve.NewSearchRequest(query) - return &Result{ - SearchResult: bleveResult, - Hits: results, - }, nil + result, err := index.search(ctx, search) + if err != nil { + return nil, err } + + return &result.Hits[0].Data, err } diff --git a/internal/nix/importable.go b/internal/nix/importable.go index 37532cd..67ba96d 100644 --- a/internal/nix/importable.go +++ b/internal/nix/importable.go @@ -1,6 +1,9 @@ package nix -import "encoding/gob" +import ( + "encoding/gob" + "searchix/internal/config" +) type Importable interface { BleveType() string @@ -12,6 +15,10 @@ func GetKey(i Importable) string { return i.BleveType() + "/" + i.GetSource() + "/" + i.GetName() } +func MakeKey(source *config.Source, id string) string { + return source.Importer.Singular() + "/" + source.Key + "/" + id +} + func init() { gob.Register(Option{}) gob.Register(Package{}) diff --git a/internal/server/mux.go b/internal/server/mux.go index 7bddbe5..79e24cd 100644 --- a/internal/server/mux.go +++ b/internal/server/mux.go @@ -18,6 +18,7 @@ import ( "searchix/frontend" "searchix/internal/config" search "searchix/internal/index" + "searchix/internal/nix" "github.com/blevesearch/bleve/v2" sentryhttp "github.com/getsentry/sentry-go/http" @@ -55,6 +56,12 @@ type ResultData struct { Next string } +type DocumentData struct { + TemplateData + Document *nix.Importable + Children *search.Result +} + var templates TemplateCollection func applyDevModeOverrides(cfg *config.Config) { @@ -222,6 +229,61 @@ func NewMux( mux.HandleFunc("/options/{source}/search", createSearchHandler(config.Options)) mux.HandleFunc("/packages/{source}/search", createSearchHandler(config.Packages)) + createSourceIDHandler := func(importerType config.ImporterType) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + source := cfg.Importer.Sources[r.PathValue("source")] + if source == nil || source.Importer != importerType { + errorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound) + + return + } + importerSingular := importerType.Singular() + + ctx, cancel := context.WithTimeout(r.Context(), searchTimeout) + defer cancel() + + doc, err := index.GetDocument(ctx, source, r.PathValue("id")) + if err != nil { + errorHandler( + w, + r, + http.StatusText(http.StatusInternalServerError), + http.StatusInternalServerError, + ) + + return + } + + if doc == nil { + errorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound) + + return + } + + tdata := DocumentData{ + TemplateData: TemplateData{ + ExtraHeadHTML: cfg.Web.ExtraHeadHTML, + Source: *source, + Sources: cfg.Importer.Sources, + Assets: frontend.Assets, + }, + Document: doc, + } + if r.Header.Get("Fetch") == "true" { + w.Header().Add("Content-Type", "text/html; charset=utf-8") + err = templates[importerSingular].ExecuteTemplate(w, "main", tdata) + } else { + err = templates[importerSingular].Execute(w, tdata) + } + if err != nil { + slog.Error("template error", "template", importerSingular, "error", err) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) + } + } + } + mux.HandleFunc("/options/{source}/{id}", createSourceIDHandler(config.Options)) + mux.HandleFunc("/packages/{source}/{id}", createSourceIDHandler(config.Packages)) + createOpenSearchXMLHandler := func(importerType config.ImporterType) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { type openSearchData struct { -- cgit 1.4.1