From 86db1d03cf86bc19b46ad30502a62f245bbbd661 Mon Sep 17 00:00:00 2001 From: Anatoy Nosov Date: Sat, 31 Jul 2021 11:55:03 +0500 Subject: [PATCH] Separate database properties from `Properties` as `PropertiesConfigs` --- block.go | 54 +++--- block_test.go | 4 +- const.go | 51 +++++- database.go | 5 +- database_test.go | 39 ++--- object.go | 18 +- page.go | 2 +- page_test.go | 166 ++++++++++++++++++- property.go | 304 ++++++++++++++-------------------- property_config.go | 312 +++++++++++++++++++++++++++++++++++ testdata/database_query.json | 66 ++++---- 11 files changed, 739 insertions(+), 282 deletions(-) create mode 100644 property_config.go diff --git a/block.go b/block.go index 186db0b..23c596f 100644 --- a/block.go +++ b/block.go @@ -94,13 +94,15 @@ type ParagraphBlock struct { CreatedTime *time.Time `json:"created_time,omitempty"` LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children,omitempty"` - Paragraph struct { - Text Paragraph `json:"text"` - Children []Block `json:"children,omitempty"` - } `json:"paragraph"` + Paragraph Paragraph `json:"paragraph"` } -func (b *ParagraphBlock) GetType() BlockType { +type Paragraph struct { + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` +} + +func (b ParagraphBlock) GetType() BlockType { return b.Type } @@ -112,11 +114,11 @@ type Heading1Block struct { LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children,omitempty"` Heading1 struct { - Text Paragraph `json:"text"` + Text []RichText `json:"text"` } `json:"heading_1"` } -func (b *Heading1Block) GetType() BlockType { +func (b Heading1Block) GetType() BlockType { return b.Type } @@ -128,11 +130,11 @@ type Heading2Block struct { LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children,omitempty"` Heading2 struct { - Text Paragraph `json:"text"` + Text []RichText `json:"text"` } `json:"heading_2"` } -func (b *Heading2Block) GetType() BlockType { +func (b Heading2Block) GetType() BlockType { return b.Type } @@ -144,11 +146,11 @@ type Heading3Block struct { LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children,omitempty"` Heading3 struct { - Text Paragraph `json:"text"` + Text []RichText `json:"text"` } `json:"heading_3"` } -func (b *Heading3Block) GetType() BlockType { +func (b Heading3Block) GetType() BlockType { return b.Type } @@ -160,12 +162,12 @@ type BulletedListItemBlock struct { LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children,omitempty"` BulletedListItem struct { - Text Paragraph `json:"text"` - Children []Block `json:"children,omitempty"` + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` } `json:"bulleted_list_item"` } -func (b *BulletedListItemBlock) GetType() BlockType { +func (b BulletedListItemBlock) GetType() BlockType { return b.Type } @@ -177,12 +179,12 @@ type NumberedListItemBlock struct { LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children,omitempty"` NumberedListItem struct { - Text Paragraph `json:"text"` - Children []Block `json:"children,omitempty"` + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` } `json:"numbered_list_item"` } -func (b *NumberedListItemBlock) GetType() BlockType { +func (b NumberedListItemBlock) GetType() BlockType { return b.Type } @@ -194,13 +196,13 @@ type ToDoBlock struct { LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children"` ToDo struct { - Text Paragraph `json:"text"` - Children []Block `json:"children,omitempty"` - Checked bool `json:"checked"` + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` + Checked bool `json:"checked"` } `json:"to_do"` } -func (b *ToDoBlock) GetType() BlockType { +func (b ToDoBlock) GetType() BlockType { return b.Type } @@ -211,15 +213,15 @@ type ToggleBlock struct { CreatedTime *time.Time `json:"created_time,omitempty"` LastEditedTime *time.Time `json:"last_edited_time,omitempty"` HasChildren bool `json:"has_children,omitempty"` - Text Paragraph `json:"text"` + Text []RichText `json:"text"` Children []Block `json:"children,omitempty"` Toggle struct { - Text Paragraph `json:"text"` - Children []Block `json:"children,omitempty"` + Text []RichText `json:"text"` + Children []Block `json:"children,omitempty"` } `json:"toggle"` } -func (b *ToggleBlock) GetType() BlockType { +func (b ToggleBlock) GetType() BlockType { return b.Type } @@ -235,7 +237,7 @@ type ChildPageBlock struct { } `json:"child_page"` } -func (b *ChildPageBlock) GetType() BlockType { +func (b ChildPageBlock) GetType() BlockType { return b.Type } diff --git a/block_test.go b/block_test.go index 6122628..d0e898c 100644 --- a/block_test.go +++ b/block_test.go @@ -73,8 +73,8 @@ func TestBlockClient(t *testing.T) { Object: notionapi.ObjectTypeBlock, Type: notionapi.BlockTypeHeading2, Heading2: struct { - Text notionapi.Paragraph `json:"text"` - }{notionapi.Paragraph{ + Text []notionapi.RichText `json:"text"` + }{[]notionapi.RichText{ { Type: notionapi.ObjectTypeText, Text: notionapi.Text{Content: "Hello"}, diff --git a/const.go b/const.go index b2a4660..4149a7b 100644 --- a/const.go +++ b/const.go @@ -10,22 +10,45 @@ const ( ObjectTypeError ObjectType = "error" ) +const ( + PropertyConfigTypeTitle PropertyConfigType = "title" + PropertyConfigTypeRichText PropertyConfigType = "rich_text" + PropertyConfigTypeNumber PropertyConfigType = "number" + PropertyConfigTypeSelect PropertyConfigType = "select" + PropertyConfigTypeMultiSelect PropertyConfigType = "multi_select" + PropertyConfigTypeDate PropertyConfigType = "date" + PropertyConfigTypePeople PropertyConfigType = "people" + PropertyConfigTypeFiles PropertyConfigType = "files" + PropertyConfigTypeCheckbox PropertyConfigType = "checkbox" + PropertyConfigTypeURL PropertyConfigType = "url" + PropertyConfigTypeEmail PropertyConfigType = "email" + PropertyConfigTypePhoneNumber PropertyConfigType = "phone_number" + PropertyConfigTypeFormula PropertyConfigType = "formula" + PropertyConfigTypeRelation PropertyConfigType = "relation" + PropertyConfigTypeRollup PropertyConfigType = "rollup" + PropertyConfigCreatedTime PropertyConfigType = "created_time" + PropertyConfigCreatedBy PropertyConfigType = "created_by" + PropertyConfigLastEditedTime PropertyConfigType = "last_edited_time" + PropertyConfigLastEditedBy PropertyConfigType = "last_edited_by" +) + const ( PropertyTypeTitle PropertyType = "title" PropertyTypeRichText PropertyType = "rich_text" + PropertyTypeText PropertyType = "text" + PropertyTypeNumber PropertyType = "number" PropertyTypeSelect PropertyType = "select" PropertyTypeMultiSelect PropertyType = "multi_select" - PropertyTypeNumber PropertyType = "number" - PropertyTypeCheckbox PropertyType = "checkbox" - PropertyTypeEmail PropertyType = "email" - PropertyTypeURL PropertyType = "url" - PropertyTypeFile PropertyType = "file" - PropertyTypePhoneNumber PropertyType = "phone_number" - PropertyTypeFormula PropertyType = "formula" PropertyTypeDate PropertyType = "date" + PropertyTypeFormula PropertyType = "formula" PropertyTypeRelation PropertyType = "relation" PropertyTypeRollup PropertyType = "rollup" PropertyTypePeople PropertyType = "people" + PropertyTypeFiles PropertyType = "files" + PropertyTypeCheckbox PropertyType = "checkbox" + PropertyTypeURL PropertyType = "url" + PropertyTypeEmail PropertyType = "email" + PropertyTypePhoneNumber PropertyType = "phone_number" PropertyTypeCreatedTime PropertyType = "created_time" PropertyTypeCreatedBy PropertyType = "created_by" PropertyTypeLastEditedTime PropertyType = "last_edited_time" @@ -143,6 +166,7 @@ const ( const ( ParentTypeDatabaseID ParentType = "database_id" ParentTypePageID ParentType = "page_id" + ParentTypeWorkspace ParentType = "workspace" ) const ( @@ -164,3 +188,16 @@ const ( BlockTypeChildPage BlockType = "child_page" BlockTypeUnsupported BlockType = "unsupported" ) + +const ( + FormulaTypeString FormulaType = "string" + FormulaTypeNumber FormulaType = "number" + FormulaTypeBoolean FormulaType = "boolean" + FormulaTypeDate FormulaType = "date" +) + +const ( + RollupTypeNumber RollupType = "number" + RollupTypeDate RollupType = "date" + RollupTypeArray RollupType = "array" +) diff --git a/database.go b/database.go index 3ccb891..d78fbd0 100644 --- a/database.go +++ b/database.go @@ -77,8 +77,9 @@ type Database struct { ID ObjectID `json:"id"` CreatedTime time.Time `json:"created_time"` LastEditedTime time.Time `json:"last_edited_time"` - Title Paragraph `json:"title"` - Properties Properties `json:"properties"` + Title []RichText `json:"title"` + // Properties is a map of property configurations that defines what Page.Properties each page of the database can use + Properties PropertyConfigs `json:"properties"` } func (db *Database) GetObject() ObjectType { diff --git a/database_test.go b/database_test.go index a8108c8..6fe3d6a 100644 --- a/database_test.go +++ b/database_test.go @@ -44,28 +44,23 @@ func TestDatabaseClient(t *testing.T) { Href: "", }, }, - - // Properties: notionapi.Properties{ - // "Tags": notionapi.MultiSelectProperty{ - // ID: ";s|V", - // Type: notionapi.PropertyTypeMultiSelect, - // MultiSelect: notionapi.Select{Options: []notionapi.Option{{ID: "id", Name: "tag", Color: "Blue"}}}, - // }, - // "Some another column": notionapi.PeopleProperty{ - // ID: "rJt\\", - // Type: notionapi.PropertyTypePeople, - // }, - // "SomeColumn": notionapi.RichTextProperty{ - // ID: "~j_@", - // Type: notionapi.PropertyTypeRichText, - // RichText: notionapi.RichText{}, - // }, - // "Name": notionapi.TitleProperty{ - // ID: "title", - // Type: notionapi.PropertyTypeTitle, - // Title: notionapi.RichText{}, - // }, + //Properties: notionapi.PropertyConfigs{ + // "Tags": notionapi.MultiSelectPropertyConfig{ + // ID: ";s|V", + // Type: notionapi.PropertyConfigTypeMultiSelect, + // MultiSelect: notionapi.Select{Options: []notionapi.Option{{ID: "id", Name: "tag", Color: "Blue"}}}, + // }, + // "Some another column": notionapi.PeoplePropertyConfig{ + // ID: "rJt\\", + // Type: notionapi.PropertyConfigTypePeople, + // }, + // + // "Name": notionapi.TitlePropertyConfig{ + // ID: "title", + // Type: notionapi.PropertyConfigTypeTitle, + // Title: notionapi.RichText{}, // }, + //}, }, wantErr: false, }, @@ -110,7 +105,7 @@ func TestDatabaseClient(t *testing.T) { ID: "some_id", CreatedTime: timestamp, LastEditedTime: timestamp, - Title: notionapi.Paragraph{ + Title: []notionapi.RichText{ { Type: notionapi.ObjectTypeText, Text: notionapi.Text{ diff --git a/object.go b/object.go index 100c399..bc09ff2 100644 --- a/object.go +++ b/object.go @@ -46,12 +46,6 @@ type Annotations struct { Color Color `json:"color"` } -type Paragraph []RichText - -type FormulaObject struct { - Value string `json:"value"` -} - type RelationObject struct { Database DatabaseID `json:"database"` SyncedPropertyName string `json:"synced_property_name"` @@ -75,6 +69,16 @@ func (d *Date) String() string { return time.Time(*d).Format(time.RFC3339) } -func (d *Date) MarshalText() ([]byte, error) { +func (d Date) MarshalText() ([]byte, error) { return []byte(d.String()), nil } + +type File struct { + Name string `json:"name"` +} + +type PropertyID string + +func (pID PropertyID) String() string { + return string(pID) +} diff --git a/page.go b/page.go index 3a8506f..a2d82b3 100644 --- a/page.go +++ b/page.go @@ -76,7 +76,7 @@ func (p *Page) GetObject() ObjectType { type ParentType string type Parent struct { - Type ParentType `json:"type"` + Type ParentType `json:"type,omitempty"` PageID PageID `json:"page_id,omitempty"` DatabaseID DatabaseID `json:"database_id,omitempty"` } diff --git a/page_test.go b/page_test.go index 665f753..cb224b4 100644 --- a/page_test.go +++ b/page_test.go @@ -2,6 +2,7 @@ package notionapi_test import ( "context" + "encoding/json" "github.com/jomei/notionapi" "net/http" "reflect" @@ -105,8 +106,8 @@ func TestPageClient(t *testing.T) { DatabaseID: "f830be5eff534859932e5b81542b3c7b", }, Properties: notionapi.Properties{ - "Name": notionapi.PageTitleProperty{ - Title: notionapi.Paragraph{ + "Name": notionapi.TitleProperty{ + Title: []notionapi.RichText{ {Text: notionapi.Text{Content: "hello"}}, }, }, @@ -165,7 +166,7 @@ func TestPageClient(t *testing.T) { Properties: notionapi.Properties{ "SomeColumn": notionapi.RichTextProperty{ Type: notionapi.PropertyTypeRichText, - RichText: notionapi.Paragraph{ + RichText: []notionapi.RichText{ { Type: notionapi.ObjectTypeText, Text: notionapi.Text{Content: "patch"}, @@ -206,3 +207,162 @@ func TestPageClient(t *testing.T) { } }) } + +func TestPageCreateRequest_MarshallJSON(t *testing.T) { + timeObj, err := time.Parse(time.RFC3339, "2020-12-08T12:00:00Z") + if err != nil { + t.Error(err) + return + } + + dateObj := notionapi.Date(timeObj) + tests := []struct { + name string + req *notionapi.PageCreateRequest + want []byte + wantErr bool + }{ + { + name: "create a page", + req: ¬ionapi.PageCreateRequest{ + Parent: notionapi.Parent{ + DatabaseID: "some_id", + }, + Properties: notionapi.Properties{ + "Type": notionapi.SelectProperty{ + Select: notionapi.Option{ + ID: "some_id", + Name: "Article", + Color: notionapi.ColorDefault, + }, + }, + "Name": notionapi.TitleProperty{ + Title: []notionapi.RichText{ + {Text: notionapi.Text{Content: "New Media Article"}}, + }, + }, + "Publishing/Release Date": notionapi.DateProperty{ + Date: notionapi.DateObject{ + Start: &dateObj, + }, + }, + "Link": notionapi.URLProperty{ + URL: "some_url", + }, + "Summary": notionapi.TextProperty{ + Text: []notionapi.RichText{ + { + Type: notionapi.ObjectTypeText, + Text: notionapi.Text{ + Content: "Some content", + }, + Annotations: ¬ionapi.Annotations{ + Bold: true, + Color: notionapi.ColorBlue, + }, + PlainText: "Some content", + }, + }, + }, + "Read": notionapi.CheckboxProperty{ + Checkbox: false, + }, + }, + }, + want: []byte(`{"parent":{"database_id":"some_id"},"properties":{"Link":{"url":"some_url"},"Name":{"title":[{"text":{"content":"New Media Article"}}]},"Publishing/Release Date":{"date":{"start":"2020-12-08T12:00:00Z","end":null}},"Read":{"checkbox":false},"Summary":{"text":[{"type":"text","text":{"content":"Some content"},"annotations":{"bold":true,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"blue"},"plain_text":"Some content"}]},"Type":{"select":{"id":"some_id","name":"Article","color":"default"}}}}`), + }, + { + name: "create a page with content", + req: ¬ionapi.PageCreateRequest{ + Parent: notionapi.Parent{ + DatabaseID: "some_id", + }, + Properties: notionapi.Properties{ + "Type": notionapi.SelectProperty{ + Select: notionapi.Option{ + ID: "some_id", + Name: "Article", + Color: notionapi.ColorDefault, + }, + }, + "Name": notionapi.TitleProperty{ + Title: []notionapi.RichText{ + {Text: notionapi.Text{Content: "New Media Article"}}, + }, + }, + "Publishing/Release Date": notionapi.DateProperty{ + Date: notionapi.DateObject{ + Start: &dateObj, + }, + }, + "Link": notionapi.URLProperty{ + URL: "some_url", + }, + "Summary": notionapi.TextProperty{ + Text: []notionapi.RichText{ + { + Type: notionapi.ObjectTypeText, + Text: notionapi.Text{ + Content: "Some content", + }, + Annotations: ¬ionapi.Annotations{ + Bold: true, + Color: notionapi.ColorBlue, + }, + PlainText: "Some content", + }, + }, + }, + "Read": notionapi.CheckboxProperty{ + Checkbox: false, + }, + }, + Children: []notionapi.Block{ + notionapi.Heading2Block{ + Object: notionapi.ObjectTypeBlock, + Type: notionapi.BlockTypeHeading2, + Heading2: struct { + Text []notionapi.RichText `json:"text"` + }{ + Text: []notionapi.RichText{ + { + Type: notionapi.ObjectTypeText, + Text: notionapi.Text{Content: "Lacinato"}, + }, + }, + }, + }, + notionapi.ParagraphBlock{ + Object: notionapi.ObjectTypeBlock, + Type: notionapi.BlockTypeParagraph, + Paragraph: notionapi.Paragraph{ + Text: []notionapi.RichText{ + { + Text: notionapi.Text{ + Content: "Lacinato", + Link: "some_url", + }, + }, + }, + Children: nil, + }, + }, + }, + }, + want: []byte(`{"parent":{"database_id":"some_id"},"properties":{"Link":{"url":"some_url"},"Name":{"title":[{"text":{"content":"New Media Article"}}]},"Publishing/Release Date":{"date":{"start":"2020-12-08T12:00:00Z","end":null}},"Read":{"checkbox":false},"Summary":{"text":[{"type":"text","text":{"content":"Some content"},"annotations":{"bold":true,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"blue"},"plain_text":"Some content"}]},"Type":{"select":{"id":"some_id","name":"Article","color":"default"}}},"children":[{"object":"block","type":"heading_2","heading_2":{"text":[{"type":"text","text":{"content":"Lacinato"}}]}},{"object":"block","type":"paragraph","paragraph":{"text":[{"text":{"content":"Lacinato","link":"some_url"}}]}}]}`), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := json.Marshal(tt.req) + 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/property.go b/property.go index d96cf71..b14af71 100644 --- a/property.go +++ b/property.go @@ -3,6 +3,7 @@ package notionapi import ( "encoding/json" "fmt" + "time" "github.com/pkg/errors" ) @@ -13,35 +14,19 @@ type Property interface { GetType() PropertyType } -type PropertyID string - -func (pID PropertyID) String() string { - return string(pID) -} - -type TextProperty struct { +type TitleProperty struct { ID PropertyID `json:"id,omitempty"` - Type PropertyType `json:"type"` + Type PropertyType `json:"type,omitempty"` Title []RichText `json:"title"` } -func (p TextProperty) GetType() PropertyType { - return p.Type -} - -type EmptyRichTextProperty struct { - ID PropertyID `json:"id,omitempty"` - Type PropertyType `json:"type"` - RichText struct{} `json:"rich_text"` -} - -func (p EmptyRichTextProperty) GetType() PropertyType { +func (p TitleProperty) GetType() PropertyType { return p.Type } type RichTextProperty struct { ID PropertyID `json:"id,omitempty"` - Type PropertyType `json:"type"` + Type PropertyType `json:"type,omitempty"` RichText []RichText `json:"rich_text"` } @@ -49,126 +34,144 @@ func (p RichTextProperty) GetType() PropertyType { return p.Type } -type DatabaseTitleProperty struct { - ID PropertyID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Title RichText `json:"title"` +type TextProperty struct { + ID PropertyID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Text []RichText `json:"text"` } -func (p DatabaseTitleProperty) GetType() PropertyType { +func (p TextProperty) GetType() PropertyType { return p.Type } -type PageTitleProperty struct { - ID PropertyID `json:"id,omitempty"` - Type PropertyType `json:"type,omitempty"` - Title Paragraph `json:"title"` +type NumberProperty struct { + ID PropertyID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Number float64 `json:"number"` } -func (p PageTitleProperty) GetType() PropertyType { +func (p NumberProperty) GetType() PropertyType { return p.Type } -type FormatType string +type SelectProperty struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Select Option `json:"select"` +} -func (ft FormatType) String() string { - return string(ft) +func (p SelectProperty) GetType() PropertyType { + return p.Type } -type NumberProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Format FormatType `json:"format"` +type MultiSelectProperty struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + MultiSelect []Option `json:"multi_select"` } -func (p NumberProperty) GetType() PropertyType { +func (p MultiSelectProperty) GetType() PropertyType { return p.Type } -type SelectProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Select Select `json:"select"` +type Option struct { + ID PropertyID `json:"id,omitempty"` + Name string `json:"name"` + Color Color `json:"color,omitempty"` } -func (p SelectProperty) GetType() PropertyType { - return p.Type +type DateProperty struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Date DateObject `json:"date"` } -type SelectOptionProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Select Option `json:"select"` +type DateObject struct { + Start *Date `json:"start"` + End *Date `json:"end"` } -func (p SelectOptionProperty) GetType() PropertyType { +func (p DateProperty) GetType() PropertyType { return p.Type } -type Select struct { - Options []Option `json:"options"` +type FormulaProperty struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Formula Formula `json:"formula"` } -type MultiSelectProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - MultiSelect Select `json:"multi_select"` +type FormulaType string + +type Formula struct { + Type FormulaType `json:"type,omitempty"` + String string `json:"string,omitempty"` + Number float64 `json:"number,omitempty"` + Boolean bool `json:"boolean,omitempty"` + Date *DateObject `json:"date,omitempty"` } -func (p MultiSelectProperty) GetType() PropertyType { +func (p FormulaProperty) GetType() PropertyType { return p.Type } -type MultiSelectOptionsProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - MultiSelect []Option `json:"multi_select"` +type RelationProperty struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Relation []Relation `json:"relation"` +} + +type Relation struct { + ID PageID `json:"id"` } -func (p MultiSelectOptionsProperty) GetType() PropertyType { +func (p RelationProperty) GetType() PropertyType { return p.Type } -type Option struct { - ID PropertyID `json:"id,omitempty"` - Name string `json:"name"` - Color Color `json:"color,omitempty"` +type RollupProperty struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Rollup Rollup `json:"rollup"` } -type DateProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Date interface{} `json:"date"` +type RollupType string + +type Rollup struct { + Type RollupType `json:"type,omitempty"` + Number float64 `json:"number,omitempty"` + Date *DateObject `json:"date,omitempty"` + Array []Property `json:"array,omitempty"` //todo: unmarshal } -func (p DateProperty) GetType() PropertyType { +func (p RollupProperty) GetType() PropertyType { return p.Type } type PeopleProperty struct { ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - People interface{} `json:"people"` + Type PropertyType `json:"type,omitempty"` + People []User `json:"people"` } func (p PeopleProperty) GetType() PropertyType { return p.Type } -type FileProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - File interface{} `json:"file"` +type FilesProperty struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + Files []File `json:"files"` } -func (p FileProperty) GetType() PropertyType { +func (p FilesProperty) GetType() PropertyType { return p.Type } type CheckboxProperty struct { ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Checkbox interface{} `json:"checkbox"` + Type PropertyType `json:"type,omitempty"` + Checkbox bool `json:"checkbox"` } func (p CheckboxProperty) GetType() PropertyType { @@ -177,8 +180,8 @@ func (p CheckboxProperty) GetType() PropertyType { type URLProperty struct { ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - URL interface{} `json:"url"` + Type PropertyType `json:"type,omitempty"` + URL string `json:"url"` } func (p URLProperty) GetType() PropertyType { @@ -187,8 +190,8 @@ func (p URLProperty) GetType() PropertyType { type EmailProperty struct { ID PropertyID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Email interface{} `json:"email"` + Type PropertyType `json:"type,omitempty"` + Email string `json:"email"` } func (p EmailProperty) GetType() PropertyType { @@ -197,61 +200,18 @@ func (p EmailProperty) GetType() PropertyType { type PhoneNumberProperty struct { ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - PhoneNumber interface{} `json:"phone_number"` + Type PropertyType `json:"type,omitempty"` + PhoneNumber string `json:"phone_number"` } func (p PhoneNumberProperty) GetType() PropertyType { return p.Type } -type FormulaProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Expression string `json:"expression"` -} - -func (p FormulaProperty) GetType() PropertyType { - return p.Type -} - -type RelationProperty struct { - Type PropertyType `json:"type"` - Relation Relation `json:"relation"` -} - -type Relation struct { - DatabaseID DatabaseID `json:"database_id"` - SyncedPropertyID PropertyID `json:"synced_property_id"` - SyncedPropertyName string `json:"synced_property_name"` -} - -func (p RelationProperty) GetType() PropertyType { - return p.Type -} - -type RollupProperty struct { - ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - Rollup Rollup `json:"rollup"` -} - -type Rollup struct { - RelationPropertyName string `json:"relation_property_name"` - RelationPropertyID PropertyID `json:"relation_property_id"` - RollupPropertyName string `json:"rollup_property_name"` - RollupPropertyID PropertyID `json:"rollup_property_id"` - Function FunctionType `json:"function"` -} - -func (p RollupProperty) GetType() PropertyType { - return p.Type -} - type CreatedTimeProperty struct { ID ObjectID `json:"id,omitempty"` - Type PropertyType `json:"type"` - CreatedTime interface{} `json:"created_time"` + Type PropertyType `json:"type,omitempty"` + CreatedTime time.Time `json:"created_time"` } func (p CreatedTimeProperty) GetType() PropertyType { @@ -259,9 +219,9 @@ func (p CreatedTimeProperty) GetType() PropertyType { } type CreatedByProperty struct { - ID ObjectID `json:"id"` - Type PropertyType `json:"type"` - CreatedBy interface{} `json:"created_by"` + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + CreatedBy User `json:"created_by"` } func (p CreatedByProperty) GetType() PropertyType { @@ -269,9 +229,9 @@ func (p CreatedByProperty) GetType() PropertyType { } type LastEditedTimeProperty struct { - ID ObjectID `json:"id"` - Type PropertyType `json:"type"` - LastEditedTime interface{} `json:"last_edited_time"` + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + LastEditedTime time.Time `json:"last_edited_time"` } func (p LastEditedTimeProperty) GetType() PropertyType { @@ -279,9 +239,9 @@ func (p LastEditedTimeProperty) GetType() PropertyType { } type LastEditedByProperty struct { - ID ObjectID `json:"id"` - Type PropertyType `json:"type"` - LastEditedBy interface{} `json:"last_edited_by"` + ID ObjectID `json:"id,omitempty"` + Type PropertyType `json:"type,omitempty"` + LastEditedBy User `json:"last_edited_by"` } func (p LastEditedByProperty) GetType() PropertyType { @@ -295,7 +255,7 @@ func (p *Properties) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &raw); err != nil { return err } - props, err := parseProperties(raw) + props, err := parsePageProperties(raw) if err != nil { return err } @@ -304,7 +264,7 @@ func (p *Properties) UnmarshalJSON(data []byte) error { return nil } -func parseProperties(raw map[string]interface{}) (map[string]Property, error) { +func parsePageProperties(raw map[string]interface{}) (map[string]Property, error) { result := make(map[string]Property) for k, v := range raw { var p Property @@ -312,59 +272,37 @@ func parseProperties(raw map[string]interface{}) (map[string]Property, error) { case map[string]interface{}: switch PropertyType(rawProperty["type"].(string)) { case PropertyTypeTitle: - switch v.(map[string]interface{})["title"].(type) { - case map[string]interface{}: - p = &DatabaseTitleProperty{} - default: - p = &PageTitleProperty{} - } + p = &TitleProperty{} case PropertyTypeRichText: - switch v.(map[string]interface{})["rich_text"].(type) { - case map[string]interface{}: - p = &EmptyRichTextProperty{} - default: - p = &RichTextProperty{} - } - case PropertyTypeSelect: - selectMap, ok := v.(map[string]interface{})["select"].(map[string]interface{}) - if !ok { - return nil, errors.Errorf("an error occured while parsing property type: %s", rawProperty) - } - _, found := selectMap["options"] - if found { - p = &SelectProperty{} - } else { - p = &SelectOptionProperty{} - } - case PropertyTypeMultiSelect: - switch v.(map[string]interface{})["multi_select"].(type) { - case map[string]interface{}: - p = &MultiSelectProperty{} - default: - p = &MultiSelectOptionsProperty{} - } + p = &RichTextProperty{} + case PropertyTypeText: + p = &RichTextProperty{} case PropertyTypeNumber: p = &NumberProperty{} - case PropertyTypeCheckbox: - p = &CheckboxProperty{} - case PropertyTypeEmail: - p = &EmailProperty{} - case PropertyTypeURL: - p = &URLProperty{} - case PropertyTypeFile: - p = &FileProperty{} - case PropertyTypePhoneNumber: - p = PhoneNumberProperty{} - case PropertyTypeFormula: - p = &FormulaProperty{} + case PropertyTypeSelect: + p = &SelectProperty{} + case PropertyTypeMultiSelect: + p = &MultiSelectProperty{} case PropertyTypeDate: p = &DateProperty{} + case PropertyTypeFormula: + p = &FormulaProperty{} case PropertyTypeRelation: p = &RelationProperty{} case PropertyTypeRollup: p = &RollupProperty{} case PropertyTypePeople: p = &PeopleProperty{} + case PropertyTypeFiles: + p = &FilesProperty{} + case PropertyTypeCheckbox: + p = &CheckboxProperty{} + case PropertyTypeURL: + p = &URLProperty{} + case PropertyTypeEmail: + p = &EmailProperty{} + case PropertyTypePhoneNumber: + p = PhoneNumberProperty{} case PropertyTypeCreatedTime: p = &CreatedTimeProperty{} case PropertyTypeCreatedBy: diff --git a/property_config.go b/property_config.go new file mode 100644 index 0000000..e739bbc --- /dev/null +++ b/property_config.go @@ -0,0 +1,312 @@ +package notionapi + +import ( + "encoding/json" + "fmt" +) + +type PropertyConfigType string + +type PropertyConfig interface { + GetType() PropertyConfigType +} + +type TitlePropertyConfig struct { + ID PropertyID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Title RichText `json:"title"` +} + +func (p TitlePropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type RichTextPropertyConfig struct { + ID PropertyID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + RichText struct{} `json:"rich_text"` +} + +func (p RichTextPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type NumberPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Format FormatType `json:"format"` +} + +type FormatType string + +func (ft FormatType) String() string { + return string(ft) +} + +func (p NumberPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type SelectPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Select Select `json:"select"` +} + +func (p SelectPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type MultiSelectPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + MultiSelect Select `json:"multi_select"` +} + +type Select struct { + Options []Option `json:"options"` +} + +func (p MultiSelectPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type DatePropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Date struct{} `json:"date"` +} + +func (p DatePropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type PeoplePropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + People struct{} `json:"people"` +} + +func (p PeoplePropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type FilesPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Files struct{} `json:"files"` +} + +func (p FilesPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type CheckboxPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Checkbox struct{} `json:"checkbox"` +} + +func (p CheckboxPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type URLPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + URL struct{} `json:"url"` +} + +func (p URLPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type EmailPropertyConfig struct { + ID PropertyID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Email struct{} `json:"email"` +} + +func (p EmailPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type PhoneNumberPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + PhoneNumber struct{} `json:"phone_number"` +} + +func (p PhoneNumberPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type FormulaPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Formula FormulaConfig `json:"formula"` +} + +type FormulaConfig struct { + Expression string `json:"expression"` +} + +func (p FormulaPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type RelationPropertyConfig struct { + Type PropertyConfigType `json:"type"` + Relation RelationConfig `json:"relation"` +} + +type RelationConfig struct { + DatabaseID DatabaseID `json:"database_id"` + SyncedPropertyID PropertyID `json:"synced_property_id"` + SyncedPropertyName string `json:"synced_property_name"` +} + +func (p RelationPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type RollupPropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + Rollup RollupConfig `json:"rollup"` +} + +type RollupConfig struct { + RelationPropertyName string `json:"relation_property_name"` + RelationPropertyID PropertyID `json:"relation_property_id"` + RollupPropertyName string `json:"rollup_property_name"` + RollupPropertyID PropertyID `json:"rollup_property_id"` + Function FunctionType `json:"function"` +} + +func (p RollupPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type CreatedTimePropertyConfig struct { + ID ObjectID `json:"id,omitempty"` + Type PropertyConfigType `json:"type"` + CreatedTime struct{} `json:"created_time"` +} + +func (p CreatedTimePropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type CreatedByPropertyConfig struct { + ID ObjectID `json:"id"` + Type PropertyConfigType `json:"type"` + CreatedBy struct{} `json:"created_by"` +} + +func (p CreatedByPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type LastEditedTimePropertyConfig struct { + ID ObjectID `json:"id"` + Type PropertyConfigType `json:"type"` + LastEditedTime struct{} `json:"last_edited_time"` +} + +func (p LastEditedTimePropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type LastEditedByPropertyConfig struct { + ID ObjectID `json:"id"` + Type PropertyConfigType `json:"type"` + LastEditedBy struct{} `json:"last_edited_by"` +} + +func (p LastEditedByPropertyConfig) GetType() PropertyConfigType { + return p.Type +} + +type PropertyConfigs map[string]PropertyConfig + +func (p *PropertyConfigs) UnmarshalJSON(data []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + props, err := parsePropertyConfigs(raw) + if err != nil { + return err + } + + *p = props + return nil +} + +func parsePropertyConfigs(raw map[string]interface{}) (PropertyConfigs, error) { + result := make(PropertyConfigs) + for k, v := range raw { + var p PropertyConfig + switch rawProperty := v.(type) { + case map[string]interface{}: + switch PropertyConfigType(rawProperty["type"].(string)) { + case PropertyConfigTypeTitle: + p = &TitlePropertyConfig{} + case PropertyConfigTypeRichText: + p = &RichTextPropertyConfig{} + case PropertyConfigTypeNumber: + p = &NumberPropertyConfig{} + case PropertyConfigTypeSelect: + p = &SelectPropertyConfig{} + case PropertyConfigTypeMultiSelect: + p = &MultiSelectPropertyConfig{} + case PropertyConfigTypeDate: + p = &DatePropertyConfig{} + case PropertyConfigTypePeople: + p = &PeoplePropertyConfig{} + case PropertyConfigTypeFiles: + p = &FilesPropertyConfig{} + case PropertyConfigTypeCheckbox: + p = &CheckboxPropertyConfig{} + case PropertyConfigTypeURL: + p = &URLPropertyConfig{} + case PropertyConfigTypeEmail: + p = &EmailPropertyConfig{} + case PropertyConfigTypePhoneNumber: + p = PhoneNumberPropertyConfig{} + case PropertyConfigTypeFormula: + p = &FormulaPropertyConfig{} + case PropertyConfigTypeRelation: + p = &RelationPropertyConfig{} + case PropertyConfigTypeRollup: + p = &RollupPropertyConfig{} + case PropertyConfigCreatedTime: + p = &CreatedTimePropertyConfig{} + case PropertyConfigCreatedBy: + p = &CreatedTimePropertyConfig{} + case PropertyConfigLastEditedTime: + p = &LastEditedTimePropertyConfig{} + case PropertyConfigLastEditedBy: + p = &LastEditedByPropertyConfig{} + default: + + return nil, fmt.Errorf("unsupported property type: %s", rawProperty["type"].(string)) + } + b, err := json.Marshal(rawProperty) + if err != nil { + return nil, err + } + + if err = json.Unmarshal(b, &p); err != nil { + return nil, err + } + + result[k] = p + default: + return nil, fmt.Errorf("unsupported property format %T", v) + } + } + + return result, nil +} diff --git a/testdata/database_query.json b/testdata/database_query.json index 3d6ffe5..c2f033d 100644 --- a/testdata/database_query.json +++ b/testdata/database_query.json @@ -11,45 +11,52 @@ "database_id": "some_id" }, "archived": false, - "url": "some_url", "properties": { "Tags": { - "id": ";s|V", + "id": "=QH|", "type": "multi_select", - "multi_select": { - "options": [ - { - "id": "id", - "name": "tag", - "color": "blue" - } - ] - } + "multi_select": [ + { + "id": "381f7327-f0a2-4e3a-8f54-9e081a981a57", + "name": "a", + "color": "pink" + } + ] }, - "Some another columg": { - "id": "rJt\\", - "type": "people", - "people": [ + "Column 1": { + "id": "Iiaa", + "type": "multi_select", + "multi_select": [ + { + "id": "some_id", + "name": "a", + "color": "blue" + }, + { + "id": "some_id", + "name": "v", + "color": "red" + }, { - "object": "user", "id": "some_id", - "name": "some_user", - "avatar_url": "some url", - "type": "person", - "person": { - "email": "some@email.com" - } + "name": "b", + "color": "red" } ] }, - "SomeColumn": { - "id": "~j_@", + "Column": { + "id": "o]ST", + "type": "people", + "people": [] + }, + "some_prop": { + "id": "xLy_", "type": "rich_text", "rich_text": [ { "type": "text", "text": { - "content": "some text", + "content": "a", "link": null }, "annotations": { @@ -60,7 +67,7 @@ "code": false, "color": "default" }, - "plain_text": "some text", + "plain_text": "a", "href": null } ] @@ -72,7 +79,7 @@ { "type": "text", "text": { - "content": "Hello", + "content": "ba", "link": null }, "annotations": { @@ -83,12 +90,13 @@ "code": false, "color": "default" }, - "plain_text": "Hello", + "plain_text": "ba", "href": null } ] } - } + }, + "url": "some_url" } ], "next_cursor": null,