Files
airtable/client.go

156 lines
3.2 KiB
Go
Raw Normal View History

package airtable
import (
"bytes"
2021-10-02 14:27:00 +03:00
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
const (
2024-06-22 15:13:30 -07:00
defaultBaseURL = "https://api.airtable.com/v0"
defaultRateLimit = 4
)
type Client struct {
2024-06-22 15:13:30 -07:00
Client *http.Client
BaseURL string
2024-06-22 15:33:14 -07:00
apiKey string
rateLimiter <-chan time.Time
}
2024-06-22 15:13:30 -07:00
func New(apiKey string) *Client {
c := &Client{
Client: http.DefaultClient,
BaseURL: defaultBaseURL,
2024-06-22 15:33:14 -07:00
apiKey: apiKey,
}
2024-06-22 15:13:30 -07:00
c.SetRateLimit(defaultRateLimit)
return c
}
2024-06-22 15:13:30 -07:00
func (at *Client) SetRateLimit(rateLimit int) {
at.rateLimiter = time.Tick(time.Second / time.Duration(rateLimit))
}
2024-06-22 15:13:30 -07:00
func (at *Client) waitForRateLimit() {
<-at.rateLimiter
}
2023-07-09 22:33:45 +05:00
func (at *Client) get(ctx context.Context, db, table, recordID string, params url.Values, target any) error {
2024-06-22 15:13:30 -07:00
return at.do(ctx, "GET", db, table, recordID, params, nil, target)
}
2024-06-22 15:13:30 -07:00
func (at *Client) post(ctx context.Context, db, table string, data, target any) error {
return at.do(ctx, "POST", db, table, "", nil, data, target)
}
2023-07-09 22:33:45 +05:00
func (at *Client) delete(ctx context.Context, db, table string, recordIDs []string, target any) error {
params := url.Values{}
2021-10-02 14:27:00 +03:00
for _, recordID := range recordIDs {
params.Add("records[]", recordID)
}
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
return at.do(ctx, "DELETE", db, table, "", params, nil, target)
}
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
func (at *Client) patch(ctx context.Context, db, table string, data, target any) error {
return at.do(ctx, "PATCH", db, table, "", nil, data, target)
}
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
func (at *Client) put(ctx context.Context, db, table string, data, target any) error {
return at.do(ctx, "PUT", db, table, "", nil, data, target)
}
2024-06-22 15:13:30 -07:00
func (at *Client) do(ctx context.Context, method, db, table, recordID string, params url.Values, data, target any) error {
var err error
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
at.waitForRateLimit()
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
url := fmt.Sprintf("%s/%s/%s", at.BaseURL, db, table)
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
if recordID != "" {
url += fmt.Sprintf("/%s", recordID)
}
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
body := []byte{}
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
if data != nil {
body, err = json.Marshal(data)
if err != nil {
return fmt.Errorf("marshalling message body: %w", err)
}
2021-10-02 14:27:00 +03:00
}
2024-06-22 15:13:30 -07:00
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(body))
2021-10-02 14:27:00 +03:00
if err != nil {
return fmt.Errorf("cannot create request: %w", err)
}
2024-06-22 15:13:30 -07:00
req.URL.RawQuery = params.Encode()
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
req.Header.Set("Content-Type", "application/json")
2024-06-22 15:33:14 -07:00
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", at.apiKey))
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
resp, err := at.Client.Do(req)
if err != nil {
2024-06-22 15:13:30 -07:00
return fmt.Errorf("http request failed: %w", err)
}
2021-10-02 14:27:00 +03:00
defer resp.Body.Close()
2021-10-02 14:27:00 +03:00
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return makeHTTPClientError(url, resp)
}
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
dec := json.NewDecoder(resp.Body)
2021-10-02 14:27:00 +03:00
2024-06-22 15:13:30 -07:00
err = dec.Decode(target)
if err != nil {
2024-06-22 15:13:30 -07:00
return fmt.Errorf("json decode failed: %w", err)
}
2021-10-02 14:27:00 +03:00
return nil
}
2024-06-22 15:33:14 -07:00
func listAll[T any](ctx context.Context, c *Client, db, table string, params url.Values, key string) ([]*T, error) {
ret := []*T{}
for {
resp := map[string]any{}
2024-06-22 15:35:58 -07:00
err := c.get(ctx, db, table, "", params, &resp)
2024-06-22 15:33:14 -07:00
if err != nil {
return nil, err
}
subresp, err := json.Marshal(resp[key])
if err != nil {
return nil, err
}
obj := []*T{}
2024-06-22 15:35:58 -07:00
err = json.Unmarshal(subresp, &obj)
2024-06-22 15:33:14 -07:00
if err != nil {
return nil, err
}
ret = append(ret, obj...)
2024-06-22 15:35:58 -07:00
off, found := resp["offset"]
if !found {
return ret, nil
}
params.Set("offset", off.(string))
2024-06-22 15:33:14 -07:00
}
}