feat: add read-only support of the meta API (#13)
* Add bases API. * Implement get tables schema * Rename GetBase to GetBaseSchema * Add tests * Update doc
This commit is contained in:
12
README.md
12
README.md
@@ -43,6 +43,18 @@ You should get `your_api_token` in the airtable [account page](https://airtable.
|
|||||||
client := airtable.NewClient("your_api_token")
|
client := airtable.NewClient("your_api_token")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### List bases
|
||||||
|
|
||||||
|
```Go
|
||||||
|
bases, err := client.GetBases().WithOffset("").Do()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get base schema
|
||||||
|
|
||||||
|
```Go
|
||||||
|
schema, err := client.GetBaseSchema("your_database_ID").Do()
|
||||||
|
```
|
||||||
|
|
||||||
### Get table
|
### Get table
|
||||||
|
|
||||||
To get the `your_database_ID` you should go to [main API page](https://airtable.com/api) and select the database.
|
To get the `your_database_ID` you should go to [main API page](https://airtable.com/api) and select the database.
|
||||||
|
|||||||
89
base.go
Normal file
89
base.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package airtable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Base type of airtable base.
|
||||||
|
type Base struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PermissionLevel string `json:"permissionLevel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base type of airtable bases.
|
||||||
|
type Bases struct {
|
||||||
|
Bases []*Base `json:"bases"`
|
||||||
|
Offset string `json:"offset,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
type View struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TableSchema struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
PrimaryFieldID string `json:"primaryFieldId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Fields []*Field `json:"fields"`
|
||||||
|
Views []*View `json:"views"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tables struct {
|
||||||
|
Tables []*TableSchema `json:"tables"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBasesWithParams get bases with url values params
|
||||||
|
// https://airtable.com/developers/web/api/list-bases
|
||||||
|
func (at *Client) GetBasesWithParams(params url.Values) (*Bases, error) {
|
||||||
|
bases := new(Bases)
|
||||||
|
|
||||||
|
err := at.get("meta", "bases", "", params, bases)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table represents table object.
|
||||||
|
type BaseConfig struct {
|
||||||
|
client *Client
|
||||||
|
dbId string
|
||||||
|
params url.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBase return Base object.
|
||||||
|
func (c *Client) GetBaseSchema(dbId string) *BaseConfig {
|
||||||
|
return &BaseConfig{
|
||||||
|
client: c,
|
||||||
|
dbId: dbId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do send the prepared
|
||||||
|
func (b *BaseConfig) Do() (*Tables, error) {
|
||||||
|
return b.GetTablesWithParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTablesWithParams get tables from a base with url values params
|
||||||
|
// https://airtable.com/developers/web/api/get-base-schema
|
||||||
|
func (b *BaseConfig) GetTablesWithParams() (*Tables, error) {
|
||||||
|
tables := new(Tables)
|
||||||
|
|
||||||
|
err := b.client.get("meta/bases", b.dbId, "tables", nil, tables)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables, nil
|
||||||
|
}
|
||||||
25
base_test.go
Normal file
25
base_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package airtable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetBaseSchema(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
baseschema := client.GetBaseSchema("test")
|
||||||
|
baseschema.client.baseURL = mockResponse("base_schema.json").URL
|
||||||
|
|
||||||
|
result, err := baseschema.Do()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("there should not be an err, but was: %v", err)
|
||||||
|
}
|
||||||
|
if len(result.Tables) != 2 {
|
||||||
|
t.Errorf("there should be 2 tales, but was %v", len(result.Tables))
|
||||||
|
}
|
||||||
|
|
||||||
|
baseschema.client.baseURL = mockErrorResponse(400).URL
|
||||||
|
_, err = baseschema.Do()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("there should be an err, but was nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
37
get-bases.go
Normal file
37
get-bases.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package airtable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetBasesConfig helper type to use in.
|
||||||
|
// step by step get bases.
|
||||||
|
type GetBasesConfig struct {
|
||||||
|
client *Client
|
||||||
|
params url.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBases prepare step to get bases.
|
||||||
|
func (c *Client) GetBases() *GetBasesConfig {
|
||||||
|
return &GetBasesConfig{
|
||||||
|
client: c,
|
||||||
|
params: url.Values{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
// The server returns one page of bases at a time.
|
||||||
|
|
||||||
|
// If there are more records, the response will contain an offset.
|
||||||
|
// To fetch the next page of records, include offset in the next request's parameters.
|
||||||
|
|
||||||
|
// WithOffset Pagination will stop when you've reached the end of your bases.
|
||||||
|
func (gbc *GetBasesConfig) WithOffset(offset string) *GetBasesConfig {
|
||||||
|
gbc.params.Set("offset", offset)
|
||||||
|
return gbc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do send the prepared get records request.
|
||||||
|
func (gbc *GetBasesConfig) Do() (*Bases, error) {
|
||||||
|
return gbc.client.GetBasesWithParams(gbc.params)
|
||||||
|
}
|
||||||
25
get-bases_test.go
Normal file
25
get-bases_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package airtable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetBases_Do(t *testing.T) {
|
||||||
|
client := testClient(t)
|
||||||
|
bases := client.GetBases()
|
||||||
|
bases.client.baseURL = mockResponse("get_bases.json").URL
|
||||||
|
|
||||||
|
result, err := bases.WithOffset("0").Do()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("there should not be an err, but was: %v", err)
|
||||||
|
}
|
||||||
|
if len(result.Bases) != 2 {
|
||||||
|
t.Errorf("there should be 2 bases, but was %v", len(result.Bases))
|
||||||
|
}
|
||||||
|
|
||||||
|
bases.client.baseURL = mockErrorResponse(400).URL
|
||||||
|
_, err = bases.Do()
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("there should be an err, but was nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
72
testdata/base_schema.json
vendored
Normal file
72
testdata/base_schema.json
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"tables": [
|
||||||
|
{
|
||||||
|
"description": "Apartments to track.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"description": "Name of the apartment",
|
||||||
|
"id": "fld1VnoyuotSTyxW1",
|
||||||
|
"name": "Name",
|
||||||
|
"type": "singleLineText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fldoaIqdn5szURHpw",
|
||||||
|
"name": "Pictures",
|
||||||
|
"type": "multipleAttachments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fldumZe00w09RYTW6",
|
||||||
|
"name": "District",
|
||||||
|
"options": {
|
||||||
|
"inverseLinkFieldId": "fldWnCJlo2z6ttT8Y",
|
||||||
|
"isReversed": false,
|
||||||
|
"linkedTableId": "tblK6MZHez0ZvBChZ",
|
||||||
|
"prefersSingleRecordLink": true
|
||||||
|
},
|
||||||
|
"type": "multipleRecordLinks"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "tbltp8DGLhqbUmjK1",
|
||||||
|
"name": "Apartments",
|
||||||
|
"primaryFieldId": "fld1VnoyuotSTyxW1",
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"id": "viwQpsuEDqHFqegkp",
|
||||||
|
"name": "Grid view",
|
||||||
|
"type": "grid"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"id": "fldEVzvQOoULO38yl",
|
||||||
|
"name": "Name",
|
||||||
|
"type": "singleLineText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Apartments that belong to this district",
|
||||||
|
"id": "fldWnCJlo2z6ttT8Y",
|
||||||
|
"name": "Apartments",
|
||||||
|
"options": {
|
||||||
|
"inverseLinkFieldId": "fldumZe00w09RYTW6",
|
||||||
|
"isReversed": false,
|
||||||
|
"linkedTableId": "tbltp8DGLhqbUmjK1",
|
||||||
|
"prefersSingleRecordLink": false
|
||||||
|
},
|
||||||
|
"type": "multipleRecordLinks"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "tblK6MZHez0ZvBChZ",
|
||||||
|
"name": "Districts",
|
||||||
|
"primaryFieldId": "fldEVzvQOoULO38yl",
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"id": "viwi3KXvrKug2mIBS",
|
||||||
|
"name": "Grid view",
|
||||||
|
"type": "grid"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
testdata/get_bases.json
vendored
Normal file
15
testdata/get_bases.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"bases": [
|
||||||
|
{
|
||||||
|
"id": "appLkNDICXNqxSDhG",
|
||||||
|
"name": "Apartment Hunting",
|
||||||
|
"permissionLevel": "create"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "appSW9R5uCNmRmfl6",
|
||||||
|
"name": "Project Tracker",
|
||||||
|
"permissionLevel": "edit"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset": "itr23sEjsdfEr3282/appSW9R5uCNmRmfl6"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user