Add search page
authorr <r@freesoftwareextremist.com>
Thu, 26 Dec 2019 19:18:09 +0000 (19:18 +0000)
committerr <r@freesoftwareextremist.com>
Thu, 26 Dec 2019 19:18:09 +0000 (19:18 +0000)
mastodon/mastodon.go
mastodon/status.go
renderer/model.go
renderer/renderer.go
service/auth.go
service/logging.go
service/service.go
service/transport.go
templates/navigation.tmpl
templates/search.tmpl [new file with mode: 0644]

index 8a8af6a9993b0a2b08f2b4d32458af0f43295369..74fa0ff5ef77266cbc34cc319077cd85c73b9329 100644 (file)
@@ -336,7 +336,7 @@ type Emoji struct {
 type Results struct {
        Accounts []*Account `json:"accounts"`
        Statuses []*Status  `json:"statuses"`
-       Hashtags []string   `json:"hashtags"`
+       // Hashtags []string   `json:"hashtags"`
 }
 
 // Pagination is a struct for specifying the get range.
index edd88e93b82838ce7eea302c3017e8e263e0a57b..816d09269a43fa9700a5c4cd429aaf3fdf6d888f 100644 (file)
@@ -284,12 +284,15 @@ func (c *Client) DeleteStatus(ctx context.Context, id string) error {
 }
 
 // Search search content with query.
-func (c *Client) Search(ctx context.Context, q string, resolve bool) (*Results, error) {
+func (c *Client) Search(ctx context.Context, q string, qType string, limit int, resolve bool, offset int) (*Results, error) {
        params := url.Values{}
        params.Set("q", q)
+       params.Set("type", qType)
+       params.Set("limit", fmt.Sprint(limit))
        params.Set("resolve", fmt.Sprint(resolve))
+       params.Set("offset", fmt.Sprint(offset))
        var results Results
-       err := c.doAPI(ctx, http.MethodGet, "/api/v1/search", params, &results, nil)
+       err := c.doAPI(ctx, http.MethodGet, "/api/v2/search", params, &results, nil)
        if err != nil {
                return nil, err
        }
index 20d11d82f8b84f66ac96cb696f73f75b450b625c..ffeb2d1b376ca0d63d31124643bdcad02be6b21d 100644 (file)
@@ -61,10 +61,10 @@ type NotificationData struct {
 
 type UserData struct {
        *CommonData
-       User       *mastodon.Account
-       Statuses   []*mastodon.Status
-       HasNext    bool
-       NextLink   string
+       User     *mastodon.Account
+       Statuses []*mastodon.Status
+       HasNext  bool
+       NextLink string
 }
 
 type AboutData struct {
@@ -73,19 +73,29 @@ type AboutData struct {
 
 type EmojiData struct {
        *CommonData
-       Emojis     []*mastodon.Emoji
+       Emojis []*mastodon.Emoji
 }
 
 type LikedByData struct {
        *CommonData
-       Users []*mastodon.Account
-       HasNext    bool
-       NextLink   string
+       Users    []*mastodon.Account
+       HasNext  bool
+       NextLink string
 }
 
 type RetweetedByData struct {
        *CommonData
-       Users []*mastodon.Account
-       HasNext    bool
-       NextLink   string
+       Users    []*mastodon.Account
+       HasNext  bool
+       NextLink string
+}
+
+type SearchData struct {
+       *CommonData
+       Q         string
+       Type      string
+       Users     []*mastodon.Account
+       Statuses  []*mastodon.Status
+       HasNext bool
+       NextLink string
 }
index 061737ceccce94b770048cb9f80c88a7384971b0..5e5f005dc145836d04b3d19a56a72403a261c354 100644 (file)
@@ -23,6 +23,7 @@ type Renderer interface {
        RenderEmojiPage(ctx context.Context, writer io.Writer, data *EmojiData) (err error)
        RenderLikedByPage(ctx context.Context, writer io.Writer, data *LikedByData) (err error)
        RenderRetweetedByPage(ctx context.Context, writer io.Writer, data *RetweetedByData) (err error)
+       RenderSearchPage(ctx context.Context, writer io.Writer, data *SearchData) (err error)
 }
 
 type renderer struct {
@@ -91,6 +92,10 @@ func (r *renderer) RenderRetweetedByPage(ctx context.Context, writer io.Writer,
        return r.template.ExecuteTemplate(writer, "retweetedby.tmpl", data)
 }
 
+func (r *renderer) RenderSearchPage(ctx context.Context, writer io.Writer, data *SearchData) (err error) {
+       return r.template.ExecuteTemplate(writer, "search.tmpl", data)
+}
+
 func EmojiFilter(content string, emojis []mastodon.Emoji) string {
        var replacements []string
        for _, e := range emojis {
index 0c2e7f6b0f129a9466e0306d1bf8514bd94ee7ba..431e093237ccb649d34e32dfb6a7db5640dc1dfa 100644 (file)
@@ -149,6 +149,14 @@ func (s *authService) ServeRetweetedByPage(ctx context.Context, client io.Writer
        return s.Service.ServeRetweetedByPage(ctx, client, c, id)
 }
 
+func (s *authService) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
+       c, err = s.getClient(ctx)
+       if err != nil {
+               return
+       }
+       return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
+}
+
 func (s *authService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
        c, err = s.getClient(ctx)
        if err != nil {
index f5e6d6605f2df1fc77044133fabc154450b79c78..a5ffd57915e534659b76efeff438a80d074a871c 100644 (file)
@@ -125,6 +125,14 @@ func (s *loggingService) ServeRetweetedByPage(ctx context.Context, client io.Wri
        return s.Service.ServeRetweetedByPage(ctx, client, c, id)
 }
 
+func (s *loggingService) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
+       defer func(begin time.Time) {
+               s.logger.Printf("method=%v, q=%v, type=%v, offset=%v, took=%v, err=%v\n",
+                       "ServeSearchPage", q, qType, offset, time.Since(begin), err)
+       }(time.Now())
+       return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
+}
+
 func (s *loggingService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) {
        defer func(begin time.Time) {
                s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
index 27870795b541184f07529b31d27d0e5a42b409e9..157ddf8be7c70d16f96d6baac19ae81adb9f48f7 100644 (file)
@@ -39,6 +39,7 @@ type Service interface {
        ServeEmojiPage(ctx context.Context, client io.Writer, c *model.Client) (err error)
        ServeLikedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
        ServeRetweetedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
+       ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error)
        Like(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
        UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
        Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
@@ -593,6 +594,51 @@ func (svc *service) ServeRetweetedByPage(ctx context.Context, client io.Writer,
 
        return
 }
+
+func (svc *service) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
+       var hasNext bool
+       var nextLink string
+
+       results, err := c.Search(ctx, q, qType, 20, true, offset)
+       if err != nil {
+               return
+       }
+
+       switch qType {
+       case "accounts":
+               hasNext = len(results.Accounts) == 20
+       case "statuses":
+               hasNext = len(results.Statuses) == 20
+       }
+
+       if hasNext {
+               offset += 20
+               nextLink = fmt.Sprintf("/search?q=%s&type=%s&offset=%d", q, qType, offset)
+       }
+
+       commonData, err := svc.getCommonData(ctx, client, c)
+       if err != nil {
+               return
+       }
+
+       data := &renderer.SearchData{
+               CommonData: commonData,
+               Q:          q,
+               Type:       qType,
+               Users:      results.Accounts,
+               Statuses:   results.Statuses,
+               HasNext:    hasNext,
+               NextLink:   nextLink,
+       }
+
+       err = svc.renderer.RenderSearchPage(ctx, client, data)
+       if err != nil {
+               return
+       }
+
+       return
+}
+
 func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *model.Client) (data *renderer.CommonData, err error) {
        data = new(renderer.CommonData)
 
index 654177264004722943c3f1359539858e6ab63153..c42462f11ff9d3861c4ba019e8d849e92470f60b 100644 (file)
@@ -6,6 +6,7 @@ import (
        "mime/multipart"
        "net/http"
        "path"
+       "strconv"
 
        "github.com/gorilla/mux"
 )
@@ -280,6 +281,30 @@ func NewHandler(s Service, staticDir string) http.Handler {
                }
        }).Methods(http.MethodGet)
 
+       r.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) {
+               ctx := getContextWithSession(context.Background(), req)
+
+               q := req.URL.Query().Get("q")
+               qType := req.URL.Query().Get("type")
+               offsetStr := req.URL.Query().Get("offset")
+
+               var offset int
+               var err error
+               if len(offsetStr) > 1 {
+                       offset, err = strconv.Atoi(offsetStr)
+                       if err != nil {
+                               s.ServeErrorPage(ctx, w, err)
+                               return
+                       }
+               }
+
+               err = s.ServeSearchPage(ctx, w, nil, q, qType, offset)
+               if err != nil {
+                       s.ServeErrorPage(ctx, w, err)
+                       return
+               }
+       }).Methods(http.MethodGet)
+
        r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
                // TODO remove session from database
                w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0"))
index bd97d2dfbc6e4f050b1f55c2945855215c9649d9..b82c7f53f60faea8470ed539da64bf257801f4ec 100644 (file)
@@ -16,6 +16,7 @@
                        <a class="nav-link" href="/notifications">notifications{{if gt .NotificationCount 0}}({{.NotificationCount}}){{end}}</a>
                        <a class="nav-link" href="/timeline/local">local</a>
                        <a class="nav-link" href="/timeline/twkn">twkn</a>
+                       <a class="nav-link" href="/search">search</a>
                        <a class="nav-link" href="/about">about</a>
                </div>
                <div>
diff --git a/templates/search.tmpl b/templates/search.tmpl
new file mode 100644 (file)
index 0000000..de80fac
--- /dev/null
@@ -0,0 +1,37 @@
+{{template "header.tmpl" .HeaderData}}
+{{template "navigation.tmpl" .NavbarData}}
+<div class="page-title"> Search </div>
+
+<div>
+       <form action="/search" method="GET">
+               <span class="post-form-field>
+                       <label for="query"> Query </label>
+                       <input id="query" name="q" value="{{.Q}}">
+               </span>
+               <span class="post-form-field>
+                       <label for="type"> Type </label>
+                       <select id="type" name="type">
+                               <option value="statuses" {{if eq .Type "statuses"}}selected{{end}}>Statuses</option>
+                               <option value="accounts" {{if eq .Type "accounts"}}selected{{end}}>Accounts</option>
+                       </select>
+               </span>
+               <button type="submit"> Search </button>
+       </form>
+</div>
+
+{{if eq .Type "statuses"}}
+{{range .Statuses}}
+{{template "status.tmpl" .}}
+{{end}}
+
+{{end}}
+{{if eq .Type "accounts"}}
+{{template "userlist.tmpl" .Users}}
+{{end}}
+
+<div class="pagination">
+       {{if .HasNext}}
+               <a href="{{.NextLink}}">next</a>
+       {{end}}
+</div>
+{{template "footer.tmpl"}}