Skip to content

Commit 6c564fe

Browse files
committed
Admin Functionality for Invites, Usage, User and Partial Project Management
1 parent 9e37b27 commit 6c564fe

12 files changed

+1661
-0
lines changed

admin_invite.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package openai
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
)
9+
10+
const (
11+
adminInvitesSuffix = "/organization/invites"
12+
)
13+
14+
var (
15+
// adminInviteRoles is a list of valid roles for an Admin Invite.
16+
adminInviteRoles = []string{"owner", "member"}
17+
)
18+
19+
// AdminInvite represents an Admin Invite.
20+
type AdminInvite struct {
21+
Object string `json:"object"`
22+
ID string `json:"id"`
23+
Email string `json:"email"`
24+
Role string `json:"role"`
25+
Status string `json:"status"`
26+
InvitedAt int64 `json:"invited_at"`
27+
ExpiresAt int64 `json:"expires_at"`
28+
AcceptedAt int64 `json:"accepted_at"`
29+
Projects []AdminInviteProject `json:"projects"`
30+
31+
httpHeader
32+
}
33+
34+
// AdminInviteProject represents a project associated with an Admin Invite.
35+
type AdminInviteProject struct {
36+
ID string `json:"id"`
37+
Role string `json:"role"`
38+
}
39+
40+
// AdminInviteList represents a list of Admin Invites.
41+
type AdminInviteList struct {
42+
Object string `json:"object"`
43+
AdminInvites []AdminInvite `json:"data"`
44+
FirstID string `json:"first_id"`
45+
LastID string `json:"last_id"`
46+
HasMore bool `json:"has_more"`
47+
48+
httpHeader
49+
}
50+
51+
// AdminInviteDeleteResponse represents the response from deleting an Admin Invite.
52+
type AdminInviteDeleteResponse struct {
53+
ID string `json:"id"`
54+
Object string `json:"object"`
55+
Deleted bool `json:"deleted"`
56+
57+
httpHeader
58+
}
59+
60+
// ListAdminInvites lists Admin Invites associated with the organization.
61+
func (c *Client) ListAdminInvites(
62+
ctx context.Context,
63+
limit *int,
64+
after *string,
65+
) (response AdminInviteList, err error) {
66+
urlValues := url.Values{}
67+
if limit != nil {
68+
urlValues.Add("limit", fmt.Sprintf("%d", *limit))
69+
}
70+
if after != nil {
71+
urlValues.Add("after", *after)
72+
}
73+
74+
encodedValues := ""
75+
if len(urlValues) > 0 {
76+
encodedValues = "?" + urlValues.Encode()
77+
}
78+
79+
urlSuffix := adminInvitesSuffix + encodedValues
80+
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))
81+
if err != nil {
82+
return
83+
}
84+
85+
err = c.sendRequest(req, &response)
86+
return
87+
}
88+
89+
// CreateAdminInvite creates a new Admin Invite.
90+
func (c *Client) CreateAdminInvite(
91+
ctx context.Context,
92+
email string,
93+
role string,
94+
projects *[]AdminInviteProject,
95+
) (response AdminInvite, err error) {
96+
// Validate the role.
97+
if !containsSubstr(adminInviteRoles, role) {
98+
return response, fmt.Errorf("invalid admin role: %s", role)
99+
}
100+
101+
// Create the request object.
102+
request := struct {
103+
Email string `json:"email"`
104+
Role string `json:"role"`
105+
Projects *[]AdminInviteProject `json:"projects,omitempty"`
106+
}{
107+
Email: email,
108+
Role: role,
109+
Projects: projects,
110+
}
111+
112+
urlSuffix := adminInvitesSuffix
113+
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix), withBody(request))
114+
if err != nil {
115+
return
116+
}
117+
118+
err = c.sendRequest(req, &response)
119+
120+
return
121+
}
122+
123+
// RetrieveAdminInvite retrieves an Admin Invite.
124+
func (c *Client) RetrieveAdminInvite(
125+
ctx context.Context,
126+
inviteID string,
127+
) (response AdminInvite, err error) {
128+
urlSuffix := fmt.Sprintf("%s/%s", adminInvitesSuffix, inviteID)
129+
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))
130+
if err != nil {
131+
return
132+
}
133+
134+
err = c.sendRequest(req, &response)
135+
return
136+
}
137+
138+
// DeleteAdminInvite deletes an Admin Invite.
139+
func (c *Client) DeleteAdminInvite(
140+
ctx context.Context,
141+
inviteID string,
142+
) (response AdminInviteDeleteResponse, err error) {
143+
urlSuffix := fmt.Sprintf("%s/%s", adminInvitesSuffix, inviteID)
144+
req, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix))
145+
if err != nil {
146+
return
147+
}
148+
149+
err = c.sendRequest(req, &response)
150+
return
151+
}

admin_invite_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package openai_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"testing"
9+
10+
"github.com/sashabaranov/go-openai"
11+
"github.com/sashabaranov/go-openai/internal/test/checks"
12+
)
13+
14+
func TestAdminInvite(t *testing.T) {
15+
adminInviteObject := "organization.invite"
16+
adminInviteID := "invite-abc-123"
17+
adminInviteEmail := "invite@openai.com"
18+
adminInviteRole := "owner"
19+
adminInviteStatus := "pending"
20+
21+
adminInviteInvitedAt := int64(1711471533)
22+
adminInviteExpiresAt := int64(1711471533)
23+
adminInviteAcceptedAt := int64(1711471533)
24+
adminInviteProjects := []openai.AdminInviteProject{
25+
{
26+
ID: "project-id",
27+
Role: "owner",
28+
},
29+
}
30+
31+
client, server, teardown := setupOpenAITestServer()
32+
defer teardown()
33+
34+
server.RegisterHandler(
35+
"/v1/organization/invites",
36+
func(w http.ResponseWriter, r *http.Request) {
37+
switch r.Method {
38+
case http.MethodGet:
39+
resBytes, _ := json.Marshal(openai.AdminInviteList{
40+
Object: "list",
41+
AdminInvites: []openai.AdminInvite{
42+
{
43+
Object: adminInviteObject,
44+
ID: adminInviteID,
45+
Email: adminInviteEmail,
46+
Role: adminInviteRole,
47+
Status: adminInviteStatus,
48+
InvitedAt: adminInviteInvitedAt,
49+
ExpiresAt: adminInviteExpiresAt,
50+
AcceptedAt: adminInviteAcceptedAt,
51+
Projects: adminInviteProjects,
52+
},
53+
},
54+
FirstID: "first_id",
55+
LastID: "last_id",
56+
HasMore: false,
57+
})
58+
fmt.Fprintln(w, string(resBytes))
59+
60+
case http.MethodPost:
61+
resBytes, _ := json.Marshal(openai.AdminInvite{
62+
Object: adminInviteObject,
63+
ID: adminInviteID,
64+
Email: adminInviteEmail,
65+
Role: adminInviteRole,
66+
Status: adminInviteStatus,
67+
InvitedAt: adminInviteInvitedAt,
68+
ExpiresAt: adminInviteExpiresAt,
69+
AcceptedAt: adminInviteAcceptedAt,
70+
Projects: adminInviteProjects,
71+
})
72+
fmt.Fprintln(w, string(resBytes))
73+
}
74+
},
75+
)
76+
77+
server.RegisterHandler(
78+
"/v1/organization/invites/"+adminInviteID,
79+
func(w http.ResponseWriter, r *http.Request) {
80+
switch r.Method {
81+
case http.MethodDelete:
82+
resBytes, _ := json.Marshal(openai.AdminInviteDeleteResponse{
83+
ID: adminInviteID,
84+
Object: adminInviteObject,
85+
Deleted: true,
86+
})
87+
fmt.Fprintln(w, string(resBytes))
88+
89+
case http.MethodGet:
90+
resBytes, _ := json.Marshal(openai.AdminInvite{
91+
Object: adminInviteObject,
92+
ID: adminInviteID,
93+
Email: adminInviteEmail,
94+
Role: adminInviteRole,
95+
Status: adminInviteStatus,
96+
InvitedAt: adminInviteInvitedAt,
97+
ExpiresAt: adminInviteExpiresAt,
98+
AcceptedAt: adminInviteAcceptedAt,
99+
Projects: adminInviteProjects,
100+
})
101+
fmt.Fprintln(w, string(resBytes))
102+
}
103+
},
104+
)
105+
106+
ctx := context.Background()
107+
108+
t.Run("ListAdminInvites", func(t *testing.T) {
109+
adminInvites, err := client.ListAdminInvites(ctx, nil, nil)
110+
checks.NoError(t, err, "ListAdminInvites error")
111+
112+
if len(adminInvites.AdminInvites) != 1 {
113+
t.Fatalf("expected 1 admin invite, got %d", len(adminInvites.AdminInvites))
114+
}
115+
116+
if adminInvites.AdminInvites[0].ID != adminInviteID {
117+
t.Errorf("expected admin invite ID %s, got %s", adminInviteID, adminInvites.AdminInvites[0].ID)
118+
}
119+
})
120+
121+
t.Run("CreateAdminInvite", func(t *testing.T) {
122+
adminInvite, err := client.CreateAdminInvite(ctx, adminInviteEmail, adminInviteRole, &adminInviteProjects)
123+
checks.NoError(t, err, "CreateAdminInvite error")
124+
125+
if adminInvite.ID != adminInviteID {
126+
t.Errorf("expected admin invite ID %s, got %s", adminInviteID, adminInvite.ID)
127+
}
128+
})
129+
130+
t.Run("RetrieveAdminInvite", func(t *testing.T) {
131+
adminInvite, err := client.RetrieveAdminInvite(ctx, adminInviteID)
132+
checks.NoError(t, err, "RetrieveAdminInvite error")
133+
134+
if adminInvite.ID != adminInviteID {
135+
t.Errorf("expected admin invite ID %s, got %s", adminInviteID, adminInvite.ID)
136+
}
137+
})
138+
139+
t.Run("DeleteAdminInvite", func(t *testing.T) {
140+
adminInviteDeleteResponse, err := client.DeleteAdminInvite(ctx, adminInviteID)
141+
checks.NoError(t, err, "DeleteAdminInvite error")
142+
143+
if !adminInviteDeleteResponse.Deleted {
144+
t.Errorf("expected admin invite to be deleted, got not deleted")
145+
}
146+
})
147+
}

0 commit comments

Comments
 (0)