mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Reimplement GetUserOrgsList to make it simple and clear (#32486)
Reimplement GetUserOrgsList and also move some functions and test to org_list file. --------- Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
		| @@ -1,78 +0,0 @@ | ||||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package organization | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"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" | ||||
|  | ||||
| 	"xorm.io/builder" | ||||
| ) | ||||
|  | ||||
| // MinimalOrg represents a simple organization with only the needed columns | ||||
| type MinimalOrg = Organization | ||||
|  | ||||
| // GetUserOrgsList returns all organizations the given user has access to | ||||
| func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) { | ||||
| 	schema, err := db.TableInfo(new(user_model.User)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	outputCols := []string{ | ||||
| 		"id", | ||||
| 		"name", | ||||
| 		"full_name", | ||||
| 		"visibility", | ||||
| 		"avatar", | ||||
| 		"avatar_email", | ||||
| 		"use_custom_avatar", | ||||
| 	} | ||||
|  | ||||
| 	groupByCols := &strings.Builder{} | ||||
| 	for _, col := range outputCols { | ||||
| 		fmt.Fprintf(groupByCols, "`%s`.%s,", schema.Name, col) | ||||
| 	} | ||||
| 	groupByStr := groupByCols.String() | ||||
| 	groupByStr = groupByStr[0 : len(groupByStr)-1] | ||||
|  | ||||
| 	sess := db.GetEngine(ctx) | ||||
| 	sess = sess.Select(groupByStr+", count(distinct repo_id) as org_count"). | ||||
| 		Table("user"). | ||||
| 		Join("INNER", "team", "`team`.org_id = `user`.id"). | ||||
| 		Join("INNER", "team_user", "`team`.id = `team_user`.team_id"). | ||||
| 		Join("LEFT", builder. | ||||
| 			Select("id as repo_id, owner_id as repo_owner_id"). | ||||
| 			From("repository"). | ||||
| 			Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), "`repository`.repo_owner_id = `team`.org_id"). | ||||
| 		Where("`team_user`.uid = ?", user.ID). | ||||
| 		GroupBy(groupByStr) | ||||
|  | ||||
| 	type OrgCount struct { | ||||
| 		Organization `xorm:"extends"` | ||||
| 		OrgCount     int | ||||
| 	} | ||||
|  | ||||
| 	orgCounts := make([]*OrgCount, 0, 10) | ||||
|  | ||||
| 	if err := sess. | ||||
| 		Asc("`user`.name"). | ||||
| 		Find(&orgCounts); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	orgs := make([]*MinimalOrg, len(orgCounts)) | ||||
| 	for i, orgCount := range orgCounts { | ||||
| 		orgCount.Organization.NumRepos = orgCount.OrgCount | ||||
| 		orgs[i] = &orgCount.Organization | ||||
| 	} | ||||
|  | ||||
| 	return orgs, nil | ||||
| } | ||||
| @@ -25,13 +25,6 @@ import ( | ||||
| 	"xorm.io/xorm" | ||||
| ) | ||||
|  | ||||
| // ________                            .__                __  .__ | ||||
| // \_____  \_______  _________    ____ |__|____________ _/  |_|__| ____   ____ | ||||
| //  /   |   \_  __ \/ ___\__  \  /    \|  \___   /\__  \\   __\  |/  _ \ /    \ | ||||
| // /    |    \  | \/ /_/  > __ \|   |  \  |/    /  / __ \|  | |  (  <_> )   |  \ | ||||
| // \_______  /__|  \___  (____  /___|  /__/_____ \(____  /__| |__|\____/|___|  / | ||||
| //         \/     /_____/     \/     \/         \/     \/                    \/ | ||||
|  | ||||
| // ErrOrgNotExist represents a "OrgNotExist" kind of error. | ||||
| type ErrOrgNotExist struct { | ||||
| 	ID   int64 | ||||
| @@ -465,42 +458,6 @@ func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*u | ||||
| 		And("team_user.org_id = ?", orgID).Find(&users) | ||||
| } | ||||
|  | ||||
| // SearchOrganizationsOptions options to filter organizations | ||||
| type SearchOrganizationsOptions struct { | ||||
| 	db.ListOptions | ||||
| 	All bool | ||||
| } | ||||
|  | ||||
| // FindOrgOptions finds orgs options | ||||
| type FindOrgOptions struct { | ||||
| 	db.ListOptions | ||||
| 	UserID         int64 | ||||
| 	IncludePrivate bool | ||||
| } | ||||
|  | ||||
| func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { | ||||
| 	cond := builder.Eq{"uid": userID} | ||||
| 	if !includePrivate { | ||||
| 		cond["is_public"] = true | ||||
| 	} | ||||
| 	return builder.Select("org_id").From("org_user").Where(cond) | ||||
| } | ||||
|  | ||||
| func (opts FindOrgOptions) ToConds() builder.Cond { | ||||
| 	var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization} | ||||
| 	if opts.UserID > 0 { | ||||
| 		cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate))) | ||||
| 	} | ||||
| 	if !opts.IncludePrivate { | ||||
| 		cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}) | ||||
| 	} | ||||
| 	return cond | ||||
| } | ||||
|  | ||||
| func (opts FindOrgOptions) ToOrders() string { | ||||
| 	return "`user`.name ASC" | ||||
| } | ||||
|  | ||||
| // HasOrgOrUserVisible tells if the given user can see the given org or user | ||||
| func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool { | ||||
| 	// If user is nil, it's an anonymous user/request. | ||||
| @@ -533,20 +490,6 @@ func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model. | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID | ||||
| // are allowed to create repos. | ||||
| func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { | ||||
| 	orgs := make([]*Organization, 0, 10) | ||||
|  | ||||
| 	return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). | ||||
| 		Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). | ||||
| 		Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). | ||||
| 		Where(builder.Eq{"`team_user`.uid": userID}). | ||||
| 		And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))). | ||||
| 		Asc("`user`.name"). | ||||
| 		Find(&orgs) | ||||
| } | ||||
|  | ||||
| // GetOrgUsersByOrgID returns all organization-user relations by organization ID. | ||||
| func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) { | ||||
| 	sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID) | ||||
|   | ||||
							
								
								
									
										138
									
								
								models/organization/org_list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								models/organization/org_list.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| // Copyright 2024 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package organization | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/models/perm" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/structs" | ||||
|  | ||||
| 	"xorm.io/builder" | ||||
| ) | ||||
|  | ||||
| // SearchOrganizationsOptions options to filter organizations | ||||
| type SearchOrganizationsOptions struct { | ||||
| 	db.ListOptions | ||||
| 	All bool | ||||
| } | ||||
|  | ||||
| // FindOrgOptions finds orgs options | ||||
| type FindOrgOptions struct { | ||||
| 	db.ListOptions | ||||
| 	UserID         int64 | ||||
| 	IncludePrivate bool | ||||
| } | ||||
|  | ||||
| func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { | ||||
| 	cond := builder.Eq{"uid": userID} | ||||
| 	if !includePrivate { | ||||
| 		cond["is_public"] = true | ||||
| 	} | ||||
| 	return builder.Select("org_id").From("org_user").Where(cond) | ||||
| } | ||||
|  | ||||
| func (opts FindOrgOptions) ToConds() builder.Cond { | ||||
| 	var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization} | ||||
| 	if opts.UserID > 0 { | ||||
| 		cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate))) | ||||
| 	} | ||||
| 	if !opts.IncludePrivate { | ||||
| 		cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}) | ||||
| 	} | ||||
| 	return cond | ||||
| } | ||||
|  | ||||
| func (opts FindOrgOptions) ToOrders() string { | ||||
| 	return "`user`.lower_name ASC" | ||||
| } | ||||
|  | ||||
| // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID | ||||
| // are allowed to create repos. | ||||
| func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { | ||||
| 	orgs := make([]*Organization, 0, 10) | ||||
|  | ||||
| 	return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). | ||||
| 		Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). | ||||
| 		Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). | ||||
| 		Where(builder.Eq{"`team_user`.uid": userID}). | ||||
| 		And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))). | ||||
| 		Asc("`user`.name"). | ||||
| 		Find(&orgs) | ||||
| } | ||||
|  | ||||
| // MinimalOrg represents a simple organization with only the needed columns | ||||
| type MinimalOrg = Organization | ||||
|  | ||||
| // GetUserOrgsList returns all organizations the given user has access to | ||||
| func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) { | ||||
| 	schema, err := db.TableInfo(new(user_model.User)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	outputCols := []string{ | ||||
| 		"id", | ||||
| 		"name", | ||||
| 		"full_name", | ||||
| 		"visibility", | ||||
| 		"avatar", | ||||
| 		"avatar_email", | ||||
| 		"use_custom_avatar", | ||||
| 	} | ||||
|  | ||||
| 	selectColumns := &strings.Builder{} | ||||
| 	for i, col := range outputCols { | ||||
| 		fmt.Fprintf(selectColumns, "`%s`.%s", schema.Name, col) | ||||
| 		if i < len(outputCols)-1 { | ||||
| 			selectColumns.WriteString(", ") | ||||
| 		} | ||||
| 	} | ||||
| 	columnsStr := selectColumns.String() | ||||
|  | ||||
| 	var orgs []*MinimalOrg | ||||
| 	if err := db.GetEngine(ctx).Select(columnsStr). | ||||
| 		Table("user"). | ||||
| 		Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))). | ||||
| 		Find(&orgs); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	type orgCount struct { | ||||
| 		OrgID     int64 | ||||
| 		RepoCount int | ||||
| 	} | ||||
| 	var orgCounts []orgCount | ||||
| 	if err := db.GetEngine(ctx). | ||||
| 		Select("owner_id AS org_id, COUNT(DISTINCT(repository.id)) as repo_count"). | ||||
| 		Table("repository"). | ||||
| 		Join("INNER", "org_user", "owner_id = org_user.org_id"). | ||||
| 		Where("org_user.uid = ?", user.ID). | ||||
| 		And(builder.Or( | ||||
| 			builder.Eq{"repository.is_private": false}, | ||||
| 			builder.In("repository.id", builder.Select("repo_id").From("team_repo"). | ||||
| 				InnerJoin("team_user", "team_user.team_id = team_repo.team_id"). | ||||
| 				Where(builder.Eq{"team_user.uid": user.ID})), | ||||
| 			builder.In("repository.id", builder.Select("repo_id").From("collaboration"). | ||||
| 				Where(builder.Eq{"user_id": user.ID})), | ||||
| 		)). | ||||
| 		GroupBy("owner_id").Find(&orgCounts); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	orgCountMap := make(map[int64]int, len(orgCounts)) | ||||
| 	for _, orgCount := range orgCounts { | ||||
| 		orgCountMap[orgCount.OrgID] = orgCount.RepoCount | ||||
| 	} | ||||
|  | ||||
| 	for _, org := range orgs { | ||||
| 		org.NumRepos = orgCountMap[org.ID] | ||||
| 	} | ||||
|  | ||||
| 	return orgs, nil | ||||
| } | ||||
							
								
								
									
										62
									
								
								models/organization/org_list_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								models/organization/org_list_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| // Copyright 2024 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package organization_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/models/organization" | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestCountOrganizations(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{}) | ||||
| 	assert.NoError(t, err) | ||||
| 	cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, expected, cnt) | ||||
| } | ||||
|  | ||||
| func TestFindOrgs(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
|  | ||||
| 	orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ | ||||
| 		UserID:         4, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	if assert.Len(t, orgs, 1) { | ||||
| 		assert.EqualValues(t, 3, orgs[0].ID) | ||||
| 	} | ||||
|  | ||||
| 	orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ | ||||
| 		UserID:         4, | ||||
| 		IncludePrivate: false, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, orgs, 0) | ||||
|  | ||||
| 	total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ | ||||
| 		UserID:         4, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.EqualValues(t, 1, total) | ||||
| } | ||||
|  | ||||
| func TestGetUserOrgsList(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4}) | ||||
| 	assert.NoError(t, err) | ||||
| 	if assert.Len(t, orgs, 1) { | ||||
| 		assert.EqualValues(t, 3, orgs[0].ID) | ||||
| 		// repo_id: 3 is in the team, 32 is public, 5 is private with no team | ||||
| 		assert.EqualValues(t, 2, orgs[0].NumRepos) | ||||
| 	} | ||||
| } | ||||
| @@ -129,15 +129,6 @@ func TestGetOrgByName(t *testing.T) { | ||||
| 	assert.True(t, organization.IsErrOrgNotExist(err)) | ||||
| } | ||||
|  | ||||
| func TestCountOrganizations(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{}) | ||||
| 	assert.NoError(t, err) | ||||
| 	cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, expected, cnt) | ||||
| } | ||||
|  | ||||
| func TestIsOrganizationOwner(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	test := func(orgID, userID int64, expected bool) { | ||||
| @@ -251,33 +242,6 @@ func TestRestrictedUserOrgMembers(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFindOrgs(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
|  | ||||
| 	orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ | ||||
| 		UserID:         4, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	if assert.Len(t, orgs, 1) { | ||||
| 		assert.EqualValues(t, 3, orgs[0].ID) | ||||
| 	} | ||||
|  | ||||
| 	orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ | ||||
| 		UserID:         4, | ||||
| 		IncludePrivate: false, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, orgs, 0) | ||||
|  | ||||
| 	total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ | ||||
| 		UserID:         4, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.EqualValues(t, 1, total) | ||||
| } | ||||
|  | ||||
| func TestGetOrgUsersByOrgID(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	auth "code.gitea.io/gitea/models/auth" | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	org_model "code.gitea.io/gitea/models/organization" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| @@ -192,7 +193,10 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server | ||||
| // returns a list of "org" and "org:team" strings, | ||||
| // that the given user is a part of. | ||||
| func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User) ([]string, error) { | ||||
| 	orgs, err := org_model.GetUserOrgsList(ctx, user) | ||||
| 	orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{ | ||||
| 		UserID:         user.ID, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("GetUserOrgList: %w", err) | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user