From a2507e3ec1fb01ddae410705cca3ad8d184d72e6 Mon Sep 17 00:00:00 2001 From: David Stotijn Date: Mon, 20 Dec 2021 19:54:19 +0100 Subject: [PATCH] Add cover support, improve icon support --- client_test.go | 216 ++++++++++++++++++++++++++++++++++++++++++++++++- cover.go | 38 +++++++++ database.go | 24 +++++- icon.go | 7 ++ page.go | 26 ++++-- 5 files changed, 301 insertions(+), 10 deletions(-) create mode 100644 cover.go diff --git a/client_test.go b/client_test.go index e2ba7df..b67225c 100644 --- a/client_test.go +++ b/client_test.go @@ -979,6 +979,12 @@ func TestCreateDatabase(t *testing.T) { Type: notion.IconTypeEmoji, Emoji: notion.StringPtr("✌️"), }, + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/image.png", + }, + }, }, respBody: func(_ *http.Request) io.Reader { return strings.NewReader( @@ -1020,6 +1026,12 @@ func TestCreateDatabase(t *testing.T) { "icon": { "type": "emoji", "emoji": "✌️" + }, + "cover": { + "type": "external", + "external": { + "url": "https://example.com/image.png" + } } }`, ) @@ -1047,6 +1059,12 @@ func TestCreateDatabase(t *testing.T) { "type": "emoji", "emoji": "✌️", }, + "cover": map[string]interface{}{ + "type": "external", + "external": map[string]interface{}{ + "url": "https://example.com/image.png", + }, + }, }, expResponse: notion.Database{ ID: "b89664e3-30b4-474a-9cce-c72a4827d1e4", @@ -1075,10 +1093,16 @@ func TestCreateDatabase(t *testing.T) { Title: ¬ion.EmptyMetadata{}, }, }, - Icon: notion.Icon{ + Icon: ¬ion.Icon{ Type: notion.IconTypeEmoji, Emoji: notion.StringPtr("✌️"), }, + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/image.png", + }, + }, }, expError: nil, }, @@ -1235,6 +1259,16 @@ func TestUpdateDatabase(t *testing.T) { }, "Removed": nil, }, + Icon: ¬ion.Icon{ + Type: notion.IconTypeEmoji, + Emoji: notion.StringPtr("✌️"), + }, + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/image.png", + }, + }, }, respBody: func(_ *http.Request) io.Reader { return strings.NewReader( @@ -1277,6 +1311,16 @@ func TestUpdateDatabase(t *testing.T) { "parent": { "type": "page_id", "page_id": "b8595b75-abd1-4cad-8dfe-f935a8ef57cb" + }, + "icon": { + "type": "emoji", + "emoji": "✌️" + }, + "cover": { + "type": "external", + "external": { + "url": "https://example.com/image.png" + } } }`, ) @@ -1297,6 +1341,16 @@ func TestUpdateDatabase(t *testing.T) { }, "Removed": nil, }, + "icon": map[string]interface{}{ + "type": "emoji", + "emoji": "✌️", + }, + "cover": map[string]interface{}{ + "type": "external", + "external": map[string]interface{}{ + "url": "https://example.com/image.png", + }, + }, }, expResponse: notion.Database{ ID: "668d797c-76fa-4934-9b05-ad288df2d136", @@ -1329,6 +1383,16 @@ func TestUpdateDatabase(t *testing.T) { Type: notion.ParentTypePage, PageID: "b8595b75-abd1-4cad-8dfe-f935a8ef57cb", }, + Icon: ¬ion.Icon{ + Type: notion.IconTypeEmoji, + Emoji: notion.StringPtr("✌️"), + }, + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/image.png", + }, + }, }, expError: nil, }, @@ -1610,6 +1674,18 @@ func TestCreatePage(t *testing.T) { }, }, }, + Icon: ¬ion.Icon{ + Type: notion.IconTypeExternal, + External: ¬ion.IconExternal{ + URL: "https://example.com/icon.png", + }, + }, + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/cover.png", + }, + }, }, respBody: func(_ *http.Request) io.Reader { return strings.NewReader( @@ -1648,6 +1724,18 @@ func TestCreatePage(t *testing.T) { } ] } + }, + "icon": { + "type": "external", + "external": { + "url": "https://example.com/icon.png" + } + }, + "cover": { + "type": "external", + "external": { + "url": "https://example.com/cover.png" + } } }`, ) @@ -1681,6 +1769,18 @@ func TestCreatePage(t *testing.T) { }, }, }, + "icon": map[string]interface{}{ + "type": "external", + "external": map[string]interface{}{ + "url": "https://example.com/icon.png", + }, + }, + "cover": map[string]interface{}{ + "type": "external", + "external": map[string]interface{}{ + "url": "https://example.com/cover.png", + }, + }, }, expResponse: notion.Page{ ID: "276ee233-e426-4ed0-9986-6b22af8550df", @@ -1707,6 +1807,18 @@ func TestCreatePage(t *testing.T) { }, }, }, + Icon: ¬ion.Icon{ + Type: notion.IconTypeExternal, + External: ¬ion.IconExternal{ + URL: "https://example.com/icon.png", + }, + }, + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/cover.png", + }, + }, }, expError: nil, }, @@ -2187,6 +2299,106 @@ func TestUpdatePageProps(t *testing.T) { }, expError: nil, }, + { + name: "page cover, successful response", + params: notion.UpdatePageParams{ + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/image.png", + }, + }, + }, + respBody: func(_ *http.Request) io.Reader { + return strings.NewReader( + `{ + "object": "page", + "id": "cb261dc5-6c85-4767-8585-3852382fb466", + "created_time": "2021-05-14T09:15:46.796Z", + "last_edited_time": "2021-05-22T15:54:31.116Z", + "parent": { + "type": "page_id", + "page_id": "b0668f48-8d66-4733-9bdb-2f82215707f7" + }, + "cover": { + "type": "external", + "external": { + "url": "https://example.com/image.png" + } + }, + "archived": false, + "url": "https://www.notion.so/Avocado-251d2b5f268c4de2afe9c71ff92ca95c", + "properties": { + "title": { + "id": "title", + "type": "title", + "title": [ + { + "type": "text", + "text": { + "content": "Lorem ipsum", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Lorem ipsum", + "href": null + } + ] + } + } + }`, + ) + }, + respStatusCode: http.StatusOK, + expPostBody: map[string]interface{}{ + "cover": map[string]interface{}{ + "type": "external", + "external": map[string]interface{}{ + "url": "https://example.com/image.png", + }, + }, + }, + expResponse: notion.Page{ + ID: "cb261dc5-6c85-4767-8585-3852382fb466", + CreatedTime: mustParseTime(time.RFC3339Nano, "2021-05-14T09:15:46.796Z"), + LastEditedTime: mustParseTime(time.RFC3339Nano, "2021-05-22T15:54:31.116Z"), + URL: "https://www.notion.so/Avocado-251d2b5f268c4de2afe9c71ff92ca95c", + Parent: notion.Parent{ + Type: notion.ParentTypePage, + PageID: "b0668f48-8d66-4733-9bdb-2f82215707f7", + }, + Cover: ¬ion.Cover{ + Type: notion.CoverTypeExternal, + External: ¬ion.CoverExternal{ + URL: "https://example.com/image.png", + }, + }, + Properties: notion.PageProperties{ + Title: notion.PageTitle{ + Title: []notion.RichText{ + { + Type: notion.RichTextTypeText, + Text: ¬ion.Text{ + Content: "Lorem ipsum", + }, + Annotations: ¬ion.Annotations{ + Color: notion.ColorDefault, + }, + PlainText: "Lorem ipsum", + }, + }, + }, + }, + }, + expError: nil, + }, { name: "database page props, successful response", params: notion.UpdatePageParams{ @@ -2325,7 +2537,7 @@ func TestUpdatePageProps(t *testing.T) { name: "missing any params", params: notion.UpdatePageParams{}, expResponse: notion.Page{}, - expError: errors.New("notion: invalid page params: at least one of database page properties, title or icon is required"), + expError: errors.New("notion: invalid page params: at least one of database page properties, title, icon or cover is required"), }, } diff --git a/cover.go b/cover.go new file mode 100644 index 0000000..b61c1bf --- /dev/null +++ b/cover.go @@ -0,0 +1,38 @@ +package notion + +import "errors" + +type CoverType string + +const ( + CoverTypeFile CoverType = "file" + CoverTypeExternal CoverType = "external" +) + +type Cover struct { + Type CoverType `json:"type"` + + File *CoverFile `json:"file,omitempty"` + External *CoverExternal `json:"external,omitempty"` +} + +type CoverFile struct { + URL string `json:"url"` + ExpiryTime DateTime `json:"expiry_time"` +} + +type CoverExternal struct { + URL string `json:"url"` +} + +func (cover Cover) Validate() error { + if cover.Type == "" { + return errors.New("cover type cannot be empty") + } + + if cover.Type == CoverTypeExternal && cover.External == nil { + return errors.New("cover external cannot be empty") + } + + return nil +} diff --git a/database.go b/database.go index e9394a8..6210f26 100644 --- a/database.go +++ b/database.go @@ -15,7 +15,8 @@ type Database struct { Title []RichText `json:"title"` Properties DatabaseProperties `json:"properties"` Parent Parent `json:"parent"` - Icon Icon `json:"icon"` + Icon *Icon `json:"icon,omitempty"` + Cover *Cover `json:"cover,omitempty"` } // DatabaseProperties is a mapping of properties defined on a database. @@ -257,6 +258,7 @@ type CreateDatabaseParams struct { Title []RichText Properties DatabaseProperties Icon *Icon + Cover *Cover } type ( @@ -409,6 +411,11 @@ func (p CreateDatabaseParams) Validate() error { return err } } + if p.Cover != nil { + if err := p.Cover.Validate(); err != nil { + return err + } + } return nil } @@ -420,6 +427,7 @@ func (p CreateDatabaseParams) MarshalJSON() ([]byte, error) { Title []RichText `json:"title,omitempty"` Properties DatabaseProperties `json:"properties"` Icon *Icon `json:"icon,omitempty"` + Cover *Cover `json:"cover,omitempty"` } parent := Parent{ @@ -432,6 +440,7 @@ func (p CreateDatabaseParams) MarshalJSON() ([]byte, error) { Title: p.Title, Properties: p.Properties, Icon: p.Icon, + Cover: p.Cover, } return json.Marshal(dto) @@ -441,6 +450,8 @@ func (p CreateDatabaseParams) MarshalJSON() ([]byte, error) { type UpdateDatabaseParams struct { Title []RichText `json:"title,omitempty"` Properties map[string]*DatabaseProperty `json:"properties,omitempty"` + Icon *Icon `json:"icon,omitempty"` + Cover *Cover `json:"cover,omitempty"` } // Validate validates params for updating a database. @@ -448,5 +459,16 @@ func (p UpdateDatabaseParams) Validate() error { if len(p.Title) == 0 && len(p.Properties) == 0 { return errors.New("either title or properties are required") } + if p.Icon != nil { + if err := p.Icon.Validate(); err != nil { + return err + } + } + if p.Cover != nil { + if err := p.Cover.Validate(); err != nil { + return err + } + } + return nil } diff --git a/icon.go b/icon.go index 8a99159..4fadf0f 100644 --- a/icon.go +++ b/icon.go @@ -6,6 +6,7 @@ type IconType string const ( IconTypeEmoji IconType = "emoji" + IconTypeFile IconType = "file" IconTypeExternal IconType = "external" ) @@ -15,9 +16,15 @@ type Icon struct { Type IconType `json:"type"` Emoji *string `json:"emoji,omitempty"` + File *IconFile `json:"file,omitempty"` External *IconExternal `json:"external,omitempty"` } +type IconFile struct { + URL string `json:"url"` + ExpiryTime DateTime `json:"expiry_time"` +} + type IconExternal struct { URL string `json:"url"` } diff --git a/page.go b/page.go index e13ffcb..9a642a9 100644 --- a/page.go +++ b/page.go @@ -17,7 +17,8 @@ type Page struct { Parent Parent `json:"parent"` Archived bool `json:"archived"` URL string `json:"url"` - Icon *Icon `json:"icon"` + Icon *Icon `json:"icon,omitempty"` + Cover *Cover `json:"cover,omitempty"` // Properties differ between parent type. // See the `UnmarshalJSON` method. @@ -74,7 +75,8 @@ type CreatePageParams struct { // Optionally, children blocks are added to the page. Children []Block - Icon *Icon + Icon *Icon + Cover *Cover } type UpdatePageParams struct { @@ -82,6 +84,7 @@ type UpdatePageParams struct { DatabasePageProperties *DatabasePageProperties Title []RichText Icon *Icon + Cover *Cover } // Value returns the underlying database page property value, based on its `type` field. @@ -149,6 +152,11 @@ func (p CreatePageParams) Validate() error { return err } } + if p.Cover != nil { + if err := p.Cover.Validate(); err != nil { + return err + } + } return nil } @@ -159,6 +167,7 @@ func (p CreatePageParams) MarshalJSON() ([]byte, error) { Properties interface{} `json:"properties"` Children []Block `json:"children,omitempty"` Icon *Icon `json:"icon,omitempty"` + Cover *Cover `json:"cover,omitempty"` } var parent Parent @@ -173,6 +182,7 @@ func (p CreatePageParams) MarshalJSON() ([]byte, error) { Parent: parent, Children: p.Children, Icon: p.Icon, + Cover: p.Cover, } if p.DatabasePageProperties != nil { @@ -237,8 +247,8 @@ func (p *Page) UnmarshalJSON(b []byte) error { func (p UpdatePageParams) Validate() error { // At least one of the params must be set. - if p.DatabasePageProperties == nil && p.Title == nil && p.Icon == nil { - return errors.New("at least one of database page properties, title or icon is required") + if p.DatabasePageProperties == nil && p.Title == nil && p.Icon == nil && p.Cover == nil { + return errors.New("at least one of database page properties, title, icon or cover is required") } if p.Icon != nil { if err := p.Icon.Validate(); err != nil { @@ -252,9 +262,13 @@ func (p UpdatePageParams) MarshalJSON() ([]byte, error) { type UpdatePageParamsDTO struct { Properties interface{} `json:"properties,omitempty"` Icon *Icon `json:"icon,omitempty"` + Cover *Cover `json:"cover,omitempty"` } - var dto UpdatePageParamsDTO + dto := UpdatePageParamsDTO{ + Icon: p.Icon, + Cover: p.Cover, + } if p.DatabasePageProperties != nil { dto.Properties = p.DatabasePageProperties @@ -264,7 +278,5 @@ func (p UpdatePageParams) MarshalJSON() ([]byte, error) { } } - dto.Icon = p.Icon - return json.Marshal(dto) }