From 8ce1b539b1aaf242903b5b0c342dd592bd8da8d9 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Wed, 29 Dec 2021 21:02:12 +0800
Subject: [PATCH] Use conditions but not repo ids as query condition (#16839)

* Use conditions but not repo ids as query condition

* Improve the performance of pulls/issue

* Remove duplicated code

* fix lint

* Fix bug

* Fix stats

* More fixes

* Fix build

* Fix lint

* Fix test

* Fix build

* Adjust the logic

* Merge

* Fix conflicts

* improve the performance

* Add comments for the query conditions functions

* Some improvements
---
 models/issue.go                      |  78 +++++++--
 models/issue_list.go                 |  11 +-
 models/issue_test.go                 |  83 +++++-----
 models/repo/repo.go                  |  31 ----
 models/repo/repo_list.go             |  46 ++++++
 models/repo_list.go                  | 226 +++++++++++++++++++++++----
 models/repo_permission.go            |  20 ---
 models/user.go                       | 104 ------------
 models/user_test.go                  |  22 ---
 routers/web/user/home.go             | 203 ++++++------------------
 templates/user/dashboard/issues.tmpl |   4 +-
 11 files changed, 401 insertions(+), 427 deletions(-)
 create mode 100644 models/repo/repo_list.go

diff --git a/models/issue.go b/models/issue.go
index b05c8cccdd3d..f0040fbbc1af 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -1186,6 +1186,9 @@ type IssuesOptions struct {
 	// prioritize issues from this repo
 	PriorityRepoID int64
 	IsArchived     util.OptionalBool
+	Org            *Organization    // issues permission scope
+	Team           *Team            // issues permission scope
+	User           *user_model.User // issues permission scope
 }
 
 // sortIssuesSession sort an issues-related session based on the provided
@@ -1337,6 +1340,44 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
 				From("milestone").
 				Where(builder.In("name", opts.IncludeMilestones)))
 	}
+
+	if opts.User != nil {
+		sess.And(
+			issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()),
+		)
+	}
+}
+
+// issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table
+func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *Organization, team *Team, isPull bool) builder.Cond {
+	var cond = builder.NewCond()
+	var unitType = unit.TypeIssues
+	if isPull {
+		unitType = unit.TypePullRequests
+	}
+	if org != nil {
+		if team != nil {
+			cond = cond.And(teamUnitsRepoCond(repoIDstr, userID, org.ID, team.ID, unitType)) // special team member repos
+		} else {
+			cond = cond.And(
+				builder.Or(
+					userOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos
+					userOrgPublicUnitRepoCond(userID, org.ID),                // user org public non-member repos, TODO: check repo has issues
+				),
+			)
+		}
+	} else {
+		cond = cond.And(
+			builder.Or(
+				userOwnedRepoCond(userID),                          // owned repos
+				userCollaborationRepoCond(repoIDstr, userID),       // collaboration repos
+				userAssignedRepoCond(repoIDstr, userID),            // user has been assigned accessible public repos
+				userMentionedRepoCond(repoIDstr, userID),           // user has been mentioned accessible public repos
+				userCreateIssueRepoCond(repoIDstr, userID, isPull), // user has created issue/pr accessible public repos
+			),
+		)
+	}
+	return cond
 }
 
 func applyReposCondition(sess *xorm.Session, repoIDs []int64) *xorm.Session {
@@ -1646,15 +1687,16 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats,
 
 // UserIssueStatsOptions contains parameters accepted by GetUserIssueStats.
 type UserIssueStatsOptions struct {
-	UserID      int64
-	RepoIDs     []int64
-	UserRepoIDs []int64
-	FilterMode  int
-	IsPull      bool
-	IsClosed    bool
-	IssueIDs    []int64
-	IsArchived  util.OptionalBool
-	LabelIDs    []int64
+	UserID     int64
+	RepoIDs    []int64
+	FilterMode int
+	IsPull     bool
+	IsClosed   bool
+	IssueIDs   []int64
+	IsArchived util.OptionalBool
+	LabelIDs   []int64
+	Org        *Organization
+	Team       *Team
 }
 
 // GetUserIssueStats returns issue statistic information for dashboard by given conditions.
@@ -1671,28 +1713,34 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
 		cond = cond.And(builder.In("issue.id", opts.IssueIDs))
 	}
 
+	if opts.UserID > 0 {
+		cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.UserID, opts.Org, opts.Team, opts.IsPull))
+	}
+
 	sess := func(cond builder.Cond) *xorm.Session {
 		s := db.GetEngine(db.DefaultContext).Where(cond)
 		if len(opts.LabelIDs) > 0 {
 			s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id").
 				In("issue_label.label_id", opts.LabelIDs)
 		}
-		if opts.IsArchived != util.OptionalBoolNone {
-			s.Join("INNER", "repository", "issue.repo_id = repository.id").
-				And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
+		if opts.UserID > 0 || opts.IsArchived != util.OptionalBoolNone {
+			s.Join("INNER", "repository", "issue.repo_id = repository.id")
+			if opts.IsArchived != util.OptionalBoolNone {
+				s.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
+			}
 		}
 		return s
 	}
 
 	switch opts.FilterMode {
 	case FilterModeAll:
-		stats.OpenCount, err = applyReposCondition(sess(cond), opts.UserRepoIDs).
+		stats.OpenCount, err = sess(cond).
 			And("issue.is_closed = ?", false).
 			Count(new(Issue))
 		if err != nil {
 			return nil, err
 		}
-		stats.ClosedCount, err = applyReposCondition(sess(cond), opts.UserRepoIDs).
+		stats.ClosedCount, err = sess(cond).
 			And("issue.is_closed = ?", true).
 			Count(new(Issue))
 		if err != nil {
@@ -1768,7 +1816,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
 		return nil, err
 	}
 
-	stats.YourRepositoriesCount, err = applyReposCondition(sess(cond), opts.UserRepoIDs).Count(new(Issue))
+	stats.YourRepositoriesCount, err = sess(cond).Count(new(Issue))
 	if err != nil {
 		return nil, err
 	}
diff --git a/models/issue_list.go b/models/issue_list.go
index 5d8a9f692173..b516e7336e02 100644
--- a/models/issue_list.go
+++ b/models/issue_list.go
@@ -25,6 +25,9 @@ const (
 func (issues IssueList) getRepoIDs() []int64 {
 	repoIDs := make(map[int64]struct{}, len(issues))
 	for _, issue := range issues {
+		if issue.Repo != nil {
+			continue
+		}
 		if _, ok := repoIDs[issue.RepoID]; !ok {
 			repoIDs[issue.RepoID] = struct{}{}
 		}
@@ -56,8 +59,12 @@ func (issues IssueList) loadRepositories(e db.Engine) ([]*repo_model.Repository,
 	}
 
 	for _, issue := range issues {
-		issue.Repo = repoMaps[issue.RepoID]
-		if issue.PullRequest != nil {
+		if issue.Repo == nil {
+			issue.Repo = repoMaps[issue.RepoID]
+		} else {
+			repoMaps[issue.RepoID] = issue.Repo
+		}
+		if issue.PullRequest != nil && issue.PullRequest.BaseRepo == nil {
 			issue.PullRequest.BaseRepo = issue.Repo
 		}
 	}
diff --git a/models/issue_test.go b/models/issue_test.go
index eadeb66ab920..cebf20af9bbb 100644
--- a/models/issue_test.go
+++ b/models/issue_test.go
@@ -206,11 +206,26 @@ func TestGetUserIssueStats(t *testing.T) {
 				FilterMode: FilterModeAll,
 			},
 			IssueStats{
-				YourRepositoriesCount: 0,
-				AssignCount:           1,
-				CreateCount:           1,
-				OpenCount:             0,
-				ClosedCount:           0,
+				YourRepositoriesCount: 1, // 6
+				AssignCount:           1, // 6
+				CreateCount:           1, // 6
+				OpenCount:             1, // 6
+				ClosedCount:           1, // 1
+			},
+		},
+		{
+			UserIssueStatsOptions{
+				UserID:     1,
+				RepoIDs:    []int64{1},
+				FilterMode: FilterModeAll,
+				IsClosed:   true,
+			},
+			IssueStats{
+				YourRepositoriesCount: 1, // 6
+				AssignCount:           0,
+				CreateCount:           0,
+				OpenCount:             1, // 6
+				ClosedCount:           1, // 1
 			},
 		},
 		{
@@ -219,10 +234,10 @@ func TestGetUserIssueStats(t *testing.T) {
 				FilterMode: FilterModeAssign,
 			},
 			IssueStats{
-				YourRepositoriesCount: 0,
-				AssignCount:           2,
-				CreateCount:           2,
-				OpenCount:             2,
+				YourRepositoriesCount: 1, // 6
+				AssignCount:           1, // 6
+				CreateCount:           1, // 6
+				OpenCount:             1, // 6
 				ClosedCount:           0,
 			},
 		},
@@ -232,37 +247,23 @@ func TestGetUserIssueStats(t *testing.T) {
 				FilterMode: FilterModeCreate,
 			},
 			IssueStats{
-				YourRepositoriesCount: 0,
-				AssignCount:           2,
-				CreateCount:           2,
-				OpenCount:             2,
+				YourRepositoriesCount: 1, // 6
+				AssignCount:           1, // 6
+				CreateCount:           1, // 6
+				OpenCount:             1, // 6
 				ClosedCount:           0,
 			},
 		},
-		{
-			UserIssueStatsOptions{
-				UserID:      2,
-				UserRepoIDs: []int64{1, 2},
-				FilterMode:  FilterModeAll,
-				IsClosed:    true,
-			},
-			IssueStats{
-				YourRepositoriesCount: 2,
-				AssignCount:           0,
-				CreateCount:           2,
-				OpenCount:             2,
-				ClosedCount:           2,
-			},
-		},
 		{
 			UserIssueStatsOptions{
 				UserID:     1,
 				FilterMode: FilterModeMention,
 			},
 			IssueStats{
-				YourRepositoriesCount: 0,
-				AssignCount:           2,
-				CreateCount:           2,
+				YourRepositoriesCount: 1, // 6
+				AssignCount:           1, // 6
+				CreateCount:           1, // 6
+				MentionCount:          0,
 				OpenCount:             0,
 				ClosedCount:           0,
 			},
@@ -274,19 +275,21 @@ func TestGetUserIssueStats(t *testing.T) {
 				IssueIDs:   []int64{1},
 			},
 			IssueStats{
-				YourRepositoriesCount: 0,
-				AssignCount:           1,
-				CreateCount:           1,
-				OpenCount:             1,
+				YourRepositoriesCount: 1, // 1
+				AssignCount:           1, // 1
+				CreateCount:           1, // 1
+				OpenCount:             1, // 1
 				ClosedCount:           0,
 			},
 		},
 	} {
-		stats, err := GetUserIssueStats(test.Opts)
-		if !assert.NoError(t, err) {
-			continue
-		}
-		assert.Equal(t, test.ExpectedIssueStats, *stats)
+		t.Run(fmt.Sprintf("%#v", test.Opts), func(t *testing.T) {
+			stats, err := GetUserIssueStats(test.Opts)
+			if !assert.NoError(t, err) {
+				return
+			}
+			assert.Equal(t, test.ExpectedIssueStats, *stats)
+		})
 	}
 }
 
diff --git a/models/repo/repo.go b/models/repo/repo.go
index d0136e9c66c4..5108231cd8d0 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -731,15 +731,6 @@ func CountUserRepositories(userID int64, private bool) int64 {
 	return countRepositories(userID, private)
 }
 
-// GetUserMirrorRepositories returns a list of mirror repositories of given user.
-func GetUserMirrorRepositories(userID int64) ([]*Repository, error) {
-	repos := make([]*Repository, 0, 10)
-	return repos, db.GetEngine(db.DefaultContext).
-		Where("owner_id = ?", userID).
-		And("is_mirror = ?", true).
-		Find(&repos)
-}
-
 func getRepositoryCount(e db.Engine, ownerID int64) (int64, error) {
 	return e.Count(&Repository{OwnerID: ownerID})
 }
@@ -766,25 +757,3 @@ func GetPublicRepositoryCount(u *user_model.User) (int64, error) {
 func GetPrivateRepositoryCount(u *user_model.User) (int64, error) {
 	return getPrivateRepositoryCount(db.GetEngine(db.DefaultContext), u)
 }
-
-// IterateRepository iterate repositories
-func IterateRepository(f func(repo *Repository) error) error {
-	var start int
-	batchSize := setting.Database.IterateBufferSize
-	for {
-		repos := make([]*Repository, 0, batchSize)
-		if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&repos); err != nil {
-			return err
-		}
-		if len(repos) == 0 {
-			return nil
-		}
-		start += len(repos)
-
-		for _, repo := range repos {
-			if err := f(repo); err != nil {
-				return err
-			}
-		}
-	}
-}
diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go
new file mode 100644
index 000000000000..571604a2c2a7
--- /dev/null
+++ b/models/repo/repo_list.go
@@ -0,0 +1,46 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/setting"
+)
+
+// GetUserMirrorRepositories returns a list of mirror repositories of given user.
+func GetUserMirrorRepositories(userID int64) ([]*Repository, error) {
+	repos := make([]*Repository, 0, 10)
+	return repos, db.GetEngine(db.DefaultContext).
+		Where("owner_id = ?", userID).
+		And("is_mirror = ?", true).
+		Find(&repos)
+}
+
+// IterateRepository iterate repositories
+func IterateRepository(f func(repo *Repository) error) error {
+	var start int
+	batchSize := setting.Database.IterateBufferSize
+	for {
+		repos := make([]*Repository, 0, batchSize)
+		if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&repos); err != nil {
+			return err
+		}
+		if len(repos) == 0 {
+			return nil
+		}
+		start += len(repos)
+
+		for _, repo := range repos {
+			if err := f(repo); err != nil {
+				return err
+			}
+		}
+	}
+}
+
+// FindReposMapByIDs find repos as map
+func FindReposMapByIDs(repoIDs []int64, res map[int64]*Repository) error {
+	return db.GetEngine(db.DefaultContext).In("id", repoIDs).Find(&res)
+}
diff --git a/models/repo_list.go b/models/repo_list.go
index 02440bec93da..9bda0d5a374a 100644
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/perm"
 	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
@@ -145,6 +146,188 @@ type SearchRepoOptions struct {
 	LowerNames []string
 }
 
+// SearchOrderBy is used to sort the result
+type SearchOrderBy string
+
+func (s SearchOrderBy) String() string {
+	return string(s)
+}
+
+// Strings for sorting result
+const (
+	SearchOrderByAlphabetically        SearchOrderBy = "name ASC"
+	SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC"
+	SearchOrderByLeastUpdated          SearchOrderBy = "updated_unix ASC"
+	SearchOrderByRecentUpdated         SearchOrderBy = "updated_unix DESC"
+	SearchOrderByOldest                SearchOrderBy = "created_unix ASC"
+	SearchOrderByNewest                SearchOrderBy = "created_unix DESC"
+	SearchOrderBySize                  SearchOrderBy = "size ASC"
+	SearchOrderBySizeReverse           SearchOrderBy = "size DESC"
+	SearchOrderByID                    SearchOrderBy = "id ASC"
+	SearchOrderByIDReverse             SearchOrderBy = "id DESC"
+	SearchOrderByStars                 SearchOrderBy = "num_stars ASC"
+	SearchOrderByStarsReverse          SearchOrderBy = "num_stars DESC"
+	SearchOrderByForks                 SearchOrderBy = "num_forks ASC"
+	SearchOrderByForksReverse          SearchOrderBy = "num_forks DESC"
+)
+
+// userOwnedRepoCond returns user ownered repositories
+func userOwnedRepoCond(userID int64) builder.Cond {
+	return builder.Eq{
+		"repository.owner_id": userID,
+	}
+}
+
+// userAssignedRepoCond return user as assignee repositories list
+func userAssignedRepoCond(id string, userID int64) builder.Cond {
+	return builder.And(
+		builder.Eq{
+			"repository.is_private": false,
+		},
+		builder.In(id,
+			builder.Select("issue.repo_id").From("issue_assignees").
+				InnerJoin("issue", "issue.id = issue_assignees.issue_id").
+				Where(builder.Eq{
+					"issue_assignees.assignee_id": userID,
+				}),
+		),
+	)
+}
+
+// userCreateIssueRepoCond return user created issues repositories list
+func userCreateIssueRepoCond(id string, userID int64, isPull bool) builder.Cond {
+	return builder.And(
+		builder.Eq{
+			"repository.is_private": false,
+		},
+		builder.In(id,
+			builder.Select("issue.repo_id").From("issue").
+				Where(builder.Eq{
+					"issue.poster_id": userID,
+					"issue.is_pull":   isPull,
+				}),
+		),
+	)
+}
+
+// userMentionedRepoCond return user metinoed repositories list
+func userMentionedRepoCond(id string, userID int64) builder.Cond {
+	return builder.And(
+		builder.Eq{
+			"repository.is_private": false,
+		},
+		builder.In(id,
+			builder.Select("issue.repo_id").From("issue_user").
+				InnerJoin("issue", "issue.id = issue_user.issue_id").
+				Where(builder.Eq{
+					"issue_user.is_mentioned": true,
+					"issue_user.uid":          userID,
+				}),
+		),
+	)
+}
+
+// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access
+func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Type) builder.Cond {
+	return builder.In(id,
+		builder.Select("repo_id").From("team_repo").Where(
+			builder.Eq{
+				"team_id": teamID,
+			}.And(
+				builder.In(
+					"team_id", builder.Select("team_id").From("team_user").Where(
+						builder.Eq{
+							"uid": userID,
+						},
+					),
+				)).And(
+				builder.In(
+					"team_id", builder.Select("team_id").From("team_unit").Where(
+						builder.Eq{
+							"org_id": orgID,
+						}.And(
+							builder.In("type", units),
+						),
+					),
+				),
+			),
+		))
+}
+
+// userCollaborationRepoCond returns user as collabrators repositories list
+func userCollaborationRepoCond(idStr string, userID int64) builder.Cond {
+	return builder.In(idStr, builder.Select("repo_id").
+		From("`access`").
+		Where(builder.And(
+			builder.Eq{"user_id": userID},
+			builder.Gt{"mode": int(perm.AccessModeNone)},
+		)),
+	)
+}
+
+// userOrgTeamRepoCond selects repos that the given user has access to through team membership
+func userOrgTeamRepoCond(idStr string, userID int64) builder.Cond {
+	return builder.In(idStr, userOrgTeamRepoBuilder(userID))
+}
+
+// userOrgTeamRepoBuilder returns repo ids where user's teams can access.
+func userOrgTeamRepoBuilder(userID int64) *builder.Builder {
+	return builder.Select("`team_repo`.repo_id").
+		From("team_repo").
+		Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id").
+		Where(builder.Eq{"`team_user`.uid": userID})
+}
+
+// userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit.
+func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder {
+	return userOrgTeamRepoBuilder(userID).
+		Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id").
+		Where(builder.Eq{"`team_unit`.`type`": unitType})
+}
+
+// userOrgUnitRepoCond selects repos that the given user has access to through org and the special unit
+func userOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) builder.Cond {
+	return builder.In(idStr,
+		userOrgTeamUnitRepoBuilder(userID, unitType).
+			And(builder.Eq{"org_id": orgID}),
+	)
+}
+
+// userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations
+func userOrgPublicRepoCond(userID int64) builder.Cond {
+	return builder.And(
+		builder.Eq{"`repository`.is_private": false},
+		builder.In("`repository`.owner_id",
+			builder.Select("`org_user`.org_id").
+				From("org_user").
+				Where(builder.Eq{"`org_user`.uid": userID}),
+		),
+	)
+}
+
+// userOrgPublicRepoCondPrivate returns the condition that one user could access all public repositories in private organizations
+func userOrgPublicRepoCondPrivate(userID int64) builder.Cond {
+	return builder.And(
+		builder.Eq{"`repository`.is_private": false},
+		builder.In("`repository`.owner_id",
+			builder.Select("`org_user`.org_id").
+				From("org_user").
+				Join("INNER", "`user`", "`user`.id = `org_user`.org_id").
+				Where(builder.Eq{
+					"`org_user`.uid":    userID,
+					"`user`.`type`":     user_model.UserTypeOrganization,
+					"`user`.visibility": structs.VisibleTypePrivate,
+				}),
+		),
+	)
+}
+
+// userOrgPublicUnitRepoCond returns the condition that one user could access all public repositories in the special organization
+func userOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond {
+	return userOrgPublicRepoCond(userID).
+		And(builder.Eq{"`repository`.owner_id": orgID})
+}
+
 // SearchRepositoryCondition creates a query condition according search repository options
 func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
 	cond := builder.NewCond()
@@ -198,27 +381,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
 				// 2. But we can see because of:
 				builder.Or(
 					// A. We have access
-					builder.In("`repository`.id",
-						builder.Select("`access`.repo_id").
-							From("access").
-							Where(builder.Eq{"`access`.user_id": opts.OwnerID})),
+					userCollaborationRepoCond("`repository`.id", opts.OwnerID),
 					// B. We are in a team for
-					builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
-						From("team_repo").
-						Where(builder.Eq{"`team_user`.uid": opts.OwnerID}).
-						Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id")),
-					// C. Public repositories in private organizations that we are member of
-					builder.And(
-						builder.Eq{"`repository`.is_private": false},
-						builder.In("`repository`.owner_id",
-							builder.Select("`org_user`.org_id").
-								From("org_user").
-								Join("INNER", "`user`", "`user`.id = `org_user`.org_id").
-								Where(builder.Eq{
-									"`org_user`.uid":    opts.OwnerID,
-									"`user`.type":       user_model.UserTypeOrganization,
-									"`user`.visibility": structs.VisibleTypePrivate,
-								})))),
+					userOrgTeamRepoCond("`repository`.id", opts.OwnerID),
+					// C. Public repositories in organizations that we are member of
+					userOrgPublicRepoCondPrivate(opts.OwnerID),
+				),
 			)
 			if !opts.Private {
 				collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
@@ -389,24 +557,14 @@ func accessibleRepositoryCondition(user *user_model.User) builder.Cond {
 	if user != nil {
 		cond = cond.Or(
 			// 2. Be able to see all repositories that we have access to
-			builder.In("`repository`.id", builder.Select("repo_id").
-				From("`access`").
-				Where(builder.And(
-					builder.Eq{"user_id": user.ID},
-					builder.Gt{"mode": int(perm.AccessModeNone)}))),
+			userCollaborationRepoCond("`repository`.id", user.ID),
 			// 3. Repositories that we directly own
 			builder.Eq{"`repository`.owner_id": user.ID},
 			// 4. Be able to see all repositories that we are in a team
-			builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
-				From("team_repo").
-				Where(builder.Eq{"`team_user`.uid": user.ID}).
-				Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id")),
+			userOrgTeamRepoCond("`repository`.id", user.ID),
 			// 5. Be able to see all public repos in private organizations that we are an org_user of
-			builder.And(builder.Eq{"`repository`.is_private": false},
-				builder.In("`repository`.owner_id",
-					builder.Select("`org_user`.org_id").
-						From("org_user").
-						Where(builder.Eq{"`org_user`.uid": user.ID}))))
+			userOrgPublicRepoCond(user.ID),
+		)
 	}
 
 	return cond
diff --git a/models/repo_permission.go b/models/repo_permission.go
index 45878c8ba4e0..40b63aa80431 100644
--- a/models/repo_permission.go
+++ b/models/repo_permission.go
@@ -400,26 +400,6 @@ func HasAccess(userID int64, repo *repo_model.Repository) (bool, error) {
 	return hasAccess(db.DefaultContext, userID, repo)
 }
 
-// FilterOutRepoIdsWithoutUnitAccess filter out repos where user has no access to repositories
-func FilterOutRepoIdsWithoutUnitAccess(u *user_model.User, repoIDs []int64, units ...unit.Type) ([]int64, error) {
-	i := 0
-	for _, rID := range repoIDs {
-		repo, err := repo_model.GetRepositoryByID(rID)
-		if err != nil {
-			return nil, err
-		}
-		perm, err := GetUserRepoPermission(repo, u)
-		if err != nil {
-			return nil, err
-		}
-		if perm.CanReadAny(units...) {
-			repoIDs[i] = rID
-			i++
-		}
-	}
-	return repoIDs[:i], nil
-}
-
 // GetRepoReaders returns all users that have explicit read access or higher to the repository.
 func GetRepoReaders(repo *repo_model.Repository) (_ []*user_model.User, err error) {
 	return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeRead)
diff --git a/models/user.go b/models/user.go
index 2e7a84273fae..5f7bedd36da2 100644
--- a/models/user.go
+++ b/models/user.go
@@ -15,7 +15,6 @@ import (
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
@@ -30,109 +29,6 @@ func GetOrganizationCount(ctx context.Context, u *user_model.User) (int64, error
 		Count(new(OrgUser))
 }
 
-// GetRepositoryIDs returns repositories IDs where user owned and has unittypes
-// Caller shall check that units is not globally disabled
-func GetRepositoryIDs(u *user_model.User, units ...unit.Type) ([]int64, error) {
-	var ids []int64
-
-	sess := db.GetEngine(db.DefaultContext).Table("repository").Cols("repository.id")
-
-	if len(units) > 0 {
-		sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id")
-		sess = sess.In("repo_unit.type", units)
-	}
-
-	return ids, sess.Where("owner_id = ?", u.ID).Find(&ids)
-}
-
-// GetActiveRepositoryIDs returns non-archived repositories IDs where user owned and has unittypes
-// Caller shall check that units is not globally disabled
-func GetActiveRepositoryIDs(u *user_model.User, units ...unit.Type) ([]int64, error) {
-	var ids []int64
-
-	sess := db.GetEngine(db.DefaultContext).Table("repository").Cols("repository.id")
-
-	if len(units) > 0 {
-		sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id")
-		sess = sess.In("repo_unit.type", units)
-	}
-
-	sess.Where(builder.Eq{"is_archived": false})
-
-	return ids, sess.Where("owner_id = ?", u.ID).GroupBy("repository.id").Find(&ids)
-}
-
-// GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes
-// Caller shall check that units is not globally disabled
-func GetOrgRepositoryIDs(u *user_model.User, units ...unit.Type) ([]int64, error) {
-	var ids []int64
-
-	if err := db.GetEngine(db.DefaultContext).Table("repository").
-		Cols("repository.id").
-		Join("INNER", "team_user", "repository.owner_id = team_user.org_id").
-		Join("INNER", "team_repo", "(? != ? and repository.is_private != ?) OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true, u.IsRestricted, true).
-		Where("team_user.uid = ?", u.ID).
-		GroupBy("repository.id").Find(&ids); err != nil {
-		return nil, err
-	}
-
-	if len(units) > 0 {
-		return FilterOutRepoIdsWithoutUnitAccess(u, ids, units...)
-	}
-
-	return ids, nil
-}
-
-// GetActiveOrgRepositoryIDs returns non-archived repositories IDs where user's team owned and has unittypes
-// Caller shall check that units is not globally disabled
-func GetActiveOrgRepositoryIDs(u *user_model.User, units ...unit.Type) ([]int64, error) {
-	var ids []int64
-
-	if err := db.GetEngine(db.DefaultContext).Table("repository").
-		Cols("repository.id").
-		Join("INNER", "team_user", "repository.owner_id = team_user.org_id").
-		Join("INNER", "team_repo", "(? != ? and repository.is_private != ?) OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true, u.IsRestricted, true).
-		Where("team_user.uid = ?", u.ID).
-		Where(builder.Eq{"is_archived": false}).
-		GroupBy("repository.id").Find(&ids); err != nil {
-		return nil, err
-	}
-
-	if len(units) > 0 {
-		return FilterOutRepoIdsWithoutUnitAccess(u, ids, units...)
-	}
-
-	return ids, nil
-}
-
-// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
-// Caller shall check that units is not globally disabled
-func GetAccessRepoIDs(u *user_model.User, units ...unit.Type) ([]int64, error) {
-	ids, err := GetRepositoryIDs(u, units...)
-	if err != nil {
-		return nil, err
-	}
-	ids2, err := GetOrgRepositoryIDs(u, units...)
-	if err != nil {
-		return nil, err
-	}
-	return append(ids, ids2...), nil
-}
-
-// GetActiveAccessRepoIDs returns all non-archived repositories IDs where user's or user is a team member organizations
-// Caller shall check that units is not globally disabled
-func GetActiveAccessRepoIDs(u *user_model.User, units ...unit.Type) ([]int64, error) {
-	ids, err := GetActiveRepositoryIDs(u, units...)
-	if err != nil {
-		return nil, err
-	}
-	ids2, err := GetActiveOrgRepositoryIDs(u, units...)
-	if err != nil {
-		return nil, err
-	}
-	return append(ids, ids2...), nil
-}
-
 // deleteBeans deletes all given beans, beans should contain delete conditions.
 func deleteBeans(e db.Engine, beans ...interface{}) (err error) {
 	for i := range beans {
diff --git a/models/user_test.go b/models/user_test.go
index 4749a3af7398..83201ff4cba4 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -98,25 +98,3 @@ func testIsUserOrgOwner(t *testing.T, uid, orgID int64, expected bool) {
 	assert.NoError(t, err)
 	assert.Equal(t, expected, is)
 }
-
-func TestGetOrgRepositoryIDs(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
-	user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-
-	accessibleRepos, err := GetOrgRepositoryIDs(user2)
-	assert.NoError(t, err)
-	// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization
-	assert.Equal(t, []int64{3, 5, 23, 24, 32}, accessibleRepos)
-
-	accessibleRepos, err = GetOrgRepositoryIDs(user4)
-	assert.NoError(t, err)
-	// User 4's team has access to private repo 3, repo 32 is a public repo of the organization
-	assert.Equal(t, []int64{3, 32}, accessibleRepos)
-
-	accessibleRepos, err = GetOrgRepositoryIDs(user5)
-	assert.NoError(t, err)
-	// User 5's team has no access to any repo
-	assert.Len(t, accessibleRepos, 0)
-}
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 975262cf9c78..8bbc6dd0c369 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -403,27 +403,26 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 	//       - Count Issues by repo
 	// --------------------------------------------------------------------------
 
+	// Get repository IDs where User/Org/Team has access.
+	var team *models.Team
+	var org *models.Organization
+	if ctx.Org != nil {
+		org = ctx.Org.Organization
+		team = ctx.Org.Team
+	}
+
 	isPullList := unitType == unit.TypePullRequests
 	opts := &models.IssuesOptions{
 		IsPull:     util.OptionalBoolOf(isPullList),
 		SortType:   sortType,
 		IsArchived: util.OptionalBoolFalse,
-	}
-
-	// Get repository IDs where User/Org/Team has access.
-	var team *models.Team
-	if ctx.Org != nil {
-		team = ctx.Org.Team
-	}
-	userRepoIDs, err := getActiveUserRepoIDs(ctxUser, team, unitType)
-	if err != nil {
-		ctx.ServerError("userRepoIDs", err)
-		return
+		Org:        org,
+		Team:       team,
+		User:       ctx.User,
 	}
 
 	switch filterMode {
 	case models.FilterModeAll:
-		opts.RepoIDs = userRepoIDs
 	case models.FilterModeAssign:
 		opts.AssigneeID = ctx.User.ID
 	case models.FilterModeCreate:
@@ -434,10 +433,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 		opts.ReviewRequestedID = ctx.User.ID
 	}
 
-	if ctxUser.IsOrganization() {
-		opts.RepoIDs = userRepoIDs
-	}
-
 	// keyword holds the search term entered into the search field.
 	keyword := strings.Trim(ctx.FormString("q"), " ")
 	ctx.Data["Keyword"] = keyword
@@ -524,27 +519,25 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 	// ----------------------------------
 
 	// showReposMap maps repository IDs to their Repository pointers.
-	showReposMap, err := repoIDMap(ctxUser, issueCountByRepo, unitType)
+	showReposMap, err := loadRepoByIDs(ctxUser, issueCountByRepo, unitType)
 	if err != nil {
 		if repo_model.IsErrRepoNotExist(err) {
 			ctx.NotFound("GetRepositoryByID", err)
 			return
 		}
-		ctx.ServerError("repoIDMap", err)
+		ctx.ServerError("loadRepoByIDs", err)
 		return
 	}
 
 	// a RepositoryList
 	showRepos := models.RepositoryListOfMap(showReposMap)
 	sort.Sort(showRepos)
-	if err = showRepos.LoadAttributes(); err != nil {
-		ctx.ServerError("LoadAttributes", err)
-		return
-	}
 
 	// maps pull request IDs to their CommitStatus. Will be posted to ctx.Data.
 	for _, issue := range issues {
-		issue.Repo = showReposMap[issue.RepoID]
+		if issue.Repo == nil {
+			issue.Repo = showReposMap[issue.RepoID]
+		}
 	}
 
 	commitStatus, err := pull_service.GetIssuesLastCommitStatus(issues)
@@ -556,86 +549,39 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 	// -------------------------------
 	// Fill stats to post to ctx.Data.
 	// -------------------------------
-
-	userIssueStatsOpts := models.UserIssueStatsOptions{
-		UserID:      ctx.User.ID,
-		UserRepoIDs: userRepoIDs,
-		FilterMode:  filterMode,
-		IsPull:      isPullList,
-		IsClosed:    isShowClosed,
-		IsArchived:  util.OptionalBoolFalse,
-		LabelIDs:    opts.LabelIDs,
-	}
-	if len(repoIDs) > 0 {
-		userIssueStatsOpts.UserRepoIDs = repoIDs
-	}
-	if ctxUser.IsOrganization() {
-		userIssueStatsOpts.RepoIDs = userRepoIDs
-	}
-	userIssueStats, err := models.GetUserIssueStats(userIssueStatsOpts)
-	if err != nil {
-		ctx.ServerError("GetUserIssueStats User", err)
-		return
-	}
-
-	var shownIssueStats *models.IssueStats
+	var issueStats *models.IssueStats
 	if !forceEmpty {
 		statsOpts := models.UserIssueStatsOptions{
-			UserID:      ctx.User.ID,
-			UserRepoIDs: userRepoIDs,
-			FilterMode:  filterMode,
-			IsPull:      isPullList,
-			IsClosed:    isShowClosed,
-			IssueIDs:    issueIDsFromSearch,
-			IsArchived:  util.OptionalBoolFalse,
-			LabelIDs:    opts.LabelIDs,
+			UserID:     ctx.User.ID,
+			FilterMode: filterMode,
+			IsPull:     isPullList,
+			IsClosed:   isShowClosed,
+			IssueIDs:   issueIDsFromSearch,
+			IsArchived: util.OptionalBoolFalse,
+			LabelIDs:   opts.LabelIDs,
+			Org:        org,
+			Team:       team,
 		}
 		if len(repoIDs) > 0 {
 			statsOpts.RepoIDs = repoIDs
-		} else if ctxUser.IsOrganization() {
-			statsOpts.RepoIDs = userRepoIDs
 		}
-		shownIssueStats, err = models.GetUserIssueStats(statsOpts)
+		issueStats, err = models.GetUserIssueStats(statsOpts)
 		if err != nil {
 			ctx.ServerError("GetUserIssueStats Shown", err)
 			return
 		}
 	} else {
-		shownIssueStats = &models.IssueStats{}
-	}
-
-	var allIssueStats *models.IssueStats
-	if !forceEmpty {
-		allIssueStatsOpts := models.UserIssueStatsOptions{
-			UserID:      ctx.User.ID,
-			UserRepoIDs: userRepoIDs,
-			FilterMode:  filterMode,
-			IsPull:      isPullList,
-			IsClosed:    isShowClosed,
-			IssueIDs:    issueIDsFromSearch,
-			IsArchived:  util.OptionalBoolFalse,
-			LabelIDs:    opts.LabelIDs,
-		}
-		if ctxUser.IsOrganization() {
-			allIssueStatsOpts.RepoIDs = userRepoIDs
-		}
-		allIssueStats, err = models.GetUserIssueStats(allIssueStatsOpts)
-		if err != nil {
-			ctx.ServerError("GetUserIssueStats All", err)
-			return
-		}
-	} else {
-		allIssueStats = &models.IssueStats{}
+		issueStats = &models.IssueStats{}
 	}
 
 	// Will be posted to ctx.Data.
 	var shownIssues int
 	if !isShowClosed {
-		shownIssues = int(shownIssueStats.OpenCount)
-		ctx.Data["TotalIssueCount"] = int(allIssueStats.OpenCount)
+		shownIssues = int(issueStats.OpenCount)
+		ctx.Data["TotalIssueCount"] = shownIssues
 	} else {
-		shownIssues = int(shownIssueStats.ClosedCount)
-		ctx.Data["TotalIssueCount"] = int(allIssueStats.ClosedCount)
+		shownIssues = int(issueStats.ClosedCount)
+		ctx.Data["TotalIssueCount"] = shownIssues
 	}
 
 	ctx.Data["IsShowClosed"] = isShowClosed
@@ -671,8 +617,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 	ctx.Data["CommitStatus"] = commitStatus
 	ctx.Data["Repos"] = showRepos
 	ctx.Data["Counts"] = issueCountByRepo
-	ctx.Data["IssueStats"] = userIssueStats
-	ctx.Data["ShownIssueStats"] = shownIssueStats
+	ctx.Data["IssueStats"] = issueStats
 	ctx.Data["ViewType"] = viewType
 	ctx.Data["SortType"] = sortType
 	ctx.Data["RepoIDs"] = repoIDs
@@ -730,56 +675,6 @@ func getRepoIDs(reposQuery string) []int64 {
 	return repoIDs
 }
 
-func getActiveUserRepoIDs(ctxUser *user_model.User, team *models.Team, unitType unit.Type) ([]int64, error) {
-	var userRepoIDs []int64
-	var err error
-
-	if ctxUser.IsOrganization() {
-		userRepoIDs, err = getActiveTeamOrOrgRepoIds(ctxUser, team, unitType)
-		if err != nil {
-			return nil, fmt.Errorf("orgRepoIds: %v", err)
-		}
-	} else {
-		userRepoIDs, err = models.GetActiveAccessRepoIDs(ctxUser, unitType)
-		if err != nil {
-			return nil, fmt.Errorf("ctxUser.GetAccessRepoIDs: %v", err)
-		}
-	}
-
-	if len(userRepoIDs) == 0 {
-		userRepoIDs = []int64{-1}
-	}
-
-	return userRepoIDs, nil
-}
-
-// getActiveTeamOrOrgRepoIds gets RepoIDs for ctxUser as Organization.
-// Should be called if and only if ctxUser.IsOrganization == true.
-func getActiveTeamOrOrgRepoIds(ctxUser *user_model.User, team *models.Team, unitType unit.Type) ([]int64, error) {
-	var orgRepoIDs []int64
-	var err error
-	var env models.AccessibleReposEnvironment
-
-	if team != nil {
-		env = models.OrgFromUser(ctxUser).AccessibleTeamReposEnv(team)
-	} else {
-		env, err = models.OrgFromUser(ctxUser).AccessibleReposEnv(ctxUser.ID)
-		if err != nil {
-			return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
-		}
-	}
-	orgRepoIDs, err = env.RepoIDs(1, ctxUser.NumRepos)
-	if err != nil {
-		return nil, fmt.Errorf("env.RepoIDs: %v", err)
-	}
-	orgRepoIDs, err = models.FilterOutRepoIdsWithoutUnitAccess(ctxUser, orgRepoIDs, unitType)
-	if err != nil {
-		return nil, fmt.Errorf("FilterOutRepoIdsWithoutUnitAccess: %v", err)
-	}
-
-	return orgRepoIDs, nil
-}
-
 func issueIDsFromSearch(ctxUser *user_model.User, keyword string, opts *models.IssuesOptions) ([]int64, error) {
 	if len(keyword) == 0 {
 		return []int64{}, nil
@@ -797,33 +692,27 @@ func issueIDsFromSearch(ctxUser *user_model.User, keyword string, opts *models.I
 	return issueIDsFromSearch, nil
 }
 
-func repoIDMap(ctxUser *user_model.User, issueCountByRepo map[int64]int64, unitType unit.Type) (map[int64]*repo_model.Repository, error) {
-	repoByID := make(map[int64]*repo_model.Repository, len(issueCountByRepo))
+func loadRepoByIDs(ctxUser *user_model.User, issueCountByRepo map[int64]int64, unitType unit.Type) (map[int64]*repo_model.Repository, error) {
+	var totalRes = make(map[int64]*repo_model.Repository, len(issueCountByRepo))
+	var repoIDs = make([]int64, 0, 500)
 	for id := range issueCountByRepo {
 		if id <= 0 {
 			continue
 		}
-		if _, ok := repoByID[id]; !ok {
-			repo, err := repo_model.GetRepositoryByID(id)
-			if repo_model.IsErrRepoNotExist(err) {
+		repoIDs = append(repoIDs, id)
+		if len(repoIDs) == 500 {
+			if err := repo_model.FindReposMapByIDs(repoIDs, totalRes); err != nil {
 				return nil, err
-			} else if err != nil {
-				return nil, fmt.Errorf("GetRepositoryByID: [%d]%v", id, err)
 			}
-			repoByID[id] = repo
-		}
-		repo := repoByID[id]
-
-		// Check if user has access to given repository.
-		perm, err := models.GetUserRepoPermission(repo, ctxUser)
-		if err != nil {
-			return nil, fmt.Errorf("GetUserRepoPermission: [%d]%v", id, err)
-		}
-		if !perm.CanRead(unitType) {
-			log.Debug("User created Issues in Repository which they no longer have access to: [%d]", id)
+			repoIDs = repoIDs[:0]
 		}
 	}
-	return repoByID, nil
+	if len(repoIDs) > 0 {
+		if err := repo_model.FindReposMapByIDs(repoIDs, totalRes); err != nil {
+			return nil, err
+		}
+	}
+	return totalRes, nil
 }
 
 // ShowSSHKeys output all the ssh keys of user by uid
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl
index aca61f9ae906..dc66859b8371 100644
--- a/templates/user/dashboard/issues.tmpl
+++ b/templates/user/dashboard/issues.tmpl
@@ -61,11 +61,11 @@
 						<div class="ui compact tiny menu">
 							<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}">
 								{{svg "octicon-issue-opened" 16 "mr-3"}}
-								{{.i18n.Tr "repo.issues.open_tab" .ShownIssueStats.OpenCount}}
+								{{.i18n.Tr "repo.issues.open_tab" .IssueStats.OpenCount}}
 							</a>
 							<a class="item{{if .IsShowClosed}} active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed&q={{$.Keyword}}">
 								{{svg "octicon-issue-closed" 16 "mr-3"}}
-								{{.i18n.Tr "repo.issues.close_tab" .ShownIssueStats.ClosedCount}}
+								{{.i18n.Tr "repo.issues.close_tab" .IssueStats.ClosedCount}}
 							</a>
 						</div>
 					</div>