Files
mopac-discourse-go/search.go
T
mrcharles 14cdaa0a2d feat: search — GET /search.json with grouped hits
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.
2026-08-29 16:25:29 -05:00

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
}