From 9c318a17f576887736e97c7bc2971d2ad7579a33 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Wed, 2 Feb 2022 08:40:04 +0000
Subject: [PATCH] Add `GetUserTeams` (#18499) (#18531)

Backport #18499

* Correct use `UserID` in `SearchTeams`

- Use `UserID` in the `SearchTeams` function, currently it was useless
to pass such information. Now it does a INNER statement to `team_user`
which obtains UserID -> TeamID data.
- Make OrgID optional.
- Resolves #18484

* Seperate searching specific user

* Add condition back

* Use correct struct type

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
---
 models/org_team.go                | 61 +++++++++++++++++++++++++++----
 models/org_team_test.go           |  4 +-
 modules/repository/create_test.go |  2 +-
 routers/api/v1/org/team.go        | 11 +++---
 routers/web/org/teams.go          |  2 +-
 5 files changed, 62 insertions(+), 18 deletions(-)

diff --git a/models/org_team.go b/models/org_team.go
index bce4afb061..17f95bb5b0 100644
--- a/models/org_team.go
+++ b/models/org_team.go
@@ -49,22 +49,67 @@ func init() {
 	db.RegisterModel(new(TeamUnit))
 }
 
-// SearchTeamOptions holds the search options
-type SearchTeamOptions struct {
+// SearchOrgTeamOptions holds the search options
+type SearchOrgTeamOptions struct {
 	db.ListOptions
-	UserID      int64
 	Keyword     string
 	OrgID       int64
 	IncludeDesc bool
 }
 
+// GetUserTeamOptions holds the search options.
+type GetUserTeamOptions struct {
+	db.ListOptions
+	UserID int64
+}
+
 // SearchMembersOptions holds the search options
 type SearchMembersOptions struct {
 	db.ListOptions
 }
 
-// SearchTeam search for teams. Caller is responsible to check permissions.
-func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
+// GetUserTeams search for org teams. Caller is responsible to check permissions.
+func GetUserTeams(opts *GetUserTeamOptions) ([]*Team, int64, error) {
+	if opts.Page <= 0 {
+		opts.Page = 1
+	}
+	if opts.PageSize == 0 {
+		// Default limit
+		opts.PageSize = 10
+	}
+
+	sess := db.GetEngine(db.DefaultContext)
+
+	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
+		And("team_user.uid=?", opts.UserID)
+
+	count, err := sess.
+		Count(new(Team))
+	if err != nil {
+		return nil, 0, err
+	}
+
+	if opts.PageSize == -1 {
+		opts.PageSize = int(count)
+	} else {
+		sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
+	}
+
+	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
+		And("team_user.uid=?", opts.UserID)
+
+	teams := make([]*Team, 0, opts.PageSize)
+	if err = sess.
+		OrderBy("lower_name").
+		Find(&teams); err != nil {
+		return nil, 0, err
+	}
+
+	return teams, count, nil
+}
+
+// SearchOrgTeams search for org teams. Caller is responsible to check permissions.
+func SearchOrgTeams(opts *SearchOrgTeamOptions) ([]*Team, int64, error) {
 	if opts.Page <= 0 {
 		opts.Page = 1
 	}
@@ -196,7 +241,7 @@ func (t *Team) getRepositories(e db.Engine) error {
 }
 
 // GetRepositories returns paginated repositories in team of organization.
-func (t *Team) GetRepositories(opts *SearchTeamOptions) error {
+func (t *Team) GetRepositories(opts *SearchOrgTeamOptions) error {
 	if opts.Page == 0 {
 		return t.getRepositories(db.GetEngine(db.DefaultContext))
 	}
@@ -716,7 +761,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
 // DeleteTeam deletes given team.
 // It's caller's responsibility to assign organization ID.
 func DeleteTeam(t *Team) error {
-	if err := t.GetRepositories(&SearchTeamOptions{}); err != nil {
+	if err := t.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 		return err
 	}
 
@@ -858,7 +903,7 @@ func AddTeamMember(team *Team, userID int64) error {
 	}
 
 	// Get team and its repositories.
-	if err := team.GetRepositories(&SearchTeamOptions{}); err != nil {
+	if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
 		return err
 	}
 
diff --git a/models/org_team_test.go b/models/org_team_test.go
index aa62cc58e2..cf3a797991 100644
--- a/models/org_team_test.go
+++ b/models/org_team_test.go
@@ -46,7 +46,7 @@ func TestTeam_GetRepositories(t *testing.T) {
 
 	test := func(teamID int64) {
 		team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
-		assert.NoError(t, team.GetRepositories(&SearchTeamOptions{}))
+		assert.NoError(t, team.GetRepositories(&SearchOrgTeamOptions{}))
 		assert.Len(t, team.Repos, team.NumRepos)
 		for _, repo := range team.Repos {
 			unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
@@ -292,7 +292,7 @@ func TestGetTeamMembers(t *testing.T) {
 func TestGetUserTeams(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	test := func(userID int64) {
-		teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID})
+		teams, _, err := GetUserTeams(&GetUserTeamOptions{UserID: userID})
 		assert.NoError(t, err)
 		for _, team := range teams {
 			unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})
diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go
index ed890ace43..4e232a8609 100644
--- a/modules/repository/create_test.go
+++ b/modules/repository/create_test.go
@@ -23,7 +23,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
 
 	testTeamRepositories := func(teamID int64, repoIds []int64) {
 		team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
-		assert.NoError(t, team.GetRepositories(&models.SearchTeamOptions{}), "%s: GetRepositories", team.Name)
+		assert.NoError(t, team.GetRepositories(&models.SearchOrgTeamOptions{}), "%s: GetRepositories", team.Name)
 		assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
 		assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name)
 		for i, rid := range repoIds {
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index cc7a63af33..62e6c0a6b4 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -47,7 +47,7 @@ func ListTeams(ctx *context.APIContext) {
 	//   "200":
 	//     "$ref": "#/responses/TeamList"
 
-	teams, count, err := models.SearchTeam(&models.SearchTeamOptions{
+	teams, count, err := models.SearchOrgTeams(&models.SearchOrgTeamOptions{
 		ListOptions: utils.GetListOptions(ctx),
 		OrgID:       ctx.Org.Organization.ID,
 	})
@@ -90,7 +90,7 @@ func ListUserTeams(ctx *context.APIContext) {
 	//   "200":
 	//     "$ref": "#/responses/TeamList"
 
-	teams, count, err := models.SearchTeam(&models.SearchTeamOptions{
+	teams, count, err := models.GetUserTeams(&models.GetUserTeamOptions{
 		ListOptions: utils.GetListOptions(ctx),
 		UserID:      ctx.User.ID,
 	})
@@ -533,7 +533,7 @@ func GetTeamRepos(ctx *context.APIContext) {
 	//     "$ref": "#/responses/RepositoryList"
 
 	team := ctx.Org.Team
-	if err := team.GetRepositories(&models.SearchTeamOptions{
+	if err := team.GetRepositories(&models.SearchOrgTeamOptions{
 		ListOptions: utils.GetListOptions(ctx),
 	}); err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
@@ -707,15 +707,14 @@ func SearchTeam(ctx *context.APIContext) {
 
 	listOptions := utils.GetListOptions(ctx)
 
-	opts := &models.SearchTeamOptions{
-		UserID:      ctx.User.ID,
+	opts := &models.SearchOrgTeamOptions{
 		Keyword:     ctx.FormTrim("q"),
 		OrgID:       ctx.Org.Organization.ID,
 		IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
 		ListOptions: listOptions,
 	}
 
-	teams, maxResults, err := models.SearchTeam(opts)
+	teams, maxResults, err := models.SearchOrgTeams(opts)
 	if err != nil {
 		log.Error("SearchTeam failed: %v", err)
 		ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index 732f24b22c..9b0212e569 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -319,7 +319,7 @@ func TeamRepositories(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Org.Team.Name
 	ctx.Data["PageIsOrgTeams"] = true
 	ctx.Data["PageIsOrgTeamRepos"] = true
-	if err := ctx.Org.Team.GetRepositories(&models.SearchTeamOptions{}); err != nil {
+	if err := ctx.Org.Team.GetRepositories(&models.SearchOrgTeamOptions{}); err != nil {
 		ctx.ServerError("GetRepositories", err)
 		return
 	}