Repository: jomei/notionapi
Pull request #111 (2022-09-11 10:30:05)

Support for comment APIs
Changes include:
- Support Comment APIs
- UTs for Comment service
- Upgraded the notion version to latest version required for comment APIs



jomei: Awesome 🔥 

Patch:
diff --git a/README.md b/README.md
index f8e6ca5..22d60d0 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 An API client for the [Notion API](https://developers.notion.com/) implemented in Golang
 
 # Supported APIs
-It supports all APIs for Notion API version `2022-02-22`
+It supports all APIs for Notion API version `2022-06-28`
 
 # Installation
 
diff --git a/client.go b/client.go
index 4c3af03..025b33e 100644
--- a/client.go
+++ b/client.go
@@ -15,7 +15,7 @@ import (
 const (
 	apiURL        = "https://api.notion.com"
 	apiVersion    = "v1"
-	notionVersion = "2022-02-22"
+	notionVersion = "2022-06-28"
 )
 
 type Token string
@@ -40,6 +40,7 @@ type Client struct {
 	Page     PageService
 	User     UserService
 	Search   SearchService
+	Comment  CommentService
 }
 
 func NewClient(token Token, opts ...ClientOption) *Client {
@@ -60,6 +61,7 @@ func NewClient(token Token, opts ...ClientOption) *Client {
 	c.Page = &PageClient{apiClient: c}
 	c.User = &UserClient{apiClient: c}
 	c.Search = &SearchClient{apiClient: c}
+	c.Comment = &CommentClient{apiClient: c}
 
 	for _, opt := range opts {
 		opt(c)
diff --git a/comment.go b/comment.go
new file mode 100644
index 0000000..22d9a2b
--- /dev/null
+++ b/comment.go
@@ -0,0 +1,106 @@
+package notionapi
+
+import (
+	"context"
+	"encoding/json"
+	"log"
+	"net/http"
+	"time"
+)
+
+type CommentID string
+
+func (cID CommentID) String() string {
+	return string(cID)
+}
+
+type CommentService interface {
+	Get(context.Context, BlockID, *Pagination) (*CommentQueryResponse, error)
+	Create(ctx context.Context, request *CommentCreateRequest) (*Comment, error)
+}
+
+type CommentClient struct {
+	apiClient *Client
+}
+
+// Get https://developers.notion.com/reference/retrieve-a-comment
+func (cc *CommentClient) Get(ctx context.Context, id BlockID, pagination *Pagination) (*CommentQueryResponse, error) {
+	queryParams := map[string]string{}
+	if pagination != nil {
+		queryParams = pagination.ToQuery()
+	}
+
+	queryParams["block_id"] = id.String()
+
+	res, err := cc.apiClient.request(ctx, http.MethodGet, "comments", queryParams, nil)
+	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 CommentQueryResponse
+
+	err = json.NewDecoder(res.Body).Decode(&response)
+	if err != nil {
+		return nil, err
+	}
+
+	return &response, nil
+}
+
+// Create https://developers.notion.com/reference/create-a-comment
+func (cc *CommentClient) Create(ctx context.Context, requestBody *CommentCreateRequest) (*Comment, error) {
+	res, err := cc.apiClient.request(ctx, http.MethodPost, "comments", nil, requestBody)
+	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 Comment
+	err = json.NewDecoder(res.Body).Decode(&response)
+	if err != nil {
+		return nil, err
+	}
+
+	return &response, nil
+}
+
+type DiscussionID string
+
+func (dID DiscussionID) String() string {
+	return string(dID)
+}
+
+type Comment struct {
+	Object         ObjectType   `json:"object"`
+	ID             ObjectID     `json:"id"`
+	DiscussionID   DiscussionID `json:"discussion_id"`
+	CreatedTime    time.Time    `json:"created_time"`
+	LastEditedTime time.Time    `json:"last_edited_time"`
+	CreatedBy      User         `json:"created_by,omitempty"`
+	RichText       []RichText   `json:"rich_text"`
+	Parent         Parent       `json:"parent"`
+}
+
+type CommentQueryResponse struct {
+	Object     ObjectType `json:"object"`
+	Results    []Comment  `json:"results"`
+	HasMore    bool       `json:"has_more"`
+	NextCursor Cursor     `json:"next_cursor"`
+}
+
+type CommentCreateRequest struct {
+	Parent       Parent       `json:"parent,omitempty"`
+	DiscussionID DiscussionID `json:"discussion_id,omitempty"`
+	RichText     []RichText   `json:"rich_text"`
+}
diff --git a/const.go b/const.go
index c5577ac..3f1bbdc 100644
--- a/const.go
+++ b/const.go
@@ -8,6 +8,7 @@ const (
 	ObjectTypeText     ObjectType = "text"
 	ObjectTypeUser     ObjectType = "user"
 	ObjectTypeError    ObjectType = "error"
+	ObjectTypeComment  ObjectType = "comment"
 )
 
 const (


Test patch:
diff --git a/comment_test.go b/comment_test.go
new file mode 100644
index 0000000..52ae1a1
--- /dev/null
+++ b/comment_test.go
@@ -0,0 +1,169 @@
+package notionapi_test
+
+import (
+	"context"
+	"net/http"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/jomei/notionapi"
+)
+
+func TestCommentClient(t *testing.T) {
+	timestamp, err := time.Parse(time.RFC3339, "2021-05-24T05:06:34.827Z")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var user = notionapi.User{
+		Object: "user",
+		ID:     "some_id",
+	}
+
+	t.Run("Get", func(t *testing.T) {
+		tests := []struct {
+			name       string
+			filePath   string
+			statusCode int
+			id         notionapi.BlockID
+			want       *notionapi.CommentQueryResponse
+			wantErr    bool
+			err        error
+		}{
+			{
+				name:       "returns comments for given block",
+				filePath:   "testdata/comment_get.json",
+				statusCode: http.StatusOK,
+				id:         "some_id",
+				want: &notionapi.CommentQueryResponse{
+					Object: notionapi.ObjectTypeList,
+					Results: []notionapi.Comment{
+						{
+							Object:         notionapi.ObjectTypeComment,
+							ID:             "some_id",
+							DiscussionID:   "some_id",
+							CreatedTime:    timestamp,
+							LastEditedTime: timestamp,
+							CreatedBy:      user,
+							Parent: notionapi.Parent{
+								Type:   notionapi.ParentTypePageID,
+								PageID: "some_id",
+							},
+							RichText: []notionapi.RichText{
+								{
+									Type: notionapi.ObjectTypeText,
+									Text: &notionapi.Text{Content: "content"},
+								},
+							},
+						},
+						{
+							Object:         notionapi.ObjectTypeComment,
+							ID:             "some_id",
+							DiscussionID:   "some_id",
+							CreatedTime:    timestamp,
+							LastEditedTime: timestamp,
+							CreatedBy:      user,
+							Parent: notionapi.Parent{
+								Type:   notionapi.ParentTypePageID,
+								PageID: "some_id",
+							},
+							RichText: []notionapi.RichText{
+								{
+									Type: notionapi.ObjectTypeText,
+									Text: &notionapi.Text{Content: "content"},
+								},
+							},
+						},
+					},
+					HasMore:    false,
+					NextCursor: "",
+				},
+				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.Comment.Get(context.Background(), tt.id, nil)
+
+				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)
+				}
+			})
+		}
+	})
+
+	t.Run("Create", func(t *testing.T) {
+		tests := []struct {
+			name       string
+			filePath   string
+			statusCode int
+			request    *notionapi.CommentCreateRequest
+			want       *notionapi.Comment
+			wantErr    bool
+			err        error
+		}{
+			{
+				name:       "returns created comment",
+				filePath:   "testdata/comment_create.json",
+				statusCode: http.StatusOK,
+				request: &notionapi.CommentCreateRequest{
+					Parent: notionapi.Parent{
+						Type:   notionapi.ParentTypePageID,
+						PageID: "some_id",
+					},
+					RichText: []notionapi.RichText{
+						{
+							Type: notionapi.ObjectTypeText,
+							Text: &notionapi.Text{Content: "Hello world"},
+						},
+					},
+				},
+				want: &notionapi.Comment{
+					Object:         notionapi.ObjectTypeComment,
+					ID:             "some_id",
+					DiscussionID:   "some_id",
+					CreatedTime:    timestamp,
+					LastEditedTime: timestamp,
+					CreatedBy:      user,
+					Parent: notionapi.Parent{
+						Type:   notionapi.ParentTypePageID,
+						PageID: "some_id",
+					},
+					RichText: []notionapi.RichText{
+						{
+							Type: notionapi.ObjectTypeText,
+							Text: &notionapi.Text{Content: "Hello world"},
+						},
+					},
+				},
+			},
+		}
+
+		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.Comment.Create(context.Background(), tt.request)
+
+				if (err != nil) != tt.wantErr {
+					t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
+					return
+				}
+
+				if !reflect.DeepEqual(got, tt.want) {
+					t.Errorf("Create() got = %v, want %v", got, tt.want)
+				}
+			})
+		}
+	})
+}
diff --git a/testdata/comment_create.json b/testdata/comment_create.json
new file mode 100644
index 0000000..7efcb5f
--- /dev/null
+++ b/testdata/comment_create.json
@@ -0,0 +1,24 @@
+{
+  "object": "comment",
+  "id": "some_id",
+  "parent": {
+      "type": "page_id",
+      "page_id": "some_id"
+  },
+  "discussion_id": "some_id",
+  "created_time": "2021-05-24T05:06:34.827Z",
+  "last_edited_time": "2021-05-24T05:06:34.827Z",
+  "created_by": {
+      "object": "user",
+      "id": "some_id"
+  },
+  "rich_text": [
+      {
+          "type": "text",
+          "text": {
+              "content": "Hello world",
+              "link": null
+          }
+      }
+  ]
+}
\ No newline at end of file
diff --git a/testdata/comment_get.json b/testdata/comment_get.json
new file mode 100644
index 0000000..a2de35f
--- /dev/null
+++ b/testdata/comment_get.json
@@ -0,0 +1,55 @@
+{
+  "object": "list",
+  "results": [
+      {
+          "object": "comment",
+          "id": "some_id",
+          "parent": {
+              "type": "page_id",
+              "page_id": "some_id"
+          },
+          "discussion_id": "some_id",
+          "created_time": "2021-05-24T05:06:34.827Z",
+          "last_edited_time": "2021-05-24T05:06:34.827Z",
+          "created_by": {
+              "object": "user",
+              "id": "some_id"
+          },
+          "rich_text": [
+              {
+                  "type": "text",
+                  "text": {
+                      "content": "content",
+                      "link": null
+                  }
+              }
+          ]
+      },
+      {
+          "object": "comment",
+          "id": "some_id",
+          "parent": {
+              "type": "page_id",
+              "page_id": "some_id"
+          },
+          "discussion_id": "some_id",
+          "created_time": "2021-05-24T05:06:34.827Z",
+          "last_edited_time": "2021-05-24T05:06:34.827Z",
+          "created_by": {
+              "object": "user",
+              "id": "some_id"
+          },
+          "rich_text": [
+              {
+                  "type": "text",
+                  "text": {
+                      "content": "content",
+                      "link": null
+                  }
+              }
+          ]
+      }
+  ],
+  "next_cursor": null,
+  "has_more": false
+}
\ No newline at end of file