Replace/UpsertReplace support

This commit is contained in:
Ian Gulliver
2024-06-23 16:32:28 -07:00
parent 6abf2d051e
commit fd97e6ecca
6 changed files with 89 additions and 25 deletions

10
base.go
View File

@@ -6,9 +6,9 @@ import (
) )
type Base struct { type Base struct {
ID string `json:"id"` ID *string `json:"id,omitempty"`
Name string `json:"name"` Name *string `json:"name,omitempty"`
PermissionLevel string `json:"permissionLevel"` PermissionLevel *string `json:"permissionLevel,omitempty"`
c *Client c *Client
} }
@@ -27,7 +27,7 @@ func (c *Client) GetBaseByName(ctx context.Context, name string) (*Base, error)
} }
for _, base := range bases { for _, base := range bases {
if base.Name == name { if *base.Name == name {
return base, nil return base, nil
} }
} }
@@ -36,5 +36,5 @@ func (c *Client) GetBaseByName(ctx context.Context, name string) (*Base, error)
} }
func (b Base) String() string { func (b Base) String() string {
return b.Name return *b.Name
} }

View File

@@ -47,3 +47,7 @@ func (c *Client) SetRateLimit(rateLimit int) {
func (c *Client) waitForRateLimit() { func (c *Client) waitForRateLimit() {
<-c.rateLimiter <-c.rateLimiter
} }
func P(s string) *string {
return &s
}

View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
) )
@@ -63,7 +64,8 @@ func do[T any](ctx context.Context, c *Client, method, path string, params url.V
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode < 200 || resp.StatusCode > 299 {
return nil, fmt.Errorf("http error: %d %s", resp.StatusCode, http.StatusText(resp.StatusCode)) errBody, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("http error: %d %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), string(errBody))
} }
dec := json.NewDecoder(resp.Body) dec := json.NewDecoder(resp.Body)

View File

@@ -6,10 +6,9 @@ import (
) )
type Record struct { type Record struct {
ID string `json:"id,omitempty"` ID *string `json:"id,omitempty"`
Fields map[string]any `json:"fields"` Fields map[string]any `json:"fields"`
CreatedTime string `json:"createdTime,omitempty"` CreatedTime *string `json:"createdTime,omitempty"`
Deleted bool `json:"deleted,omitempty"`
c *Client c *Client
b *Base b *Base
@@ -20,7 +19,7 @@ type ListRecordOptions struct {
} }
func (t *Table) ListRecords(ctx context.Context, opts *ListRecordOptions) ([]*Record, error) { func (t *Table) ListRecords(ctx context.Context, opts *ListRecordOptions) ([]*Record, error) {
return listAll[Record](ctx, t.c, fmt.Sprintf("%s/%s", t.b.ID, t.ID), nil, "records", func(r *Record) error { return listAll[Record](ctx, t.c, fmt.Sprintf("%s/%s", *t.b.ID, *t.ID), nil, "records", func(r *Record) error {
r.c = t.c r.c = t.c
r.b = t.b r.b = t.b
r.t = t r.t = t
@@ -28,6 +27,53 @@ func (t *Table) ListRecords(ctx context.Context, opts *ListRecordOptions) ([]*Re
}) })
} }
func (r Record) String() string { type updateRecordsRequest struct {
return r.Fields[r.t.Fields[0].Name].(string) PerformUpsert *performUpsertRequest `json:"performUpsert,omitempty"`
Records []*Record `json:"records"`
}
type performUpsertRequest struct {
FieldsToMergeOn []string `json:"fieldsToMergeOn"`
}
type updateRecordsResponse struct {
Records []*Record `json:"records"`
}
func (t *Table) ReplaceRecords(ctx context.Context, records []*Record, matchFields []string) ([]*Record, error) {
ret := []*Record{}
req := &updateRecordsRequest{}
if len(matchFields) > 0 {
req.PerformUpsert = &performUpsertRequest{
FieldsToMergeOn: matchFields,
}
}
for _, chk := range chunk(records, 10) {
req.Records = chk
resp, err := put[updateRecordsResponse](ctx, t.c, fmt.Sprintf("%s/%s", *t.b.ID, *t.ID), req)
if err != nil {
return nil, err
}
for _, rec := range resp.Records {
rec.c = t.c
rec.b = t.b
rec.t = t
ret = append(ret, rec)
}
}
return ret, nil
}
func (r Record) String() string {
if r.ID == nil {
return fmt.Sprintf("(%s [nil])", r.Fields[*r.t.Fields[0].Name].(string))
} else {
return fmt.Sprintf("(%s [%s])", r.Fields[*r.t.Fields[0].Name].(string), *r.ID)
}
} }

View File

@@ -6,10 +6,10 @@ import (
) )
type Table struct { type Table struct {
ID string `json:"id"` ID *string `json:"id,omitempty"`
PrimaryFieldID string `json:"primaryFieldId"` PrimaryFieldID *string `json:"primaryFieldId,omitempty"`
Name string `json:"name"` Name *string `json:"name,omitempty"`
Description string `json:"description"` Description *string `json:"description,omitempty"`
Fields []*Field `json:"fields"` Fields []*Field `json:"fields"`
Views []*View `json:"views"` Views []*View `json:"views"`
@@ -18,21 +18,21 @@ type Table struct {
} }
type Field struct { type Field struct {
ID string `json:"id"` ID *string `json:"id"`
Type string `json:"type"` Type *string `json:"type"`
Name string `json:"name"` Name *string `json:"name"`
Description string `json:"description"` Description *string `json:"description"`
Options map[string]any `json:"options"` Options map[string]any `json:"options"`
} }
type View struct { type View struct {
ID string `json:"id"` ID *string `json:"id"`
Type string `json:"type"` Type *string `json:"type"`
Name string `json:"name"` Name *string `json:"name"`
} }
func (b *Base) ListTables(ctx context.Context) ([]*Table, error) { func (b *Base) ListTables(ctx context.Context) ([]*Table, error) {
return listAll[Table](ctx, b.c, fmt.Sprintf("meta/bases/%s/tables", b.ID), nil, "tables", func(t *Table) error { return listAll[Table](ctx, b.c, fmt.Sprintf("meta/bases/%s/tables", *b.ID), nil, "tables", func(t *Table) error {
t.c = b.c t.c = b.c
t.b = b t.b = b
return nil return nil
@@ -46,7 +46,7 @@ func (b *Base) GetTableByName(ctx context.Context, name string) (*Table, error)
} }
for _, table := range tables { for _, table := range tables {
if table.Name == name { if *table.Name == name {
return table, nil return table, nil
} }
} }

12
util.go Normal file
View File

@@ -0,0 +1,12 @@
package airtable
func chunk[T any](items []T, chunkSize int) [][]T {
chunks := [][]T{}
for chunkSize < len(items) {
chunks = append(chunks, items[0:chunkSize:chunkSize])
items = items[chunkSize:]
}
return append(chunks, items)
}