diff --git a/README.md b/README.md index d1d650a..4d57828 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Go client for the [Notion API](https://developers.notion.com/reference). ### Search -- [ ] Search +- [x] [Search](client.go) ## License diff --git a/client.go b/client.go index 2fac324..e160ce8 100644 --- a/client.go +++ b/client.go @@ -360,3 +360,39 @@ func (c *Client) ListUsers(ctx context.Context, query *PaginationQuery) (result return result, nil } + +// Search fetches all pages and child pages that are shared with the integration. Optionally uses query, filter and +// pagination options. +// See: https://developers.notion.com/reference/post-search +func (c *Client) Search(ctx context.Context, opts *SearchOpts) (result SearchResponse, err error) { + body := &bytes.Buffer{} + + if opts != nil { + err = json.NewEncoder(body).Encode(opts) + if err != nil { + return SearchResponse{}, fmt.Errorf("notion: failed to encode filter to JSON: %w", err) + } + } + + req, err := c.newRequest(ctx, http.MethodPost, "/search", body) + if err != nil { + return SearchResponse{}, fmt.Errorf("notion: invalid request: %w", err) + } + + res, err := c.httpClient.Do(req) + if err != nil { + return SearchResponse{}, fmt.Errorf("notion: failed to make HTTP request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return SearchResponse{}, fmt.Errorf("notion: failed to search: %w", parseErrorResponse(res)) + } + + err = json.NewDecoder(res.Body).Decode(&result) + if err != nil { + return SearchResponse{}, fmt.Errorf("notion: failed to parse HTTP response: %w", err) + } + + return result, nil +} diff --git a/search.go b/search.go new file mode 100644 index 0000000..0b2fa54 --- /dev/null +++ b/search.go @@ -0,0 +1,78 @@ +package notion + +import ( + "encoding/json" + "fmt" +) + +type SearchOpts struct { + Query string `json:"query,omitempty"` + Sort *SearchFilter `json:"sort,omitempty"` + Filter *SearchFilter `json:"filter,omitempty"` + StartCursor string `json:"start_cursor,omitempty"` + PageSize int `json:"page_size,omitempty"` +} + +type SearchSort struct { + Direction SortDirection `json:"direction,omitempty"` + Timestamp string `json:"timestamp"` +} + +type SearchFilter struct { + Value string `json:"value"` + Property string `json:"property"` +} + +type SearchResponse struct { + // Results are either pages or databases. See `SearchResponse.UnmarshalJSON`. + Results SearchResults `json:"results"` + HasMore bool `json:"has_more"` + NextCursor *string `json:"next_cursor"` +} + +type SearchResults []interface{} + +func (sr *SearchResults) UnmarshalJSON(b []byte) error { + rawResults := []json.RawMessage{} + err := json.Unmarshal(b, &rawResults) + if err != nil { + return err + } + + type Object struct { + Object string `json:"object"` + } + + results := make(SearchResults, len(rawResults)) + + for i, rawResult := range rawResults { + obj := Object{} + err := json.Unmarshal(rawResult, &obj) + if err != nil { + return err + } + + switch obj.Object { + case "database": + var db Database + err := json.Unmarshal(rawResult, &db) + if err != nil { + return err + } + results[i] = db + case "page": + var page Page + err := json.Unmarshal(rawResult, &page) + if err != nil { + return err + } + results[i] = page + default: + return fmt.Errorf("unsupported result object %q", obj.Object) + } + } + + *sr = results + + return nil +}