Skip to content

Commit

Permalink
Feature/user api (#1) (#2)
Browse files Browse the repository at this point in the history
* Init user api

* Add first method

* Add ClientTokenSource interface as an abstract layer of api token provider

* Add request interface

* Fix readme

* fix token url

* fix oauth

* fix interface

* fix tokensource
  • Loading branch information
joyqi authored Sep 6, 2022
1 parent 9e19e19 commit 3691860
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 186 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,20 @@ Features:
Initialize Feishu API client:

```go
client := conf.Client(ctx)
import "github.com/joyqi/go-feishu/oauth2"

client := conf.TenantTokenSource(ctx).Client()
```

Use the client to access the Feishu API. For example, to list all groups:

```go
import "github.com/joyqi/go-feishu/contact"
import (
"github.com/joyqi/go-feishu/oauth2"
"github.com/joyqi/go-feishu/api/contact"
)

client := conf.Client(ctx)
client := conf.TenantTokenSource(ctx).Client()
api := &contact.Group{Client: client}

api.SimpleList(&contact.GroupSimpleListParams{
Expand Down
10 changes: 7 additions & 3 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package api

import (
"errors"
"github.com/joyqi/go-feishu/oauth2"
)

type Api struct {
Client *oauth2.Client
Client
}

// Client defines the interface of api client
type Client interface {
Request(method string, uri string, body interface{}, data interface{}) error
}

// Response represents the response data of a standard Api request
Expand All @@ -17,7 +21,7 @@ type Response[T any] struct {
}

// MakeApi make a standard Api request and handle the response accordingly
func MakeApi[T any](c *oauth2.Client, method string, uri string, body interface{}) (*T, error) {
func MakeApi[T any](c Client, method string, uri string, body interface{}) (*T, error) {
resp := &Response[T]{}
err := c.Request(method, uri, body, resp)

Expand Down
12 changes: 12 additions & 0 deletions api/contact/department.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package contact

const (
DepartmentIdTypeDepartmentId = "department_id"
DepartmentIdTypeOpenDepartmentId = "open_department_id"
)

type DepartmentI18nName struct {
ZhCn string `json:"zh_cn"`
EnUs string `json:"en_us"`
JaJp string `json:"ja_jp"`
}
54 changes: 27 additions & 27 deletions api/contact/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"net/http"
)

var (
const (
GroupURL = "https://open.feishu.cn/open-apis/contact/v3/group/:group_id"
GroupCreateURL = "https://open.feishu.cn/open-apis/contact/v3/group"
GroupSimpleListURL = "https://open.feishu.cn/open-apis/contact/v3/group/simplelist"
Expand All @@ -24,8 +24,8 @@ const (
GroupTypeDynamic
)

// GroupCreateParams represents the params of Group.Create
type GroupCreateParams struct {
// GroupCreateBody represents the params of Group.Create
type GroupCreateBody struct {
GroupId string `json:"group_id"`
Name string `json:"name"`
Description string `json:"description"`
Expand All @@ -37,8 +37,8 @@ type GroupCreateData struct {
GroupId string `json:"group_id"`
}

// GroupPatchParams represents the params of Group.Patch
type GroupPatchParams struct {
// GroupPatchBody represents the params of Group.Patch
type GroupPatchBody struct {
Name string `json:"name"`
Description string `json:"description"`
}
Expand Down Expand Up @@ -85,11 +85,11 @@ type GroupSimpleListData struct {

// GroupMemberBelongParams represents the params of Group.MemberBelong
type GroupMemberBelongParams struct {
MemberId string `url:"member_id"`
MemberIdType UserIdType `url:"member_id_type" default:"open_id"`
GroupType GroupType `url:"group_type" default:"1"`
PageSize int `url:"page_size" default:"500"`
PageToken string `url:"page_token"`
MemberId string `url:"member_id"`
MemberIdType string `url:"member_id_type" default:"open_id"`
GroupType GroupType `url:"group_type" default:"1"`
PageSize int `url:"page_size" default:"500"`
PageToken string `url:"page_token"`
}

// GroupMemberBelongData represents the response data of Group.MemberBelong
Expand All @@ -101,46 +101,46 @@ type GroupMemberBelongData struct {

type Group api.Api

// Create creates a group by given GroupCreateParams.
// Create creates a group by given GroupCreateBody.
// See https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/group/create for more details.
func (g *Group) Create(params *GroupCreateParams) (*GroupCreateData, error) {
if err := defaults.Set(params); err != nil {
func (g *Group) Create(body *GroupCreateBody) (*GroupCreateData, error) {
if err := defaults.Set(body); err != nil {
return nil, err
}

return api.MakeApi[GroupCreateData](g.Client, http.MethodPost, GroupCreateURL, params)
return api.MakeApi[GroupCreateData](g.Client, http.MethodPost, GroupCreateURL, body)
}

// Delete deletes a group through group_id.
// See https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/group/delete for more details.
func (g *Group) Delete(groupId string) (*GroupDeleteData, error) {
u := httptool.MakeTemplateURL(GroupURL, map[string]string{"group_id": groupId})
return api.MakeApi[GroupDeleteData](g.Client, http.MethodDelete, u, nil)
url := httptool.MakeTemplateURL(GroupURL, map[string]string{"group_id": groupId})
return api.MakeApi[GroupDeleteData](g.Client, http.MethodDelete, url, nil)
}

// Get retrieves the group information through group_id.
// See https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/group/get for more details.
func (g *Group) Get(groupId string) (*GroupGetData, error) {
u := httptool.MakeTemplateURL(GroupURL, map[string]string{"group_id": groupId})
return api.MakeApi[GroupGetData](g.Client, http.MethodGet, u, nil)
url := httptool.MakeTemplateURL(GroupURL, map[string]string{"group_id": groupId})
return api.MakeApi[GroupGetData](g.Client, http.MethodGet, url, nil)
}

// Patch updates the specified group information by given GroupPatchParams.
// Patch updates the specified group information by given GroupPatchBody.
// See https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/group/patch for more details.
func (g *Group) Patch(groupId string, params *GroupPatchParams) (*GroupPatchData, error) {
u := httptool.MakeTemplateURL(GroupURL, map[string]string{"group_id": groupId})
return api.MakeApi[GroupPatchData](g.Client, http.MethodPatch, u, params)
func (g *Group) Patch(groupId string, body *GroupPatchBody) (*GroupPatchData, error) {
url := httptool.MakeTemplateURL(GroupURL, map[string]string{"group_id": groupId})
return api.MakeApi[GroupPatchData](g.Client, http.MethodPatch, url, body)
}

// SimpleList retrieves the group list through given GroupSimpleListParams.
// See https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/group/simplelist for more details.
func (g Group) SimpleList(params *GroupSimpleListParams) (*GroupSimpleListData, error) {
func (g *Group) SimpleList(params *GroupSimpleListParams) (*GroupSimpleListData, error) {
if err := defaults.Set(params); err != nil {
return nil, err
}

u := httptool.MakeStructureURL(GroupSimpleListURL, params)
return api.MakeApi[GroupSimpleListData](g.Client, http.MethodGet, u, params)
url := httptool.MakeStructureURL(GroupSimpleListURL, params)
return api.MakeApi[GroupSimpleListData](g.Client, http.MethodGet, url, params)
}

// MemberBelong returns the groups that the member belongs to.
Expand All @@ -150,6 +150,6 @@ func (g *Group) MemberBelong(params *GroupMemberBelongParams) (*GroupMemberBelon
return nil, err
}

u := httptool.MakeStructureURL(GroupMemberBelongURL, params)
return api.MakeApi[GroupMemberBelongData](g.Client, http.MethodGet, u, nil)
url := httptool.MakeStructureURL(GroupMemberBelongURL, params)
return api.MakeApi[GroupMemberBelongData](g.Client, http.MethodGet, url, nil)
}
24 changes: 10 additions & 14 deletions api/contact/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ var conf = &oauth2.Config{
RedirectURL: "https://example.com",
}

var client = conf.TenantTokenSource(context.Background()).Client()

func TestGroup_Create(t *testing.T) {
c := conf.Client(context.Background())
a := &Group{Client: c}
a := &Group{Client: client}

data, err := a.Create(&GroupCreateParams{
data, err := a.Create(&GroupCreateBody{
GroupId: "test001",
Name: "test group",
})
Expand All @@ -32,8 +33,7 @@ func TestGroup_Create(t *testing.T) {
}

func TestGroup_Get(t *testing.T) {
c := conf.Client(context.Background())
a := &Group{Client: c}
a := &Group{Client: client}

g, err := a.Get("test001")

Expand All @@ -45,10 +45,9 @@ func TestGroup_Get(t *testing.T) {
}

func TestGroup_Patch(t *testing.T) {
c := conf.Client(context.Background())
a := &Group{Client: c}
a := &Group{Client: client}

_, err := a.Patch("test001", &GroupPatchParams{
_, err := a.Patch("test001", &GroupPatchBody{
Name: "test group 2",
})

Expand All @@ -66,8 +65,7 @@ func TestGroup_Patch(t *testing.T) {
}

func TestGroup_SimpleList(t *testing.T) {
c := conf.Client(context.Background())
a := &Group{Client: c}
a := &Group{Client: client}

g, err := a.SimpleList(&GroupSimpleListParams{
PageSize: 10,
Expand All @@ -81,8 +79,7 @@ func TestGroup_SimpleList(t *testing.T) {
}

func TestGroup_Delete(t *testing.T) {
c := conf.Client(context.Background())
a := &Group{Client: c}
a := &Group{Client: client}

_, err := a.Delete("test001")

Expand All @@ -92,8 +89,7 @@ func TestGroup_Delete(t *testing.T) {
}

func TestGroup_MemberBelong(t *testing.T) {
c := conf.Client(context.Background())
a := &Group{Client: c}
a := &Group{Client: client}

_, err := a.MemberBelong(&GroupMemberBelongParams{
MemberId: "9a4386bc",
Expand Down
44 changes: 40 additions & 4 deletions api/contact/user.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,45 @@
package contact

type UserIdType string
import (
"github.com/creasty/defaults"
"github.com/joyqi/go-feishu/api"
"github.com/joyqi/go-feishu/httptool"
"net/http"
)

const (
UserIdTypeUserId UserIdType = "user_id"
UserIdTypeOpenId = "open_id"
UserIdTypeUnionId = "union_id"
UserCreateURL = "https://open.feishu.cn/open-apis/contact/v3/users"
)

const (
UserIdTypeUserId = "user_id"
UserIdTypeOpenId = "open_id"
UserIdTypeUnionId = "union_id"
)

type UserCreateParams struct {
UserIdType string `url:"user_id_type" default:"open_id"`
DepartmentIdType string `url:"department_id_type" default:"department_id"`
ClientToken string `url:"client_token"`
}

type UserCreateBody struct {
Name string `json:"name"`
I18nName DepartmentI18nName `json:"i18n_name"`
}

type UserCreateData struct {
}

type User api.Api

// Create creates a user by given UserCreateParams and UserCreateBody.
// See https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/create for more details.
func (u *User) Create(params *UserCreateParams, body *UserCreateBody) (*UserCreateData, error) {
if err := defaults.Set(&params); err != nil {
return nil, err
}

url := httptool.MakeStructureURL(UserCreateURL, params)
return api.MakeApi[UserCreateData](u.Client, http.MethodPost, url, body)
}
2 changes: 1 addition & 1 deletion httptool/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestRequest_Timeout(t *testing.T) {
}

if err, ok := err.(net.Error); ok && err.Timeout() {
t.SkipNow()
return
}

t.Fail()
Expand Down
20 changes: 6 additions & 14 deletions oauth2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,22 @@ import (
"net/http"
)

// Client returns http client with an authorized token.
func (c *Config) Client(ctx context.Context) *Client {
return &Client{
ctx: ctx,
conf: c,
}
}

// A Client represents a http client with an authorized token.
type Client struct {
ctx context.Context
conf *Config
type tokenClient struct {
ctx context.Context
ts TokenSource
}

// Request performs a http request to the given endpoint with the authorized token.
func (c *Client) Request(method string, uri string, body interface{}, data interface{}) error {
token, err := c.conf.TenantToken(c.ctx)
func (c *tokenClient) Request(method string, uri string, body interface{}, data interface{}) error {
token, err := c.ts.Token()
if err != nil {
return err
}

header := httptool.Header{
Key: "Authorization",
Value: "Bearer " + token,
Value: "Bearer " + token.AccessToken,
}

return httptool.Request(c.ctx, &httptool.RequestOptions{
Expand Down
Loading

0 comments on commit 3691860

Please sign in to comment.