1
0
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:
David Stotijn
2021-05-13 22:11:32 +02:00
commit e9e9dca708
8 changed files with 384 additions and 0 deletions

21
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/dstotijn/go-notion
go 1.16

58
rich_text.go Normal file
View 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
View 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"`
}