mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add endpoint to fetch singular org member (#21732)
This commit is contained in:
Generated
+39
@@ -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": [
|
||||
{
|
||||
|
||||
Generated
+35
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -644,6 +644,19 @@ func OrganizationMembersQueryOptionGithubUserID(githubUserID int64) Organization
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) OrganizationMember(ctx context.Context, organizationIdent, userIdent string) (OrganizationMemberWithUserData, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/members/%s", organizationIdent, userIdent), nil)
|
||||
if err != nil {
|
||||
return OrganizationMemberWithUserData{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return OrganizationMemberWithUserData{}, ReadBodyAsError(res)
|
||||
}
|
||||
var member OrganizationMemberWithUserData
|
||||
return member, json.NewDecoder(res.Body).Decode(&member)
|
||||
}
|
||||
|
||||
// OrganizationMembers lists all members in an organization
|
||||
func (c *Client) OrganizationMembers(ctx context.Context, organizationID uuid.UUID, opts ...OrganizationMembersQueryOption) ([]OrganizationMemberWithUserData, error) {
|
||||
var query OrganizationMembersQuery
|
||||
|
||||
Generated
+59
@@ -540,6 +540,65 @@ Status Code **200**
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get organization member
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members/{user} \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /organizations/{organization}/members/{user}`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|----------------|------|--------|----------|----------------------|
|
||||
| `organization` | path | string | true | Organization ID |
|
||||
| `user` | path | string | true | User ID, name, or me |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "string",
|
||||
"global_roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|----------------------------------------------------------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.OrganizationMemberWithUserData](schemas.md#codersdkorganizationmemberwithuserdata) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Add organization member
|
||||
|
||||
### Code samples
|
||||
|
||||
Reference in New Issue
Block a user