mirror of https://github.com/go-gitea/gitea
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>pull/32498/head^2
parent
3f9c3e7bc3
commit
b4abb6deff
@ -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 |
|
||||||
} |
|
@ -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 |
||||||
|
} |
@ -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) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue