mirror of
https://github.com/dstotijn/go-notion.git
synced 2025-06-15 00:05:04 +02:00
Initial commit
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 David Stotijn
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
43
README.md
Normal file
43
README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# go-notion
|
||||||
|
|
||||||
|
Go client for the [Notion API](https://developers.notion.com/reference).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
🐣 Early development
|
||||||
|
|
||||||
|
## API endpoints
|
||||||
|
|
||||||
|
### Databases
|
||||||
|
|
||||||
|
- [x] [Retrieve a database](database.go)
|
||||||
|
- [ ] Query a database
|
||||||
|
- [ ] List databases
|
||||||
|
|
||||||
|
## Pages
|
||||||
|
|
||||||
|
- [ ] Retrieve a page
|
||||||
|
- [ ] Create a page
|
||||||
|
- [ ] Update page properties
|
||||||
|
|
||||||
|
### Blocks
|
||||||
|
|
||||||
|
- [ ] Retrieve block children
|
||||||
|
- [ ] Append block children
|
||||||
|
|
||||||
|
### Users
|
||||||
|
|
||||||
|
- [ ] Retrieve a user
|
||||||
|
- [ ] List all users
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
- [ ] Search
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT License](LICENSE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
© 2021 David Stotijn — [Twitter](https://twitter.com/dstotijn), [Email](mailto:dstotijn@gmail.com)
|
54
client.go
Normal file
54
client.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package notion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseURL = "https://api.notion.com/v1"
|
||||||
|
apiVersion = "2021-05-13"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is used for HTTP requests to the Notion API.
|
||||||
|
type Client struct {
|
||||||
|
apiKey string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientOption is used to override default client behavior.
|
||||||
|
type ClientOption func(*Client)
|
||||||
|
|
||||||
|
// NewClient returns a new Client.
|
||||||
|
func NewClient(apiKey string, opts ...ClientOption) *Client {
|
||||||
|
c := &Client{
|
||||||
|
apiKey: apiKey,
|
||||||
|
httpClient: http.DefaultClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHTTPClient overrides the default http.Client.
|
||||||
|
func WithHTTPClient(httpClient *http.Client) ClientOption {
|
||||||
|
return func(c *Client) {
|
||||||
|
c.httpClient = httpClient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) {
|
||||||
|
req, err := http.NewRequest(method, baseURL+url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", c.apiKey))
|
||||||
|
req.Header.Set("Notion-Version", apiVersion)
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
113
database.go
Normal file
113
database.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package notion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database is a resource on the Notion platform.
|
||||||
|
// See: https://developers.notion.com/reference/database
|
||||||
|
type Database struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CreatedTime time.Time `json:"created_time"`
|
||||||
|
LastEditedTime time.Time `json:"last_edited_time"`
|
||||||
|
Title []RichText `json:"title"`
|
||||||
|
Properties DatabaseProperties `json:"properties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseProperties map[string]interface{}
|
||||||
|
|
||||||
|
// Database property types.
|
||||||
|
type (
|
||||||
|
TitleDatabaseProperty struct{}
|
||||||
|
RichTextDatabaseProperty struct{}
|
||||||
|
DateDatabaseProperty struct{}
|
||||||
|
PeopleDatabaseProperty struct{}
|
||||||
|
FileDatabaseProperty struct{}
|
||||||
|
CheckboxDatabaseProperty struct{}
|
||||||
|
URLDatabaseProperty struct{}
|
||||||
|
EmailDatabaseProperty struct{}
|
||||||
|
PhoneNumberDatabaseProperty struct{}
|
||||||
|
CreatedTimeDatabaseProperty struct{}
|
||||||
|
CreatedByDatabaseProperty struct{}
|
||||||
|
LastEditedTimeDatabaseProperty struct{}
|
||||||
|
LastEditedByDatabaseProperty struct{}
|
||||||
|
|
||||||
|
NumberDatabaseProperty struct {
|
||||||
|
Format string `json:"format"`
|
||||||
|
}
|
||||||
|
SelectDatabaseProperty struct {
|
||||||
|
Options []SelectOptions `json:"options"`
|
||||||
|
}
|
||||||
|
FormulaDatabaseProperty struct {
|
||||||
|
Expression string `json:"expression"`
|
||||||
|
}
|
||||||
|
RelationDatabaseProperty struct {
|
||||||
|
DatabaseID string `json:"database_id"`
|
||||||
|
SyncedPropName *string `json:"synced_property_name"`
|
||||||
|
SyncedPropID *string `json:"synced_property_id"`
|
||||||
|
}
|
||||||
|
RollupDatabaseProperty struct {
|
||||||
|
RelationPropName string `json:"relation_property_name"`
|
||||||
|
RelationPropID string `json:"relation_property_id"`
|
||||||
|
RollupPropName string `json:"rollup_property_name"`
|
||||||
|
RollupPropID string `json:"rollup_property_id"`
|
||||||
|
Function string `json:"function"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelectOptions struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseProperty struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
Title *TitleDatabaseProperty `json:"title"`
|
||||||
|
RichText *RichTextDatabaseProperty `json:"rich_text"`
|
||||||
|
Number *NumberDatabaseProperty `json:"number"`
|
||||||
|
Select *SelectDatabaseProperty `json:"select"`
|
||||||
|
MultiSelect *SelectDatabaseProperty `json:"multi_select"`
|
||||||
|
Date *DateDatabaseProperty `json:"date"`
|
||||||
|
People *PeopleDatabaseProperty `json:"people"`
|
||||||
|
File *FileDatabaseProperty `json:"file"`
|
||||||
|
Checkbox *CheckboxDatabaseProperty `json:"checkbox"`
|
||||||
|
URL *URLDatabaseProperty `json:"url"`
|
||||||
|
Email *EmailDatabaseProperty `json:"email"`
|
||||||
|
PhoneNumber *PhoneNumberDatabaseProperty `json:"phone_number"`
|
||||||
|
Formula *FormulaDatabaseProperty `json:"formula"`
|
||||||
|
Relation *RelationDatabaseProperty `json:"relation"`
|
||||||
|
Rollup *RollupDatabaseProperty `json:"rollup"`
|
||||||
|
CreatedTime *CreatedTimeDatabaseProperty `json:"created_time"`
|
||||||
|
CreatedBy *CreatedByDatabaseProperty `json:"created_by"`
|
||||||
|
LastEditedTime *LastEditedTimeDatabaseProperty `json:"last_edited_time"`
|
||||||
|
LastEditedBy *LastEditedByDatabaseProperty `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FindDatabaseByID(id string) (db Database, err error) {
|
||||||
|
req, err := c.newRequest("GET", "/databases/"+id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return Database{}, fmt.Errorf("invalid URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return Database{}, fmt.Errorf("failed to make HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return Database{}, fmt.Errorf("notion: failed to get database: %w", parseErrorResponse(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&db)
|
||||||
|
if err != nil {
|
||||||
|
return Database{}, fmt.Errorf("failed to parse HTTP response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
75
error.go
Normal file
75
error.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package notion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See: https://developers.notion.com/reference/errors.
|
||||||
|
var (
|
||||||
|
ErrInvalidJSON = errors.New("notion: request body could not be decoded as JSON")
|
||||||
|
ErrInvalidRequestURL = errors.New("notion: request URL is not valid")
|
||||||
|
ErrInvalidRequest = errors.New("notion: request is not supported")
|
||||||
|
ErrValidation = errors.New("notion: request body does not match the schema for the expected parameters")
|
||||||
|
ErrUnauthorized = errors.New("notion: bearer token is not valid")
|
||||||
|
ErrRestrictedResource = errors.New("notion: client doesn't have permission to perform this operation")
|
||||||
|
ErrObjectNotFound = errors.New("notion: the resource does not exist")
|
||||||
|
ErrConflict = errors.New("notion: the transaction could not be completed, potentially due to a data collision")
|
||||||
|
ErrRateLimited = errors.New("notion: this request exceeds the number of requests allowed")
|
||||||
|
ErrInternalServer = errors.New("notion: an unexpected error occurred")
|
||||||
|
ErrServiceUnavailable = errors.New("notion: service is unavailable")
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIError struct {
|
||||||
|
Object string `json:"object"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements `error`.
|
||||||
|
func (err *APIError) Error() string {
|
||||||
|
return fmt.Sprintf("%v (code: %v, status: %v)", err.Message, err.Code, err.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *APIError) Unwrap() error {
|
||||||
|
switch err.Code {
|
||||||
|
case "invalid_json":
|
||||||
|
return ErrInvalidJSON
|
||||||
|
case "invalid_request_url":
|
||||||
|
return ErrInvalidRequestURL
|
||||||
|
case "invalid_request":
|
||||||
|
return ErrInvalidRequest
|
||||||
|
case "validation_error":
|
||||||
|
return ErrValidation
|
||||||
|
case "unauthorized":
|
||||||
|
return ErrUnauthorized
|
||||||
|
case "restricted_resource":
|
||||||
|
return ErrRestrictedResource
|
||||||
|
case "object_not_found":
|
||||||
|
return ErrObjectNotFound
|
||||||
|
case "conflict_error":
|
||||||
|
return ErrConflict
|
||||||
|
case "rate_limited":
|
||||||
|
return ErrRateLimited
|
||||||
|
case "internal_server_error":
|
||||||
|
return ErrInternalServer
|
||||||
|
case "service_unavailable":
|
||||||
|
return ErrServiceUnavailable
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("notion: unknown error (%v)", err.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseErrorResponse(res *http.Response) error {
|
||||||
|
var apiErr APIError
|
||||||
|
|
||||||
|
err := json.NewDecoder(res.Body).Decode(&apiErr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse error from HTTP response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiErr
|
||||||
|
}
|
58
rich_text.go
Normal file
58
rich_text.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package notion
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type RichText struct {
|
||||||
|
PlainText string `json:"plain_text"`
|
||||||
|
HRef *string `json:"href"`
|
||||||
|
Annotations Annotations `json:"annotations"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
Text *Text `json:"text"`
|
||||||
|
Mention *Mention `json:"mention"`
|
||||||
|
Equation *Equation `json:"equation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Equation struct {
|
||||||
|
Expression string `json:"expression"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Annotations struct {
|
||||||
|
Bold bool `json:"bold"`
|
||||||
|
Italic bool `json:"italic"`
|
||||||
|
Strikethrough bool `json:"strikethrough"`
|
||||||
|
Underline bool `json:"underline"`
|
||||||
|
Code bool `json:"code"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mention struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
User *User `json:"user"`
|
||||||
|
Page *PageMention `json:"page"`
|
||||||
|
Database *DatabaseMention `json:"database"`
|
||||||
|
Date *Date `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Date struct {
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
End *time.Time `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Text struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Link *Link `json:"link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageMention struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseMention struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
17
user.go
Normal file
17
user.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package notion
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bot struct{}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AvatarURL *string `json:"avatar_url"`
|
||||||
|
|
||||||
|
Person *Person `json:"person"`
|
||||||
|
Bot *Bot `json:"bot"`
|
||||||
|
}
|
Reference in New Issue
Block a user