|
@@ -1,13 +1,13 @@
|
|
|
package routes
|
|
|
|
|
|
import (
|
|
|
- "bytes"
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
"io/ioutil"
|
|
|
"net/http"
|
|
|
- "strconv"
|
|
|
+ "strings"
|
|
|
|
|
|
+ "github.com/G-Node/gogs/models"
|
|
|
"github.com/G-Node/gogs/pkg/context"
|
|
|
"github.com/G-Node/gogs/pkg/setting"
|
|
|
"github.com/G-Node/libgin/libgin"
|
|
@@ -19,99 +19,215 @@ const (
|
|
|
EXPLORE_COMMITS = "explore/commits"
|
|
|
)
|
|
|
|
|
|
-func Search(c *context.Context, keywords string, sType int64) ([]byte, error) {
|
|
|
- if !setting.Search.Do {
|
|
|
- c.Status(http.StatusNotImplemented)
|
|
|
+type set map[int64]interface{}
|
|
|
+
|
|
|
+func newset() set {
|
|
|
+ return make(map[int64]interface{}, 0)
|
|
|
+}
|
|
|
+
|
|
|
+func (s set) add(item int64) {
|
|
|
+ s[item] = nil
|
|
|
+}
|
|
|
+
|
|
|
+func (s set) contains(item int64) bool {
|
|
|
+ _, yes := s[item]
|
|
|
+ return yes
|
|
|
+}
|
|
|
+
|
|
|
+func (s set) remove(item int64) {
|
|
|
+ delete(s, item)
|
|
|
+}
|
|
|
+
|
|
|
+func (s set) asSlice() []int64 {
|
|
|
+ slice := make([]int64, len(s))
|
|
|
+ idx := 0
|
|
|
+ for item := range s {
|
|
|
+ slice[idx] = item
|
|
|
+ idx++
|
|
|
+ }
|
|
|
+ return slice
|
|
|
+}
|
|
|
+
|
|
|
+func collectSearchableRepoIDs(c *context.Context) ([]int64, error) {
|
|
|
+ repoIDSet := newset()
|
|
|
+
|
|
|
+ updateSet := func(repos []*models.Repository) {
|
|
|
+ for _, r := range repos {
|
|
|
+ repoIDSet.add(r.ID)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if c.User != nil {
|
|
|
+ ownRepos := c.User.Repos // user's own repositories
|
|
|
+ updateSet(ownRepos)
|
|
|
+
|
|
|
+ accessibleRepos, _ := c.User.GetAccessibleRepositories(0) // shared and org repos
|
|
|
+ updateSet(accessibleRepos)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Run a full repository search (with no keywords) to get public
|
|
|
+ // repositories and then filter out the unlisted ones.
|
|
|
+ repos, _, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
|
|
|
+ Keyword: "",
|
|
|
+ UserID: c.UserID(),
|
|
|
+ OrderBy: "updated_unix DESC",
|
|
|
+ Page: 0,
|
|
|
+ PageSize: 0,
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ c.ServerError("SearchRepositoryByName", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // If it's not unlisted, add it to the set
|
|
|
+ // This will add public (listed) repositories
|
|
|
+ for _, r := range repos {
|
|
|
+ if !r.Unlisted {
|
|
|
+ repoIDSet.add(r.ID)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return repoIDSet.asSlice(), nil
|
|
|
+}
|
|
|
+
|
|
|
+func search(c *context.Context, keywords string, sType int) ([]byte, error) {
|
|
|
+ if setting.Search.SearchURL == "" {
|
|
|
+ log.Error(2, "Unable to perform search: SearchURL not configured")
|
|
|
return nil, fmt.Errorf("Extended search not implemented")
|
|
|
}
|
|
|
|
|
|
- ireq := libgin.SearchRequest{Token: c.GetCookie(setting.SessionConfig.CookieName),
|
|
|
- Query: keywords, CsrfT: c.GetCookie(setting.CSRFCookieName), SType: sType, UserID: -10}
|
|
|
- if c.IsLogged {
|
|
|
- ireq.UserID = c.User.ID
|
|
|
+ key := []byte(setting.Search.Key)
|
|
|
+
|
|
|
+ repoids, err := collectSearchableRepoIDs(c)
|
|
|
+ if err != nil {
|
|
|
+ log.Error(2, "Failed to collect searchable repository IDs: %v", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ searchdata := libgin.SearchRequest{Keywords: keywords, SType: sType, RepoIDs: repoids}
|
|
|
+
|
|
|
+ data, err := json.Marshal(searchdata)
|
|
|
+ if err != nil {
|
|
|
+ log.Error(2, "Failed to marshal search request for gin-dex: %v", err)
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- data, err := json.Marshal(ireq)
|
|
|
+ // encrypt query
|
|
|
+ encdata, err := libgin.EncryptString(key, string(data))
|
|
|
if err != nil {
|
|
|
- c.Status(http.StatusInternalServerError)
|
|
|
+ log.Error(2, "Failed to encrypt search data for gin-dex: %v", err)
|
|
|
return nil, err
|
|
|
}
|
|
|
- req, _ := http.NewRequest("Post", setting.Search.SearchURL, bytes.NewReader(data))
|
|
|
+
|
|
|
+ // Send query to gin-dex
|
|
|
+ req, err := http.NewRequest("POST", setting.Search.SearchURL, strings.NewReader(encdata))
|
|
|
+ if err != nil {
|
|
|
+ log.Error(2, "Failed to build request for gin-dex: %v", err)
|
|
|
+ }
|
|
|
cl := http.Client{}
|
|
|
resp, err := cl.Do(req)
|
|
|
if err != nil {
|
|
|
- c.Status(http.StatusInternalServerError)
|
|
|
+ log.Error(2, "Failed to send request to gin-dex: %v", err)
|
|
|
return nil, err
|
|
|
}
|
|
|
- data, err = ioutil.ReadAll(resp.Body)
|
|
|
+
|
|
|
+ encrespdata, err := ioutil.ReadAll(resp.Body) // response is encrypted
|
|
|
if err != nil {
|
|
|
- c.Status(http.StatusInternalServerError)
|
|
|
+ log.Error(2, "Failed to read response body from gin-dex: %v", err)
|
|
|
return nil, err
|
|
|
}
|
|
|
- return data, nil
|
|
|
+
|
|
|
+ // decrypt response
|
|
|
+ respdata, err := libgin.DecryptString(key, string(encrespdata))
|
|
|
+ if err != nil {
|
|
|
+ log.Error(2, "Failed to decrypt response body form gin-dex: %v", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return []byte(respdata), nil
|
|
|
}
|
|
|
|
|
|
+// ExploreData handles the search box served at /explore/data
|
|
|
func ExploreData(c *context.Context) {
|
|
|
+ keywords := c.Query("q")
|
|
|
+ sType := c.QueryInt("stype") // non integer stype will return 0
|
|
|
+
|
|
|
c.Data["Title"] = c.Tr("explore")
|
|
|
c.Data["PageIsExplore"] = true
|
|
|
c.Data["PageIsExploreData"] = true
|
|
|
|
|
|
- keywords := c.Query("q")
|
|
|
+ // send query data back even if the search fails or is aborted to fill in
|
|
|
+ // the form on refresh
|
|
|
c.Data["Keywords"] = keywords
|
|
|
- sType, err := strconv.ParseInt(c.Query("stype"), 10, 0)
|
|
|
- if err != nil {
|
|
|
- log.Error(2, "Search type not understood: %s", err.Error())
|
|
|
- sType = 0
|
|
|
+ c.Data["opsel"] = sType
|
|
|
+
|
|
|
+ res := libgin.SearchResults{}
|
|
|
+ if keywords == "" {
|
|
|
+ // no keywords submitted: don't search
|
|
|
+ log.Trace("Loading empty data search page")
|
|
|
+ c.Data["Blobs"] = res.Blobs
|
|
|
+ c.HTML(200, EXPLORE_DATA)
|
|
|
+ return
|
|
|
}
|
|
|
- data, err := Search(c, keywords, sType)
|
|
|
+
|
|
|
+ log.Trace("Searching data/blobs")
|
|
|
+ data, err := search(c, keywords, sType)
|
|
|
if err != nil {
|
|
|
- c.Handle(http.StatusInternalServerError, "Could not query", err)
|
|
|
+ log.Error(2, "Query returned error: %v", err)
|
|
|
+ c.Data["Blobs"] = res.Blobs
|
|
|
+ c.HTML(200, EXPLORE_DATA)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- res := libgin.SearchResults{}
|
|
|
err = json.Unmarshal(data, &res)
|
|
|
if err != nil {
|
|
|
- c.Handle(http.StatusInternalServerError, "Could not display result", err)
|
|
|
+ log.Error(2, "Failed to unmarshal response: %v", err)
|
|
|
+ c.Data["Blobs"] = res.Blobs
|
|
|
+ c.HTML(200, EXPLORE_DATA)
|
|
|
return
|
|
|
}
|
|
|
c.Data["Blobs"] = res.Blobs
|
|
|
- c.Data["opsel"] = sType
|
|
|
c.HTML(200, EXPLORE_DATA)
|
|
|
}
|
|
|
|
|
|
+// ExploreCommits handles the search box served at /explore/commits
|
|
|
func ExploreCommits(c *context.Context) {
|
|
|
+ keywords := c.Query("q")
|
|
|
+ sType := c.QueryInt("stype") // non integer stype will return 0
|
|
|
+
|
|
|
c.Data["Title"] = c.Tr("explore")
|
|
|
c.Data["PageIsExplore"] = true
|
|
|
c.Data["PageIsExploreCommits"] = true
|
|
|
|
|
|
- keywords := c.Query("q")
|
|
|
- sType, err := strconv.ParseInt(c.Query("stype"), 10, 0)
|
|
|
- if err != nil {
|
|
|
- log.Error(2, "Search type not understood: %s", err.Error())
|
|
|
- sType = 0
|
|
|
+ // send query data back even if the search fails or is aborted to fill in
|
|
|
+ // the form on refresh
|
|
|
+ c.Data["Keywords"] = keywords
|
|
|
+ c.Data["opsel"] = sType
|
|
|
+
|
|
|
+ res := libgin.SearchResults{}
|
|
|
+ if keywords == "" {
|
|
|
+ log.Trace("Loading empty commit search page")
|
|
|
+ // no keywords submitted: don't search
|
|
|
+ c.Data["Commits"] = res.Commits
|
|
|
+ c.HTML(200, EXPLORE_COMMITS)
|
|
|
+ return
|
|
|
}
|
|
|
- data, err := Search(c, keywords, sType)
|
|
|
+
|
|
|
+ log.Trace("Searching commits")
|
|
|
+ data, err := search(c, keywords, sType)
|
|
|
|
|
|
if err != nil {
|
|
|
- c.Handle(http.StatusInternalServerError, "Could not query", err)
|
|
|
- return
|
|
|
+ log.Error(2, "Query returned error: %v", err)
|
|
|
+ c.Data["Commits"] = res.Commits
|
|
|
+ c.HTML(200, EXPLORE_COMMITS)
|
|
|
}
|
|
|
|
|
|
- res := libgin.SearchResults{}
|
|
|
err = json.Unmarshal(data, &res)
|
|
|
if err != nil {
|
|
|
- c.Handle(http.StatusInternalServerError, "Could not display result", err)
|
|
|
+ log.Error(2, "Failed to unmarshal response: %v", err)
|
|
|
+ c.Data["Commits"] = res.Commits
|
|
|
+ c.HTML(200, EXPLORE_COMMITS)
|
|
|
return
|
|
|
}
|
|
|
c.Data["Commits"] = res.Commits
|
|
|
c.HTML(200, EXPLORE_COMMITS)
|
|
|
}
|
|
|
-
|
|
|
-type SearchRequest struct {
|
|
|
- Token string
|
|
|
- CsrfT string
|
|
|
- UserID int64
|
|
|
- Query string
|
|
|
- SType int64
|
|
|
-}
|