mirror of
https://github.com/dstotijn/go-notion.git
synced 2025-06-15 00:05:04 +02:00
Add "query database" endpoint
This commit is contained in:
@ -11,8 +11,7 @@ Go client for the [Notion API](https://developers.notion.com/reference).
|
|||||||
### Databases
|
### Databases
|
||||||
|
|
||||||
- [x] [Retrieve a database](database.go)
|
- [x] [Retrieve a database](database.go)
|
||||||
- [ ] Query a database
|
- [x] [Query a database](database.go)
|
||||||
- [ ] List databases
|
|
||||||
|
|
||||||
## Pages
|
## Pages
|
||||||
|
|
||||||
|
@ -52,5 +52,9 @@ func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request,
|
|||||||
req.Header.Set("Notion-Version", apiVersion)
|
req.Header.Set("Notion-Version", apiVersion)
|
||||||
req.Header.Set("User-Agent", "go-notion/"+clientVersion)
|
req.Header.Set("User-Agent", "go-notion/"+clientVersion)
|
||||||
|
|
||||||
|
if method == http.MethodPost || method == http.MethodPatch {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
180
database.go
180
database.go
@ -1,8 +1,10 @@
|
|||||||
package notion
|
package notion
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,6 +18,7 @@ type Database struct {
|
|||||||
Properties DatabaseProperties `json:"properties"`
|
Properties DatabaseProperties `json:"properties"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DatabaseProperties is a mapping of properties defined on a database.
|
||||||
type DatabaseProperties map[string]DatabaseProperty
|
type DatabaseProperties map[string]DatabaseProperty
|
||||||
|
|
||||||
// Database property metadata types.
|
// Database property metadata types.
|
||||||
@ -61,6 +64,144 @@ type DatabaseProperty struct {
|
|||||||
Rollup *RollupMetadata `json:"rollup"`
|
Rollup *RollupMetadata `json:"rollup"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DatabaseQuery is used for quering a database.
|
||||||
|
type DatabaseQuery struct {
|
||||||
|
Filter DatabaseQueryFilter `json:"filter,omitempty"`
|
||||||
|
Sorts []DatabaseQuerySort `json:"sorts,omitempty"`
|
||||||
|
StartCursor string `json:"start_cursor,omitempty"`
|
||||||
|
PageSize int `json:"page_size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseQueryResponse contains the results and pagination data from a query request.
|
||||||
|
type DatabaseQueryResponse struct {
|
||||||
|
Results []Page `json:"results"`
|
||||||
|
HasMore bool `json:"has_more"`
|
||||||
|
NextCursor *string `json:"next_cursor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseQueryFilter is used to filter database contents.
|
||||||
|
// See: https://developers.notion.com/reference/post-database-query#post-database-query-filter
|
||||||
|
type DatabaseQueryFilter struct {
|
||||||
|
Property string `json:"property,omitempty"`
|
||||||
|
|
||||||
|
Text TextDatabaseQueryFilter `json:"text,omitempty"`
|
||||||
|
Number NumberDatabaseQueryFilter `json:"number,omitempty"`
|
||||||
|
Checkbox CheckboxDatabaseQueryFilter `json:"checkbox,omitempty"`
|
||||||
|
Select SelectDatabaseQueryFilter `json:"select,omitempty"`
|
||||||
|
MultiSelect MultiSelectDatabaseQueryFilter `json:"multi_select,omitempty"`
|
||||||
|
Date DateDatabaseQueryFilter `json:"date,omitempty"`
|
||||||
|
People PeopleDatabaseQueryFilter `json:"people,omitempty"`
|
||||||
|
Files FilesDatabaseQueryFilter `json:"files,omitempty"`
|
||||||
|
Relation RelationDatabaseQueryFilter `json:"relation,omitempty"`
|
||||||
|
|
||||||
|
Or []DatabaseQueryFilter `json:"or,omitempty"`
|
||||||
|
And []DatabaseQueryFilter `json:"and,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextDatabaseQueryFilter struct {
|
||||||
|
Equals string `json:"equals,omitempty"`
|
||||||
|
DoesNotEqual string `json:"does_not_equal,omitempty"`
|
||||||
|
Contains string `json:"contains,omitempty"`
|
||||||
|
DoesNotContain string `json:"does_not_contain,omitempty"`
|
||||||
|
StartsWith string `json:"starts_with,omitempty"`
|
||||||
|
EndsWith string `json:"ends_with,omitempty"`
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NumberDatabaseQueryFilter struct {
|
||||||
|
Equals *int `json:"equals,omitempty"`
|
||||||
|
DoesNotEqual *int `json:"does_not_equal,omitempty"`
|
||||||
|
GreaterThan *int `json:"greater_than,omitempty"`
|
||||||
|
LessThan *int `json:"less_than,omitempty"`
|
||||||
|
GreaterThanOrEqualTo *int `json:"greater_than_or_equal_to,omitempty"`
|
||||||
|
LessThanOrEqualTo *int `json:"less_than_or_equal_to,omitempty"`
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckboxDatabaseQueryFilter struct {
|
||||||
|
Equals *bool `json:"equals,omitempty"`
|
||||||
|
DoesNotEqual *bool `json:"does_not_equal,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelectDatabaseQueryFilter struct {
|
||||||
|
Equals string `json:"equals,omitempty"`
|
||||||
|
DoesNotEqual string `json:"does_not_equal,omitempty"`
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MultiSelectDatabaseQueryFilter struct {
|
||||||
|
Contains string `json:"contains,omitempty"`
|
||||||
|
DoesNotContain string `json:"does_not_contain,omitempty"`
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateDatabaseQueryFilter struct {
|
||||||
|
Equals time.Time `json:"equals,omitempty"`
|
||||||
|
Before time.Time `json:"before,omitempty"`
|
||||||
|
After time.Time `json:"after,omitempty"`
|
||||||
|
OnOrBefore time.Time `json:"on_or_before,omitempty"`
|
||||||
|
OnOrAfter time.Time `json:"on_or_after,omitempty"`
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
PastWeek *struct{} `json:"past_week,omitempty"`
|
||||||
|
PastMonth *struct{} `json:"past_month,omitempty"`
|
||||||
|
PastYear *struct{} `json:"past_year,omitempty"`
|
||||||
|
NextWeek *struct{} `json:"next_week,omitempty"`
|
||||||
|
NextMonth *struct{} `json:"next_month,omitempty"`
|
||||||
|
NextYear *struct{} `json:"next_year,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeopleDatabaseQueryFilter struct {
|
||||||
|
Contains string `json:"contains,omitempty"`
|
||||||
|
DoesNotContain string `json:"does_not_contain,omitempty"`
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilesDatabaseQueryFilter struct {
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RelationDatabaseQueryFilter struct {
|
||||||
|
Contains string `json:"contains,omitempty"`
|
||||||
|
DoesNotContain string `json:"does_not_contain,omitempty"`
|
||||||
|
IsEmpty bool `json:"is_empty,omitempty"`
|
||||||
|
IsNotEmpty bool `json:"is_not_empty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormulaDatabaseQueryFilter struct {
|
||||||
|
Text TextDatabaseQueryFilter `json:"text,omitempty"`
|
||||||
|
Checkbox CheckboxDatabaseQueryFilter `json:"checkbox,omitempty"`
|
||||||
|
Number NumberDatabaseQueryFilter `json:"number,omitempty"`
|
||||||
|
Date DateDatabaseQueryFilter `json:"date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseQuerySort struct {
|
||||||
|
Property string `json:"property,omitempty"`
|
||||||
|
Timestamp SortTimestamp `json:"timestamp,omitempty"`
|
||||||
|
Direction SortDirection `json:"direction,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
SortTimestamp string
|
||||||
|
SortDirection string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Sort timestamp enums.
|
||||||
|
SortTimeStampCreatedTime SortTimestamp = "created_time"
|
||||||
|
SortTimeStampLastEditedTime SortTimestamp = "last_edited_time"
|
||||||
|
|
||||||
|
// Sort direction enums.
|
||||||
|
SortDirAsc SortDirection = "ascending"
|
||||||
|
SortDirDesc SortDirection = "descending"
|
||||||
|
)
|
||||||
|
|
||||||
// Metadata returns the underlying property metadata, based on its `type` field.
|
// Metadata returns the underlying property metadata, based on its `type` field.
|
||||||
// When type is unknown/unmapped or doesn't have additional properies, `nil` is returned.
|
// When type is unknown/unmapped or doesn't have additional properies, `nil` is returned.
|
||||||
func (prop DatabaseProperty) Metadata() interface{} {
|
func (prop DatabaseProperty) Metadata() interface{} {
|
||||||
@ -82,8 +223,10 @@ func (prop DatabaseProperty) Metadata() interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindDatabaseByID fetches a database by ID.
|
||||||
|
// See: https://developers.notion.com/reference/get-database
|
||||||
func (c *Client) FindDatabaseByID(id string) (db Database, err error) {
|
func (c *Client) FindDatabaseByID(id string) (db Database, err error) {
|
||||||
req, err := c.newRequest("GET", "/databases/"+id, nil)
|
req, err := c.newRequest(http.MethodGet, "/databases/"+id, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Database{}, fmt.Errorf("notion: invalid URL: %w", err)
|
return Database{}, fmt.Errorf("notion: invalid URL: %w", err)
|
||||||
}
|
}
|
||||||
@ -94,7 +237,7 @@ func (c *Client) FindDatabaseByID(id string) (db Database, err error) {
|
|||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != http.StatusOK {
|
||||||
return Database{}, fmt.Errorf("notion: failed to find database: %w", parseErrorResponse(res))
|
return Database{}, fmt.Errorf("notion: failed to find database: %w", parseErrorResponse(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,3 +248,36 @@ func (c *Client) FindDatabaseByID(id string) (db Database, err error) {
|
|||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryDatabase returns database contents, with optional filters, sorts and pagination.
|
||||||
|
// See: https://developers.notion.com/reference/post-database-query
|
||||||
|
func (c *Client) QueryDatabase(id string, query DatabaseQuery) (result DatabaseQueryResponse, err error) {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
|
err = json.NewEncoder(body).Encode(query)
|
||||||
|
if err != nil {
|
||||||
|
return DatabaseQueryResponse{}, fmt.Errorf("notion: failed to encode filter to JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := c.newRequest(http.MethodPost, fmt.Sprintf("/databases/%v/query", id), body)
|
||||||
|
if err != nil {
|
||||||
|
return DatabaseQueryResponse{}, fmt.Errorf("notion: invalid URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return DatabaseQueryResponse{}, fmt.Errorf("notion: failed to make HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return DatabaseQueryResponse{}, fmt.Errorf("notion: failed to find database: %w", parseErrorResponse(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&result)
|
||||||
|
if err != nil {
|
||||||
|
return DatabaseQueryResponse{}, fmt.Errorf("notion: failed to parse HTTP response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
95
page.go
Normal file
95
page.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package notion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Page is a resource on the Notion platform. Its parent is either a workspace,
|
||||||
|
// another page, or a database.
|
||||||
|
// See: https://developers.notion.com/reference/page
|
||||||
|
type Page struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CreatedTime time.Time `json:"created_time"`
|
||||||
|
LastEditedTime time.Time `json:"last_edited_time"`
|
||||||
|
Parent PageParent `json:"parent"`
|
||||||
|
Archived bool `json:"archived"`
|
||||||
|
|
||||||
|
// Properties differ between parent type.
|
||||||
|
// See the `UnmarshalJSON` method.
|
||||||
|
Properties interface{} `json:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageParent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
PageID *string `json:"page_id"`
|
||||||
|
DatabaseID *string `json:"database_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageProperties are properties of a page whose parent is a page or a workspace.
|
||||||
|
type PageProperties struct {
|
||||||
|
Title struct {
|
||||||
|
Title []RichText `json:"title"`
|
||||||
|
} `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabasePageProperties are properties of a page whose parent is a database.
|
||||||
|
type DatabasePageProperties map[string]DatabasePageProperty
|
||||||
|
|
||||||
|
type DatabasePageProperty struct {
|
||||||
|
DatabaseProperty
|
||||||
|
RichText []RichText `json:"rich_text"`
|
||||||
|
Select *SelectMetadata `json:"select"`
|
||||||
|
MultiSelect []SelectMetadata `json:"multi_select"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
|
//
|
||||||
|
// Pages get a different Properties type based on the parent of the page.
|
||||||
|
// If parent type is `workspace` or `page_id`, PageProperties is used. Else if
|
||||||
|
// parent type is `database_id`, DatabasePageProperties is used.
|
||||||
|
func (p *Page) UnmarshalJSON(b []byte) error {
|
||||||
|
type (
|
||||||
|
PageAlias Page
|
||||||
|
PageDTO struct {
|
||||||
|
PageAlias
|
||||||
|
Properties json.RawMessage `json:"properties"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var dto PageDTO
|
||||||
|
|
||||||
|
err := json.Unmarshal(b, &dto)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
page := dto.PageAlias
|
||||||
|
|
||||||
|
switch dto.Parent.Type {
|
||||||
|
case "workspace":
|
||||||
|
fallthrough
|
||||||
|
case "page_id":
|
||||||
|
var props PageProperties
|
||||||
|
err := json.Unmarshal(dto.Properties, &props)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
page.Properties = props
|
||||||
|
case "database_id":
|
||||||
|
var props DatabasePageProperties
|
||||||
|
err := json.Unmarshal(dto.Properties, &props)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
page.Properties = props
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown page parent type %q", dto.Parent.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = Page(page)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Reference in New Issue
Block a user