mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add members settings page for organizations (#13817)
This commit is contained in:
Generated
+16
-10
@@ -2340,7 +2340,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.OrganizationMemberWithName"
|
||||
"$ref": "#/definitions/codersdk.OrganizationMemberWithUserData"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2467,9 +2467,6 @@ const docTemplate = `{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Members"
|
||||
],
|
||||
@@ -2492,11 +2489,8 @@ const docTemplate = `{
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.OrganizationMember"
|
||||
}
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10500,13 +10494,25 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.OrganizationMemberWithName": {
|
||||
"codersdk.OrganizationMemberWithUserData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"global_roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.SlimRole"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
|
||||
Generated
+16
-8
@@ -2044,7 +2044,7 @@
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.OrganizationMemberWithName"
|
||||
"$ref": "#/definitions/codersdk.OrganizationMemberWithUserData"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2159,7 +2159,6 @@
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Members"],
|
||||
"summary": "Remove organization member",
|
||||
"operationId": "remove-organization-member",
|
||||
@@ -2180,11 +2179,8 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.OrganizationMember"
|
||||
}
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9446,13 +9442,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.OrganizationMemberWithName": {
|
||||
"codersdk.OrganizationMemberWithUserData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"global_roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.SlimRole"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
|
||||
@@ -166,25 +166,7 @@ func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
|
||||
convertedUser := codersdk.User{
|
||||
ReducedUser: ReducedUser(user),
|
||||
OrganizationIDs: organizationIDs,
|
||||
Roles: make([]codersdk.SlimRole, 0, len(user.RBACRoles)),
|
||||
}
|
||||
|
||||
for _, roleName := range user.RBACRoles {
|
||||
// TODO: Currently the api only returns site wide roles.
|
||||
// Should it return organization roles?
|
||||
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{
|
||||
Name: roleName,
|
||||
OrganizationID: uuid.Nil,
|
||||
})
|
||||
if err == nil {
|
||||
convertedUser.Roles = append(convertedUser.Roles, SlimRole(rbacRole))
|
||||
} else {
|
||||
// TODO: Fix this for custom roles to display the actual display_name
|
||||
// Requires plumbing either a cached role value, or the db.
|
||||
convertedUser.Roles = append(convertedUser.Roles, codersdk.SlimRole{
|
||||
Name: roleName,
|
||||
})
|
||||
}
|
||||
Roles: SlimRolesFromNames(user.RBACRoles),
|
||||
}
|
||||
|
||||
return convertedUser
|
||||
@@ -537,6 +519,27 @@ func SlimRole(role rbac.Role) codersdk.SlimRole {
|
||||
}
|
||||
}
|
||||
|
||||
func SlimRolesFromNames(names []string) []codersdk.SlimRole {
|
||||
convertedRoles := make([]codersdk.SlimRole, 0, len(names))
|
||||
|
||||
for _, name := range names {
|
||||
convertedRoles = append(convertedRoles, SlimRoleFromName(name))
|
||||
}
|
||||
|
||||
return convertedRoles
|
||||
}
|
||||
|
||||
func SlimRoleFromName(name string) codersdk.SlimRole {
|
||||
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{Name: name})
|
||||
var convertedRole codersdk.SlimRole
|
||||
if err == nil {
|
||||
convertedRole = SlimRole(rbacRole)
|
||||
} else {
|
||||
convertedRole = codersdk.SlimRole{Name: name}
|
||||
}
|
||||
return convertedRole
|
||||
}
|
||||
|
||||
func RBACRole(role rbac.Role) codersdk.Role {
|
||||
slim := SlimRole(role)
|
||||
|
||||
|
||||
@@ -4283,7 +4283,7 @@ func (q *sqlQuerier) InsertOrganizationMember(ctx context.Context, arg InsertOrg
|
||||
const organizationMembers = `-- name: OrganizationMembers :many
|
||||
SELECT
|
||||
organization_members.user_id, organization_members.organization_id, organization_members.created_at, organization_members.updated_at, organization_members.roles,
|
||||
users.username
|
||||
users.username, users.avatar_url, users.name, users.rbac_roles as "global_roles"
|
||||
FROM
|
||||
organization_members
|
||||
INNER JOIN
|
||||
@@ -4311,6 +4311,9 @@ type OrganizationMembersParams struct {
|
||||
type OrganizationMembersRow struct {
|
||||
OrganizationMember OrganizationMember `db:"organization_member" json:"organization_member"`
|
||||
Username string `db:"username" json:"username"`
|
||||
AvatarURL string `db:"avatar_url" json:"avatar_url"`
|
||||
Name string `db:"name" json:"name"`
|
||||
GlobalRoles pq.StringArray `db:"global_roles" json:"global_roles"`
|
||||
}
|
||||
|
||||
// Arguments are optional with uuid.Nil to ignore.
|
||||
@@ -4333,6 +4336,9 @@ func (q *sqlQuerier) OrganizationMembers(ctx context.Context, arg OrganizationMe
|
||||
&i.OrganizationMember.UpdatedAt,
|
||||
pq.Array(&i.OrganizationMember.Roles),
|
||||
&i.Username,
|
||||
&i.AvatarURL,
|
||||
&i.Name,
|
||||
&i.GlobalRoles,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
-- - Use both to get a specific org member row
|
||||
SELECT
|
||||
sqlc.embed(organization_members),
|
||||
users.username
|
||||
users.username, users.avatar_url, users.name, users.rbac_roles as "global_roles"
|
||||
FROM
|
||||
organization_members
|
||||
INNER JOIN
|
||||
|
||||
+16
-8
@@ -83,15 +83,15 @@ func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request)
|
||||
// @Summary Remove organization member
|
||||
// @ID remove-organization-member
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Members
|
||||
// @Param organization path string true "Organization ID"
|
||||
// @Param user path string true "User ID, name, or me"
|
||||
// @Success 200 {object} codersdk.OrganizationMember
|
||||
// @Success 204
|
||||
// @Router /organizations/{organization}/members/{user} [delete]
|
||||
func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
apiKey = httpmw.APIKey(r)
|
||||
organization = httpmw.OrganizationParam(r)
|
||||
member = httpmw.OrganizationMemberParam(r)
|
||||
auditor = api.Auditor.Load()
|
||||
@@ -106,6 +106,11 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
|
||||
aReq.Old = member.OrganizationMember.Auditable(member.Username)
|
||||
defer commitAudit()
|
||||
|
||||
if member.UserID == apiKey.UserID {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{Message: "cannot remove self from an organization"})
|
||||
return
|
||||
}
|
||||
|
||||
err := api.Database.DeleteOrganizationMember(ctx, database.DeleteOrganizationMemberParams{
|
||||
OrganizationID: organization.ID,
|
||||
UserID: member.UserID,
|
||||
@@ -120,7 +125,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
aReq.New = database.AuditableOrganizationMember{}
|
||||
httpapi.Write(ctx, rw, http.StatusOK, "organization member removed")
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// @Summary List organization members
|
||||
@@ -129,7 +134,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
|
||||
// @Produce json
|
||||
// @Tags Members
|
||||
// @Param organization path string true "Organization ID"
|
||||
// @Success 200 {object} []codersdk.OrganizationMemberWithName
|
||||
// @Success 200 {object} []codersdk.OrganizationMemberWithUserData
|
||||
// @Router /organizations/{organization}/members [get]
|
||||
func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
@@ -150,7 +155,7 @@ func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := convertOrganizationMemberRows(ctx, api.Database, members)
|
||||
resp, err := convertOrganizationMembersWithUserData(ctx, api.Database, members)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
@@ -294,7 +299,7 @@ func convertOrganizationMembers(ctx context.Context, db database.Store, mems []d
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
func convertOrganizationMemberRows(ctx context.Context, db database.Store, rows []database.OrganizationMembersRow) ([]codersdk.OrganizationMemberWithName, error) {
|
||||
func convertOrganizationMembersWithUserData(ctx context.Context, db database.Store, rows []database.OrganizationMembersRow) ([]codersdk.OrganizationMemberWithUserData, error) {
|
||||
members := make([]database.OrganizationMember, 0)
|
||||
for _, row := range rows {
|
||||
members = append(members, row.OrganizationMember)
|
||||
@@ -308,10 +313,13 @@ func convertOrganizationMemberRows(ctx context.Context, db database.Store, rows
|
||||
return nil, xerrors.Errorf("conversion failed, mismatch slice lengths")
|
||||
}
|
||||
|
||||
converted := make([]codersdk.OrganizationMemberWithName, 0)
|
||||
converted := make([]codersdk.OrganizationMemberWithUserData, 0)
|
||||
for i := range convertedMembers {
|
||||
converted = append(converted, codersdk.OrganizationMemberWithName{
|
||||
converted = append(converted, codersdk.OrganizationMemberWithUserData{
|
||||
Username: rows[i].Username,
|
||||
AvatarURL: rows[i].AvatarURL,
|
||||
Name: rows[i].Name,
|
||||
GlobalRoles: db2sdk.SlimRolesFromNames(rows[i].GlobalRoles),
|
||||
OrganizationMember: convertedMembers[i],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -185,6 +185,6 @@ func TestRemoveMember(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func onlyIDs(u codersdk.OrganizationMemberWithName) uuid.UUID {
|
||||
func onlyIDs(u codersdk.OrganizationMemberWithUserData) uuid.UUID {
|
||||
return u.UserID
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user