2021-09-21 03:39:15 +00:00
|
|
|
package client
|
2021-09-05 20:18:11 +00:00
|
|
|
|
2021-09-11 03:53:50 +00:00
|
|
|
import "bytes"
|
2021-09-06 18:19:04 +00:00
|
|
|
import "encoding/json"
|
2021-09-05 20:18:11 +00:00
|
|
|
import "fmt"
|
2021-09-11 03:53:50 +00:00
|
|
|
import "io/ioutil"
|
2021-09-05 20:18:11 +00:00
|
|
|
import "net/http"
|
2021-09-06 18:19:04 +00:00
|
|
|
import "net/url"
|
2021-09-05 20:18:11 +00:00
|
|
|
import "os"
|
|
|
|
|
|
2021-09-21 03:40:35 +00:00
|
|
|
import "github.com/firestuff/automana/headers"
|
2021-09-10 05:19:28 +00:00
|
|
|
|
2021-09-06 18:19:04 +00:00
|
|
|
type Client struct {
|
2021-09-26 04:25:06 +00:00
|
|
|
client *http.Client
|
|
|
|
|
rateLimit *RateLimit
|
|
|
|
|
concurrencyLimitRead *ConcurrencyLimit
|
|
|
|
|
concurrencyLimitWrite *ConcurrencyLimit
|
2021-09-06 18:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-23 04:30:50 +00:00
|
|
|
type errorDetails struct {
|
|
|
|
|
Message string `json:"message"`
|
2021-09-06 18:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-23 04:30:50 +00:00
|
|
|
type errorResponse struct {
|
|
|
|
|
Errors []*errorDetails `json:"errors"`
|
2021-09-06 18:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-23 04:30:50 +00:00
|
|
|
type emptyResponse struct {
|
|
|
|
|
Data interface{} `json:"data"`
|
|
|
|
|
}
|
2021-09-22 03:22:04 +00:00
|
|
|
|
2021-09-24 04:43:44 +00:00
|
|
|
type nextPage struct {
|
|
|
|
|
Offset string `json:"offset"`
|
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
URI string `json:"uri"`
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-06 18:19:04 +00:00
|
|
|
func NewClient(token string) *Client {
|
2021-09-10 04:57:54 +00:00
|
|
|
c := &Client{
|
2021-09-26 04:25:06 +00:00
|
|
|
client: &http.Client{},
|
|
|
|
|
rateLimit: NewRateLimitPerMinute(1500, 1500),
|
|
|
|
|
concurrencyLimitRead: NewConcurrencyLimit(50),
|
|
|
|
|
concurrencyLimitWrite: NewConcurrencyLimit(15),
|
2021-09-10 04:57:54 +00:00
|
|
|
}
|
2021-09-06 18:19:04 +00:00
|
|
|
|
2021-09-10 04:57:54 +00:00
|
|
|
hdrs := headers.NewHeaders(c.client)
|
2021-09-06 18:19:04 +00:00
|
|
|
hdrs.Add("Accept", "application/json")
|
|
|
|
|
hdrs.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
|
|
|
|
2021-09-10 04:57:54 +00:00
|
|
|
return c
|
2021-09-06 18:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewClientFromEnv() *Client {
|
2021-09-10 04:57:54 +00:00
|
|
|
return NewClient(os.Getenv("ASANA_TOKEN"))
|
2021-09-05 20:18:11 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-06 18:19:04 +00:00
|
|
|
const baseURL = "https://app.asana.com/api/1.0/"
|
2021-09-23 04:30:50 +00:00
|
|
|
const perPage = 100
|
2021-09-06 18:19:04 +00:00
|
|
|
|
|
|
|
|
func (c *Client) get(path string, values *url.Values, out interface{}) error {
|
2021-09-10 04:57:54 +00:00
|
|
|
if values == nil {
|
|
|
|
|
values = &url.Values{}
|
|
|
|
|
}
|
2021-09-22 03:22:04 +00:00
|
|
|
values.Set("limit", fmt.Sprintf("%d", perPage))
|
2021-09-10 04:57:54 +00:00
|
|
|
|
|
|
|
|
url := fmt.Sprintf("%s%s?%s", baseURL, path, values.Encode())
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-26 04:33:08 +00:00
|
|
|
c.rateLimit.Acquire1()
|
|
|
|
|
c.concurrencyLimitRead.Acquire1()
|
2021-09-10 04:57:54 +00:00
|
|
|
resp, err := c.client.Do(req)
|
2021-09-26 04:33:08 +00:00
|
|
|
c.concurrencyLimitRead.Release1()
|
2021-09-10 04:57:54 +00:00
|
|
|
if err != nil {
|
2021-09-11 03:53:50 +00:00
|
|
|
return err
|
|
|
|
|
}
|
2021-09-13 00:11:30 +00:00
|
|
|
defer resp.Body.Close()
|
2021-09-11 03:53:50 +00:00
|
|
|
|
2021-09-28 04:43:38 +00:00
|
|
|
err = c.rateLimit.MaybeRetryAfter(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-12 20:08:13 +00:00
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
|
|
2021-09-11 03:53:50 +00:00
|
|
|
if resp.StatusCode != 200 {
|
2021-09-12 21:49:54 +00:00
|
|
|
errorResp := &errorResponse{}
|
|
|
|
|
err = dec.Decode(errorResp)
|
2021-09-11 03:53:50 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-09-12 20:08:13 +00:00
|
|
|
return fmt.Errorf("%s: %s", resp.Status, errorResp.Errors[0].Message)
|
2021-09-11 03:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = dec.Decode(out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) post(path string, body interface{}, out interface{}) error {
|
2021-09-19 06:49:00 +00:00
|
|
|
return c.doWithBody("POST", path, body, out)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) put(path string, body interface{}, out interface{}) error {
|
|
|
|
|
return c.doWithBody("PUT", path, body, out)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) doWithBody(method string, path string, body interface{}, out interface{}) error {
|
2021-09-11 03:53:50 +00:00
|
|
|
url := fmt.Sprintf("%s%s", baseURL, path)
|
|
|
|
|
|
2021-09-19 06:49:00 +00:00
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
|
enc := json.NewEncoder(buf)
|
|
|
|
|
enc.SetEscapeHTML(false)
|
|
|
|
|
|
|
|
|
|
err := enc.Encode(body)
|
2021-09-11 03:53:50 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-19 06:49:00 +00:00
|
|
|
req, err := http.NewRequest(method, url, buf)
|
2021-09-11 03:53:50 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-26 04:33:08 +00:00
|
|
|
c.rateLimit.Acquire1()
|
|
|
|
|
c.concurrencyLimitWrite.Acquire1()
|
2021-09-11 03:53:50 +00:00
|
|
|
resp, err := c.client.Do(req)
|
2021-09-26 04:33:08 +00:00
|
|
|
c.concurrencyLimitWrite.Release1()
|
2021-09-11 03:53:50 +00:00
|
|
|
if err != nil {
|
2021-09-10 04:57:54 +00:00
|
|
|
return err
|
|
|
|
|
}
|
2021-09-13 00:11:30 +00:00
|
|
|
defer resp.Body.Close()
|
2021-09-10 04:57:54 +00:00
|
|
|
|
|
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("%s: %s", resp.Status, string(body))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
|
err = dec.Decode(out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|