Skip to content

Commit

Permalink
feat: Organization Memberships API (#231)
Browse files Browse the repository at this point in the history
Added support for the Organization Memberships API. Available operations
are Create, Update, Delete and List.
  • Loading branch information
gkats authored Feb 8, 2024
1 parent ff3e770 commit 06fddc8
Show file tree
Hide file tree
Showing 8 changed files with 436 additions and 22 deletions.
2 changes: 1 addition & 1 deletion clerk.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ func setRequestQuery(req *http.Request, params Params) {
paramsQuery := params.ToQuery()
for k, values := range paramsQuery {
for _, v := range values {
q.Set(k, v)
q.Add(k, v)
}
}
req.URL.RawQuery = q.Encode()
Expand Down
25 changes: 9 additions & 16 deletions clerk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,15 @@ type testResourceList struct {
type testResourceListParams struct {
APIParams
ListParams
Name string
Overriden string
Name string
Appended string
}

// We need to implement the Params interface.
func (params testResourceListParams) ToQuery() url.Values {
q := url.Values{}
q := params.ListParams.ToQuery()
q.Set("name", params.Name)
q.Set("overriden", params.Overriden)
listQ := params.ListParams.ToQuery()
for k, values := range listQ {
for _, v := range values {
q.Add(k, v)
}
}
q.Set("appended", params.Appended)
return q
}

Expand Down Expand Up @@ -234,7 +228,6 @@ func TestBackendCall_SuccessfulResponse_PostRequest(t *testing.T) {
func TestBackendCall_SuccessfulResponse_GetRequest(t *testing.T) {
ctx := context.Background()
name := "the-name"
overriden := "true"
limit := 1
rawJSON := `{"data": [{"id":"res_123","object":"resource"}], "total_count": 1}`

Expand All @@ -249,8 +242,8 @@ func TestBackendCall_SuccessfulResponse_GetRequest(t *testing.T) {
assert.False(t, ok)
// Existing query parameters are preserved
assert.Equal(t, "still-here", q.Get("existing"))
// Existing query parameters can be overriden
assert.Equal(t, overriden, q.Get("overriden"))
// Existing query parameters will be appended, not overriden
assert.Equal(t, []string{"false", "true"}, q["appended"])

_, err := w.Write([]byte(rawJSON))
require.NoError(t, err)
Expand All @@ -266,13 +259,13 @@ func TestBackendCall_SuccessfulResponse_GetRequest(t *testing.T) {
// Simulate usage for an API operation on a testResourceList.
// We need to initialize a request and use the Backend to send it.
resource := &testResourceList{}
req := NewAPIRequest(http.MethodGet, "/resources?existing=still-here&overriden=false")
req := NewAPIRequest(http.MethodGet, "/resources?existing=still-here&appended=false")
req.SetParams(&testResourceListParams{
ListParams: ListParams{
Limit: Int64(int64(limit)),
},
Name: name,
Overriden: overriden,
Name: name,
Appended: "true",
})
err := GetBackend().Call(ctx, req, resource)
require.NoError(t, err)
Expand Down
9 changes: 4 additions & 5 deletions organization/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,10 @@ func (c *Client) DeleteLogo(ctx context.Context, id string) (*clerk.Organization
type ListParams struct {
clerk.APIParams
clerk.ListParams
IncludeMembersCount *bool `json:"include_members_count,omitempty"`
OrderBy *string `json:"order_by,omitempty"`
Query *string `json:"query,omitempty"`
// TODO do we need a pointer here? Probably not.
UserIDs []string `json:"user_id,omitempty"`
IncludeMembersCount *bool `json:"include_members_count,omitempty"`
OrderBy *string `json:"order_by,omitempty"`
Query *string `json:"query,omitempty"`
UserIDs []string `json:"user_id,omitempty"`
}

// ToQuery returns query string values from the params.
Expand Down
2 changes: 2 additions & 0 deletions organization/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,15 @@ func TestOrganizationClientList(t *testing.T) {
"offset": []string{"2"},
"order_by": []string{"-created_at"},
"query": []string{"Acme"},
"user_id": []string{"user_123", "user_456"},
},
},
}
client := NewClient(config)
params := &ListParams{
OrderBy: clerk.String("-created_at"),
Query: clerk.String("Acme"),
UserIDs: []string{"user_123", "user_456"},
}
params.Limit = clerk.Int64(1)
params.Offset = clerk.Int64(2)
Expand Down
32 changes: 32 additions & 0 deletions organization_membership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package clerk

import "encoding/json"

type OrganizationMembership struct {
APIResource
Object string `json:"object"`
ID string `json:"id"`
Organization *Organization `json:"organization"`
Permissions []string `json:"permissions"`
PublicMetadata json.RawMessage `json:"public_metadata"`
PrivateMetadata json.RawMessage `json:"private_metadata"`
Role string `json:"role"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
PublicUserData *OrganizationMembershipPublicUserData `json:"public_user_data,omitempty"`
}

type OrganizationMembershipList struct {
APIResource
OrganizationMemberships []*OrganizationMembership `json:"data"`
TotalCount int64 `json:"total_count"`
}

type OrganizationMembershipPublicUserData struct {
UserID string `json:"user_id"`
FirstName *string `json:"first_name"`
LastName *string `json:"last_name"`
ImageURL *string `json:"image_url"`
HasImage bool `json:"has_image"`
Identifier string `json:"identifier"`
}
36 changes: 36 additions & 0 deletions organizationmembership/api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

145 changes: 145 additions & 0 deletions organizationmembership/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Package organizationmembership provides the Organization Memberships API.
package organizationmembership

import (
"context"
"net/http"
"net/url"

"github.com/clerk/clerk-sdk-go/v2"
)

//go:generate go run ../cmd/gen/main.go

const path = "/organizations"

// Client is used to invoke the Organization Memberships API.
type Client struct {
Backend clerk.Backend
}

type ClientConfig struct {
clerk.BackendConfig
}

func NewClient(config *ClientConfig) *Client {
return &Client{
Backend: clerk.NewBackend(&config.BackendConfig),
}
}

type CreateParams struct {
clerk.APIParams
UserID *string `json:"user_id,omitempty"`
Role *string `json:"role,omitempty"`
OrganizationID string `json:"-"`
}

// Create adds a new member to the organization.
func (c *Client) Create(ctx context.Context, params *CreateParams) (*clerk.OrganizationMembership, error) {
path, err := clerk.JoinPath(path, params.OrganizationID, "/memberships")
if err != nil {
return nil, err
}
req := clerk.NewAPIRequest(http.MethodPost, path)
req.SetParams(params)
membership := &clerk.OrganizationMembership{}
err = c.Backend.Call(ctx, req, membership)
return membership, err
}

type UpdateParams struct {
clerk.APIParams
Role *string `json:"role,omitempty"`
OrganizationID string `json:"-"`
UserID string `json:"-"`
}

// Update updates an organization membership.
func (c *Client) Update(ctx context.Context, params *UpdateParams) (*clerk.OrganizationMembership, error) {
path, err := clerk.JoinPath(path, params.OrganizationID, "/memberships", params.UserID)
if err != nil {
return nil, err
}
req := clerk.NewAPIRequest(http.MethodPatch, path)
req.SetParams(params)
membership := &clerk.OrganizationMembership{}
err = c.Backend.Call(ctx, req, membership)
return membership, err
}

type DeleteParams struct {
clerk.APIParams
OrganizationID string `json:"-"`
UserID string `json:"-"`
}

// Delete removes a member from an organization.
func (c *Client) Delete(ctx context.Context, params *DeleteParams) (*clerk.OrganizationMembership, error) {
path, err := clerk.JoinPath(path, params.OrganizationID, "/memberships", params.UserID)
if err != nil {
return nil, err
}
req := clerk.NewAPIRequest(http.MethodDelete, path)
membership := &clerk.OrganizationMembership{}
err = c.Backend.Call(ctx, req, membership)
return membership, err
}

type ListParams struct {
clerk.APIParams
clerk.ListParams
OrderBy *string `json:"order_by,omitempty"`
Query *string `json:"query,omitempty"`
Roles []string `json:"role,omitempty"`
UserIDs []string `json:"user_id,omitempty"`
EmailAddresses []string `json:"email_address,omitempty"`
PhoneNumbers []string `json:"phone_number,omitempty"`
Usernames []string `json:"username,omitempty"`
Web3Wallets []string `json:"web3_wallet,omitempty"`
OrganizationID string `json:"-"`
}

// ToQuery returns the parameters as url.Values so they can be used
// in a URL query string.
func (params *ListParams) ToQuery() url.Values {
q := params.ListParams.ToQuery()
if params.OrderBy != nil {
q.Set("order_by", *params.OrderBy)
}
if params.Query != nil {
q.Set("query", *params.Query)
}
if params.Roles != nil {
q["role"] = params.Roles
}
if params.UserIDs != nil {
q["user_id"] = params.UserIDs
}
if params.EmailAddresses != nil {
q["email_address"] = params.EmailAddresses
}
if params.PhoneNumbers != nil {
q["phone_number"] = params.PhoneNumbers
}
if params.Usernames != nil {
q["username"] = params.Usernames
}
if params.Web3Wallets != nil {
q["web3_wallet"] = params.Web3Wallets
}
return q
}

// List returns a list of organization memberships.
func (c *Client) List(ctx context.Context, params *ListParams) (*clerk.OrganizationMembershipList, error) {
path, err := clerk.JoinPath(path, params.OrganizationID, "/memberships")
if err != nil {
return nil, err
}
req := clerk.NewAPIRequest(http.MethodGet, path)
req.SetParams(params)
list := &clerk.OrganizationMembershipList{}
err = c.Backend.Call(ctx, req, list)
return list, err
}
Loading

0 comments on commit 06fddc8

Please sign in to comment.