feat: enable soft delete for organizations (#16584)

- Add deleted column to organizations table
- Add trigger to check for existing workspaces, templates, groups and
members in a org before allowing the soft delete

---------

Co-authored-by: Steven Masley <stevenmasley@gmail.com>
Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
This commit is contained in:
Jaayden Halko
2025-02-24 17:59:41 +00:00
committed by GitHub
parent dfa33b11d9
commit 546a549dcf
28 changed files with 605 additions and 215 deletions

View File

@ -1302,10 +1302,6 @@ func (q *querier) DeleteOldWorkspaceAgentStats(ctx context.Context) error {
return q.db.DeleteOldWorkspaceAgentStats(ctx)
}
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)))
@ -1926,7 +1922,7 @@ func (q *querier) GetOrganizationByID(ctx context.Context, id uuid.UUID) (databa
return fetch(q.log, q.auth, q.db.GetOrganizationByID)(ctx, id)
}
func (q *querier) GetOrganizationByName(ctx context.Context, name string) (database.Organization, error) {
func (q *querier) GetOrganizationByName(ctx context.Context, name database.GetOrganizationByNameParams) (database.Organization, error) {
return fetch(q.log, q.auth, q.db.GetOrganizationByName)(ctx, name)
}
@ -1943,7 +1939,7 @@ func (q *querier) GetOrganizations(ctx context.Context, args database.GetOrganiz
return fetchWithPostFilter(q.auth, policy.ActionRead, fetch)(ctx, nil)
}
func (q *querier) GetOrganizationsByUserID(ctx context.Context, userID uuid.UUID) ([]database.Organization, error) {
func (q *querier) GetOrganizationsByUserID(ctx context.Context, userID database.GetOrganizationsByUserIDParams) ([]database.Organization, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetOrganizationsByUserID)(ctx, userID)
}
@ -3737,6 +3733,16 @@ func (q *querier) UpdateOrganization(ctx context.Context, arg database.UpdateOrg
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateOrganization)(ctx, arg)
}
func (q *querier) UpdateOrganizationDeletedByID(ctx context.Context, arg database.UpdateOrganizationDeletedByIDParams) error {
deleteF := func(ctx context.Context, id uuid.UUID) error {
return q.db.UpdateOrganizationDeletedByID(ctx, database.UpdateOrganizationDeletedByIDParams{
ID: id,
UpdatedAt: dbtime.Now(),
})
}
return deleteQ(q.log, q.auth, q.db.GetOrganizationByID, deleteF)(ctx, arg.ID)
}
func (q *querier) UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg database.UpdateProvisionerDaemonLastSeenAtParams) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceProvisionerDaemon); err != nil {
return err

View File

@ -815,7 +815,7 @@ func (s *MethodTestSuite) TestOrganization() {
}))
s.Run("GetOrganizationByName", s.Subtest(func(db database.Store, check *expects) {
o := dbgen.Organization(s.T(), db, database.Organization{})
check.Args(o.Name).Asserts(o, policy.ActionRead).Returns(o)
check.Args(database.GetOrganizationByNameParams{Name: o.Name, Deleted: o.Deleted}).Asserts(o, policy.ActionRead).Returns(o)
}))
s.Run("GetOrganizationIDsByMemberIDs", s.Subtest(func(db database.Store, check *expects) {
oa := dbgen.Organization(s.T(), db, database.Organization{})
@ -839,7 +839,7 @@ func (s *MethodTestSuite) TestOrganization() {
_ = dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{UserID: u.ID, OrganizationID: a.ID})
b := dbgen.Organization(s.T(), db, database.Organization{})
_ = dbgen.OrganizationMember(s.T(), db, database.OrganizationMember{UserID: u.ID, OrganizationID: b.ID})
check.Args(u.ID).Asserts(a, policy.ActionRead, b, policy.ActionRead).Returns(slice.New(a, b))
check.Args(database.GetOrganizationsByUserIDParams{UserID: u.ID, Deleted: false}).Asserts(a, policy.ActionRead, b, policy.ActionRead).Returns(slice.New(a, b))
}))
s.Run("InsertOrganization", s.Subtest(func(db database.Store, check *expects) {
check.Args(database.InsertOrganizationParams{
@ -960,13 +960,14 @@ func (s *MethodTestSuite) TestOrganization() {
Name: "something-different",
}).Asserts(o, policy.ActionUpdate)
}))
s.Run("DeleteOrganization", s.Subtest(func(db database.Store, check *expects) {
s.Run("UpdateOrganizationDeletedByID", s.Subtest(func(db database.Store, check *expects) {
o := dbgen.Organization(s.T(), db, database.Organization{
Name: "doomed",
})
check.Args(
o.ID,
).Asserts(o, policy.ActionDelete)
check.Args(database.UpdateOrganizationDeletedByIDParams{
ID: o.ID,
UpdatedAt: o.UpdatedAt,
}).Asserts(o, policy.ActionDelete).Returns()
}))
s.Run("OrganizationMembers", s.Subtest(func(db database.Store, check *expects) {
o := dbgen.Organization(s.T(), db, database.Organization{})