feat: add endpoint to fetch singular org member (#21732)

This commit is contained in:
Steven Masley
2026-02-03 12:48:25 -06:00
committed by GitHub
parent 1e2d2b92af
commit 6759b51cd6
7 changed files with 215 additions and 4 deletions
+39
View File
@@ -3482,6 +3482,45 @@ const docTemplate = `{
}
},
"/organizations/{organization}/members/{user}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Members"
],
"summary": "Get organization member",
"operationId": "get-organization-member",
"parameters": [
{
"type": "string",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"type": "string",
"description": "User ID, name, or me",
"name": "user",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.OrganizationMemberWithUserData"
}
}
}
},
"post": {
"security": [
{
+35
View File
@@ -3059,6 +3059,41 @@
}
},
"/organizations/{organization}/members/{user}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Members"],
"summary": "Get organization member",
"operationId": "get-organization-member",
"parameters": [
{
"type": "string",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"type": "string",
"description": "User ID, name, or me",
"name": "user",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.OrganizationMemberWithUserData"
}
}
}
},
"post": {
"security": [
{
+1
View File
@@ -1228,6 +1228,7 @@ func New(options *Options) *API {
r.Use(
httpmw.ExtractOrganizationMemberParam(options.Database),
)
r.Get("/", api.organizationMember)
r.Delete("/", api.deleteOrganizationMember)
r.Put("/roles", api.putMemberRoles)
r.Post("/workspaces", api.postWorkspacesByOrganization)
+48
View File
@@ -145,6 +145,54 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
rw.WriteHeader(http.StatusNoContent)
}
// @Summary Get organization member
// @ID get-organization-member
// @Security CoderSessionToken
// @Tags Members
// @Param organization path string true "Organization ID"
// @Param user path string true "User ID, name, or me"
// @Success 200 {object} codersdk.OrganizationMemberWithUserData
// @Produce json
// @Router /organizations/{organization}/members/{user} [get]
func (api *API) organizationMember(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
organization = httpmw.OrganizationParam(r)
member = httpmw.OrganizationMemberParam(r)
)
// This is unfortunate to fetch like this, but we need the user table data.
// The listing route uses this data format, so it is just easier to reuse the
// list query.
rows, err := api.Database.OrganizationMembers(ctx, database.OrganizationMembersParams{
OrganizationID: organization.ID,
UserID: member.UserID,
IncludeSystem: false,
GithubUserID: 0,
})
if httpapi.Is404Error(err) || len(rows) == 0 {
httpapi.ResourceNotFound(rw)
return
}
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
resp, err := convertOrganizationMembersWithUserData(ctx, api.Database, rows)
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
if len(resp) != 1 {
httpapi.InternalServerError(rw, xerrors.Errorf("unexpected organization members, something went wrong"))
return
}
httpapi.Write(ctx, rw, http.StatusOK, resp[0])
}
// @Deprecated use /organizations/{organization}/paginated-members [get]
// @Summary List organization members
// @ID list-organization-members
+20 -4
View File
@@ -18,17 +18,33 @@ import (
func TestAddMember(t *testing.T) {
t.Parallel()
owner := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, owner)
_, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID)
t.Run("AlreadyMember", func(t *testing.T) {
t.Parallel()
owner := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, owner)
_, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID)
ctx := testutil.Context(t, testutil.WaitMedium)
// Add user to org, even though they already exist
// nolint:gocritic // must be an owner to see the user
_, err := owner.PostOrganizationMember(ctx, first.OrganizationID, user.Username)
require.ErrorContains(t, err, "already an organization member")
org, err := owner.Organization(ctx, first.OrganizationID)
require.NoError(t, err)
member, err := owner.OrganizationMember(ctx, org.Name, user.Username)
require.NoError(t, err)
require.Equal(t, member.UserID, user.ID)
})
t.Run("Me", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
member, err := owner.OrganizationMember(ctx, first.OrganizationID.String(), codersdk.Me)
require.NoError(t, err)
require.Equal(t, member.UserID, first.UserID)
})
}