diff options
-rw-r--r-- | frontend/templates/blocks/packages.gotmpl | 26 | ||||
-rw-r--r-- | frontend/templates/index.gotmpl | 6 | ||||
-rw-r--r-- | frontend/templates/opensearch.xml.gotmpl | 10 | ||||
-rw-r--r-- | internal/index/search.go | 8 | ||||
-rw-r--r-- | internal/server/mux.go | 208 | ||||
-rw-r--r-- | internal/server/templates.go | 11 |
6 files changed, 156 insertions, 113 deletions
diff --git a/frontend/templates/blocks/packages.gotmpl b/frontend/templates/blocks/packages.gotmpl new file mode 100644 index 0000000..68f9246 --- /dev/null +++ b/frontend/templates/blocks/packages.gotmpl @@ -0,0 +1,26 @@ +{{- define "hits" }} + {{- range . }} + {{- with .Data }} + <details id="{{ .Name }}"> + <summary> + <h3> + {{ .Name }} + </h3> + </summary> + {{ .Description }} + <dl> + {{- with .Version }} + <dt>Version</dt> + <dd>{{ . }}</dd> + {{- end }} + {{- with .Definition }} + <dt>Defined</dt> + <dd> + <a href="{{ . }}">Source</a> + </dd> + {{- end }} + </dl> + </details> + {{- end }} + {{- end }} +{{- end }} diff --git a/frontend/templates/index.gotmpl b/frontend/templates/index.gotmpl index 90f875b..0211a5b 100644 --- a/frontend/templates/index.gotmpl +++ b/frontend/templates/index.gotmpl @@ -13,8 +13,8 @@ <link rel="search" type="application/opensearchdescription+xml" - title="Searchix {{ $value.Name }}" - href="/options/{{ $key }}/opensearch.xml" + title="Searchix {{ sourceNameAndType $value }}" + href="/{{ .Importer }}/{{ $key }}/opensearch.xml" /> {{- end }} </head> @@ -25,7 +25,7 @@ {{- range $key, $value := .Sources }} <a {{ if eq $.Source.Name $value.Name }}class="current"{{ end }} - href="/options/{{ $key }}/search{{ and $.Query (printf "?query=%s" $.Query) }}" + href="/{{ .Importer }}/{{ $key }}/search{{ and $.Query (printf "?query=%s" $.Query) }}" > {{- $value.Name -}} </a> diff --git a/frontend/templates/opensearch.xml.gotmpl b/frontend/templates/opensearch.xml.gotmpl index 8d978ea..c761fd4 100644 --- a/frontend/templates/opensearch.xml.gotmpl +++ b/frontend/templates/opensearch.xml.gotmpl @@ -2,15 +2,15 @@ xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/" > - <ShortName>Searchix {{ .Source.Name }}</ShortName> - <LongName>Search {{ .Source.Name }} options with Searchix</LongName> - <Description>Search options {{ .Source.Name }}</Description> + <ShortName>Searchix {{ sourceNameAndType .Source }}</ShortName> + <LongName>Search {{ sourceNameAndType .Source }} with Searchix</LongName> + <Description>Search {{ sourceNameAndType .Source }}</Description> <Url type="text/html" method="get" - template="{{ .BaseURL }}/options/{{ .Source.Key }}/search?query={searchTerms}&from=opensearch" + template="{{ .BaseURL }}/{{ .Source.ImporterType }}/{{ .Source.Key }}/search?query={searchTerms}&from=opensearch" /> <moz:SearchForm - >{{ .BaseURL }}/options/{{ .Source.Key }}/search</moz:SearchForm + >{{ .BaseURL }}/{{ .Source.ImporterType }}/{{ .Source.Key }}/search</moz:SearchForm > </OpenSearchDescription> diff --git a/internal/index/search.go b/internal/index/search.go index a86cc02..5c18edb 100644 --- a/internal/index/search.go +++ b/internal/index/search.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/gob" + "searchix/internal/config" "searchix/internal/nix" "github.com/blevesearch/bleve/v2" @@ -15,7 +16,7 @@ const ResultsPerPage = 20 type DocumentMatch struct { search.DocumentMatch - Data nix.Option + Data nix.Importable } type Result struct { @@ -53,11 +54,12 @@ func (index *ReadIndex) GetSource(ctx context.Context, name string) (*bleve.Sear func (index *ReadIndex) Search( ctx context.Context, - source string, + source *config.Source, keyword string, from uint64, ) (*Result, error) { - sourceQuery := bleve.NewTermQuery(source) + sourceQuery := bleve.NewTermQuery(source.Key) + sourceQuery.SetField("Source") userQuery := bleve.NewMatchQuery(keyword) userQuery.Analyzer = "option_name" diff --git a/internal/server/mux.go b/internal/server/mux.go index 78fee60..27cd4bf 100644 --- a/internal/server/mux.go +++ b/internal/server/mux.go @@ -17,7 +17,6 @@ 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" @@ -51,7 +50,7 @@ type TemplateData struct { Message string } -type ResultData[T nix.Option] struct { +type ResultData struct { TemplateData Query string ResultsPerPage int @@ -109,131 +108,133 @@ func NewMux( }) const searchTimeout = 1 * time.Second - mux.HandleFunc("/options/{source}/search", func(w http.ResponseWriter, r *http.Request) { - sourceKey := r.PathValue("source") + createSearchHandler := func(importerType config.ImporterType) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var err error + source := cfg.Importer.Sources[r.PathValue("source")] + if source == nil || importerType != source.Importer { + errorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound) - source := cfg.Importer.Sources[sourceKey] - if source == nil { - errorHandler(w, r, "Source not found", http.StatusNotFound) - - return - } + return + } - ctx, cancel := context.WithTimeout(r.Context(), searchTimeout) - defer cancel() - - if r.URL.Query().Has("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 { - errorHandler(w, r, "Bad query string", http.StatusBadRequest) + ctx, cancel := context.WithTimeout(r.Context(), searchTimeout) + defer cancel() + + if r.URL.Query().Has("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 { + errorHandler(w, r, "Bad query string", http.StatusBadRequest) + } } - } - results, err := index.Search(ctx, sourceKey, qs, (page-1)*search.ResultsPerPage) - if err != nil { - if err == context.DeadlineExceeded { - errorHandler(w, r, "Search timed out", http.StatusInternalServerError) + results, err := index.Search(ctx, source, qs, (page-1)*search.ResultsPerPage) + if err != nil { + if err == context.DeadlineExceeded { + errorHandler(w, r, "Search timed out", http.StatusInternalServerError) - return + return + } + slog.Error("search error", "error", err) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) } - slog.Error("search error", "error", err) - errorHandler(w, r, err.Error(), http.StatusInternalServerError) - } - tdata := ResultData[nix.Option]{ - TemplateData: TemplateData{ - ExtraHeadHTML: cfg.Web.ExtraHeadHTML, - Source: *source, - Sources: cfg.Importer.Sources, - Version: *versionInfo, - }, - ResultsPerPage: search.ResultsPerPage, - Query: qs, - Results: results, - } + tdata := ResultData{ + TemplateData: TemplateData{ + ExtraHeadHTML: cfg.Web.ExtraHeadHTML, + Source: *source, + Sources: cfg.Importer.Sources, + Version: *versionInfo, + }, + 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 { - errorHandler(w, r, "Query string error", http.StatusBadRequest) + hits := uint64(len(results.Hits)) + if results.Total > hits { + q, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + errorHandler(w, r, "Query string error", http.StatusBadRequest) - return - } + return + } - if page*search.ResultsPerPage > results.Total { - errorHandler(w, r, "Not found", http.StatusNotFound) + if page*search.ResultsPerPage > results.Total { + errorHandler(w, r, "Not found", http.StatusNotFound) - return - } + return + } - if page*search.ResultsPerPage < results.Total { - q.Set("page", strconv.FormatUint(page+1, 10)) - tdata.Next = "search?" + q.Encode() - } + if page*search.ResultsPerPage < results.Total { + q.Set("page", strconv.FormatUint(page+1, 10)) + tdata.Next = "search?" + q.Encode() + } - if page > 1 { - p := page - 1 - if p == 1 { - q.Del("page") - } else { - q.Set("page", strconv.FormatUint(p, 10)) + if page > 1 { + p := page - 1 + if p == 1 { + q.Del("page") + } else { + q.Set("page", strconv.FormatUint(p, 10)) + } + tdata.Prev = "search?" + q.Encode() } - tdata.Prev = "search?" + q.Encode() } - } - w.Header().Add("Cache-Control", "max-age=300") - w.Header().Add("Vary", "Fetch") - if r.Header.Get("Fetch") == "true" { - w.Header().Add("Content-Type", "text/html; charset=utf-8") - err = templates["options"].ExecuteTemplate(w, "results", tdata) + w.Header().Add("Cache-Control", "max-age=300") + w.Header().Add("Vary", "Fetch") + if r.Header.Get("Fetch") == "true" { + w.Header().Add("Content-Type", "text/html; charset=utf-8") + err = templates[importerType.String()].ExecuteTemplate(w, "results", tdata) + } else { + err = templates[importerType.String()].Execute(w, tdata) + } + if err != nil { + slog.Error("template error", "template", importerType, "error", err) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) + } } else { - err = templates["options"].Execute(w, tdata) - } - if err != nil { - slog.Error("template error", "template", "options", "error", err) - errorHandler(w, r, err.Error(), http.StatusInternalServerError) - } - } else { - sourceResult, err := index.GetSource(ctx, sourceKey) - if err != nil { - errorHandler(w, r, err.Error(), http.StatusInternalServerError) + sourceResult, err := index.GetSource(ctx, source.Key) + if err != nil { + errorHandler(w, r, err.Error(), http.StatusInternalServerError) - return - } + return + } - w.Header().Add("Cache-Control", "max-age=14400") - err = templates["search"].Execute(w, TemplateData{ - ExtraHeadHTML: cfg.Web.ExtraHeadHTML, - Sources: cfg.Importer.Sources, - Source: *source, - SourceResult: sourceResult, - Version: *versionInfo, - }) - if err != nil { - errorHandler(w, r, err.Error(), http.StatusInternalServerError) + w.Header().Add("Cache-Control", "max-age=14400") + err = templates["search"].Execute(w, TemplateData{ + ExtraHeadHTML: cfg.Web.ExtraHeadHTML, + Sources: cfg.Importer.Sources, + Source: *source, + SourceResult: sourceResult, + Version: *versionInfo, + }) + if err != nil { + errorHandler(w, r, err.Error(), http.StatusInternalServerError) - return + return + } } } - }) + } - mux.HandleFunc( - "/options/{source}/opensearch.xml", - func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/options/{source}/search", createSearchHandler(config.Options)) + mux.HandleFunc("/packages/{source}/search", createSearchHandler(config.Packages)) + + createOpenSearchXMLHandler := func(importerType config.ImporterType) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { type openSearchData struct { BaseURL string Source *config.Source } - sourceKey := r.PathValue("source") - source := cfg.Importer.Sources[sourceKey] - if source == nil { - errorHandler(w, r, "Source not found", http.StatusNotFound) + source := cfg.Importer.Sources[r.PathValue("source")] + if source == nil || importerType != source.Importer { + errorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } @@ -256,8 +257,11 @@ func NewMux( http.StatusInternalServerError, ) } - }, - ) + } + } + + mux.HandleFunc("/options/{source}/opensearch.xml", createOpenSearchXMLHandler(config.Options)) + mux.HandleFunc("/packages/{source}/opensearch.xml", createOpenSearchXMLHandler(config.Packages)) fs := http.FileServer(http.FS(frontend.Files)) mux.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/server/templates.go b/internal/server/templates.go index 3d45167..8967599 100644 --- a/internal/server/templates.go +++ b/internal/server/templates.go @@ -8,6 +8,7 @@ import ( "log/slog" "path" "searchix/frontend" + "searchix/internal/config" "searchix/internal/nix" "strings" @@ -33,6 +34,16 @@ var templateFuncs = template.FuncMap{ return template.HTML(out.String()) // #nosec G203 }, + "sourceNameAndType": func(source config.Source) (string, error) { + switch source.Importer { + case config.Options: + return source.Name + " " + source.Importer.String(), nil + case config.Packages: + return source.Name, nil + default: + return "", errors.Errorf("unknown source importer type %s", source.Importer.String()) + } + }, "sourceName": func(input string) string { switch input { case "nixos": |