diff --git a/cmd/kafka/main.go b/cmd/kafka/main.go index f16aa96..cdc81b5 100644 --- a/cmd/kafka/main.go +++ b/cmd/kafka/main.go @@ -77,7 +77,7 @@ func main() { acSvc := autocomplete.NewService(cfg.Upstream.URL, cfg.HTTPTimeout()) - h := httpapi.NewHandler(svc, acSvc.Suggestions) + h := httpapi.NewHandler(svc, acSvc.Suggestions, cfg.Server.SourceURL) mux := http.NewServeMux() mux.HandleFunc("/", h.Index) diff --git a/config.example.toml b/config.example.toml index 34f60a6..042bb63 100644 --- a/config.example.toml +++ b/config.example.toml @@ -14,6 +14,11 @@ http_timeout = "10s" # Example: "https://search.example.com" base_url = "" +# Link to the source code (shown in footer as "Source" link) +# Defaults to the upstream kafka repo if not set. +# Example: "https://git.example.com/my-kafka-fork" +source_url = "" + [upstream] # URL of an upstream metasearch instance for unported engines (env: UPSTREAM_SEARXNG_URL) # Leave empty to run without an upstream proxy. diff --git a/internal/config/config.go b/internal/config/config.go index 2644c70..e5d1fbb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -40,7 +40,8 @@ type Config struct { type ServerConfig struct { Port int `toml:"port"` HTTPTimeout string `toml:"http_timeout"` - BaseURL string `toml:"base_url"` // Public URL for OpenSearch XML (e.g. "https://search.example.com") + BaseURL string `toml:"base_url"` // Public URL for OpenSearch XML (e.g. "https://search.example.com") + SourceURL string `toml:"source_url"` // Link to the source code (e.g. "https://git.example.com/fork/kafka") } type UpstreamConfig struct { diff --git a/internal/httpapi/handlers.go b/internal/httpapi/handlers.go index 9e62c1b..cc19b4b 100644 --- a/internal/httpapi/handlers.go +++ b/internal/httpapi/handlers.go @@ -30,12 +30,14 @@ import ( type Handler struct { searchSvc *search.Service autocompleteSvc func(ctx context.Context, query string) ([]string, error) + sourceURL string } -func NewHandler(searchSvc *search.Service, autocompleteSuggestions func(ctx context.Context, query string) ([]string, error)) *Handler { +func NewHandler(searchSvc *search.Service, autocompleteSuggestions func(ctx context.Context, query string) ([]string, error), sourceURL string) *Handler { return &Handler{ searchSvc: searchSvc, autocompleteSvc: autocompleteSuggestions, + sourceURL: sourceURL, } } @@ -51,7 +53,7 @@ func (h *Handler) Index(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - if err := views.RenderIndex(w); err != nil { + if err := views.RenderIndex(w, h.sourceURL); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } @@ -80,7 +82,7 @@ func (h *Handler) Search(w http.ResponseWriter, r *http.Request) { if err != nil { // For HTML, render error on the results page. if req.Format == contracts.FormatHTML || r.FormValue("format") == "html" { - pd := views.PageData{Query: r.FormValue("q")} + pd := views.PageData{SourceURL: h.sourceURL, Query: r.FormValue("q")} if views.IsHTMXRequest(r) { views.RenderSearchFragment(w, pd) } else { @@ -95,7 +97,7 @@ func (h *Handler) Search(w http.ResponseWriter, r *http.Request) { resp, err := h.searchSvc.Search(r.Context(), req) if err != nil { if req.Format == contracts.FormatHTML { - pd := views.PageData{Query: req.Query} + pd := views.PageData{SourceURL: h.sourceURL, Query: req.Query} if views.IsHTMXRequest(r) { views.RenderSearchFragment(w, pd) } else { diff --git a/internal/views/templates/base.html b/internal/views/templates/base.html index 5b58487..d2fec79 100644 --- a/internal/views/templates/base.html +++ b/internal/views/templates/base.html @@ -35,7 +35,7 @@ diff --git a/internal/views/views.go b/internal/views/views.go index 50cec56..4d7289c 100644 --- a/internal/views/views.go +++ b/internal/views/views.go @@ -35,6 +35,7 @@ var staticFS embed.FS // PageData holds all data passed to templates. type PageData struct { + SourceURL string Query string Pageno int PrevPage int @@ -187,9 +188,9 @@ func FromResponse(resp contracts.SearchResponse, query string, pageno int) PageD } // RenderIndex renders the homepage (search box only). -func RenderIndex(w http.ResponseWriter) error { +func RenderIndex(w http.ResponseWriter, sourceURL string) error { w.Header().Set("Content-Type", "text/html; charset=utf-8") - return tmplIndex.ExecuteTemplate(w, "base", PageData{ShowHeader: true}) + return tmplIndex.ExecuteTemplate(w, "base", PageData{ShowHeader: true, SourceURL: sourceURL}) } // RenderSearch renders the full search results page (with base layout).