diff --git a/base.go b/base.go index 67b75d0..dbaeb7b 100644 --- a/base.go +++ b/base.go @@ -6,9 +6,9 @@ import ( ) type Base struct { - ID string `json:"id"` - Name string `json:"name"` - PermissionLevel string `json:"permissionLevel"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + PermissionLevel *string `json:"permissionLevel,omitempty"` c *Client } @@ -27,7 +27,7 @@ func (c *Client) GetBaseByName(ctx context.Context, name string) (*Base, error) } for _, base := range bases { - if base.Name == name { + if *base.Name == name { return base, nil } } @@ -36,5 +36,5 @@ func (c *Client) GetBaseByName(ctx context.Context, name string) (*Base, error) } func (b Base) String() string { - return b.Name + return *b.Name } diff --git a/client.go b/client.go index 0972582..41641bc 100644 --- a/client.go +++ b/client.go @@ -47,3 +47,7 @@ func (c *Client) SetRateLimit(rateLimit int) { func (c *Client) waitForRateLimit() { <-c.rateLimiter } + +func P(s string) *string { + return &s +} diff --git a/http.go b/http.go index 935c5da..3958494 100644 --- a/http.go +++ b/http.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "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() 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) diff --git a/record.go b/record.go index a52c6ab..4758429 100644 --- a/record.go +++ b/record.go @@ -6,10 +6,9 @@ import ( ) type Record struct { - ID string `json:"id,omitempty"` + ID *string `json:"id,omitempty"` Fields map[string]any `json:"fields"` - CreatedTime string `json:"createdTime,omitempty"` - Deleted bool `json:"deleted,omitempty"` + CreatedTime *string `json:"createdTime,omitempty"` c *Client b *Base @@ -20,7 +19,7 @@ type ListRecordOptions struct { } 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.b = t.b r.t = t @@ -28,6 +27,53 @@ func (t *Table) ListRecords(ctx context.Context, opts *ListRecordOptions) ([]*Re }) } -func (r Record) String() string { - return r.Fields[r.t.Fields[0].Name].(string) +type updateRecordsRequest struct { + 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) + } } diff --git a/table.go b/table.go index f6bef56..2c4763d 100644 --- a/table.go +++ b/table.go @@ -6,10 +6,10 @@ import ( ) type Table struct { - ID string `json:"id"` - PrimaryFieldID string `json:"primaryFieldId"` - Name string `json:"name"` - Description string `json:"description"` + ID *string `json:"id,omitempty"` + PrimaryFieldID *string `json:"primaryFieldId,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` Fields []*Field `json:"fields"` Views []*View `json:"views"` @@ -18,21 +18,21 @@ type Table struct { } type Field struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Description string `json:"description"` + ID *string `json:"id"` + Type *string `json:"type"` + Name *string `json:"name"` + Description *string `json:"description"` Options map[string]any `json:"options"` } type View struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` + ID *string `json:"id"` + Type *string `json:"type"` + Name *string `json:"name"` } 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.b = b return nil @@ -46,7 +46,7 @@ func (b *Base) GetTableByName(ctx context.Context, name string) (*Table, error) } for _, table := range tables { - if table.Name == name { + if *table.Name == name { return table, nil } } diff --git a/util.go b/util.go new file mode 100644 index 0000000..d86bbcc --- /dev/null +++ b/util.go @@ -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) +}