Client.Search(term, page) url-encodes the term (operators like @user / #category / order:latest pass through) and decodes the four hit groups Discourse returns: posts (with blurb and highlighted topic title), topics, users and categories. Empty term is rejected locally with ErrInvalidRequest; server failures keep the typed error classes. Fake-server tests cover encoding, parsing and the 403 path. Part of Redmine 507 (Discourse Go client): search is how the 495 briefing pipeline finds landing spots and prior threads.
75 lines
2.2 KiB
Go
75 lines
2.2 KiB
Go
// Search: GET /search.json — full-text search across posts, topics,
|
|
// users and categories. The term supports Discourse's search operators
|
|
// (@user, #category, order:latest, ...); they pass through untouched.
|
|
package discourse
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// SearchRequest is the GET /search.json query. Term is required; Page is
|
|
// 1-based (0 means "leave unset" — Discourse then returns page 1).
|
|
type SearchRequest struct {
|
|
Term string
|
|
Page int
|
|
}
|
|
|
|
// SearchPost is one post hit: the match lives in Blurb (a plain-text
|
|
// excerpt around the match) while TopicTitleHeadline carries the topic
|
|
// title with <em> highlights around matched words.
|
|
type SearchPost struct {
|
|
ID int `json:"id"`
|
|
TopicID int `json:"topic_id"`
|
|
PostNumber int `json:"post_number"`
|
|
Username string `json:"username"`
|
|
Name string `json:"name"`
|
|
Blurb string `json:"blurb"`
|
|
TopicTitleHeadline string `json:"topic_title_headline"`
|
|
CreatedAt string `json:"created_at"`
|
|
LikeCount int `json:"like_count"`
|
|
}
|
|
|
|
// SearchTopic is one topic hit.
|
|
type SearchTopic struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
Slug string `json:"slug"`
|
|
}
|
|
|
|
// SearchUser is one user hit.
|
|
type SearchUser struct {
|
|
ID int `json:"id"`
|
|
Username string `json:"username"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// SearchResult groups the four hit lists Discourse returns; whichever
|
|
// classes matched are non-empty, the rest are nil.
|
|
type SearchResult struct {
|
|
Posts []SearchPost `json:"posts"`
|
|
Topics []SearchTopic `json:"topics"`
|
|
Users []SearchUser `json:"users"`
|
|
Categories []Category `json:"categories"`
|
|
}
|
|
|
|
// Search runs a full-text search and returns the grouped hits.
|
|
func (c *Client) Search(ctx context.Context, req SearchRequest) (*SearchResult, error) {
|
|
if strings.TrimSpace(req.Term) == "" {
|
|
return nil, fmt.Errorf("%w: search term is required", ErrInvalidRequest)
|
|
}
|
|
q := url.Values{}
|
|
q.Set("term", req.Term)
|
|
if req.Page > 1 {
|
|
q.Set("page", strconv.Itoa(req.Page))
|
|
}
|
|
var res SearchResult
|
|
if err := c.Get(ctx, "/search.json?"+q.Encode(), &res); err != nil {
|
|
return nil, err
|
|
}
|
|
return &res, nil
|
|
}
|