Repository: jomei/notionapi
Pull request #129 (2023-04-03 18:44:16)

Support for create token API
Support for create token API
Ref: https://developers.notion.com/reference/create-a-token 


Patch:
diff --git a/authentication.go b/authentication.go
new file mode 100644
index 0000000..f0deb83
--- /dev/null
+++ b/authentication.go
@@ -0,0 +1,63 @@
+package notionapi
+
+import (
+	"context"
+	"encoding/json"
+	"log"
+	"net/http"
+)
+
+type AuthenticationService interface {
+	CreateToken(ctx context.Context, request *TokenCreateRequest) (*TokenCreateResponse, error)
+}
+
+type AuthenticationClient struct {
+	apiClient *Client
+}
+
+// Create https://developers.notion.com/reference/create-a-token
+func (cc *AuthenticationClient) CreateToken(ctx context.Context, request *TokenCreateRequest) (*TokenCreateResponse, error) {
+	res, err := cc.apiClient.request(ctx, http.MethodPost, "oauth/token", nil, request)
+	if err != nil {
+		return nil, err
+	}
+
+	defer func() {
+		if errClose := res.Body.Close(); errClose != nil {
+			log.Println("failed to close body, should never happen")
+		}
+	}()
+
+	var response TokenCreateResponse
+	err = json.NewDecoder(res.Body).Decode(&response)
+	if err != nil {
+		return nil, err
+	}
+
+	return &response, nil
+}
+
+type TokenCreateRequest struct {
+	Code            string          `json:"code"`
+	GrantType       string          `json:"grant_type"` // Default value of grant_type is always "authorization_code"
+	RedirectUri     string          `json:"redirect_uri"`
+	ExternalAccount ExternalAccount `json:"external_account,omitempty"`
+}
+
+type ExternalAccount struct {
+	Key  string `json:"key"`
+	Name string `json:"name"`
+}
+
+type TokenCreateResponse struct {
+	AccessToken          string `json:"access_token"`
+	BotId                string `json:"bot_id"`
+	DuplicatedTemplateId string `json:"duplicated_template_id,omitempty"`
+
+	// Owner can be { "workspace": true } OR a User object.
+	// Ref: https://developers.notion.com/docs/authorization#step-4-notion-responds-with-an-access_token-and-some-additional-information
+	Owner         interface{} `json:"owner,omitempty"`
+	WorkspaceIcon string      `json:"workspace_icon"`
+	WorkspaceId   string      `json:"workspace_id"`
+	WorkspaceName string      `json:"workspace_name"`
+}
diff --git a/client.go b/client.go
index 69faaac..69455ab 100644
--- a/client.go
+++ b/client.go
@@ -39,12 +39,13 @@ type Client struct {
 
 	Token Token
 
-	Database DatabaseService
-	Block    BlockService
-	Page     PageService
-	User     UserService
-	Search   SearchService
-	Comment  CommentService
+	Database       DatabaseService
+	Block          BlockService
+	Page           PageService
+	User           UserService
+	Search         SearchService
+	Comment        CommentService
+	Authentication AuthenticationService
 }
 
 func NewClient(token Token, opts ...ClientOption) *Client {
@@ -67,6 +68,7 @@ func NewClient(token Token, opts ...ClientOption) *Client {
 	c.User = &UserClient{apiClient: c}
 	c.Search = &SearchClient{apiClient: c}
 	c.Comment = &CommentClient{apiClient: c}
+	c.Authentication = &AuthenticationClient{apiClient: c}
 
 	for _, opt := range opts {
 		opt(c)


Test patch:
diff --git a/authentication_test.go b/authentication_test.go
new file mode 100644
index 0000000..56f39a9
--- /dev/null
+++ b/authentication_test.go
@@ -0,0 +1,61 @@
+package notionapi_test
+
+import (
+	"context"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"github.com/jomei/notionapi"
+)
+
+func TestAuthenticationClient(t *testing.T) {
+	t.Run("CreateToken", func(t *testing.T) {
+		tests := []struct {
+			name       string
+			filePath   string
+			statusCode int
+			request    *notionapi.TokenCreateRequest
+			want       *notionapi.TokenCreateResponse
+			wantErr    bool
+			err        error
+		}{
+			{
+				name:       "Creates token",
+				filePath:   "testdata/create_token.json",
+				statusCode: http.StatusOK,
+				request: &notionapi.TokenCreateRequest{
+					Code:        "code1",
+					GrantType:   "authorization_code",
+					RedirectUri: "www.example.com",
+				},
+				want: &notionapi.TokenCreateResponse{
+					AccessToken:          "token1",
+					BotId:                "bot1",
+					DuplicatedTemplateId: "template_id1",
+					WorkspaceIcon:        "🎉",
+					WorkspaceId:          "workspaceid_1",
+					WorkspaceName:        "workspace_1",
+				},
+				wantErr: false,
+				err:     nil,
+			},
+		}
+
+		for _, tt := range tests {
+			t.Run(tt.name, func(t *testing.T) {
+				c := newMockedClient(t, tt.filePath, tt.statusCode)
+				client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c))
+				got, err := client.Authentication.CreateToken(context.Background(), tt.request)
+
+				if (err != nil) != tt.wantErr {
+					t.Errorf("Query() error = %v, wantErr %v", err, tt.wantErr)
+					return
+				}
+				if !reflect.DeepEqual(got, tt.want) {
+					t.Errorf("Query() got = %v, want %v", got, tt.want)
+				}
+			})
+		}
+	})
+}
diff --git a/testdata/create_token.json b/testdata/create_token.json
new file mode 100644
index 0000000..93908f4
--- /dev/null
+++ b/testdata/create_token.json
@@ -0,0 +1,8 @@
+{
+    "access_token": "token1",
+    "bot_id": "bot1",
+    "duplicated_template_id": "template_id1",
+    "workspace_icon": "🎉",
+    "workspace_id": "workspaceid_1",
+    "workspace_name": "workspace_1"
+}
\ No newline at end of file