diff --git a/client.go b/client.go index 8c04144..56214fc 100644 --- a/client.go +++ b/client.go @@ -18,7 +18,7 @@ import ( const ( apiURL = "https://api.notion.com" apiVersion = "v1" - notionVersion = "2022-06-28" + notionVersion = "2025-09-03" maxRetries = 3 ) @@ -47,13 +47,14 @@ type Client struct { oauthID string oauthSecret string - Database DatabaseService + Authentication AuthenticationService Block BlockService + Comment CommentService + Database DatabaseService + DataSource DataSourceService Page PageService - User UserService Search SearchService - Comment CommentService - Authentication AuthenticationService + User UserService } func NewClient(token Token, opts ...ClientOption) *Client { @@ -70,13 +71,14 @@ func NewClient(token Token, opts ...ClientOption) *Client { maxRetries: maxRetries, } - c.Database = &DatabaseClient{apiClient: c} + c.Authentication = &AuthenticationClient{apiClient: c} c.Block = &BlockClient{apiClient: c} + c.Comment = &CommentClient{apiClient: c} + c.Database = &DatabaseClient{apiClient: c} + c.DataSource = &DataSourceClient{apiClient: c} c.Page = &PageClient{apiClient: c} - c.User = &UserClient{apiClient: c} c.Search = &SearchClient{apiClient: c} - c.Comment = &CommentClient{apiClient: c} - c.Authentication = &AuthenticationClient{apiClient: c} + c.User = &UserClient{apiClient: c} for _, opt := range opts { opt(c) diff --git a/const.go b/const.go index eaa0933..b37a1f0 100644 --- a/const.go +++ b/const.go @@ -1,14 +1,15 @@ package notionapi const ( - ObjectTypeDatabase ObjectType = "database" - ObjectTypeBlock ObjectType = "block" - ObjectTypePage ObjectType = "page" - ObjectTypeList ObjectType = "list" - ObjectTypeText ObjectType = "text" - ObjectTypeUser ObjectType = "user" - ObjectTypeError ObjectType = "error" - ObjectTypeComment ObjectType = "comment" + ObjectTypeDatabase ObjectType = "database" + ObjectTypeDataSource ObjectType = "data_source" + ObjectTypeBlock ObjectType = "block" + ObjectTypePage ObjectType = "page" + ObjectTypeList ObjectType = "list" + ObjectTypeText ObjectType = "text" + ObjectTypeUser ObjectType = "user" + ObjectTypeError ObjectType = "error" + ObjectTypeComment ObjectType = "comment" ) const ( @@ -192,10 +193,11 @@ const ( ) const ( - ParentTypeDatabaseID ParentType = "database_id" - ParentTypePageID ParentType = "page_id" - ParentTypeWorkspace ParentType = "workspace" - ParentTypeBlockID ParentType = "block_id" + ParentTypeDatabaseID ParentType = "database_id" + ParentTypeDataSourceID ParentType = "data_source_id" + ParentTypePageID ParentType = "page_id" + ParentTypeWorkspace ParentType = "workspace" + ParentTypeBlockID ParentType = "block_id" ) const ( diff --git a/data_source.go b/data_source.go new file mode 100644 index 0000000..4e6cbe3 --- /dev/null +++ b/data_source.go @@ -0,0 +1,198 @@ +package notionapi + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "time" +) + +type DataSourceID string + +func (dsID DataSourceID) String() string { + return string(dsID) +} + +type DataSourceService interface { + Create(ctx context.Context, request *DataSourceCreateRequest) (*DataSource, error) + Query(ctx context.Context, id DataSourceID, request *DataSourceQueryRequest) (*DataSourceQueryResponse, error) + Get(ctx context.Context, id DataSourceID) (*DataSource, error) + Update(ctx context.Context, id DataSourceID, request *DataSourceUpdateRequest) (*DataSource, error) +} + +type DataSourceClient struct { + apiClient *Client +} + +// Create a data source. +// https://developers.notion.com/reference/create-a-data-source +func (c *DataSourceClient) Create(ctx context.Context, requestBody *DataSourceCreateRequest) (*DataSource, error) { + res, err := c.apiClient.request(ctx, http.MethodPost, "data_sources", nil, requestBody) + if err != nil { + return nil, err + } + + defer func() { + if errClose := res.Body.Close(); errClose != nil { + log.Println("failed to close body, should never happen") + } + }() + + var response DataSource + if err = json.NewDecoder(res.Body).Decode(&response); err != nil { + return nil, err + } + + return &response, nil +} + +type DataSourceCreateRequest struct { + Parent Parent `json:"parent"` + Properties PropertyConfigs `json:"properties"` + Title []RichText `json:"title"` + IsInline bool `json:"is_inline,omitempty"` +} + +// Query gets a list of Pages contained in the data source, filtered and ordered +// according to the filter conditions and sort criteria provided in the request. +// The response may contain fewer than page_size of results. If the response +// includes a next_cursor value, refer to the pagination reference for details +// about how to use a cursor to iterate through the list. +// +// Filters are similar to the filters provided in the Notion UI where the set of +// filters and filter groups chained by "And" in the UI is equivalent to having +// each filter in the array of the compound "and" filter. A similar set of +// filters chained by "Or" in the UI would be represented as filters in the +// array of the "or" compound filter. +// +// Filters operate on data source properties and can be combined. If no filter is +// provided, all the pages in the data source will be returned with pagination. +// +// See https://developers.notion.com/reference/post-data-source-query. +func (c *DataSourceClient) Query(ctx context.Context, id DataSourceID, requestBody *DataSourceQueryRequest) (*DataSourceQueryResponse, error) { + res, err := c.apiClient.request(ctx, http.MethodPost, fmt.Sprintf("data_sources/%s/query", id.String()), nil, requestBody) + if err != nil { + return nil, err + } + + defer func() { + if errClose := res.Body.Close(); errClose != nil { + log.Println("failed to close body, should never happen") + } + }() + + var response DataSourceQueryResponse + if err = json.NewDecoder(res.Body).Decode(&response); err != nil { + return nil, err + } + + return &response, nil +} + +// Get a data source. +// https://developers.notion.com/reference/get-data-source +func (c *DataSourceClient) Get(ctx context.Context, id DataSourceID) (*DataSource, error) { + if id == "" { + return nil, errors.New("empty data source id") + } + + res, err := c.apiClient.request(ctx, http.MethodGet, fmt.Sprintf("data_sources/%s", id.String()), nil, nil) + if err != nil { + return nil, err + } + + defer func() { + if errClose := res.Body.Close(); errClose != nil { + log.Println("failed to close body, should never happen") + } + }() + + var response DataSource + if err = json.NewDecoder(res.Body).Decode(&response); err != nil { + return nil, err + } + + return &response, nil +} + +// Update a data source. +// https://developers.notion.com/reference/update-a-data-source +func (c *DataSourceClient) Update(ctx context.Context, id DataSourceID, requestBody *DataSourceUpdateRequest) (*DataSource, error) { + res, err := c.apiClient.request(ctx, http.MethodPatch, fmt.Sprintf("data_sources/%s", id.String()), nil, requestBody) + if err != nil { + return nil, err + } + + defer func() { + if errClose := res.Body.Close(); errClose != nil { + log.Println("failed to close body, should never happen") + } + }() + + var response DataSource + if err = json.NewDecoder(res.Body).Decode(&response); err != nil { + return nil, err + } + return &response, nil +} + +type DataSourceUpdateRequest struct { + Title []RichText `json:"title,omitempty"` + Properties PropertyConfigs `json:"properties,omitempty"` +} + +// DataSource object. +type DataSource struct { + Object ObjectType `json:"object"` + ID ObjectID `json:"id"` + CreatedTime time.Time `json:"created_time"` + LastEditedTime time.Time `json:"last_edited_time"` + CreatedBy User `json:"created_by,omitempty"` + LastEditedBy User `json:"last_edited_by,omitempty"` + Title []RichText `json:"title"` + Parent Parent `json:"parent"` + DatabaseParent *Parent `json:"database_parent,omitempty"` + URL string `json:"url"` + PublicURL string `json:"public_url"` + Properties PropertyConfigs `json:"properties"` + IsInline bool `json:"is_inline"` + Archived bool `json:"archived"` + Icon *Icon `json:"icon,omitempty"` + Cover *Image `json:"cover,omitempty"` + Description []RichText `json:"description,omitempty"` +} + +func (ds *DataSource) GetObject() ObjectType { + return ds.Object +} + +type DataSourceQueryRequest struct { + Filter Filter `json:"filter,omitempty"` + Sorts []SortObject `json:"sorts,omitempty"` + StartCursor Cursor `json:"start_cursor,omitempty"` + PageSize int `json:"page_size,omitempty"` +} + +func (qr *DataSourceQueryRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Sorts []SortObject `json:"sorts,omitempty"` + StartCursor Cursor `json:"start_cursor,omitempty"` + PageSize int `json:"page_size,omitempty"` + Filter interface{} `json:"filter,omitempty"` + }{ + Sorts: qr.Sorts, + StartCursor: qr.StartCursor, + PageSize: qr.PageSize, + Filter: qr.Filter, + }) +} + +type DataSourceQueryResponse struct { + Object ObjectType `json:"object"` + Results []Page `json:"results"` + HasMore bool `json:"has_more"` + NextCursor Cursor `json:"next_cursor"` +} diff --git a/data_source_test.go b/data_source_test.go new file mode 100644 index 0000000..4876000 --- /dev/null +++ b/data_source_test.go @@ -0,0 +1,210 @@ +package notionapi_test + +import ( + "context" + "net/http" + "reflect" + "testing" + "time" + + "github.com/jomei/notionapi" +) + +func TestDataSourceClient(t *testing.T) { + timestamp, err := time.Parse(time.RFC3339, "2021-05-24T05:06:34.827Z") + if err != nil { + t.Fatal(err) + } + + var user = notionapi.User{ + Object: "user", + ID: "some_id", + } + + t.Run("Query", func(t *testing.T) { + tests := []struct { + name string + filePath string + statusCode int + id notionapi.DataSourceID + request *notionapi.DataSourceQueryRequest + want *notionapi.DataSourceQueryResponse + wantErr bool + err error + }{ + { + name: "returns query results", + id: "some_id", + filePath: "testdata/data_source_query.json", + statusCode: http.StatusOK, + request: ¬ionapi.DataSourceQueryRequest{ + Filter: ¬ionapi.PropertyFilter{ + Property: "Name", + RichText: ¬ionapi.TextFilterCondition{ + Contains: "Hel", + }, + }, + }, + want: ¬ionapi.DataSourceQueryResponse{ + Object: notionapi.ObjectTypeList, + Results: []notionapi.Page{ + { + Object: notionapi.ObjectTypePage, + ID: "some_id", + CreatedTime: timestamp, + LastEditedTime: timestamp, + CreatedBy: user, + LastEditedBy: user, + Parent: notionapi.Parent{ + Type: notionapi.ParentTypeDataSourceID, + DatabaseID: "some_id", + }, + Archived: false, + URL: "some_url", + }, + }, + HasMore: false, + NextCursor: "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := newMockedClient(t, tt.filePath, tt.statusCode) + client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) + got, err := client.DataSource.Query(context.Background(), tt.id, tt.request) + + if (err != nil) != tt.wantErr { + t.Errorf("Query() error = %v, wantErr %v", err, tt.wantErr) + return + } + got.Results[0].Properties = nil + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Query() got = %v, want %v", got, tt.want) + } + }) + } + }) + +} + +func TestDataSourceQueryRequest_MarshalJSON(t *testing.T) { + timeObj, err := time.Parse(time.RFC3339, "2021-05-10T02:43:42Z") + if err != nil { + t.Error(err) + return + } + dateObj := notionapi.Date(timeObj) + tests := []struct { + name string + req *notionapi.DataSourceQueryRequest + want []byte + wantErr bool + }{ + { + name: "timestamp created", + req: ¬ionapi.DataSourceQueryRequest{ + Filter: ¬ionapi.TimestampFilter{ + Timestamp: notionapi.TimestampCreated, + CreatedTime: ¬ionapi.DateFilterCondition{ + NextWeek: &struct{}{}, + }, + }, + }, + want: []byte(`{"filter":{"timestamp":"created_time","created_time":{"next_week":{}}}}`), + }, + { + name: "timestamp last edited", + req: ¬ionapi.DataSourceQueryRequest{ + Filter: ¬ionapi.TimestampFilter{ + Timestamp: notionapi.TimestampLastEdited, + LastEditedTime: ¬ionapi.DateFilterCondition{ + Before: &dateObj, + }, + }, + }, + want: []byte(`{"filter":{"timestamp":"last_edited_time","last_edited_time":{"before":"2021-05-10T02:43:42Z"}}}`), + }, + { + name: "or compound filter one level", + req: ¬ionapi.DataSourceQueryRequest{ + Filter: notionapi.OrCompoundFilter{ + notionapi.PropertyFilter{ + Property: "Status", + Select: ¬ionapi.SelectFilterCondition{ + Equals: "Reading", + }, + }, + notionapi.PropertyFilter{ + Property: "Publisher", + Select: ¬ionapi.SelectFilterCondition{ + Equals: "NYT", + }, + }, + }, + }, + want: []byte(`{"filter":{"or":[{"property":"Status","select":{"equals":"Reading"}},{"property":"Publisher","select":{"equals":"NYT"}}]}}`), + }, + { + name: "and compound filter one level", + req: ¬ionapi.DataSourceQueryRequest{ + Filter: notionapi.AndCompoundFilter{ + notionapi.PropertyFilter{ + Property: "Status", + Select: ¬ionapi.SelectFilterCondition{ + Equals: "Reading", + }, + }, + notionapi.PropertyFilter{ + Property: "Publisher", + Select: ¬ionapi.SelectFilterCondition{ + Equals: "NYT", + }, + }, + }, + }, + want: []byte(`{"filter":{"and":[{"property":"Status","select":{"equals":"Reading"}},{"property":"Publisher","select":{"equals":"NYT"}}]}}`), + }, + { + name: "compound filter two levels", + req: ¬ionapi.DataSourceQueryRequest{ + Filter: notionapi.OrCompoundFilter{ + notionapi.PropertyFilter{ + Property: "Description", + RichText: ¬ionapi.TextFilterCondition{ + Contains: "fish", + }, + }, + notionapi.AndCompoundFilter{ + notionapi.PropertyFilter{ + Property: "Food group", + Select: ¬ionapi.SelectFilterCondition{ + Equals: "🄦Vegetable", + }, + }, + notionapi.PropertyFilter{ + Property: "Is protein rich?", + Checkbox: ¬ionapi.CheckboxFilterCondition{ + Equals: true, + }, + }, + }, + }, + }, + want: []byte(`{"filter":{"or":[{"property":"Description","rich_text":{"contains":"fish"}},{"and":[{"property":"Food group","select":{"equals":"🄦Vegetable"}},{"property":"Is protein rich?","checkbox":{"equals":true}}]}]}}`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.req.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalJSON() got = %s, want %s", got, tt.want) + } + }) + } +} diff --git a/database.go b/database.go index 7c24622..156676b 100644 --- a/database.go +++ b/database.go @@ -18,7 +18,6 @@ func (dID DatabaseID) String() string { type DatabaseService interface { Create(ctx context.Context, request *DatabaseCreateRequest) (*Database, error) - Query(context.Context, DatabaseID, *DatabaseQueryRequest) (*DatabaseQueryResponse, error) Get(context.Context, DatabaseID) (*Database, error) Update(context.Context, DatabaseID, *DatabaseUpdateRequest) (*Database, error) } @@ -65,57 +64,6 @@ type DatabaseCreateRequest struct { IsInline bool `json:"is_inline"` } -// Gets a list of Pages contained in the database, filtered and ordered -// according to the filter conditions and sort criteria provided in the request. -// The response may contain fewer than page_size of results. If the response -// includes a next_cursor value, refer to the pagination reference for details -// about how to use a cursor to iterate through the list. -// -// Filters are similar to the filters provided in the Notion UI where the set of -// filters and filter groups chained by "And" in the UI is equivalent to having -// each filter in the array of the compound "and" filter. Similar a set of -// filters chained by "Or" in the UI would be represented as filters in the -// array of the "or" compound filter. -// -// Filters operate on database properties and can be combined. If no filter is -// provided, all the pages in the database will be returned with pagination. -// -// See https://developers.notion.com/reference/post-database-query -func (dc *DatabaseClient) Query(ctx context.Context, id DatabaseID, requestBody *DatabaseQueryRequest) (*DatabaseQueryResponse, error) { - res, err := dc.apiClient.request(ctx, http.MethodPost, fmt.Sprintf("databases/%s/query", id.String()), nil, requestBody) - if err != nil { - return nil, err - } - - defer func() { - if errClose := res.Body.Close(); errClose != nil { - log.Println("failed to close body, should never happen") - } - }() - - var response DatabaseQueryResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, err - } - - return &response, nil -} - -// DatabaseQueryRequest represents the request body for DatabaseClient.Query. -type DatabaseQueryRequest struct { - // When supplied, limits which pages are returned based on the filter - // conditions. - Filter Filter - // When supplied, orders the results based on the provided sort criteria. - Sorts []SortObject `json:"sorts,omitempty"` - // When supplied, returns a page of results starting after the cursor provided. - // If not supplied, this endpoint will return the first page of results. - StartCursor Cursor `json:"start_cursor,omitempty"` - // The number of items from the full list desired in the response. Maximum: 100 - PageSize int `json:"page_size,omitempty"` -} - // See https://developers.notion.com/reference/get-database func (dc *DatabaseClient) Get(ctx context.Context, id DatabaseID) (*Database, error) { if id == "" { @@ -189,35 +137,20 @@ type Database struct { URL string `json:"url"` PublicURL string `json:"public_url"` // Properties is a map of property configurations that defines what Page.Properties each page of the database can use - Properties PropertyConfigs `json:"properties"` - Description []RichText `json:"description"` - IsInline bool `json:"is_inline"` - Archived bool `json:"archived"` - Icon *Icon `json:"icon,omitempty"` - Cover *Image `json:"cover,omitempty"` + Properties PropertyConfigs `json:"properties"` + Description []RichText `json:"description"` + IsInline bool `json:"is_inline"` + Archived bool `json:"archived"` + Icon *Icon `json:"icon,omitempty"` + Cover *Image `json:"cover,omitempty"` + DataSources []DatabaseDataSource `json:"data_sources"` } -func (db *Database) GetObject() ObjectType { - return db.Object +type DatabaseDataSource struct { + ID DataSourceID `json:"id"` + Name string `json:"name"` } -type DatabaseQueryResponse struct { - Object ObjectType `json:"object"` - Results []Page `json:"results"` - HasMore bool `json:"has_more"` - NextCursor Cursor `json:"next_cursor"` -} - -func (qr *DatabaseQueryRequest) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Sorts []SortObject `json:"sorts,omitempty"` - StartCursor Cursor `json:"start_cursor,omitempty"` - PageSize int `json:"page_size,omitempty"` - Filter interface{} `json:"filter,omitempty"` - }{ - Sorts: qr.Sorts, - StartCursor: qr.StartCursor, - PageSize: qr.PageSize, - Filter: qr.Filter, - }) +func (db *Database) GetObject() ObjectType { + return db.Object } diff --git a/database_test.go b/database_test.go index 590a2a2..84ecbcc 100644 --- a/database_test.go +++ b/database_test.go @@ -54,7 +54,7 @@ func TestDatabaseClient(t *testing.T) { Href: "", }, }, - //Properties: notionapi.PropertyConfigs{ + // Properties: notionapi.PropertyConfigs{ // "Tags": notionapi.MultiSelectPropertyConfig{ // ID: ";s|V", // Type: notionapi.PropertyConfigTypeMultiSelect, @@ -70,7 +70,7 @@ func TestDatabaseClient(t *testing.T) { // Type: notionapi.PropertyConfigTypeTitle, // Title: notionapi.RichText{}, // }, - //}, + // }, }, wantErr: false, }, @@ -94,72 +94,6 @@ func TestDatabaseClient(t *testing.T) { } }) - t.Run("Query", func(t *testing.T) { - tests := []struct { - name string - filePath string - statusCode int - id notionapi.DatabaseID - request *notionapi.DatabaseQueryRequest - want *notionapi.DatabaseQueryResponse - wantErr bool - err error - }{ - { - name: "returns query results", - id: "some_id", - filePath: "testdata/database_query.json", - statusCode: http.StatusOK, - request: ¬ionapi.DatabaseQueryRequest{ - Filter: ¬ionapi.PropertyFilter{ - Property: "Name", - RichText: ¬ionapi.TextFilterCondition{ - Contains: "Hel", - }, - }, - }, - want: ¬ionapi.DatabaseQueryResponse{ - Object: notionapi.ObjectTypeList, - Results: []notionapi.Page{ - { - Object: notionapi.ObjectTypePage, - ID: "some_id", - CreatedTime: timestamp, - LastEditedTime: timestamp, - CreatedBy: user, - LastEditedBy: user, - Parent: notionapi.Parent{ - Type: notionapi.ParentTypeDatabaseID, - DatabaseID: "some_id", - }, - Archived: false, - URL: "some_url", - }, - }, - HasMore: false, - NextCursor: "", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath, tt.statusCode) - client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) - got, err := client.Database.Query(context.Background(), tt.id, tt.request) - - if (err != nil) != tt.wantErr { - t.Errorf("Query() error = %v, wantErr %v", err, tt.wantErr) - return - } - got.Results[0].Properties = nil - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Query() got = %v, want %v", got, tt.want) - } - }) - } - }) - t.Run("Update", func(t *testing.T) { tests := []struct { name string @@ -448,123 +382,3 @@ func TestDatabaseClient(t *testing.T) { } }) } - -func TestDatabaseQueryRequest_MarshalJSON(t *testing.T) { - timeObj, err := time.Parse(time.RFC3339, "2021-05-10T02:43:42Z") - if err != nil { - t.Error(err) - return - } - dateObj := notionapi.Date(timeObj) - tests := []struct { - name string - req *notionapi.DatabaseQueryRequest - want []byte - wantErr bool - }{ - { - name: "timestamp created", - req: ¬ionapi.DatabaseQueryRequest{ - Filter: ¬ionapi.TimestampFilter{ - Timestamp: notionapi.TimestampCreated, - CreatedTime: ¬ionapi.DateFilterCondition{ - NextWeek: &struct{}{}, - }, - }, - }, - want: []byte(`{"filter":{"timestamp":"created_time","created_time":{"next_week":{}}}}`), - }, - { - name: "timestamp last edited", - req: ¬ionapi.DatabaseQueryRequest{ - Filter: ¬ionapi.TimestampFilter{ - Timestamp: notionapi.TimestampLastEdited, - LastEditedTime: ¬ionapi.DateFilterCondition{ - Before: &dateObj, - }, - }, - }, - want: []byte(`{"filter":{"timestamp":"last_edited_time","last_edited_time":{"before":"2021-05-10T02:43:42Z"}}}`), - }, - { - name: "or compound filter one level", - req: ¬ionapi.DatabaseQueryRequest{ - Filter: notionapi.OrCompoundFilter{ - notionapi.PropertyFilter{ - Property: "Status", - Select: ¬ionapi.SelectFilterCondition{ - Equals: "Reading", - }, - }, - notionapi.PropertyFilter{ - Property: "Publisher", - Select: ¬ionapi.SelectFilterCondition{ - Equals: "NYT", - }, - }, - }, - }, - want: []byte(`{"filter":{"or":[{"property":"Status","select":{"equals":"Reading"}},{"property":"Publisher","select":{"equals":"NYT"}}]}}`), - }, - { - name: "and compound filter one level", - req: ¬ionapi.DatabaseQueryRequest{ - Filter: notionapi.AndCompoundFilter{ - notionapi.PropertyFilter{ - Property: "Status", - Select: ¬ionapi.SelectFilterCondition{ - Equals: "Reading", - }, - }, - notionapi.PropertyFilter{ - Property: "Publisher", - Select: ¬ionapi.SelectFilterCondition{ - Equals: "NYT", - }, - }, - }, - }, - want: []byte(`{"filter":{"and":[{"property":"Status","select":{"equals":"Reading"}},{"property":"Publisher","select":{"equals":"NYT"}}]}}`), - }, - { - name: "compound filter two levels", - req: ¬ionapi.DatabaseQueryRequest{ - Filter: notionapi.OrCompoundFilter{ - notionapi.PropertyFilter{ - Property: "Description", - RichText: ¬ionapi.TextFilterCondition{ - Contains: "fish", - }, - }, - notionapi.AndCompoundFilter{ - notionapi.PropertyFilter{ - Property: "Food group", - Select: ¬ionapi.SelectFilterCondition{ - Equals: "🄦Vegetable", - }, - }, - notionapi.PropertyFilter{ - Property: "Is protein rich?", - Checkbox: ¬ionapi.CheckboxFilterCondition{ - Equals: true, - }, - }, - }, - }, - }, - want: []byte(`{"filter":{"or":[{"property":"Description","rich_text":{"contains":"fish"}},{"and":[{"property":"Food group","select":{"equals":"🄦Vegetable"}},{"property":"Is protein rich?","checkbox":{"equals":true}}]}]}}`), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.req.MarshalJSON() - if (err != nil) != tt.wantErr { - t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("MarshalJSON() got = %s, want %s", got, tt.want) - } - }) - } -} diff --git a/page.go b/page.go index f2bf6e6..0aad738 100644 --- a/page.go +++ b/page.go @@ -176,11 +176,12 @@ type ParentType string // // See https://developers.notion.com/reference/parent-object type Parent struct { - Type ParentType `json:"type,omitempty"` - PageID PageID `json:"page_id,omitempty"` - DatabaseID DatabaseID `json:"database_id,omitempty"` - BlockID BlockID `json:"block_id,omitempty"` - Workspace bool `json:"workspace,omitempty"` + Type ParentType `json:"type,omitempty"` + PageID PageID `json:"page_id,omitempty"` + DatabaseID DatabaseID `json:"database_id,omitempty"` + DataSourceID DataSourceID `json:"data_source_id,omitempty"` + BlockID BlockID `json:"block_id,omitempty"` + Workspace bool `json:"workspace,omitempty"` } func handlePageResponse(res *http.Response) (*Page, error) { diff --git a/property_config.go b/property_config.go index 9a3f674..d66c6cc 100644 --- a/property_config.go +++ b/property_config.go @@ -229,6 +229,7 @@ type DualProperty struct{} type RelationConfig struct { DatabaseID DatabaseID `json:"database_id"` + DataSourceID DataSourceID `json:"data_source_id"` SyncedPropertyID PropertyID `json:"synced_property_id,omitempty"` SyncedPropertyName string `json:"synced_property_name,omitempty"` Type RelationConfigType `json:"type,omitempty"` diff --git a/search.go b/search.go index bc46f69..47a55dc 100644 --- a/search.go +++ b/search.go @@ -16,16 +16,16 @@ type SearchClient struct { apiClient *Client } -// Searches all parent or child pages and databases that have been shared with +// Searches all parent or child pages and data sources that have been shared with // an integration. // -// Returns all pages or databases, excluding duplicated linked databases, that +// Returns all pages or data sources, excluding duplicated linked data sources, that // have titles that include the query param. If no query param is provided, then -// the response contains all pages or databases that have been shared with the +// the response contains all pages or data sources that have been shared with the // integration. The results adhere to any limitations related to an integration’s // capabilities. -// To limit the request to search only pages or to search only databases, use +// To limit the request to search only pages or to search only data sources, use // the filter param. // // See https://developers.notion.com/reference/post-search @@ -51,7 +51,7 @@ func (sc *SearchClient) Do(ctx context.Context, request *SearchRequest) (*Search } type SearchRequest struct { - // The text that the API compares page and database titles against. + // The text that the API compares page and data source titles against. Query string `json:"query,omitempty"` // A set of criteria, direction and timestamp keys, that orders the results. // The only supported timestamp value is "last_edited_time". Supported @@ -59,8 +59,8 @@ type SearchRequest struct { // then the most recently edited results are returned first. Sort *SortObject `json:"sort,omitempty"` // A set of criteria, value and property keys, that limits the results to - // either only pages or only databases. Possible value values are "page" or - // "database". The only supported property value is "object". + // either only pages or only data sources. Possible value values are "page" or + // "data_source". The only supported property value is "object". Filter SearchFilter `json:"filter,omitempty"` // A cursor value returned in a previous response that If supplied, limits the // response to results starting after the cursor. If not supplied, then the @@ -85,30 +85,31 @@ func (sr *SearchResponse) UnmarshalJSON(data []byte) error { NextCursor Cursor `json:"next_cursor"` } - err := json.Unmarshal(data, &tmp) - if err != nil { + if err := json.Unmarshal(data, &tmp); err != nil { return err } + objects := make([]Object, len(tmp.Results)) for i, rawObject := range tmp.Results { var o Object switch rawObject.(map[string]interface{})["object"].(string) { - case ObjectTypeDatabase.String(): - o = &Database{} + case ObjectTypeDataSource.String(): + o = &DataSource{} case ObjectTypePage.String(): o = &Page{} default: return fmt.Errorf("unsupported object type %s", rawObject.(map[string]interface{})["object"].(string)) } + j, err := json.Marshal(rawObject) if err != nil { return err } - err = json.Unmarshal(j, o) - if err != nil { + if err = json.Unmarshal(j, o); err != nil { return err } + objects[i] = o } diff --git a/search_test.go b/search_test.go index 94f4313..e1bfefc 100644 --- a/search_test.go +++ b/search_test.go @@ -2,9 +2,10 @@ package notionapi_test import ( "context" - "github.com/jomei/notionapi" "net/http" "testing" + + "github.com/jomei/notionapi" ) func TestSearchClient(t *testing.T) { diff --git a/testdata/database_query.json b/testdata/data_source_query.json similarity index 98% rename from testdata/database_query.json rename to testdata/data_source_query.json index 9f72fab..cf585f3 100644 --- a/testdata/database_query.json +++ b/testdata/data_source_query.json @@ -15,7 +15,7 @@ "id": "some_id" }, "parent": { - "type": "database_id", + "type": "data_source_id", "database_id": "some_id" }, "archived": false, diff --git a/testdata/search.json b/testdata/search.json index 46ef805..ba5a1f8 100644 --- a/testdata/search.json +++ b/testdata/search.json @@ -15,8 +15,8 @@ "id": "some_id" }, "parent": { - "type": "database_id", - "database_id": "some_id" + "type": "data_source_id", + "data_source_id": "some_id" }, "archived": false, "properties": { @@ -61,7 +61,7 @@ } }, { - "object": "database", + "object": "data_source", "id": "some_id", "created_time": "2021-05-24T05:06:34.827Z", "last_edited_time": "2021-05-24T05:06:34.827Z", @@ -69,7 +69,7 @@ { "type": "text", "text": { - "content": "Test Database", + "content": "Test Data Source", "link": null }, "annotations": { @@ -80,7 +80,7 @@ "code": false, "color": "default" }, - "plain_text": "Test Database", + "plain_text": "Test Data Source", "href": null } ],