diff --git a/client.go b/client.go index 0e2d6c9..d619481 100644 --- a/client.go +++ b/client.go @@ -485,6 +485,32 @@ func (c *Client) FindUserByID(ctx context.Context, id string) (user User, err er return user, nil } +// FindCurrentUser fetches the current bot user based on authentication API key. +// See: https://developers.notion.com/reference/get-self +func (c *Client) FindCurrentUser(ctx context.Context) (user User, err error) { + req, err := c.newRequest(ctx, http.MethodGet, "/users/me", nil) + if err != nil { + return User{}, fmt.Errorf("notion: invalid request: %w", err) + } + + res, err := c.httpClient.Do(req) + if err != nil { + return User{}, fmt.Errorf("notion: failed to make HTTP request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return User{}, fmt.Errorf("notion: failed to find current user: %w", parseErrorResponse(res)) + } + + err = json.NewDecoder(res.Body).Decode(&user) + if err != nil { + return User{}, fmt.Errorf("notion: failed to parse HTTP response: %w", err) + } + + return user, nil +} + // ListUsers returns a list of all users, and pagination metadata. // See: https://developers.notion.com/reference/get-users func (c *Client) ListUsers(ctx context.Context, query *PaginationQuery) (result ListUsersResponse, err error) { diff --git a/client_test.go b/client_test.go index 6af9938..c3e2063 100644 --- a/client_test.go +++ b/client_test.go @@ -709,8 +709,8 @@ func TestQueryDatabase(t *testing.T) { { ID: "be32e790-8292-46df-a248-b784fdf483cf", Name: "Jane Doe", - AvatarURL: notion.StringPtr("https://example.com/image.png"), - Type: "person", + AvatarURL: "https://example.com/image.png", + Type: notion.UserTypePerson, Person: ¬ion.Person{ Email: "jane@example.com", }, @@ -773,8 +773,8 @@ func TestQueryDatabase(t *testing.T) { CreatedBy: ¬ion.User{ ID: "be32e790-8292-46df-a248-b784fdf483cf", Name: "Jane Doe", - AvatarURL: notion.StringPtr("https://example.com/image.png"), - Type: "person", + AvatarURL: "https://example.com/image.png", + Type: notion.UserTypePerson, Person: ¬ion.Person{ Email: "jane@example.com", }, @@ -793,8 +793,8 @@ func TestQueryDatabase(t *testing.T) { LastEditedBy: ¬ion.User{ ID: "be32e790-8292-46df-a248-b784fdf483cf", Name: "Jane Doe", - AvatarURL: notion.StringPtr("https://example.com/image.png"), - Type: "person", + AvatarURL: "https://example.com/image.png", + Type: notion.UserTypePerson, Person: ¬ion.Person{ Email: "jane@example.com", }, @@ -3033,8 +3033,8 @@ func TestFindUserByID(t *testing.T) { expUser: notion.User{ ID: "be32e790-8292-46df-a248-b784fdf483cf", Name: "Jane Doe", - AvatarURL: notion.StringPtr("https://example.com/avatar.png"), - Type: "person", + AvatarURL: "https://example.com/avatar.png", + Type: notion.UserTypePerson, Person: ¬ion.Person{ Email: "jane@example.com", }, @@ -3093,6 +3093,115 @@ func TestFindUserByID(t *testing.T) { } } +func TestFindCurrentUser(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + respBody func(r *http.Request) io.Reader + respStatusCode int + expUser notion.User + expError error + }{ + { + name: "successful response", + respBody: func(_ *http.Request) io.Reader { + return strings.NewReader( + `{ + "object": "user", + "id": "be32e790-8292-46df-a248-b784fdf483cf", + "type": "bot", + "bot": { + "owner": { + "type": "user", + "user": { + "object": "user", + "id": "5389a034-eb5c-47b5-8a9e-f79c99ef166c", + "name": "Jane Doe", + "avatar_url": "https://example.com/avatar.png", + "type": "person", + "person": { + "email": "jane@example.com" + } + } + } + } + }`, + ) + }, + respStatusCode: http.StatusOK, + expUser: notion.User{ + ID: "be32e790-8292-46df-a248-b784fdf483cf", + Type: notion.UserTypeBot, + Bot: ¬ion.Bot{ + Owner: notion.BotOwner{ + Type: notion.BotOwnerTypeUser, + User: ¬ion.User{ + ID: "5389a034-eb5c-47b5-8a9e-f79c99ef166c", + Name: "Jane Doe", + AvatarURL: "https://example.com/avatar.png", + Type: notion.UserTypePerson, + Person: ¬ion.Person{ + Email: "jane@example.com", + }, + }, + }, + }, + }, + expError: nil, + }, + { + name: "error response", + respBody: func(_ *http.Request) io.Reader { + return strings.NewReader( + `{ + "object": "error", + "status": 404, + "code": "object_not_found", + "message": "foobar" + }`, + ) + }, + respStatusCode: http.StatusNotFound, + expUser: notion.User{}, + expError: errors.New("notion: failed to find current user: foobar (code: object_not_found, status: 404)"), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + httpClient := &http.Client{ + Transport: &mockRoundtripper{fn: func(r *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: tt.respStatusCode, + Status: http.StatusText(tt.respStatusCode), + Body: ioutil.NopCloser(tt.respBody(r)), + }, nil + }}, + } + client := notion.NewClient("secret-api-key", notion.WithHTTPClient(httpClient)) + user, err := client.FindCurrentUser(context.Background()) + + if tt.expError == nil && err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tt.expError != nil && err == nil { + t.Fatalf("error not equal (expected: %v, got: nil)", tt.expError) + } + if tt.expError != nil && err != nil && tt.expError.Error() != err.Error() { + t.Fatalf("error not equal (expected: %v, got: %v)", tt.expError, err) + } + + if diff := cmp.Diff(tt.expUser, user); diff != "" { + t.Fatalf("user not equal (-exp, +got):\n%v", diff) + } + }) + } +} + func TestListUsers(t *testing.T) { t.Parallel() @@ -3150,8 +3259,8 @@ func TestListUsers(t *testing.T) { { ID: "be32e790-8292-46df-a248-b784fdf483cf", Name: "Jane Doe", - AvatarURL: notion.StringPtr("https://example.com/avatar.png"), - Type: "person", + AvatarURL: "https://example.com/avatar.png", + Type: notion.UserTypePerson, Person: ¬ion.Person{ Email: "jane@example.com", }, @@ -3159,7 +3268,7 @@ func TestListUsers(t *testing.T) { { ID: "25c9cc08-1afd-4d22-b9e6-31b0f6e7b44f", Name: "Johnny 5", - Type: "bot", + Type: notion.UserTypeBot, Bot: ¬ion.Bot{}, }, }, diff --git a/user.go b/user.go index bc31f0a..b24451d 100644 --- a/user.go +++ b/user.go @@ -1,16 +1,38 @@ package notion +type UserType string + +const ( + UserTypePerson UserType = "person" + UserTypeBot UserType = "bot" +) + type Person struct { Email string `json:"email"` } -type Bot struct{} +type Bot struct { + Owner BotOwner `json:"owner"` +} + +type BotOwnerType string + +const ( + BotOwnerTypeWorkspace BotOwnerType = "workspace" + BotOwnerTypeUser BotOwnerType = "user" +) + +type BotOwner struct { + Type BotOwnerType `json:"type"` + Workspace bool `json:"workspace"` + User *User `json:"user"` +} type User struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - AvatarURL *string `json:"avatar_url"` + ID string `json:"id"` + Type UserType `json:"type"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` Person *Person `json:"person"` Bot *Bot `json:"bot"`