Skip to content

Commit d634fc7

Browse files
feat: Crawler commands (#129)
1 parent 26ef30e commit d634fc7

22 files changed

Lines changed: 1961 additions & 8 deletions

File tree

api/crawler/client.go

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
package crawler
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"io/ioutil"
10+
"net/http"
11+
)
12+
13+
const (
14+
// DefaultBaseURL is the default base URL for the Algolia Crawler API.
15+
DefaultBaseURL = "https://crawler.algolia.com/api/1/"
16+
)
17+
18+
// Client provides methods to interact with the Algolia Crawler API.
19+
type Client struct {
20+
UserID string
21+
APIKey string
22+
23+
client *http.Client
24+
}
25+
26+
// NewClient returns a new Crawler API client.
27+
func NewClient(userID, apiKey string) *Client {
28+
return &Client{
29+
UserID: userID,
30+
APIKey: apiKey,
31+
client: http.DefaultClient,
32+
}
33+
}
34+
35+
// NewClientWithHTTPClient returns a new Crawler API client with a custom HTTP client.
36+
func NewClientWithHTTPClient(userID, apiKey string, client *http.Client) *Client {
37+
return &Client{
38+
UserID: userID,
39+
APIKey: apiKey,
40+
client: client,
41+
}
42+
}
43+
44+
// Request sends an HTTP request and returns an HTTP response.
45+
// It unmarshals the response body to the given interface.
46+
func (c *Client) request(res interface{}, method string, path string, body interface{}, urlParams map[string]string) error {
47+
r, err := c.buildRequest(method, path, body, urlParams)
48+
if err != nil {
49+
return err
50+
}
51+
52+
resp, err := c.client.Do(r)
53+
if err != nil {
54+
return err
55+
}
56+
57+
if resp.StatusCode >= 400 {
58+
var errResp ErrResponse
59+
if err := unmarshalTo(resp, &errResp); err != nil {
60+
return err
61+
}
62+
63+
if errResp.Err.Errors != nil {
64+
var errs []string
65+
for _, e := range errResp.Err.Errors {
66+
errs = append(errs, e.Message)
67+
}
68+
return fmt.Errorf("%s: %s", errResp.Err.Message, errs)
69+
}
70+
71+
return errors.New(errResp.Err.Message)
72+
}
73+
74+
if res != nil {
75+
if err := unmarshalTo(resp, res); err != nil {
76+
return err
77+
}
78+
}
79+
80+
return nil
81+
}
82+
83+
// buildRequestWithoutBody builds an HTTP request without a body.
84+
func (c *Client) buildRequestWithoutBody(method, url string) (*http.Request, error) {
85+
return http.NewRequest(method, url, nil)
86+
}
87+
88+
// buildRequestWithBody builds an HTTP request with a body.
89+
func (c *Client) buildRequestWithBody(method, url string, body interface{}) (*http.Request, error) {
90+
var r io.ReadCloser
91+
if body != nil {
92+
b, err := json.Marshal(body)
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
r = ioutil.NopCloser(bytes.NewReader(b))
98+
}
99+
100+
return http.NewRequest(method, url, r)
101+
}
102+
103+
// buildRequest builds an HTTP request.
104+
func (c *Client) buildRequest(method, path string, body interface{}, urlParams map[string]string) (req *http.Request, err error) {
105+
url := DefaultBaseURL + path
106+
107+
if body == nil {
108+
req, err = c.buildRequestWithoutBody(method, url)
109+
} else {
110+
req, err = c.buildRequestWithBody(method, url, body)
111+
}
112+
113+
req.SetBasicAuth(c.UserID, c.APIKey)
114+
req.Header.Set("Content-Type", "application/json")
115+
116+
// Add URL params
117+
values := req.URL.Query()
118+
for k, v := range urlParams {
119+
values.Set(k, v)
120+
}
121+
req.URL.RawQuery = values.Encode()
122+
123+
return req, err
124+
}
125+
126+
// unmarshalTo unmarshals an HTTP response body to a given interface.
127+
func unmarshalTo(r *http.Response, v interface{}) error {
128+
defer r.Body.Close()
129+
return json.NewDecoder(r.Body).Decode(v)
130+
}
131+
132+
// Create creates a new Crawler.
133+
// It returns the Crawler ID if successful.
134+
func (c *Client) Create(name string, config Config) (string, error) {
135+
var res struct {
136+
ID string `json:"id"`
137+
}
138+
path := "crawlers"
139+
140+
crawler := &Crawler{
141+
Name: name,
142+
Config: &config,
143+
}
144+
145+
err := c.request(&res, http.MethodPost, path, crawler, nil)
146+
if err != nil {
147+
return "", err
148+
}
149+
150+
return res.ID, nil
151+
}
152+
153+
// List lists Crawlers.
154+
func (c *Client) List(itemsPerPage, page int, name, appID string) (*CrawlersResponse, error) {
155+
var res CrawlersResponse
156+
path := "crawlers"
157+
params := map[string]string{
158+
"itemsPerPage": fmt.Sprintf("%d", itemsPerPage),
159+
"page": fmt.Sprintf("%d", page),
160+
}
161+
162+
if name != "" {
163+
params["name"] = name
164+
}
165+
if appID != "" {
166+
params["appId"] = appID
167+
}
168+
169+
err := c.request(&res, http.MethodGet, path, nil, params)
170+
if err != nil {
171+
return nil, err
172+
}
173+
174+
return &res, nil
175+
}
176+
177+
// ListAll lists all Crawlers
178+
func (c *Client) ListAll(name, appID string) ([]*CrawlerListItem, error) {
179+
var crawlers []*CrawlerListItem
180+
181+
while := true
182+
page := 0
183+
for while {
184+
page++
185+
res, err := c.List(20, page, name, appID)
186+
if err != nil {
187+
return nil, err
188+
}
189+
190+
crawlers = append(crawlers, res.Items...)
191+
192+
if len(crawlers) >= res.Total {
193+
while = false
194+
}
195+
}
196+
197+
return crawlers, nil
198+
}
199+
200+
// Get gets a Crawler.
201+
func (c *Client) Get(crawlerID string, withConfig bool) (*Crawler, error) {
202+
var res Crawler
203+
path := fmt.Sprintf("crawlers/%s", crawlerID)
204+
params := map[string]string{
205+
"withConfig": fmt.Sprintf("%t", withConfig),
206+
}
207+
208+
err := c.request(&res, http.MethodGet, path, nil, params)
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
return &res, nil
214+
}
215+
216+
// Run runs a Crawler.
217+
// It returns the Task ID if successful.
218+
func (c *Client) Run(crawlerID string) (string, error) {
219+
var res TaskIDResponse
220+
path := fmt.Sprintf("crawlers/%s/run", crawlerID)
221+
222+
err := c.request(&res, http.MethodPost, path, nil, nil)
223+
if err != nil {
224+
return "", err
225+
}
226+
227+
return res.TaskID, nil
228+
}
229+
230+
// Pause pauses a Crawler.
231+
// It returns the Task ID if successful.
232+
func (c *Client) Pause(crawlerID string) (string, error) {
233+
var res TaskIDResponse
234+
path := fmt.Sprintf("crawlers/%s/pause", crawlerID)
235+
236+
err := c.request(&res, http.MethodPost, path, nil, nil)
237+
if err != nil {
238+
return "", err
239+
}
240+
241+
return res.TaskID, nil
242+
}
243+
244+
// Reindex reindexes a Crawler.
245+
// It returns the Task ID if successful.
246+
func (c *Client) Reindex(crawlerID string) (string, error) {
247+
var res TaskIDResponse
248+
path := fmt.Sprintf("crawlers/%s/reindex", crawlerID)
249+
250+
err := c.request(&res, http.MethodPost, path, nil, nil)
251+
if err != nil {
252+
return "", err
253+
}
254+
255+
return res.TaskID, nil
256+
}
257+
258+
// Stats gets the stats of a Crawler.
259+
func (c *Client) Stats(crawlerID string) (*StatsResponse, error) {
260+
var res StatsResponse
261+
path := fmt.Sprintf("crawlers/%s/stats/urls", crawlerID)
262+
263+
err := c.request(&res, http.MethodGet, path, nil, nil)
264+
if err != nil {
265+
return nil, err
266+
}
267+
268+
return &res, nil
269+
}
270+
271+
// CrawlURLs crawls the specified URLs on the specified Crawler.
272+
// It returns the Task ID if successful.
273+
func (c *Client) CrawlURLs(crawlerID string, URLs []string, save, saveSpecified bool) (string, error) {
274+
var res TaskIDResponse
275+
path := fmt.Sprintf("crawlers/%s/urls/crawl", crawlerID)
276+
277+
var body interface{}
278+
if saveSpecified {
279+
body = struct {
280+
URLs []string `json:"urls"`
281+
Save bool `json:"save"`
282+
}{URLs, save}
283+
} else {
284+
body = struct {
285+
URL []string `json:"urls"`
286+
}{URLs}
287+
}
288+
289+
err := c.request(&res, http.MethodPost, path, body, nil)
290+
if err != nil {
291+
return "", err
292+
}
293+
294+
return res.TaskID, nil
295+
}
296+
297+
// Test tests an URL on the specified Crawler.
298+
func (c *Client) Test(crawlerID, URL string, config *Config) (*TestResponse, error) {
299+
var res TestResponse
300+
path := fmt.Sprintf("crawlers/%s/test", crawlerID)
301+
302+
var body interface{}
303+
if config != nil {
304+
body = struct {
305+
URL string `json:"url"`
306+
Config *Config `json:"config"`
307+
}{URL, config}
308+
} else {
309+
body = struct {
310+
URL string `json:"url"`
311+
}{URL}
312+
}
313+
314+
err := c.request(&res, http.MethodPost, path, body, nil)
315+
if err != nil {
316+
return nil, err
317+
}
318+
319+
return &res, nil
320+
}
321+
322+
// CancelTask cancels a blocking task.
323+
func (c *Client) CancelTask(crawlerID, taskID string) error {
324+
path := fmt.Sprintf("crawlers/%s/tasks/%s/cancel", crawlerID, taskID)
325+
326+
err := c.request(nil, http.MethodPost, path, nil, nil)
327+
if err != nil {
328+
return err
329+
}
330+
331+
return nil
332+
}

0 commit comments

Comments
 (0)