2019-04-17 18:12:27 +03:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package frontend
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
2019-04-17 20:47:58 +03:00
|
|
|
"strconv"
|
2019-04-17 18:12:27 +03:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"golang.org/x/discovery/internal"
|
|
|
|
"golang.org/x/discovery/internal/postgres"
|
|
|
|
)
|
|
|
|
|
2019-04-17 20:47:58 +03:00
|
|
|
const (
|
|
|
|
defaultSearchLimit = 20
|
|
|
|
)
|
|
|
|
|
2019-04-17 18:12:27 +03:00
|
|
|
// SearchPage contains all of the data that the search template needs to
|
|
|
|
// populate.
|
|
|
|
type SearchPage struct {
|
|
|
|
Query string
|
|
|
|
Results []*SearchResult
|
2019-04-17 20:47:58 +03:00
|
|
|
Total int
|
|
|
|
Prev int
|
|
|
|
Next int
|
|
|
|
Page int
|
2019-04-17 18:12:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// SearchResult contains data needed to display a single search result.
|
|
|
|
type SearchResult struct {
|
2019-04-19 19:10:27 +03:00
|
|
|
Name string
|
|
|
|
PackagePath string
|
|
|
|
ModulePath string
|
|
|
|
Synopsis string
|
|
|
|
Version string
|
|
|
|
Licenses []*internal.LicenseInfo
|
|
|
|
CommitTime string
|
|
|
|
NumImportedBy uint64
|
2019-04-17 18:12:27 +03:00
|
|
|
}
|
|
|
|
|
2019-04-17 20:47:58 +03:00
|
|
|
func offset(page, limit int) int {
|
|
|
|
if page < 2 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return (page - 1) * limit
|
|
|
|
}
|
|
|
|
|
|
|
|
func prev(page int) int {
|
|
|
|
if page < 2 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return page - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
func next(page, limit, numResults int) int {
|
|
|
|
if numResults < limit {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return page + 1
|
|
|
|
}
|
|
|
|
|
2019-04-17 18:12:27 +03:00
|
|
|
// fetchSearchPage fetches data matching the search query from the database and
|
|
|
|
// returns a SearchPage.
|
2019-04-17 20:47:58 +03:00
|
|
|
func fetchSearchPage(ctx context.Context, db *postgres.DB, query string, limit, page int) (*SearchPage, error) {
|
2019-04-17 18:12:27 +03:00
|
|
|
terms := strings.Fields(query)
|
2019-04-17 20:47:58 +03:00
|
|
|
dbresults, err := db.Search(ctx, terms, limit, offset(page, limit))
|
2019-04-17 18:12:27 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("db.Search(%v): %v", terms, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var results []*SearchResult
|
|
|
|
for _, r := range dbresults {
|
|
|
|
results = append(results, &SearchResult{
|
2019-04-19 19:10:27 +03:00
|
|
|
Name: r.Package.Name,
|
|
|
|
PackagePath: r.Package.Path,
|
2019-04-22 21:43:02 +03:00
|
|
|
ModulePath: r.Package.VersionInfo.ModulePath,
|
|
|
|
Synopsis: r.Package.Package.Synopsis,
|
|
|
|
Version: r.Package.VersionInfo.Version,
|
2019-04-19 19:10:27 +03:00
|
|
|
Licenses: r.Package.Licenses,
|
2019-04-22 21:43:02 +03:00
|
|
|
CommitTime: elapsedTime(r.Package.VersionInfo.CommitTime),
|
2019-04-19 19:10:27 +03:00
|
|
|
NumImportedBy: r.NumImportedBy,
|
2019-04-17 18:12:27 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-17 20:47:58 +03:00
|
|
|
var total int
|
|
|
|
if len(dbresults) > 0 {
|
|
|
|
total = int(dbresults[0].Total)
|
|
|
|
}
|
2019-04-17 18:12:27 +03:00
|
|
|
return &SearchPage{
|
2019-04-17 20:47:58 +03:00
|
|
|
Page: page,
|
2019-04-17 18:12:27 +03:00
|
|
|
Query: query,
|
|
|
|
Results: results,
|
2019-04-17 20:47:58 +03:00
|
|
|
Total: total,
|
|
|
|
Prev: prev(page),
|
|
|
|
Next: next(page, limit, len(results)),
|
2019-04-17 18:12:27 +03:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleSearch applies database data to the search template. Handles endpoint
|
|
|
|
// /search?q=<query>.
|
|
|
|
func (c *Controller) HandleSearch(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
query := strings.TrimSpace(r.FormValue("q"))
|
|
|
|
if query == "" {
|
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
2019-04-17 20:47:58 +03:00
|
|
|
|
|
|
|
var (
|
|
|
|
limit, pageNum int
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
l := r.URL.Query().Get("limit")
|
|
|
|
if l != "" {
|
|
|
|
limit, err = strconv.Atoi(l)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("strconv.Atoi(%q) for limit: %v", l, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if limit < 1 {
|
|
|
|
limit = defaultSearchLimit
|
|
|
|
}
|
|
|
|
|
|
|
|
p := r.URL.Query().Get("page")
|
|
|
|
if p != "" {
|
|
|
|
pageNum, err = strconv.Atoi(p)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("strconv.Atoi(%q) for page: %v", l, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if pageNum <= 1 {
|
|
|
|
pageNum = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
page, err := fetchSearchPage(ctx, c.db, query, limit, pageNum)
|
2019-04-17 18:12:27 +03:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
|
|
|
log.Printf("fetchSearchDetails(ctx, db, %q): %v", query, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.renderPage(w, "search.tmpl", page)
|
|
|
|
}
|