mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-28 23:44:04 +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> (cherry picked from commit b4abb6deff14b741c7666d7579e0eea68443306c) Conflicts: models/organization/org_test.go services/oauth2_provider/access_token.go trivial conflicts due to codeblocks moving to different files
This commit is contained in:
parent
7751bb64cb
commit
f6a46055aa
5 changed files with 201 additions and 171 deletions
|
@ -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
|
|
||||||
}
|
|
|
@ -24,13 +24,6 @@ import (
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ________ .__ __ .__
|
|
||||||
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
|
|
||||||
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
|
|
||||||
// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \
|
|
||||||
// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| /
|
|
||||||
// \/ /_____/ \/ \/ \/ \/ \/
|
|
||||||
|
|
||||||
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
||||||
type ErrOrgNotExist struct {
|
type ErrOrgNotExist struct {
|
||||||
ID int64
|
ID int64
|
||||||
|
@ -446,42 +439,6 @@ func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*u
|
||||||
And("team_user.org_id = ?", orgID).Find(&users)
|
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
|
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||||
// If user is nil, it's an anonymous user/request.
|
// If user is nil, it's an anonymous user/request.
|
||||||
|
@ -514,20 +471,6 @@ func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model.
|
||||||
return false
|
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.
|
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
|
||||||
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
||||||
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
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
|
||||||
|
}
|
63
models/organization/org_list_test.go
Normal file
63
models/organization/org_list_test.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// 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"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCountOrganizations(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindOrgs(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
require.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,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, orgs)
|
||||||
|
|
||||||
|
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserOrgsList(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
|
||||||
|
require.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))
|
assert.True(t, organization.IsErrOrgNotExist(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCountOrganizations(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, expected, cnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsOrganizationOwner(t *testing.T) {
|
func TestIsOrganizationOwner(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
test := func(orgID, userID int64, expected bool) {
|
test := func(orgID, userID int64, expected bool) {
|
||||||
|
@ -182,33 +173,6 @@ func TestIsPublicMembership(t *testing.T) {
|
||||||
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindOrgs(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
|
||||||
UserID: 4,
|
|
||||||
IncludePrivate: true,
|
|
||||||
})
|
|
||||||
require.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,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, orgs)
|
|
||||||
|
|
||||||
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
|
||||||
UserID: 4,
|
|
||||||
IncludePrivate: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 1, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetOrgUsersByOrgID(t *testing.T) {
|
func TestGetOrgUsersByOrgID(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue