Skip to content

Commit 300c246

Browse files
committed
chore: Merge branch 'main' of github.com:coder/coder into bq/return-only-active-users
2 parents 677cb6f + 816441e commit 300c246

File tree

8 files changed

+164
-33
lines changed

8 files changed

+164
-33
lines changed

coderd/database/databasefake/databasefake.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,29 @@ func (q *fakeQuerier) GetOrganizationMemberByUserID(_ context.Context, arg datab
720720
return database.OrganizationMember{}, sql.ErrNoRows
721721
}
722722

723+
func (q *fakeQuerier) GetOrganizationIDsByMemberIDs(_ context.Context, ids []uuid.UUID) ([]database.GetOrganizationIDsByMemberIDsRow, error) {
724+
q.mutex.RLock()
725+
defer q.mutex.RUnlock()
726+
727+
getOrganizationIDsByMemberIDRows := make([]database.GetOrganizationIDsByMemberIDsRow, 0, len(ids))
728+
for _, userID := range ids {
729+
userOrganizationIDs := make([]uuid.UUID, 0)
730+
for _, membership := range q.organizationMembers {
731+
if membership.UserID == userID {
732+
userOrganizationIDs = append(userOrganizationIDs, membership.OrganizationID)
733+
}
734+
}
735+
getOrganizationIDsByMemberIDRows = append(getOrganizationIDsByMemberIDRows, database.GetOrganizationIDsByMemberIDsRow{
736+
UserID: userID,
737+
OrganizationIDs: userOrganizationIDs,
738+
})
739+
}
740+
if len(getOrganizationIDsByMemberIDRows) == 0 {
741+
return nil, sql.ErrNoRows
742+
}
743+
return getOrganizationIDsByMemberIDRows, nil
744+
}
745+
723746
func (q *fakeQuerier) GetProvisionerDaemons(_ context.Context) ([]database.ProvisionerDaemon, error) {
724747
q.mutex.RLock()
725748
defer q.mutex.RUnlock()

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/organizationmembers.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,13 @@ INSERT INTO
2020
)
2121
VALUES
2222
($1, $2, $3, $4, $5) RETURNING *;
23+
24+
-- name: GetOrganizationIDsByMemberIDs :many
25+
SELECT
26+
user_id, array_agg(organization_id) :: uuid [ ] AS "organization_IDs"
27+
FROM
28+
organization_members
29+
WHERE
30+
user_id = ANY(@ids :: uuid [ ])
31+
GROUP BY
32+
user_id;

coderd/users.go

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,34 @@ func (api *api) users(rw http.ResponseWriter, r *http.Request) {
139139
Search: searchName,
140140
Status: statusFilter,
141141
})
142+
if err != nil {
143+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
144+
Message: err.Error(),
145+
})
146+
return
147+
}
142148

149+
userIDs := make([]uuid.UUID, 0, len(users))
150+
for _, user := range users {
151+
userIDs = append(userIDs, user.ID)
152+
}
153+
organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(r.Context(), userIDs)
154+
if xerrors.Is(err, sql.ErrNoRows) {
155+
err = nil
156+
}
143157
if err != nil {
144158
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
145159
Message: err.Error(),
146160
})
147161
return
148162
}
163+
organizationIDsByUserID := map[uuid.UUID][]uuid.UUID{}
164+
for _, organizationIDsByMemberIDsRow := range organizationIDsByMemberIDsRows {
165+
organizationIDsByUserID[organizationIDsByMemberIDsRow.UserID] = organizationIDsByMemberIDsRow.OrganizationIDs
166+
}
149167

150168
render.Status(r, http.StatusOK)
151-
render.JSON(rw, r, convertUsers(users))
169+
render.JSON(rw, r, convertUsers(users, organizationIDsByUserID))
152170
}
153171

154172
// Creates a new user.
@@ -215,15 +233,23 @@ func (api *api) postUser(rw http.ResponseWriter, r *http.Request) {
215233
return
216234
}
217235

218-
httpapi.Write(rw, http.StatusCreated, convertUser(user))
236+
httpapi.Write(rw, http.StatusCreated, convertUser(user, []uuid.UUID{createUser.OrganizationID}))
219237
}
220238

221239
// Returns the parameterized user requested. All validation
222240
// is completed in the middleware for this route.
223-
func (*api) userByName(rw http.ResponseWriter, r *http.Request) {
241+
func (api *api) userByName(rw http.ResponseWriter, r *http.Request) {
224242
user := httpmw.UserParam(r)
243+
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
244+
245+
if err != nil {
246+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
247+
Message: fmt.Sprintf("get organization IDs: %s", err.Error()),
248+
})
249+
return
250+
}
225251

226-
httpapi.Write(rw, http.StatusOK, convertUser(user))
252+
httpapi.Write(rw, http.StatusOK, convertUser(user, organizationIDs))
227253
}
228254

229255
func (api *api) putUserProfile(rw http.ResponseWriter, r *http.Request) {
@@ -280,7 +306,15 @@ func (api *api) putUserProfile(rw http.ResponseWriter, r *http.Request) {
280306
return
281307
}
282308

283-
httpapi.Write(rw, http.StatusOK, convertUser(updatedUserProfile))
309+
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
310+
if err != nil {
311+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
312+
Message: fmt.Sprintf("get organization IDs: %s", err.Error()),
313+
})
314+
return
315+
}
316+
317+
httpapi.Write(rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs))
284318
}
285319

286320
func (api *api) putUserSuspend(rw http.ResponseWriter, r *http.Request) {
@@ -299,7 +333,15 @@ func (api *api) putUserSuspend(rw http.ResponseWriter, r *http.Request) {
299333
return
300334
}
301335

302-
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser))
336+
organizations, err := userOrganizationIDs(r.Context(), api, user)
337+
if err != nil {
338+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
339+
Message: fmt.Sprintf("get organization IDs: %s", err.Error()),
340+
})
341+
return
342+
}
343+
344+
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations))
303345
}
304346

305347
// Returns organizations the parameterized user has access to.
@@ -628,20 +670,34 @@ func (api *api) createUser(ctx context.Context, req codersdk.CreateUserRequest)
628670
})
629671
}
630672

631-
func convertUser(user database.User) codersdk.User {
673+
func convertUser(user database.User, organizationIDs []uuid.UUID) codersdk.User {
632674
return codersdk.User{
633-
ID: user.ID,
634-
Email: user.Email,
635-
CreatedAt: user.CreatedAt,
636-
Username: user.Username,
637-
Status: codersdk.UserStatus(user.Status),
675+
ID: user.ID,
676+
Email: user.Email,
677+
CreatedAt: user.CreatedAt,
678+
Username: user.Username,
679+
Status: codersdk.UserStatus(user.Status),
680+
OrganizationIDs: organizationIDs,
638681
}
639682
}
640683

641-
func convertUsers(users []database.User) []codersdk.User {
684+
func convertUsers(users []database.User, organizationIDsByUserID map[uuid.UUID][]uuid.UUID) []codersdk.User {
642685
converted := make([]codersdk.User, 0, len(users))
643686
for _, u := range users {
644-
converted = append(converted, convertUser(u))
687+
userOrganizationIDs := organizationIDsByUserID[u.ID]
688+
converted = append(converted, convertUser(u, userOrganizationIDs))
645689
}
646690
return converted
647691
}
692+
693+
func userOrganizationIDs(ctx context.Context, api *api, user database.User) ([]uuid.UUID, error) {
694+
organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(ctx, []uuid.UUID{user.ID})
695+
if errors.Is(err, sql.ErrNoRows) || len(organizationIDsByMemberIDsRows) == 0 {
696+
return []uuid.UUID{}, nil
697+
}
698+
if err != nil {
699+
return []uuid.UUID{}, err
700+
}
701+
member := organizationIDsByMemberIDsRows[0]
702+
return member.OrganizationIDs, nil
703+
}

coderd/users_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,11 @@ func TestPutUserSuspend(t *testing.T) {
321321
func TestUserByName(t *testing.T) {
322322
t.Parallel()
323323
client := coderdtest.New(t, nil)
324-
_ = coderdtest.CreateFirstUser(t, client)
325-
_, err := client.User(context.Background(), codersdk.Me)
324+
firstUser := coderdtest.CreateFirstUser(t, client)
325+
user, err := client.User(context.Background(), codersdk.Me)
326+
326327
require.NoError(t, err)
328+
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
327329
}
328330

329331
func TestGetUsers(t *testing.T) {
@@ -342,6 +344,7 @@ func TestGetUsers(t *testing.T) {
342344
users, err := client.Users(context.Background(), codersdk.UsersRequest{})
343345
require.NoError(t, err)
344346
require.Len(t, users, 2)
347+
require.Len(t, users[0].OrganizationIDs, 1)
345348
})
346349
t.Run("ActiveUsers", func(t *testing.T) {
347350
t.Parallel()
@@ -477,14 +480,12 @@ func TestPaginatedUsers(t *testing.T) {
477480
coderdtest.CreateFirstUser(t, client)
478481
me, err := client.User(context.Background(), codersdk.Me)
479482
require.NoError(t, err)
483+
orgID := me.OrganizationIDs[0]
480484

481485
allUsers := make([]codersdk.User, 0)
482486
allUsers = append(allUsers, me)
483487
specialUsers := make([]codersdk.User, 0)
484488

485-
org, err := client.CreateOrganization(ctx, me.ID, codersdk.CreateOrganizationRequest{
486-
Name: "default",
487-
})
488489
require.NoError(t, err)
489490

490491
// When 100 users exist
@@ -507,7 +508,7 @@ func TestPaginatedUsers(t *testing.T) {
507508
Email: email,
508509
Username: username,
509510
Password: "password",
510-
OrganizationID: org.ID,
511+
OrganizationID: orgID,
511512
})
512513
require.NoError(t, err)
513514
allUsers = append(allUsers, newUser)

codersdk/users.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ type UsersRequest struct {
3939

4040
// User represents a user in Coder.
4141
type User struct {
42-
ID uuid.UUID `json:"id" validate:"required"`
43-
Email string `json:"email" validate:"required"`
44-
CreatedAt time.Time `json:"created_at" validate:"required"`
45-
Username string `json:"username" validate:"required"`
46-
Status UserStatus `json:"status"`
42+
ID uuid.UUID `json:"id" validate:"required"`
43+
Email string `json:"email" validate:"required"`
44+
CreatedAt time.Time `json:"created_at" validate:"required"`
45+
Username string `json:"username" validate:"required"`
46+
Status UserStatus `json:"status"`
47+
OrganizationIDs []uuid.UUID `json:"organization_ids"`
4748
}
4849

4950
type CreateFirstUserRequest struct {

site/src/api/typesGenerated.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,49 +91,49 @@ export interface User {
9191
readonly status: UserStatus
9292
}
9393

94-
// From codersdk/users.go:49:6.
94+
// From codersdk/users.go:50:6.
9595
export interface CreateFirstUserRequest {
9696
readonly email: string
9797
readonly username: string
9898
readonly password: string
9999
readonly organization: string
100100
}
101101

102-
// From codersdk/users.go:62:6.
102+
// From codersdk/users.go:63:6.
103103
export interface CreateUserRequest {
104104
readonly email: string
105105
readonly username: string
106106
readonly password: string
107107
}
108108

109-
// From codersdk/users.go:69:6.
109+
// From codersdk/users.go:70:6.
110110
export interface UpdateUserProfileRequest {
111111
readonly email: string
112112
readonly username: string
113113
}
114114

115-
// From codersdk/users.go:75:6.
115+
// From codersdk/users.go:76:6.
116116
export interface LoginWithPasswordRequest {
117117
readonly email: string
118118
readonly password: string
119119
}
120120

121-
// From codersdk/users.go:81:6.
121+
// From codersdk/users.go:82:6.
122122
export interface LoginWithPasswordResponse {
123123
readonly session_token: string
124124
}
125125

126-
// From codersdk/users.go:86:6.
126+
// From codersdk/users.go:87:6.
127127
export interface GenerateAPIKeyResponse {
128128
readonly key: string
129129
}
130130

131-
// From codersdk/users.go:90:6.
131+
// From codersdk/users.go:91:6.
132132
export interface CreateOrganizationRequest {
133133
readonly name: string
134134
}
135135

136-
// From codersdk/users.go:95:6.
136+
// From codersdk/users.go:96:6.
137137
export interface AuthMethods {
138138
readonly password: boolean
139139
readonly github: boolean

0 commit comments

Comments
 (0)