mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: implement delete organization member (#13589)
Side effects of removing an organization member will orphan their user resources. These side effects are not addressed here
This commit is contained in:
@@ -1035,6 +1035,16 @@ func (q *querier) DeleteOrganization(ctx context.Context, id uuid.UUID) error {
|
||||
return deleteQ(q.log, q.auth, q.db.GetOrganizationByID, q.db.DeleteOrganization)(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteOrganizationMember(ctx context.Context, arg database.DeleteOrganizationMemberParams) error {
|
||||
return deleteQ[database.OrganizationMember](q.log, q.auth, func(ctx context.Context, arg database.DeleteOrganizationMemberParams) (database.OrganizationMember, error) {
|
||||
member, err := database.ExpectOne(q.OrganizationMembers(ctx, database.OrganizationMembersParams(arg)))
|
||||
if err != nil {
|
||||
return database.OrganizationMember{}, err
|
||||
}
|
||||
return member.OrganizationMember, nil
|
||||
}, q.db.DeleteOrganizationMember)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
|
||||
@@ -628,6 +628,23 @@ func (s *MethodTestSuite) TestOrganization() {
|
||||
rbac.ResourceAssignOrgRole.InOrg(o.ID), policy.ActionAssign,
|
||||
rbac.ResourceOrganizationMember.InOrg(o.ID).WithID(u.ID), policy.ActionCreate)
|
||||
}))
|
||||
s.Run("DeleteOrganizationMember", s.Subtest(func(db database.Store, check *expects) {
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
member := dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{UserID: u.ID, OrganizationID: o.ID})
|
||||
|
||||
check.Args(database.DeleteOrganizationMemberParams{
|
||||
OrganizationID: o.ID,
|
||||
UserID: u.ID,
|
||||
}).Asserts(
|
||||
// Reads the org member before it tries to delete it
|
||||
member, policy.ActionRead,
|
||||
member, policy.ActionDelete).
|
||||
// SQL Filter returns a 404
|
||||
WithNotAuthorized("no rows").
|
||||
WithCancelled("no rows").
|
||||
Errors(sql.ErrNoRows)
|
||||
}))
|
||||
s.Run("UpdateOrganization", s.Subtest(func(db database.Store, check *expects) {
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{
|
||||
Name: "something-unique",
|
||||
|
||||
@@ -1632,6 +1632,24 @@ func (q *FakeQuerier) DeleteOrganization(_ context.Context, id uuid.UUID) error
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteOrganizationMember(_ context.Context, arg database.DeleteOrganizationMemberParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
deleted := slices.DeleteFunc(q.data.organizationMembers, func(member database.OrganizationMember) bool {
|
||||
return member.OrganizationID == arg.OrganizationID && member.UserID == arg.UserID
|
||||
})
|
||||
if len(deleted) == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteReplicasUpdatedBefore(_ context.Context, before time.Time) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
@@ -291,6 +291,13 @@ func (m metricsStore) DeleteOrganization(ctx context.Context, id uuid.UUID) erro
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) DeleteOrganizationMember(ctx context.Context, arg database.DeleteOrganizationMemberParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteOrganizationMember(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("DeleteOrganizationMember").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error {
|
||||
start := time.Now()
|
||||
err := m.s.DeleteReplicasUpdatedBefore(ctx, updatedAt)
|
||||
|
||||
@@ -469,6 +469,20 @@ func (mr *MockStoreMockRecorder) DeleteOrganization(arg0, arg1 any) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOrganization", reflect.TypeOf((*MockStore)(nil).DeleteOrganization), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteOrganizationMember mocks base method.
|
||||
func (m *MockStore) DeleteOrganizationMember(arg0 context.Context, arg1 database.DeleteOrganizationMemberParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteOrganizationMember", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteOrganizationMember indicates an expected call of DeleteOrganizationMember.
|
||||
func (mr *MockStoreMockRecorder) DeleteOrganizationMember(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOrganizationMember", reflect.TypeOf((*MockStore)(nil).DeleteOrganizationMember), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteReplicasUpdatedBefore mocks base method.
|
||||
func (m *MockStore) DeleteReplicasUpdatedBefore(arg0 context.Context, arg1 time.Time) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -75,6 +75,7 @@ type sqlcQuerier interface {
|
||||
DeleteOldWorkspaceAgentLogs(ctx context.Context) error
|
||||
DeleteOldWorkspaceAgentStats(ctx context.Context) error
|
||||
DeleteOrganization(ctx context.Context, id uuid.UUID) error
|
||||
DeleteOrganizationMember(ctx context.Context, arg DeleteOrganizationMemberParams) error
|
||||
DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error
|
||||
DeleteTailnetAgent(ctx context.Context, arg DeleteTailnetAgentParams) (DeleteTailnetAgentRow, error)
|
||||
DeleteTailnetClient(ctx context.Context, arg DeleteTailnetClientParams) (DeleteTailnetClientRow, error)
|
||||
|
||||
@@ -3756,6 +3756,25 @@ func (q *sqlQuerier) UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteOrganizationMember = `-- name: DeleteOrganizationMember :exec
|
||||
DELETE
|
||||
FROM
|
||||
organization_members
|
||||
WHERE
|
||||
organization_id = $1 AND
|
||||
user_id = $2
|
||||
`
|
||||
|
||||
type DeleteOrganizationMemberParams struct {
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) DeleteOrganizationMember(ctx context.Context, arg DeleteOrganizationMemberParams) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOrganizationMember, arg.OrganizationID, arg.UserID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getOrganizationIDsByMemberIDs = `-- name: GetOrganizationIDsByMemberIDs :many
|
||||
SELECT
|
||||
user_id, array_agg(organization_id) :: uuid [ ] AS "organization_IDs"
|
||||
|
||||
@@ -36,6 +36,15 @@ INSERT INTO
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5) RETURNING *;
|
||||
|
||||
-- name: DeleteOrganizationMember :exec
|
||||
DELETE
|
||||
FROM
|
||||
organization_members
|
||||
WHERE
|
||||
organization_id = @organization_id AND
|
||||
user_id = @user_id
|
||||
;
|
||||
|
||||
|
||||
-- name: GetOrganizationIDsByMemberIDs :many
|
||||
SELECT
|
||||
|
||||
Reference in New Issue
Block a user