From ac636b53cf74c5d851e09f734adadc6a30d071f2 Mon Sep 17 00:00:00 2001
From: Joe Chen <jc@unknwon.io>
Date: Fri, 3 Nov 2023 22:22:21 -0400
Subject: [PATCH] `DeleteByID`

---
 internal/db/org.go            | 34 ----------------------------------
 internal/db/organizations.go  | 34 ++++++++++++++++++++++++++++++++++
 internal/db/repo.go           | 10 ++++++++++
 internal/db/users.go          |  3 +--
 internal/route/org/setting.go |  5 +++--
 5 files changed, 48 insertions(+), 38 deletions(-)

diff --git a/internal/db/org.go b/internal/db/org.go
index f504993df..5c791adf9 100644
--- a/internal/db/org.go
+++ b/internal/db/org.go
@@ -5,46 +5,12 @@
 package db
 
 import (
-	"context"
 	"fmt"
 
 	"xorm.io/builder"
 	"xorm.io/xorm"
 )
 
-// deleteBeans deletes all given beans, beans should contain delete conditions.
-func deleteBeans(e Engine, beans ...any) (err error) {
-	for i := range beans {
-		if _, err = e.Delete(beans[i]); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// DeleteOrganization completely and permanently deletes everything of organization.
-func DeleteOrganization(org *User) error {
-	err := Users.DeleteByID(context.TODO(), org.ID, false)
-	if err != nil {
-		return err
-	}
-
-	sess := x.NewSession()
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
-		return err
-	}
-
-	if err = deleteBeans(sess,
-		&Team{OrgID: org.ID},
-		&OrgUser{OrgID: org.ID},
-		&TeamUser{OrgID: org.ID},
-	); err != nil {
-		return fmt.Errorf("deleteBeans: %v", err)
-	}
-	return sess.Commit()
-}
-
 func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) {
 	orgs := make([]*User, 0, 10)
 	if !showAll {
diff --git a/internal/db/organizations.go b/internal/db/organizations.go
index 3d65b0d15..d5a2f2add 100644
--- a/internal/db/organizations.go
+++ b/internal/db/organizations.go
@@ -40,6 +40,9 @@ type OrganizationsStore interface {
 	CountByUser(ctx context.Context, userID int64) (int64, error)
 	// Count returns the total number of organizations.
 	Count(ctx context.Context) int64
+	// DeleteByID deletes the given organization and all their resources. It returns
+	// ErrOrganizationOwnRepos when the user still has repository ownership.
+	DeleteByID(ctx context.Context, orgID int64) error
 
 	// AddMember adds a new member to the given organization.
 	AddMember(ctx context.Context, orgID, userID int64) error
@@ -541,6 +544,37 @@ func (db *organizations) Count(ctx context.Context) int64 {
 	return count
 }
 
+type ErrOrganizationOwnRepos struct {
+	args errutil.Args
+}
+
+// IsErrOrganizationOwnRepos returns true if the underlying error has the type
+// ErrOrganizationOwnRepos.
+func IsErrOrganizationOwnRepos(err error) bool {
+	return errors.As(errors.Cause(err), &ErrOrganizationOwnRepos{})
+}
+
+func (err ErrOrganizationOwnRepos) Error() string {
+	return fmt.Sprintf("organization still has repository ownership: %v", err.args)
+}
+
+func (db *organizations) DeleteByID(ctx context.Context, orgID int64) error {
+	return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
+		for _, t := range []any{&Team{}, &OrgUser{}, &TeamUser{}} {
+			err := tx.Where("org_id = ?", orgID).Delete(t).Error
+			if err != nil {
+				return errors.Wrapf(err, "clean up table %T", t)
+			}
+		}
+
+		err := NewUsersStore(tx).DeleteByID(ctx, orgID, false)
+		if err != nil {
+			return errors.Wrap(err, "delete organization")
+		}
+		return nil
+	})
+}
+
 var _ errutil.NotFound = (*ErrTeamNotExist)(nil)
 
 type ErrTeamNotExist struct {
diff --git a/internal/db/repo.go b/internal/db/repo.go
index 184d9fb2e..a878344e0 100644
--- a/internal/db/repo.go
+++ b/internal/db/repo.go
@@ -1791,6 +1791,16 @@ func DeleteRepository(ownerID, repoID int64) error {
 	return nil
 }
 
+// deleteBeans deletes all given beans, beans should contain delete conditions.
+func deleteBeans(e Engine, beans ...any) (err error) {
+	for i := range beans {
+		if _, err = e.Delete(beans[i]); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // GetRepositoryByRef returns a Repository specified by a GFM reference.
 // See https://help.github.com/articles/writing-on-github#references for more information on the syntax.
 func GetRepositoryByRef(ref string) (*Repository, error) {
diff --git a/internal/db/users.go b/internal/db/users.go
index 68170c943..53be95965 100644
--- a/internal/db/users.go
+++ b/internal/db/users.go
@@ -467,8 +467,7 @@ type ErrUserOwnRepos struct {
 // IsErrUserOwnRepos returns true if the underlying error has the type
 // ErrUserOwnRepos.
 func IsErrUserOwnRepos(err error) bool {
-	_, ok := errors.Cause(err).(ErrUserOwnRepos)
-	return ok
+	return errors.As(errors.Cause(err), &ErrUserOwnRepos{})
 }
 
 func (err ErrUserOwnRepos) Error() string {
diff --git a/internal/route/org/setting.go b/internal/route/org/setting.go
index e632e8a7a..817022d84 100644
--- a/internal/route/org/setting.go
+++ b/internal/route/org/setting.go
@@ -115,8 +115,9 @@ func SettingsDelete(c *context.Context) {
 			return
 		}
 
-		if err := db.DeleteOrganization(org); err != nil {
-			if db.IsErrUserOwnRepos(err) {
+		err := db.Organizations.DeleteByID(c.Req.Context(), org.ID)
+		if err != nil {
+			if db.IsErrOrganizationOwnRepos(err) {
 				c.Flash.Error(c.Tr("form.org_still_own_repo"))
 				c.Redirect(c.Org.OrgLink + "/settings/delete")
 			} else {