From e2f0ab3343cd4ed710221d96c253566c20cfab04 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Wed, 15 Sep 2021 03:41:40 +0800
Subject: [PATCH] Add doctor dbconsistency check for release and attachment
 (#16978)

---
 models/attachment.go            | 13 ++++++++++++
 modules/doctor/dbconsistency.go | 36 +++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/models/attachment.go b/models/attachment.go
index 330e965bb1..96048db00a 100644
--- a/models/attachment.go
+++ b/models/attachment.go
@@ -272,3 +272,16 @@ func IterateAttachment(f func(attach *Attachment) error) error {
 		}
 	}
 }
+
+// CountOrphanedAttachments returns the number of bad attachments
+func CountOrphanedAttachments() (int64, error) {
+	return x.Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))").
+		Count(new(Attachment))
+}
+
+// DeleteOrphanedAttachments delete all bad attachments
+func DeleteOrphanedAttachments() error {
+	_, err := x.Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))").
+		Delete(new(Attachment))
+	return err
+}
diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go
index 23e8331e77..0d84c63976 100644
--- a/modules/doctor/dbconsistency.go
+++ b/modules/doctor/dbconsistency.go
@@ -74,6 +74,24 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 		}
 	}
 
+	// find releases without existing repository
+	count, err = models.CountOrphanedObjects("release", "repository", "release.repo_id=repository.id")
+	if err != nil {
+		logger.Critical("Error: %v whilst counting orphaned objects", err)
+		return err
+	}
+	if count > 0 {
+		if autofix {
+			if err = models.DeleteOrphanedObjects("release", "repository", "release.repo_id=repository.id"); err != nil {
+				logger.Critical("Error: %v whilst deleting orphaned objects", err)
+				return err
+			}
+			logger.Info("%d releases without existing repository deleted", count)
+		} else {
+			logger.Warn("%d releases without existing repository", count)
+		}
+	}
+
 	// find pulls without existing issues
 	count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
 	if err != nil {
@@ -110,6 +128,24 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 		}
 	}
 
+	// find attachments without existing issues or releases
+	count, err = models.CountOrphanedAttachments()
+	if err != nil {
+		logger.Critical("Error: %v whilst counting orphaned objects", err)
+		return err
+	}
+	if count > 0 {
+		if autofix {
+			if err = models.DeleteOrphanedAttachments(); err != nil {
+				logger.Critical("Error: %v whilst deleting orphaned objects", err)
+				return err
+			}
+			logger.Info("%d attachments without existing issue or release deleted", count)
+		} else {
+			logger.Warn("%d attachments without existing issue or release", count)
+		}
+	}
+
 	// find null archived repositories
 	count, err = models.CountNullArchivedRepository()
 	if err != nil {