Gitfed
bastien-mrq/gitfed / cmd / gitfed-web / handlers_search.go
package main

import (
	"bytes"
	"html/template"
	"net/http"
	"strings"

	"git.neuromancer.ovh/bastien-mrq/gitfed/internal/i18n"
	"git.neuromancer.ovh/bastien-mrq/gitfed/internal/store"
)

var searchTpl = newTpl("search", `
<h1>{{t .Lang "search.title"}}{{if .Query}}: "{{.Query}}"{{end}}</h1>
<form action="/search" method="get" style="max-width:420px;">
  <input type="search" name="q" value="{{.Query}}" placeholder="{{t .Lang "nav.search_placeholder"}}" autofocus>
</form>
<table>
<tr><th>{{t .Lang "explore.col_repo"}}</th><th>{{t .Lang "explore.col_owner"}}</th><th>{{t .Lang "explore.col_topics"}}</th></tr>
{{range .Results}}
<tr>
  <td><a href="/r/{{.Name}}">{{.Name}}</a>{{if .Description}}<div class="muted" style="font-size:0.82rem;">{{.Description}}</div>{{end}}</td>
  <td class="muted">{{.Owner}}</td>
  <td>{{range .Topics}}<span class="badge plain">{{.}}</span>{{end}}</td>
</tr>
{{else}}
<tr><td colspan="3" class="muted">{{if .Query}}{{t .Lang "search.no_matches"}}{{else}}{{t .Lang "search.prompt"}}{{end}}</td></tr>
{{end}}
</table>
`)

// searchVisible reports whether repo should be considered for search
// results for the given (possibly anonymous) session: same visibility rule
// as everywhere else — public repos to anyone, private ones only to an
// owner/collaborator.
func (s *server) searchVisible(r *http.Request, repo store.Repo) bool {
	return s.canView(r, repo)
}

func (s *server) handleSearch(w http.ResponseWriter, r *http.Request) {
	q := strings.TrimSpace(r.URL.Query().Get("q"))
	lang := s.lang(r)

	var results []store.Repo
	if q != "" {
		all, err := s.ops.ListRepos()
		if err != nil {
			s.serverError(w, r, err)
			return
		}
		needle := strings.ToLower(q)
		for _, repo := range all {
			if !s.searchVisible(r, repo) {
				continue
			}
			if matchesSearch(repo, needle) {
				results = append(results, repo)
			}
		}
	}

	var buf bytes.Buffer
	_ = searchTpl.Execute(&buf, struct {
		Query, Lang string
		Results     []store.Repo
	}{q, string(lang), results})
	s.render(w, r, i18n.T(lang, "search.title"), "", template.HTML(buf.String()))
}

func matchesSearch(repo store.Repo, lowerNeedle string) bool {
	if strings.Contains(strings.ToLower(repo.Name), lowerNeedle) {
		return true
	}
	if strings.Contains(strings.ToLower(repo.Description), lowerNeedle) {
		return true
	}
	for _, t := range repo.Topics {
		if strings.Contains(strings.ToLower(t), lowerNeedle) {
			return true
		}
	}
	return false
}