package api import ( "bytes" "encoding/json" "errors" "go.uber.org/zap" "io" "net/http" "net/url" "strconv" "strings" "time" ) type Client struct { URL string Key string client *http.Client log *zap.SugaredLogger } func New(url, key string, log *zap.SugaredLogger) *Client { return &Client{ URL: strings.TrimSuffix(url, "/"), Key: key, client: &http.Client{Timeout: time.Second * 2}, log: log, } } func (c *Client) Issue(id int) (*Issue, error) { var resp IssueResponse _, err := c.sendJSONRequest(http.MethodGet, "/issues/"+strconv.Itoa(id), nil, &resp) if err != nil { return nil, err } if resp.IsError() { return nil, resp } return resp.Issue, nil } func (c *Client) SearchIssues(query string, offset, limit int) (*SearchIssuesResponse, error) { req := url.Values{ "q": []string{query}, "offset": []string{strconv.Itoa(offset)}, "limit": []string{strconv.Itoa(limit)}, } var resp SearchIssuesResponse _, err := c.sendJSONRequest(http.MethodGet, "/search.json?"+req.Encode(), nil, &resp) if err != nil { return nil, err } if resp.IsError() { return nil, resp } return &resp, nil } func (c *Client) UpdateIssue(id int, issue *Issue) error { st, err := c.sendJSONRequest(http.MethodPut, "/issues/"+strconv.Itoa(id), IssueResponse{Issue: issue}, nil) if err != nil { return err } if st != http.StatusOK { return errors.New("unexpected status code: " + strconv.Itoa(st)) } return nil } func (c *Client) sendRequest(method, path string, headers map[string]string, body io.Reader) (*http.Response, error) { req, err := http.NewRequest(method, c.URL+path, body) if err != nil { return nil, err } req.Header.Set("X-Redmine-API-Key", c.Key) for name, value := range headers { req.Header.Set(name, value) } return c.client.Do(req) } func (c *Client) sendJSONRequest(method, path string, body, out interface{}) (int, error) { var buf io.Reader if body != nil { data, err := json.Marshal(body) if err != nil { return 0, err } buf = bytes.NewBuffer(data) } path = path + ".json" c.log.Debugf("[Redmine Request] %s %s; body: %s", method, path, func() string { if buf != nil { return buf.(*bytes.Buffer).String() } return "" }()) resp, err := c.sendRequest(method, path, map[string]string{ "Content-Type": "application/json", }, buf) if err != nil { return 0, err } defer func() { _ = resp.Body.Close() }() if out != nil { return resp.StatusCode, json.NewDecoder(resp.Body).Decode(out) } c.log.Debugf("[Redmine Response] code: %d; body: %#v", resp.StatusCode, out) return resp.StatusCode, nil }