diff --git a/integrations/api_repo_teams_test.go b/integrations/api_repo_teams_test.go index a3baeba63c..efd6ddb457 100644 --- a/integrations/api_repo_teams_test.go +++ b/integrations/api_repo_teams_test.go @@ -37,7 +37,7 @@ func TestAPIRepoTeams(t *testing.T) { DecodeJSON(t, res, &teams) if assert.Len(t, teams, 2) { assert.EqualValues(t, "Owners", teams[0].Name) - assert.False(t, teams[0].CanCreateOrgRepo) + assert.True(t, teams[0].CanCreateOrgRepo) assert.True(t, util.IsEqualSlice(unit.AllUnitKeyNames(), teams[0].Units), fmt.Sprintf("%v == %v", unit.AllUnitKeyNames(), teams[0].Units)) assert.EqualValues(t, "owner", teams[0].Permission) diff --git a/integrations/api_team_test.go b/integrations/api_team_test.go index a622c63145..7f6d1b81d4 100644 --- a/integrations/api_team_test.go +++ b/integrations/api_team_test.go @@ -10,7 +10,7 @@ import ( "sort" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -23,8 +23,8 @@ import ( func TestAPITeam(t *testing.T) { defer prepareTestEnv(t)() - teamUser := unittest.AssertExistsAndLoadBean(t, &models.TeamUser{}).(*models.TeamUser) - team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamUser.TeamID}).(*models.Team) + teamUser := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{}).(*organization.TeamUser) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamUser.TeamID}).(*organization.Team) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}).(*user_model.User) session := loginUser(t, user.Name) @@ -38,7 +38,7 @@ func TestAPITeam(t *testing.T) { assert.Equal(t, team.Name, apiTeam.Name) // non team member user will not access the teams details - teamUser2 := unittest.AssertExistsAndLoadBean(t, &models.TeamUser{ID: 3}).(*models.TeamUser) + teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}).(*organization.TeamUser) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}).(*user_model.User) session = loginUser(t, user2.Name) @@ -107,7 +107,7 @@ func TestAPITeam(t *testing.T) { teamToEdit.Permission, unit.AllUnitKeyNames(), nil) // Read team. - teamRead := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) + teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) assert.NoError(t, teamRead.GetUnits()) req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID) resp = session.MakeRequest(t, req, http.StatusOK) @@ -119,7 +119,7 @@ func TestAPITeam(t *testing.T) { // Delete team. req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID) session.MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &models.Team{ID: teamID}) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) // create team again via UnitsMap // Create team. @@ -173,7 +173,7 @@ func TestAPITeam(t *testing.T) { "read", nil, teamToEdit.UnitsMap) // Read team. - teamRead = unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) + teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID) resp = session.MakeRequest(t, req, http.StatusOK) apiTeam = api.Team{} @@ -185,7 +185,7 @@ func TestAPITeam(t *testing.T) { // Delete team. req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID) session.MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &models.Team{ID: teamID}) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) } func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) { @@ -206,7 +206,7 @@ func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string } func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) { - team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: id}).(*organization.Team) assert.NoError(t, team.GetUnits(), "GetUnits") checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units, unitsMap) } diff --git a/integrations/auth_ldap_test.go b/integrations/auth_ldap_test.go index 234e7c3ee9..93334aec00 100644 --- a/integrations/auth_ldap_test.go +++ b/integrations/auth_ldap_test.go @@ -12,6 +12,8 @@ import ( "testing" "code.gitea.io/gitea/models" + "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" "code.gitea.io/gitea/services/auth" @@ -317,37 +319,37 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) { } defer prepareTestEnv(t)() addAuthSourceLDAP(t, "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`) - org, err := models.GetOrgByName("org26") + org, err := organization.GetOrgByName("org26") assert.NoError(t, err) - team, err := models.GetTeam(org.ID, "team11") + team, err := organization.GetTeam(org.ID, "team11") assert.NoError(t, err) auth.SyncExternalUsers(context.Background(), true) for _, gitLDAPUser := range gitLDAPUsers { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ Name: gitLDAPUser.UserName, }).(*user_model.User) - usersOrgs, err := models.FindOrgs(models.FindOrgOptions{ + usersOrgs, err := organization.FindOrgs(organization.FindOrgOptions{ UserID: user.ID, IncludePrivate: true, }) assert.NoError(t, err) - allOrgTeams, err := models.GetUserOrgTeams(org.ID, user.ID) + allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID) assert.NoError(t, err) if user.Name == "fry" || user.Name == "leela" || user.Name == "bender" { // assert members of LDAP group "cn=ship_crew" are added to mapped teams assert.Equal(t, len(usersOrgs), 1, "User [%s] should be member of one organization", user.Name) assert.Equal(t, usersOrgs[0].Name, "org26", "Membership should be added to the right organization") - isMember, err := models.IsTeamMember(usersOrgs[0].ID, team.ID, user.ID) + isMember, err := organization.IsTeamMember(db.DefaultContext, usersOrgs[0].ID, team.ID, user.ID) assert.NoError(t, err) assert.True(t, isMember, "Membership should be added to the right team") - err = team.RemoveMember(user.ID) + err = models.RemoveTeamMember(team, user.ID) assert.NoError(t, err) - err = usersOrgs[0].RemoveMember(user.ID) + err = models.RemoveOrgUser(usersOrgs[0].ID, user.ID) assert.NoError(t, err) } else { // assert members of LDAP group "cn=admin_staff" keep initial team membership since mapped team does not exist assert.Empty(t, usersOrgs, "User should be member of no organization") - isMember, err := models.IsTeamMember(org.ID, team.ID, user.ID) + isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) assert.NoError(t, err) assert.False(t, isMember, "User should no be added to this team") assert.Empty(t, allOrgTeams, "User should not be added to any team") @@ -362,30 +364,30 @@ func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) { } defer prepareTestEnv(t)() addAuthSourceLDAP(t, "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) - org, err := models.GetOrgByName("org26") + org, err := organization.GetOrgByName("org26") assert.NoError(t, err) - team, err := models.GetTeam(org.ID, "team11") + team, err := organization.GetTeam(org.ID, "team11") assert.NoError(t, err) loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ Name: gitLDAPUsers[0].UserName, }).(*user_model.User) - err = org.AddMember(user.ID) + err = organization.AddOrgUser(org.ID, user.ID) assert.NoError(t, err) - err = team.AddMember(user.ID) + err = models.AddTeamMember(team, user.ID) assert.NoError(t, err) - isMember, err := models.IsOrganizationMember(org.ID, user.ID) + isMember, err := organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID) assert.NoError(t, err) assert.True(t, isMember, "User should be member of this organization") - isMember, err = models.IsTeamMember(org.ID, team.ID, user.ID) + isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) assert.NoError(t, err) assert.True(t, isMember, "User should be member of this team") // assert team member "professor" gets removed from org26 team11 loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password) - isMember, err = models.IsOrganizationMember(org.ID, user.ID) + isMember, err = organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID) assert.NoError(t, err) assert.False(t, isMember, "User membership should have been removed from organization") - isMember, err = models.IsTeamMember(org.ID, team.ID, user.ID) + isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) assert.NoError(t, err) assert.False(t, isMember, "User membership should have been removed from team") } diff --git a/integrations/delete_user_test.go b/integrations/delete_user_test.go index f24d75065f..4b67c05951 100644 --- a/integrations/delete_user_test.go +++ b/integrations/delete_user_test.go @@ -10,6 +10,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -21,9 +22,9 @@ func assertUserDeleted(t *testing.T, userID int64) { unittest.AssertNotExistsBean(t, &user_model.Follow{FollowID: userID}) unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerID: userID}) unittest.AssertNotExistsBean(t, &models.Access{UserID: userID}) - unittest.AssertNotExistsBean(t, &models.OrgUser{UID: userID}) + unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: userID}) unittest.AssertNotExistsBean(t, &models.IssueUser{UID: userID}) - unittest.AssertNotExistsBean(t, &models.TeamUser{UID: userID}) + unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID}) unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID}) } diff --git a/integrations/org_count_test.go b/integrations/org_count_test.go index b7d0da2f4b..eca51eb0f6 100644 --- a/integrations/org_count_test.go +++ b/integrations/org_count_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" @@ -117,7 +117,7 @@ func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, ca Name: username, }).(*user_model.User) - orgs, err := models.FindOrgs(models.FindOrgOptions{ + orgs, err := organization.FindOrgs(organization.FindOrgOptions{ UserID: user.ID, IncludePrivate: true, }) diff --git a/models/access.go b/models/access.go index 48b65c2c0f..d49d3430dc 100644 --- a/models/access.go +++ b/models/access.go @@ -10,6 +10,7 @@ import ( "fmt" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -149,7 +150,7 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i return fmt.Errorf("refreshCollaboratorAccesses: %v", err) } - teams, err := OrgFromUser(repo.Owner).loadTeams(e) + teams, err := organization.FindOrgTeams(ctx, repo.Owner.ID) if err != nil { return err } @@ -163,11 +164,11 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i // have relations with repository. if t.IsOwnerTeam() { t.AccessMode = perm.AccessModeOwner - } else if !t.hasRepository(e, repo.ID) { + } else if !hasRepository(ctx, t, repo.ID) { continue } - if err = t.getMembers(e); err != nil { + if err = t.GetMembersCtx(ctx); err != nil { return fmt.Errorf("getMembers '%d': %v", t.ID, err) } for _, m := range t.Members { @@ -198,7 +199,7 @@ func recalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid if err = repo.GetOwner(ctx); err != nil { return err } else if repo.Owner.IsOrganization() { - var teams []Team + var teams []organization.Team if err := e.Join("INNER", "team_repo", "team_repo.team_id = team.id"). Join("INNER", "team_user", "team_user.team_id = team.id"). Where("team.org_id = ?", repo.OwnerID). diff --git a/models/access_test.go b/models/access_test.go index 43e61e812b..7533381dca 100644 --- a/models/access_test.go +++ b/models/access_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -127,7 +128,7 @@ func TestRepository_RecalculateAccesses2(t *testing.T) { func TestRepository_RecalculateAccesses3(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - team5 := unittest.AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team) + team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team) user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}).(*user_model.User) has, err := db.GetEngine(db.DefaultContext).Get(&Access{UserID: 29, RepoID: 23}) diff --git a/models/action.go b/models/action.go index e9f5138b40..d3231641d9 100644 --- a/models/action.go +++ b/models/action.go @@ -15,6 +15,7 @@ import ( "time" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -318,7 +319,7 @@ func (a *Action) GetIssueContent() string { type GetFeedsOptions struct { db.ListOptions RequestedUser *user_model.User // the user we want activity for - RequestedTeam *Team // the team we want activity for + RequestedTeam *organization.Team // the team we want activity for RequestedRepo *repo_model.Repository // the repo we want activity for Actor *user_model.User // the user viewing the activity IncludePrivate bool // include private actions @@ -399,7 +400,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { } if opts.RequestedTeam != nil { - env := OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam) + env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam) teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos) if err != nil { return nil, fmt.Errorf("GetTeamRepositories: %v", err) diff --git a/models/branches.go b/models/branches.go index e6d8b7441a..8156fd0908 100644 --- a/models/branches.go +++ b/models/branches.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -94,7 +95,7 @@ func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool { return false } - in, err := IsUserInTeams(userID, protectBranch.WhitelistTeamIDs) + in, err := organization.IsUserInTeams(db.DefaultContext, userID, protectBranch.WhitelistTeamIDs) if err != nil { log.Error("IsUserInTeams: %v", err) return false @@ -117,7 +118,7 @@ func IsUserMergeWhitelisted(protectBranch *ProtectedBranch, userID int64, permis return false } - in, err := IsUserInTeams(userID, protectBranch.MergeWhitelistTeamIDs) + in, err := organization.IsUserInTeams(db.DefaultContext, userID, protectBranch.MergeWhitelistTeamIDs) if err != nil { log.Error("IsUserInTeams: %v", err) return false @@ -149,7 +150,7 @@ func isUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, return true, nil } - inTeam, err := isUserInTeams(db.GetEngine(ctx), user.ID, protectBranch.ApprovalsWhitelistTeamIDs) + inTeam, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ApprovalsWhitelistTeamIDs) if err != nil { return false, err } @@ -471,7 +472,7 @@ func updateTeamWhitelist(repo *repo_model.Repository, currentWhitelist, newWhite return currentWhitelist, nil } - teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) + teams, err := organization.GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) if err != nil { return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err) } diff --git a/models/error.go b/models/error.go index f0e8751d75..3c4d219775 100644 --- a/models/error.go +++ b/models/error.go @@ -58,19 +58,6 @@ func (err ErrUserHasOrgs) Error() string { return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID) } -// ErrUserNotAllowedCreateOrg represents a "UserNotAllowedCreateOrg" kind of error. -type ErrUserNotAllowedCreateOrg struct{} - -// IsErrUserNotAllowedCreateOrg checks if an error is an ErrUserNotAllowedCreateOrg. -func IsErrUserNotAllowedCreateOrg(err error) bool { - _, ok := err.(ErrUserNotAllowedCreateOrg) - return ok -} - -func (err ErrUserNotAllowedCreateOrg) Error() string { - return "user is not allowed to create organizations" -} - // __ __.__ __ .__ // / \ / \__| | _|__| // \ \/\/ / | |/ / | @@ -158,44 +145,6 @@ func (err ErrAccessTokenEmpty) Error() string { return "access token is empty" } -// ________ .__ __ .__ -// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____ -// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \ -// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \ -// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| / -// \/ /_____/ \/ \/ \/ \/ \/ - -// ErrOrgNotExist represents a "OrgNotExist" kind of error. -type ErrOrgNotExist struct { - ID int64 - Name string -} - -// IsErrOrgNotExist checks if an error is a ErrOrgNotExist. -func IsErrOrgNotExist(err error) bool { - _, ok := err.(ErrOrgNotExist) - return ok -} - -func (err ErrOrgNotExist) Error() string { - return fmt.Sprintf("org does not exist [id: %d, name: %s]", err.ID, err.Name) -} - -// ErrLastOrgOwner represents a "LastOrgOwner" kind of error. -type ErrLastOrgOwner struct { - UID int64 -} - -// IsErrLastOrgOwner checks if an error is a ErrLastOrgOwner. -func IsErrLastOrgOwner(err error) bool { - _, ok := err.(ErrLastOrgOwner) - return ok -} - -func (err ErrLastOrgOwner) Error() string { - return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID) -} - //.____ ____________________ //| | \_ _____/ _____/ //| | | __) \_____ \ @@ -1195,46 +1144,6 @@ func (err ErrMilestoneNotExist) Error() string { return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID) } -// ___________ -// \__ ___/___ _____ _____ -// | |_/ __ \\__ \ / \ -// | |\ ___/ / __ \| Y Y \ -// |____| \___ >____ /__|_| / -// \/ \/ \/ - -// ErrTeamAlreadyExist represents a "TeamAlreadyExist" kind of error. -type ErrTeamAlreadyExist struct { - OrgID int64 - Name string -} - -// IsErrTeamAlreadyExist checks if an error is a ErrTeamAlreadyExist. -func IsErrTeamAlreadyExist(err error) bool { - _, ok := err.(ErrTeamAlreadyExist) - return ok -} - -func (err ErrTeamAlreadyExist) Error() string { - return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name) -} - -// ErrTeamNotExist represents a "TeamNotExist" error -type ErrTeamNotExist struct { - OrgID int64 - TeamID int64 - Name string -} - -// IsErrTeamNotExist checks if an error is a ErrTeamNotExist. -func IsErrTeamNotExist(err error) bool { - _, ok := err.(ErrTeamNotExist) - return ok -} - -func (err ErrTeamNotExist) Error() string { - return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name) -} - // ____ ___ .__ .___ // | | \______ | | _________ __| _/ // | | /\____ \| | / _ \__ \ / __ | diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index 9a8b0aff76..f6dfd1e9d0 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -6,6 +6,7 @@ authorize: 4 # owner num_repos: 3 num_members: 1 + can_create_org_repo: true - id: 2 @@ -15,6 +16,7 @@ authorize: 2 # write num_repos: 1 num_members: 2 + can_create_org_repo: false - id: 3 @@ -24,6 +26,7 @@ authorize: 4 # owner num_repos: 0 num_members: 1 + can_create_org_repo: true - id: 4 @@ -33,6 +36,7 @@ authorize: 4 # owner num_repos: 0 num_members: 1 + can_create_org_repo: true - id: 5 @@ -42,6 +46,7 @@ authorize: 4 # owner num_repos: 2 num_members: 2 + can_create_org_repo: true - id: 6 @@ -51,6 +56,7 @@ authorize: 4 # owner num_repos: 2 num_members: 1 + can_create_org_repo: true - id: 7 @@ -60,6 +66,7 @@ authorize: 2 # write num_repos: 1 num_members: 1 + can_create_org_repo: false - id: 8 @@ -69,6 +76,7 @@ authorize: 2 # write num_repos: 1 num_members: 1 + can_create_org_repo: false - id: 9 @@ -78,6 +86,7 @@ authorize: 1 # read num_repos: 1 num_members: 2 + can_create_org_repo: false - id: 10 @@ -87,6 +96,7 @@ authorize: 1 # owner num_repos: 0 num_members: 1 + can_create_org_repo: false - id: 11 @@ -96,6 +106,7 @@ authorize: 1 # read num_repos: 0 num_members: 0 + can_create_org_repo: false - id: 12 diff --git a/models/issue.go b/models/issue.go index 79771ce15c..31f7a0edb0 100644 --- a/models/issue.go +++ b/models/issue.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/foreignreference" "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -1238,9 +1239,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 + Org *organization.Organization // issues permission scope + Team *organization.Team // issues permission scope + User *user_model.User // issues permission scope } // sortIssuesSession sort an issues-related session based on the provided @@ -1401,7 +1402,7 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) { } // 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 { +func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organization.Organization, team *organization.Team, isPull bool) builder.Cond { cond := builder.NewCond() unitType := unit.TypeIssues if isPull { @@ -1749,8 +1750,8 @@ type UserIssueStatsOptions struct { IsArchived util.OptionalBool LabelIDs []int64 RepoCond builder.Cond - Org *Organization - Team *Team + Org *organization.Organization + Team *organization.Team } // GetUserIssueStats returns issue statistic information for dashboard by given conditions. @@ -2296,7 +2297,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx context.Context, doer *user_ } if issue.Repo.Owner.IsOrganization() && len(mentionTeams) > 0 { - teams := make([]*Team, 0, len(mentionTeams)) + teams := make([]*organization.Team, 0, len(mentionTeams)) if err := db.GetEngine(ctx). Join("INNER", "team_repo", "team_repo.team_id = team.id"). Where("team_repo.repo_id=?", issue.Repo.ID). @@ -2316,7 +2317,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx context.Context, doer *user_ resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true continue } - has, err := db.GetEngine(ctx).Get(&TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype}) + has, err := db.GetEngine(ctx).Get(&organization.TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype}) if err != nil { return nil, fmt.Errorf("get team units (%d): %v", team.ID, err) } diff --git a/models/issue_comment.go b/models/issue_comment.go index 8390d90f8a..7927b0037b 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" @@ -213,9 +214,9 @@ type Comment struct { Time *TrackedTime `xorm:"-"` AssigneeID int64 RemovedAssignee bool - Assignee *user_model.User `xorm:"-"` - AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"` - AssigneeTeam *Team `xorm:"-"` + Assignee *user_model.User `xorm:"-"` + AssigneeTeamID int64 `xorm:"NOT NULL DEFAULT 0"` + AssigneeTeam *organization.Team `xorm:"-"` ResolveDoerID int64 ResolveDoer *user_model.User `xorm:"-"` OldTitle string @@ -581,8 +582,8 @@ func (c *Comment) LoadAssigneeUserAndTeam() error { } if c.Issue.Repo.Owner.IsOrganization() { - c.AssigneeTeam, err = GetTeamByID(c.AssigneeTeamID) - if err != nil && !IsErrTeamNotExist(err) { + c.AssigneeTeam, err = organization.GetTeamByID(c.AssigneeTeamID) + if err != nil && !organization.IsErrTeamNotExist(err) { return err } } diff --git a/models/main_test.go b/models/main_test.go index 8d5291a8aa..08cb2386c0 100644 --- a/models/main_test.go +++ b/models/main_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -30,7 +31,7 @@ func TestFixturesAreConsistent(t *testing.T) { &PullRequest{}, &Milestone{}, &Label{}, - &Team{}, + &organization.Team{}, &Action{}) } diff --git a/models/notification.go b/models/notification.go index b53d236e43..b5217777bf 100644 --- a/models/notification.go +++ b/models/notification.go @@ -11,6 +11,7 @@ import ( "strconv" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -151,7 +152,7 @@ func CreateRepoTransferNotification(doer, newOwner *user_model.User, repo *repo_ var notify []*Notification if newOwner.IsOrganization() { - users, err := getUsersWhoCanCreateOrgRepo(db.GetEngine(ctx), newOwner.ID) + users, err := organization.GetUsersWhoCanCreateOrgRepo(ctx, newOwner.ID) if err != nil || len(users) == 0 { return err } diff --git a/models/org.go b/models/org.go index 70b3fe27b9..1e5b403f12 100644 --- a/models/org.go +++ b/models/org.go @@ -11,443 +11,15 @@ import ( "strings" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/organization" 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/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" "xorm.io/builder" ) -// Organization represents an organization -type Organization user_model.User - -// OrgFromUser converts user to organization -func OrgFromUser(user *user_model.User) *Organization { - return (*Organization)(user) -} - -// TableName represents the real table name of Organization -func (Organization) TableName() string { - return "user" -} - -// IsOwnedBy returns true if given user is in the owner team. -func (org *Organization) IsOwnedBy(uid int64) (bool, error) { - return IsOrganizationOwner(org.ID, uid) -} - -// IsOrgMember returns true if given user is member of organization. -func (org *Organization) IsOrgMember(uid int64) (bool, error) { - return IsOrganizationMember(org.ID, uid) -} - -// CanCreateOrgRepo returns true if given user can create repo in organization -func (org *Organization) CanCreateOrgRepo(uid int64) (bool, error) { - return CanCreateOrgRepo(org.ID, uid) -} - -func (org *Organization) getTeam(e db.Engine, name string) (*Team, error) { - return getTeam(e, org.ID, name) -} - -// GetTeam returns named team of organization. -func (org *Organization) GetTeam(name string) (*Team, error) { - return org.getTeam(db.GetEngine(db.DefaultContext), name) -} - -func (org *Organization) getOwnerTeam(e db.Engine) (*Team, error) { - return org.getTeam(e, ownerTeamName) -} - -// GetOwnerTeam returns owner team of organization. -func (org *Organization) GetOwnerTeam() (*Team, error) { - return org.getOwnerTeam(db.GetEngine(db.DefaultContext)) -} - -func (org *Organization) loadTeams(e db.Engine) ([]*Team, error) { - var teams []*Team - return teams, e. - Where("org_id=?", org.ID). - OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END"). - Find(&teams) -} - -// LoadTeams load teams if not loaded. -func (org *Organization) LoadTeams() ([]*Team, error) { - return org.loadTeams(db.GetEngine(db.DefaultContext)) -} - -// GetMembers returns all members of organization. -func (org *Organization) GetMembers() (user_model.UserList, map[int64]bool, error) { - return FindOrgMembers(&FindOrgMembersOpts{ - OrgID: org.ID, - }) -} - -// HasMemberWithUserID returns true if user with userID is part of the u organisation. -func (org *Organization) HasMemberWithUserID(userID int64) bool { - return org.hasMemberWithUserID(db.GetEngine(db.DefaultContext), userID) -} - -func (org *Organization) hasMemberWithUserID(e db.Engine, userID int64) bool { - isMember, err := isOrganizationMember(e, org.ID, userID) - if err != nil { - log.Error("IsOrganizationMember: %v", err) - return false - } - return isMember -} - -// AvatarLink returns the full avatar link with http host -func (org *Organization) AvatarLink() string { - return org.AsUser().AvatarLink() -} - -// HTMLURL returns the organization's full link. -func (org *Organization) HTMLURL() string { - return org.AsUser().HTMLURL() -} - -// OrganisationLink returns the organization sub page link. -func (org *Organization) OrganisationLink() string { - return org.AsUser().OrganisationLink() -} - -// ShortName ellipses username to length -func (org *Organization) ShortName(length int) string { - return org.AsUser().ShortName(length) -} - -// HomeLink returns the user or organization home page link. -func (org *Organization) HomeLink() string { - return org.AsUser().HomeLink() -} - -// CanCreateRepo returns if user login can create a repository -// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised -func (org *Organization) CanCreateRepo() bool { - return org.AsUser().CanCreateRepo() -} - -// FindOrgMembersOpts represensts find org members conditions -type FindOrgMembersOpts struct { - db.ListOptions - OrgID int64 - PublicOnly bool -} - -// CountOrgMembers counts the organization's members -func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) { - sess := db.GetEngine(db.DefaultContext).Where("org_id=?", opts.OrgID) - if opts.PublicOnly { - sess.And("is_public = ?", true) - } - return sess.Count(new(OrgUser)) -} - -// FindOrgMembers loads organization members according conditions -func FindOrgMembers(opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bool, error) { - ous, err := GetOrgUsersByOrgID(opts) - if err != nil { - return nil, nil, err - } - - ids := make([]int64, len(ous)) - idsIsPublic := make(map[int64]bool, len(ous)) - for i, ou := range ous { - ids[i] = ou.UID - idsIsPublic[ou.UID] = ou.IsPublic - } - - users, err := user_model.GetUsersByIDs(ids) - if err != nil { - return nil, nil, err - } - return users, idsIsPublic, nil -} - -// AddMember adds new member to organization. -func (org *Organization) AddMember(uid int64) error { - return AddOrgUser(org.ID, uid) -} - -// RemoveMember removes member from organization. -func (org *Organization) RemoveMember(uid int64) error { - return RemoveOrgUser(org.ID, uid) -} - -func (org *Organization) removeOrgRepo(e db.Engine, repoID int64) error { - return removeOrgRepo(e, org.ID, repoID) -} - -// RemoveOrgRepo removes all team-repository relations of organization. -func (org *Organization) RemoveOrgRepo(repoID int64) error { - return org.removeOrgRepo(db.GetEngine(db.DefaultContext), repoID) -} - -// AsUser returns the org as user object -func (org *Organization) AsUser() *user_model.User { - return (*user_model.User)(org) -} - -// DisplayName returns full name if it's not empty, -// returns username otherwise. -func (org *Organization) DisplayName() string { - return org.AsUser().DisplayName() -} - -// CustomAvatarRelativePath returns user custom avatar relative path. -func (org *Organization) CustomAvatarRelativePath() string { - return org.Avatar -} - -// CreateOrganization creates record of a new organization. -func CreateOrganization(org *Organization, owner *user_model.User) (err error) { - if !owner.CanCreateOrganization() { - return ErrUserNotAllowedCreateOrg{} - } - - if err = user_model.IsUsableUsername(org.Name); err != nil { - return err - } - - isExist, err := user_model.IsUserExist(0, org.Name) - if err != nil { - return err - } else if isExist { - return user_model.ErrUserAlreadyExist{Name: org.Name} - } - - org.LowerName = strings.ToLower(org.Name) - if org.Rands, err = user_model.GetUserSalt(); err != nil { - return err - } - if org.Salt, err = user_model.GetUserSalt(); err != nil { - return err - } - org.UseCustomAvatar = true - org.MaxRepoCreation = -1 - org.NumTeams = 1 - org.NumMembers = 1 - org.Type = user_model.UserTypeOrganization - - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil { - return err - } - - if err = db.Insert(ctx, org); err != nil { - return fmt.Errorf("insert organization: %v", err) - } - if err = user_model.GenerateRandomAvatarCtx(ctx, org.AsUser()); err != nil { - return fmt.Errorf("generate random avatar: %v", err) - } - - // Add initial creator to organization and owner team. - if err = db.Insert(ctx, &OrgUser{ - UID: owner.ID, - OrgID: org.ID, - }); err != nil { - return fmt.Errorf("insert org-user relation: %v", err) - } - - // Create default owner team. - t := &Team{ - OrgID: org.ID, - LowerName: strings.ToLower(ownerTeamName), - Name: ownerTeamName, - AccessMode: perm.AccessModeOwner, - NumMembers: 1, - IncludesAllRepositories: true, - CanCreateOrgRepo: true, - } - if err = db.Insert(ctx, t); err != nil { - return fmt.Errorf("insert owner team: %v", err) - } - - // insert units for team - units := make([]TeamUnit, 0, len(unit.AllRepoUnitTypes)) - for _, tp := range unit.AllRepoUnitTypes { - units = append(units, TeamUnit{ - OrgID: org.ID, - TeamID: t.ID, - Type: tp, - }) - } - - if err = db.Insert(ctx, &units); err != nil { - return err - } - - if err = db.Insert(ctx, &TeamUser{ - UID: owner.ID, - OrgID: org.ID, - TeamID: t.ID, - }); err != nil { - return fmt.Errorf("insert team-user relation: %v", err) - } - - return committer.Commit() -} - -// GetOrgByName returns organization by given name. -func GetOrgByName(name string) (*Organization, error) { - if len(name) == 0 { - return nil, ErrOrgNotExist{0, name} - } - u := &Organization{ - LowerName: strings.ToLower(name), - Type: user_model.UserTypeOrganization, - } - has, err := db.GetEngine(db.DefaultContext).Get(u) - if err != nil { - return nil, err - } else if !has { - return nil, ErrOrgNotExist{0, name} - } - return u, nil -} - -// CountOrganizations returns number of organizations. -func CountOrganizations() int64 { - count, _ := db.GetEngine(db.DefaultContext). - Where("type=1"). - Count(new(Organization)) - return count -} - -// DeleteOrganization deletes models associated to an organization. -func DeleteOrganization(ctx context.Context, org *Organization) error { - if org.Type != user_model.UserTypeOrganization { - return fmt.Errorf("%s is a user not an organization", org.Name) - } - - if err := db.DeleteBeans(ctx, - &Team{OrgID: org.ID}, - &OrgUser{OrgID: org.ID}, - &TeamUser{OrgID: org.ID}, - &TeamUnit{OrgID: org.ID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %v", err) - } - - if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil { - return fmt.Errorf("Delete: %v", err) - } - - return nil -} - -// ________ ____ ___ -// \_____ \_______ ____ | | \______ ___________ -// / | \_ __ \/ ___\| | / ___// __ \_ __ \ -// / | \ | \/ /_/ > | /\___ \\ ___/| | \/ -// \_______ /__| \___ /|______//____ >\___ >__| -// \/ /_____/ \/ \/ - -// OrgUser represents an organization-user relation. -type OrgUser struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"INDEX UNIQUE(s)"` - OrgID int64 `xorm:"INDEX UNIQUE(s)"` - IsPublic bool `xorm:"INDEX"` -} - -func init() { - db.RegisterModel(new(OrgUser)) -} - -func isOrganizationOwner(e db.Engine, orgID, uid int64) (bool, error) { - ownerTeam, err := getOwnerTeam(e, orgID) - if err != nil { - if IsErrTeamNotExist(err) { - log.Error("Organization does not have owner team: %d", orgID) - return false, nil - } - return false, err - } - return isTeamMember(e, orgID, ownerTeam.ID, uid) -} - -// IsOrganizationOwner returns true if given user is in the owner team. -func IsOrganizationOwner(orgID, uid int64) (bool, error) { - return isOrganizationOwner(db.GetEngine(db.DefaultContext), orgID, uid) -} - -// IsOrganizationMember returns true if given user is member of organization. -func IsOrganizationMember(orgID, uid int64) (bool, error) { - return isOrganizationMember(db.GetEngine(db.DefaultContext), orgID, uid) -} - -func isOrganizationMember(e db.Engine, orgID, uid int64) (bool, error) { - return e. - Where("uid=?", uid). - And("org_id=?", orgID). - Table("org_user"). - Exist() -} - -// IsPublicMembership returns true if given user public his/her membership. -func IsPublicMembership(orgID, uid int64) (bool, error) { - return db.GetEngine(db.DefaultContext). - Where("uid=?", uid). - And("org_id=?", orgID). - And("is_public=?", true). - Table("org_user"). - Exist() -} - -// CanCreateOrgRepo returns true if user can create repo in organization -func CanCreateOrgRepo(orgID, uid int64) (bool, error) { - if owner, err := IsOrganizationOwner(orgID, uid); owner || err != nil { - return owner, err - } - return db.GetEngine(db.DefaultContext). - Where(builder.Eq{"team.can_create_org_repo": true}). - Join("INNER", "team_user", "team_user.team_id = team.id"). - And("team_user.uid = ?", uid). - And("team_user.org_id = ?", orgID). - Exist(new(Team)) -} - -// GetOrgUserMaxAuthorizeLevel returns highest authorize level of user in an organization -func (org *Organization) GetOrgUserMaxAuthorizeLevel(uid int64) (perm.AccessMode, error) { - var authorize perm.AccessMode - _, err := db.GetEngine(db.DefaultContext). - Select("max(team.authorize)"). - Table("team"). - Join("INNER", "team_user", "team_user.team_id = team.id"). - Where("team_user.uid = ?", uid). - And("team_user.org_id = ?", org.ID). - Get(&authorize) - return authorize, err -} - -// GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization -func GetUsersWhoCanCreateOrgRepo(orgID int64) ([]*user_model.User, error) { - return getUsersWhoCanCreateOrgRepo(db.GetEngine(db.DefaultContext), orgID) -} - -func getUsersWhoCanCreateOrgRepo(e db.Engine, orgID int64) ([]*user_model.User, error) { - users := make([]*user_model.User, 0, 10) - return users, e. - Join("INNER", "`team_user`", "`team_user`.uid=`user`.id"). - Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). - Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": perm.AccessModeOwner})). - And("team_user.org_id = ?", orgID).Asc("`user`.name").Find(&users) -} - // MinimalOrg represents a simple orgnization with only needed columns -type MinimalOrg = Organization +type MinimalOrg = organization.Organization // GetUserOrgsList returns one user's all orgs list func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { @@ -486,8 +58,8 @@ func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { GroupBy(groupByStr) type OrgCount struct { - Organization `xorm:"extends"` - OrgCount int + organization.Organization `xorm:"extends"` + OrgCount int } orgCounts := make([]*OrgCount, 0, 10) @@ -507,239 +79,8 @@ func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { return orgs, nil } -// 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 { - cond := builder.NewCond() - 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 -} - -// FindOrgs returns a list of organizations according given conditions -func FindOrgs(opts FindOrgOptions) ([]*Organization, error) { - orgs := make([]*Organization, 0, 10) - sess := db.GetEngine(db.DefaultContext). - Where(opts.toConds()). - Asc("`user`.name") - if opts.Page > 0 && opts.PageSize > 0 { - sess.Limit(opts.PageSize, opts.PageSize*(opts.Page-1)) - } - return orgs, sess.Find(&orgs) -} - -// CountOrgs returns total count organizations according options -func CountOrgs(opts FindOrgOptions) (int64, error) { - return db.GetEngine(db.DefaultContext). - Where(opts.toConds()). - Count(new(user_model.User)) -} - -func getOwnedOrgsByUserID(sess db.Engine, userID int64) ([]*Organization, error) { - orgs := make([]*Organization, 0, 10) - return orgs, sess. - Join("INNER", "`team_user`", "`team_user`.org_id=`user`.id"). - Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). - Where("`team_user`.uid=?", userID). - And("`team`.authorize=?", perm.AccessModeOwner). - Asc("`user`.name"). - Find(&orgs) -} - -// HasOrgOrUserVisible tells if the given user can see the given org or user -func HasOrgOrUserVisible(org, user *user_model.User) bool { - return hasOrgOrUserVisible(db.GetEngine(db.DefaultContext), org, user) -} - -func hasOrgOrUserVisible(e db.Engine, orgOrUser, user *user_model.User) bool { - // Not SignedUser - if user == nil { - return orgOrUser.Visibility == structs.VisibleTypePublic - } - - if user.IsAdmin || orgOrUser.ID == user.ID { - return true - } - - if (orgOrUser.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !OrgFromUser(orgOrUser).hasMemberWithUserID(e, user.ID) { - return false - } - return true -} - -// HasOrgsVisible tells if the given user can see at least one of the orgs provided -func HasOrgsVisible(orgs []*Organization, user *user_model.User) bool { - if len(orgs) == 0 { - return false - } - - for _, org := range orgs { - if HasOrgOrUserVisible(org.AsUser(), user) { - return true - } - } - return false -} - -// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID. -func GetOwnedOrgsByUserID(userID int64) ([]*Organization, error) { - return getOwnedOrgsByUserID(db.GetEngine(db.DefaultContext), userID) -} - -// GetOwnedOrgsByUserIDDesc returns a list of organizations are owned by -// given user ID, ordered descending by the given condition. -func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*Organization, error) { - return getOwnedOrgsByUserID(db.GetEngine(db.DefaultContext).Desc(desc), userID) -} - -// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID -// are allowed to create repos. -func GetOrgsCanCreateRepoByUserID(userID int64) ([]*Organization, error) { - orgs := make([]*Organization, 0, 10) - - return orgs, db.GetEngine(db.DefaultContext).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) -} - -// GetOrgUsersByUserID returns all organization-user relations by user ID. -func GetOrgUsersByUserID(uid int64, opts *SearchOrganizationsOptions) ([]*OrgUser, error) { - ous := make([]*OrgUser, 0, 10) - sess := db.GetEngine(db.DefaultContext). - Join("LEFT", "`user`", "`org_user`.org_id=`user`.id"). - Where("`org_user`.uid=?", uid) - if !opts.All { - // Only show public organizations - sess.And("is_public=?", true) - } - - if opts.PageSize != 0 { - sess = db.SetSessionPagination(sess, opts) - } - - err := sess. - Asc("`user`.name"). - Find(&ous) - return ous, err -} - -// GetOrgUsersByOrgID returns all organization-user relations by organization ID. -func GetOrgUsersByOrgID(opts *FindOrgMembersOpts) ([]*OrgUser, error) { - return getOrgUsersByOrgID(db.GetEngine(db.DefaultContext), opts) -} - -func getOrgUsersByOrgID(e db.Engine, opts *FindOrgMembersOpts) ([]*OrgUser, error) { - sess := e.Where("org_id=?", opts.OrgID) - if opts.PublicOnly { - sess.And("is_public = ?", true) - } - if opts.ListOptions.PageSize > 0 { - sess = db.SetSessionPagination(sess, opts) - - ous := make([]*OrgUser, 0, opts.PageSize) - return ous, sess.Find(&ous) - } - - var ous []*OrgUser - return ous, sess.Find(&ous) -} - -// ChangeOrgUserStatus changes public or private membership status. -func ChangeOrgUserStatus(orgID, uid int64, public bool) error { - ou := new(OrgUser) - has, err := db.GetEngine(db.DefaultContext). - Where("uid=?", uid). - And("org_id=?", orgID). - Get(ou) - if err != nil { - return err - } else if !has { - return nil - } - - ou.IsPublic = public - _, err = db.GetEngine(db.DefaultContext).ID(ou.ID).Cols("is_public").Update(ou) - return err -} - -// AddOrgUser adds new user to given organization. -func AddOrgUser(orgID, uid int64) error { - isAlreadyMember, err := IsOrganizationMember(orgID, uid) - if err != nil || isAlreadyMember { - return err - } - - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - ou := &OrgUser{ - UID: uid, - OrgID: orgID, - IsPublic: setting.Service.DefaultOrgMemberVisible, - } - - if err := db.Insert(ctx, ou); err != nil { - return err - } else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil { - return err - } - - return committer.Commit() -} - -// GetOrgByIDCtx returns the user object by given ID if exists. -func GetOrgByIDCtx(ctx context.Context, id int64) (*Organization, error) { - u := new(Organization) - has, err := db.GetEngine(ctx).ID(id).Get(u) - if err != nil { - return nil, err - } else if !has { - return nil, user_model.ErrUserNotExist{ - UID: id, - Name: "", - KeyID: 0, - } - } - return u, nil -} - -// GetOrgByID returns the user object by given ID if exists. -func GetOrgByID(id int64) (*Organization, error) { - return GetOrgByIDCtx(db.DefaultContext, id) -} - func removeOrgUser(ctx context.Context, orgID, userID int64) error { - ou := new(OrgUser) + ou := new(organization.OrgUser) sess := db.GetEngine(ctx) @@ -753,25 +94,25 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { return nil } - org, err := GetOrgByIDCtx(ctx, orgID) + org, err := organization.GetOrgByIDCtx(ctx, orgID) if err != nil { return fmt.Errorf("GetUserByID [%d]: %v", orgID, err) } // Check if the user to delete is the last member in owner team. - if isOwner, err := isOrganizationOwner(sess, orgID, userID); err != nil { + if isOwner, err := organization.IsOrganizationOwner(ctx, orgID, userID); err != nil { return err } else if isOwner { - t, err := org.getOwnerTeam(sess) + t, err := organization.GetOwnerTeam(ctx, org.ID) if err != nil { return err } if t.NumMembers == 1 { - if err := t.getMembers(sess); err != nil { + if err := t.GetMembersCtx(ctx); err != nil { return err } if t.Members[0].ID == userID { - return ErrLastOrgOwner{UID: userID} + return organization.ErrLastOrgOwner{UID: userID} } } } @@ -783,7 +124,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { } // Delete all repository accesses and unwatch them. - env, err := org.accessibleReposEnv(sess, userID) + env, err := organization.AccessibleReposEnv(ctx, org, userID) if err != nil { return fmt.Errorf("AccessibleReposEnv: %v", err) } @@ -807,7 +148,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { } // Delete member in his/her teams. - teams, err := getUserOrgTeams(sess, org.ID, userID) + teams, err := organization.GetUserOrgTeams(ctx, org.ID, userID) if err != nil { return err } @@ -832,233 +173,3 @@ func RemoveOrgUser(orgID, userID int64) error { } return committer.Commit() } - -func removeOrgRepo(e db.Engine, orgID, repoID int64) error { - teamRepos := make([]*TeamRepo, 0, 10) - if err := e.Find(&teamRepos, &TeamRepo{OrgID: orgID, RepoID: repoID}); err != nil { - return err - } - - if len(teamRepos) == 0 { - return nil - } - - if _, err := e.Delete(&TeamRepo{ - OrgID: orgID, - RepoID: repoID, - }); err != nil { - return err - } - - teamIDs := make([]int64, len(teamRepos)) - for i, teamRepo := range teamRepos { - teamIDs[i] = teamRepo.TeamID - } - - _, err := e.Decr("num_repos").In("id", teamIDs).Update(new(Team)) - return err -} - -func (org *Organization) getUserTeams(e db.Engine, userID int64, cols ...string) ([]*Team, error) { - teams := make([]*Team, 0, org.NumTeams) - return teams, e. - Where("`team_user`.org_id = ?", org.ID). - Join("INNER", "team_user", "`team_user`.team_id = team.id"). - Join("INNER", "`user`", "`user`.id=team_user.uid"). - And("`team_user`.uid = ?", userID). - Asc("`user`.name"). - Cols(cols...). - Find(&teams) -} - -func (org *Organization) getUserTeamIDs(e db.Engine, userID int64) ([]int64, error) { - teamIDs := make([]int64, 0, org.NumTeams) - return teamIDs, e. - Table("team"). - Cols("team.id"). - Where("`team_user`.org_id = ?", org.ID). - Join("INNER", "team_user", "`team_user`.team_id = team.id"). - And("`team_user`.uid = ?", userID). - Find(&teamIDs) -} - -// TeamsWithAccessToRepo returns all teams that have given access level to the repository. -func (org *Organization) TeamsWithAccessToRepo(repoID int64, mode perm.AccessMode) ([]*Team, error) { - return GetTeamsWithAccessToRepo(org.ID, repoID, mode) -} - -// GetUserTeamIDs returns of all team IDs of the organization that user is member of. -func (org *Organization) GetUserTeamIDs(userID int64) ([]int64, error) { - return org.getUserTeamIDs(db.GetEngine(db.DefaultContext), userID) -} - -// GetUserTeams returns all teams that belong to user, -// and that the user has joined. -func (org *Organization) GetUserTeams(userID int64) ([]*Team, error) { - return org.getUserTeams(db.GetEngine(db.DefaultContext), userID) -} - -// AccessibleReposEnvironment operations involving the repositories that are -// accessible to a particular user -type AccessibleReposEnvironment interface { - CountRepos() (int64, error) - RepoIDs(page, pageSize int) ([]int64, error) - Repos(page, pageSize int) ([]*repo_model.Repository, error) - MirrorRepos() ([]*repo_model.Repository, error) - AddKeyword(keyword string) - SetSort(db.SearchOrderBy) -} - -type accessibleReposEnv struct { - org *Organization - user *user_model.User - team *Team - teamIDs []int64 - e db.Engine - keyword string - orderBy db.SearchOrderBy -} - -// AccessibleReposEnv builds an AccessibleReposEnvironment for the repositories in `org` -// that are accessible to the specified user. -func (org *Organization) AccessibleReposEnv(userID int64) (AccessibleReposEnvironment, error) { - return org.accessibleReposEnv(db.GetEngine(db.DefaultContext), userID) -} - -func (org *Organization) accessibleReposEnv(e db.Engine, userID int64) (AccessibleReposEnvironment, error) { - var user *user_model.User - - if userID > 0 { - u, err := user_model.GetUserByIDEngine(e, userID) - if err != nil { - return nil, err - } - user = u - } - - teamIDs, err := org.getUserTeamIDs(e, userID) - if err != nil { - return nil, err - } - return &accessibleReposEnv{ - org: org, - user: user, - teamIDs: teamIDs, - e: e, - orderBy: db.SearchOrderByRecentUpdated, - }, nil -} - -// AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org` -// that are accessible to the specified team. -func (org *Organization) AccessibleTeamReposEnv(team *Team) AccessibleReposEnvironment { - return &accessibleReposEnv{ - org: org, - team: team, - e: db.GetEngine(db.DefaultContext), - orderBy: db.SearchOrderByRecentUpdated, - } -} - -func (env *accessibleReposEnv) cond() builder.Cond { - cond := builder.NewCond() - if env.team != nil { - cond = cond.And(builder.Eq{"team_repo.team_id": env.team.ID}) - } else { - if env.user == nil || !env.user.IsRestricted { - cond = cond.Or(builder.Eq{ - "`repository`.owner_id": env.org.ID, - "`repository`.is_private": false, - }) - } - if len(env.teamIDs) > 0 { - cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs)) - } - } - if env.keyword != "" { - cond = cond.And(builder.Like{"`repository`.lower_name", strings.ToLower(env.keyword)}) - } - return cond -} - -func (env *accessibleReposEnv) CountRepos() (int64, error) { - repoCount, err := env.e. - Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). - Where(env.cond()). - Distinct("`repository`.id"). - Count(&repo_model.Repository{}) - if err != nil { - return 0, fmt.Errorf("count user repositories in organization: %v", err) - } - return repoCount, nil -} - -func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) { - if page <= 0 { - page = 1 - } - - repoIDs := make([]int64, 0, pageSize) - return repoIDs, env.e. - Table("repository"). - Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). - Where(env.cond()). - GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]). - OrderBy(string(env.orderBy)). - Limit(pageSize, (page-1)*pageSize). - Cols("`repository`.id"). - Find(&repoIDs) -} - -func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*repo_model.Repository, error) { - repoIDs, err := env.RepoIDs(page, pageSize) - if err != nil { - return nil, fmt.Errorf("GetUserRepositoryIDs: %v", err) - } - - repos := make([]*repo_model.Repository, 0, len(repoIDs)) - if len(repoIDs) == 0 { - return repos, nil - } - - return repos, env.e. - In("`repository`.id", repoIDs). - OrderBy(string(env.orderBy)). - Find(&repos) -} - -func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { - repoIDs := make([]int64, 0, 10) - return repoIDs, env.e. - Table("repository"). - Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). - Where(env.cond()). - GroupBy("`repository`.id, `repository`.updated_unix"). - OrderBy(string(env.orderBy)). - Cols("`repository`.id"). - Find(&repoIDs) -} - -func (env *accessibleReposEnv) MirrorRepos() ([]*repo_model.Repository, error) { - repoIDs, err := env.MirrorRepoIDs() - if err != nil { - return nil, fmt.Errorf("MirrorRepoIDs: %v", err) - } - - repos := make([]*repo_model.Repository, 0, len(repoIDs)) - if len(repoIDs) == 0 { - return repos, nil - } - - return repos, env.e. - In("`repository`.id", repoIDs). - Find(&repos) -} - -func (env *accessibleReposEnv) AddKeyword(keyword string) { - env.keyword = keyword -} - -func (env *accessibleReposEnv) SetSort(orderBy db.SearchOrderBy) { - env.orderBy = orderBy -} diff --git a/models/org_team.go b/models/org_team.go index faee23f4f8..cf810cad33 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -9,288 +9,24 @@ import ( "context" "errors" "fmt" - "sort" "strings" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/organization" 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/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) -const ownerTeamName = "Owners" - -// Team represents a organization team. -type Team struct { - ID int64 `xorm:"pk autoincr"` - OrgID int64 `xorm:"INDEX"` - LowerName string - Name string - Description string - AccessMode perm.AccessMode `xorm:"'authorize'"` - Repos []*repo_model.Repository `xorm:"-"` - Members []*user_model.User `xorm:"-"` - NumRepos int - NumMembers int - Units []*TeamUnit `xorm:"-"` - IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"` - CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"` -} - -func init() { - db.RegisterModel(new(Team)) - db.RegisterModel(new(TeamUser)) - db.RegisterModel(new(TeamRepo)) - db.RegisterModel(new(TeamUnit)) -} - -// SearchOrgTeamOptions holds the search options -type SearchOrgTeamOptions struct { - db.ListOptions - 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 -} - -// 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 - } - if opts.PageSize == 0 { - // Default limit - opts.PageSize = 10 - } - - cond := builder.NewCond() - - if len(opts.Keyword) > 0 { - lowerKeyword := strings.ToLower(opts.Keyword) - var keywordCond builder.Cond = builder.Like{"lower_name", lowerKeyword} - if opts.IncludeDesc { - keywordCond = keywordCond.Or(builder.Like{"LOWER(description)", lowerKeyword}) - } - cond = cond.And(keywordCond) - } - - cond = cond.And(builder.Eq{"org_id": opts.OrgID}) - - sess := db.GetEngine(db.DefaultContext) - - count, err := sess. - Where(cond). - Count(new(Team)) - if err != nil { - return nil, 0, err - } - - sess = sess.Where(cond) - if opts.PageSize == -1 { - opts.PageSize = int(count) - } else { - sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) - } - - teams := make([]*Team, 0, opts.PageSize) - if err = sess. - OrderBy("lower_name"). - Find(&teams); err != nil { - return nil, 0, err - } - - return teams, count, nil -} - -// ColorFormat provides a basic color format for a Team -func (t *Team) ColorFormat(s fmt.State) { - if t == nil { - log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v", - log.NewColoredIDValue(0), - "", - log.NewColoredIDValue(0), - 0) - return - } - log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v", - log.NewColoredIDValue(t.ID), - t.Name, - log.NewColoredIDValue(t.OrgID), - t.AccessMode) -} - -// GetUnits return a list of available units for a team -func (t *Team) GetUnits() error { - return t.getUnits(db.GetEngine(db.DefaultContext)) -} - -func (t *Team) getUnits(e db.Engine) (err error) { - if t.Units != nil { - return nil - } - - t.Units, err = getUnitsByTeamID(e, t.ID) - return err -} - -// GetUnitNames returns the team units names -func (t *Team) GetUnitNames() (res []string) { - if t.AccessMode >= perm.AccessModeAdmin { - return unit.AllUnitKeyNames() - } - - for _, u := range t.Units { - res = append(res, unit.Units[u.Type].NameKey) - } - return -} - -// GetUnitsMap returns the team units permissions -func (t *Team) GetUnitsMap() map[string]string { - m := make(map[string]string) - if t.AccessMode >= perm.AccessModeAdmin { - for _, u := range unit.Units { - m[u.NameKey] = t.AccessMode.String() - } - } else { - for _, u := range t.Units { - m[u.Unit().NameKey] = u.AccessMode.String() - } - } - return m -} - -// IsOwnerTeam returns true if team is owner team. -func (t *Team) IsOwnerTeam() bool { - return t.Name == ownerTeamName -} - -// IsMember returns true if given user is a member of team. -func (t *Team) IsMember(userID int64) bool { - isMember, err := IsTeamMember(t.OrgID, t.ID, userID) - if err != nil { - log.Error("IsMember: %v", err) - return false - } - return isMember -} - -func (t *Team) getRepositories(e db.Engine) error { - if t.Repos != nil { - return nil - } - return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id"). - Where("team_repo.team_id=?", t.ID). - OrderBy("repository.name"). - Find(&t.Repos) -} - -// GetRepositories returns paginated repositories in team of organization. -func (t *Team) GetRepositories(opts *SearchOrgTeamOptions) error { - if opts.Page == 0 { - return t.getRepositories(db.GetEngine(db.DefaultContext)) - } - - return t.getRepositories(db.GetPaginatedSession(opts)) -} - -func (t *Team) getMembers(e db.Engine) (err error) { - t.Members, err = getTeamMembers(e, t.ID) - return err -} - -// GetMembers returns paginated members in team of organization. -func (t *Team) GetMembers(opts *SearchMembersOptions) (err error) { - if opts.Page == 0 { - return t.getMembers(db.GetEngine(db.DefaultContext)) - } - - return t.getMembers(db.GetPaginatedSession(opts)) -} - -// AddMember adds new membership of the team to the organization, -// the user will have membership to the organization automatically when needed. -func (t *Team) AddMember(userID int64) error { - return AddTeamMember(t, userID) -} - -// RemoveMember removes member from team of organization. -func (t *Team) RemoveMember(userID int64) error { - return RemoveTeamMember(t, userID) -} - -func (t *Team) hasRepository(e db.Engine, repoID int64) bool { - return hasTeamRepo(e, t.OrgID, t.ID, repoID) -} - -// HasRepository returns true if given repository belong to team. -func (t *Team) HasRepository(repoID int64) bool { - return t.hasRepository(db.GetEngine(db.DefaultContext), repoID) -} - -func (t *Team) addRepository(ctx context.Context, repo *repo_model.Repository) (err error) { - e := db.GetEngine(ctx) - if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil { +func addRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) { + if err = organization.AddTeamRepo(ctx, t.OrgID, t.ID, repo.ID); err != nil { return err } - if _, err = e.Incr("num_repos").ID(t.ID).Update(new(Team)); err != nil { + if _, err = db.GetEngine(ctx).Incr("num_repos").ID(t.ID).Update(new(organization.Team)); err != nil { return fmt.Errorf("update team: %v", err) } @@ -302,7 +38,7 @@ func (t *Team) addRepository(ctx context.Context, repo *repo_model.Repository) ( // Make all team members watch this repo if enabled in global settings if setting.Service.AutoWatchNewRepos { - if err = t.getMembers(e); err != nil { + if err = t.GetMembersCtx(ctx); err != nil { return fmt.Errorf("getMembers: %v", err) } for _, u := range t.Members { @@ -317,7 +53,7 @@ func (t *Team) addRepository(ctx context.Context, repo *repo_model.Repository) ( // addAllRepositories adds all repositories to the team. // If the team already has some repositories they will be left unchanged. -func (t *Team) addAllRepositories(ctx context.Context) error { +func addAllRepositories(ctx context.Context, t *organization.Team) error { var orgRepos []repo_model.Repository e := db.GetEngine(ctx) if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil { @@ -325,8 +61,8 @@ func (t *Team) addAllRepositories(ctx context.Context) error { } for _, repo := range orgRepos { - if !t.hasRepository(e, repo.ID) { - if err := t.addRepository(ctx, &repo); err != nil { + if !hasRepository(ctx, t, repo.ID) { + if err := addRepository(ctx, t, &repo); err != nil { return fmt.Errorf("addRepository: %v", err) } } @@ -336,14 +72,14 @@ func (t *Team) addAllRepositories(ctx context.Context) error { } // AddAllRepositories adds all repositories to the team -func (t *Team) AddAllRepositories() (err error) { +func AddAllRepositories(t *organization.Team) (err error) { ctx, committer, err := db.TxContext() if err != nil { return err } defer committer.Close() - if err = t.addAllRepositories(ctx); err != nil { + if err = addAllRepositories(ctx, t); err != nil { return err } @@ -351,10 +87,10 @@ func (t *Team) AddAllRepositories() (err error) { } // AddRepository adds new repository to team of organization. -func (t *Team) AddRepository(repo *repo_model.Repository) (err error) { +func AddRepository(t *organization.Team, repo *repo_model.Repository) (err error) { if repo.OwnerID != t.OrgID { return errors.New("Repository does not belong to organization") - } else if t.HasRepository(repo.ID) { + } else if HasRepository(t, repo.ID) { return nil } @@ -364,15 +100,20 @@ func (t *Team) AddRepository(repo *repo_model.Repository) (err error) { } defer committer.Close() - if err = t.addRepository(ctx, repo); err != nil { + if err = addRepository(ctx, t, repo); err != nil { return err } return committer.Commit() } +// HasRepository returns true if given repository belong to team. +func HasRepository(t *organization.Team, repoID int64) bool { + return hasRepository(db.DefaultContext, t, repoID) +} + // RemoveAllRepositories removes all repositories from team and recalculates access -func (t *Team) RemoveAllRepositories() (err error) { +func RemoveAllRepositories(t *organization.Team) (err error) { if t.IncludesAllRepositories { return nil } @@ -383,7 +124,7 @@ func (t *Team) RemoveAllRepositories() (err error) { } defer committer.Close() - if err = t.removeAllRepositories(ctx); err != nil { + if err = removeAllRepositories(ctx, t); err != nil { return err } @@ -392,7 +133,7 @@ func (t *Team) RemoveAllRepositories() (err error) { // removeAllRepositories removes all repositories from team and recalculates access // Note: Shall not be called if team includes all repositories -func (t *Team) removeAllRepositories(ctx context.Context) (err error) { +func removeAllRepositories(ctx context.Context, t *organization.Team) (err error) { e := db.GetEngine(ctx) // Delete all accesses. for _, repo := range t.Repos { @@ -423,7 +164,7 @@ func (t *Team) removeAllRepositories(ctx context.Context) (err error) { // Delete team-repo if _, err := e. Where("team_id=?", t.ID). - Delete(new(TeamRepo)); err != nil { + Delete(new(organization.TeamRepo)); err != nil { return err } @@ -435,11 +176,15 @@ func (t *Team) removeAllRepositories(ctx context.Context) (err error) { return nil } +func hasRepository(ctx context.Context, t *organization.Team, repoID int64) bool { + return organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) +} + // removeRepository removes a repository from a team and recalculates access // Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted) -func (t *Team) removeRepository(ctx context.Context, repo *repo_model.Repository, recalculate bool) (err error) { +func removeRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) { e := db.GetEngine(ctx) - if err = removeTeamRepo(e, t.ID, repo.ID); err != nil { + if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil { return err } @@ -455,7 +200,7 @@ func (t *Team) removeRepository(ctx context.Context, repo *repo_model.Repository } } - teamUsers, err := getTeamUsersByTeamID(e, t.ID) + teamUsers, err := organization.GetTeamUsersByTeamID(ctx, t.ID) if err != nil { return fmt.Errorf("getTeamUsersByTeamID: %v", err) } @@ -482,8 +227,8 @@ func (t *Team) removeRepository(ctx context.Context, repo *repo_model.Repository // RemoveRepository removes repository from team of organization. // If the team shall include all repositories the request is ignored. -func (t *Team) RemoveRepository(repoID int64) error { - if !t.HasRepository(repoID) { +func RemoveRepository(t *organization.Team, repoID int64) error { + if !HasRepository(t, repoID) { return nil } @@ -502,58 +247,21 @@ func (t *Team) RemoveRepository(repoID int64) error { } defer committer.Close() - if err = t.removeRepository(ctx, repo, true); err != nil { + if err = removeRepository(ctx, t, repo, true); err != nil { return err } return committer.Commit() } -// UnitEnabled returns if the team has the given unit type enabled -func (t *Team) UnitEnabled(tp unit.Type) bool { - return t.unitEnabled(db.GetEngine(db.DefaultContext), tp) -} - -func (t *Team) unitEnabled(e db.Engine, tp unit.Type) bool { - return t.unitAccessMode(e, tp) > perm.AccessModeNone -} - -// UnitAccessMode returns if the team has the given unit type enabled -func (t *Team) UnitAccessMode(tp unit.Type) perm.AccessMode { - return t.unitAccessMode(db.GetEngine(db.DefaultContext), tp) -} - -func (t *Team) unitAccessMode(e db.Engine, tp unit.Type) perm.AccessMode { - if err := t.getUnits(e); err != nil { - log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error()) - } - - for _, unit := range t.Units { - if unit.Type == tp { - return unit.AccessMode - } - } - return perm.AccessModeNone -} - -// IsUsableTeamName tests if a name could be as team name -func IsUsableTeamName(name string) error { - switch name { - case "new": - return db.ErrNameReserved{Name: name} - default: - return nil - } -} - // NewTeam creates a record of new team. // It's caller's responsibility to assign organization ID. -func NewTeam(t *Team) (err error) { +func NewTeam(t *organization.Team) (err error) { if len(t.Name) == 0 { return errors.New("empty team name") } - if err = IsUsableTeamName(t.Name); err != nil { + if err = organization.IsUsableTeamName(t.Name); err != nil { return err } @@ -562,19 +270,19 @@ func NewTeam(t *Team) (err error) { return err } if !has { - return ErrOrgNotExist{t.OrgID, ""} + return organization.ErrOrgNotExist{ID: t.OrgID} } t.LowerName = strings.ToLower(t.Name) has, err = db.GetEngine(db.DefaultContext). Where("org_id=?", t.OrgID). And("lower_name=?", t.LowerName). - Get(new(Team)) + Get(new(organization.Team)) if err != nil { return err } if has { - return ErrTeamAlreadyExist{t.OrgID, t.LowerName} + return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName} } ctx, committer, err := db.TxContext() @@ -599,7 +307,7 @@ func NewTeam(t *Team) (err error) { // Add all repositories to the team if it has access to all of them. if t.IncludesAllRepositories { - err = t.addAllRepositories(ctx) + err = addAllRepositories(ctx, t) if err != nil { return fmt.Errorf("addAllRepositories: %v", err) } @@ -612,81 +320,8 @@ func NewTeam(t *Team) (err error) { return committer.Commit() } -func getTeam(e db.Engine, orgID int64, name string) (*Team, error) { - t := &Team{ - OrgID: orgID, - LowerName: strings.ToLower(name), - } - has, err := e.Get(t) - if err != nil { - return nil, err - } else if !has { - return nil, ErrTeamNotExist{orgID, 0, name} - } - return t, nil -} - -// GetTeam returns team by given team name and organization. -func GetTeam(orgID int64, name string) (*Team, error) { - return getTeam(db.GetEngine(db.DefaultContext), orgID, name) -} - -// GetTeamIDsByNames returns a slice of team ids corresponds to names. -func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) { - ids := make([]int64, 0, len(names)) - for _, name := range names { - u, err := GetTeam(orgID, name) - if err != nil { - if ignoreNonExistent { - continue - } else { - return nil, err - } - } - ids = append(ids, u.ID) - } - return ids, nil -} - -// getOwnerTeam returns team by given team name and organization. -func getOwnerTeam(e db.Engine, orgID int64) (*Team, error) { - return getTeam(e, orgID, ownerTeamName) -} - -func getTeamByID(e db.Engine, teamID int64) (*Team, error) { - t := new(Team) - has, err := e.ID(teamID).Get(t) - if err != nil { - return nil, err - } else if !has { - return nil, ErrTeamNotExist{0, teamID, ""} - } - return t, nil -} - -// GetTeamByID returns team by given ID. -func GetTeamByID(teamID int64) (*Team, error) { - return getTeamByID(db.GetEngine(db.DefaultContext), teamID) -} - -// GetTeamNamesByID returns team's lower name from a list of team ids. -func GetTeamNamesByID(teamIDs []int64) ([]string, error) { - if len(teamIDs) == 0 { - return []string{}, nil - } - - var teamNames []string - err := db.GetEngine(db.DefaultContext).Table("team"). - Select("lower_name"). - In("id", teamIDs). - Asc("name"). - Find(&teamNames) - - return teamNames, err -} - // UpdateTeam updates information of team. -func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) { +func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err error) { if len(t.Name) == 0 { return errors.New("empty team name") } @@ -707,11 +342,11 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) { Where("org_id=?", t.OrgID). And("lower_name=?", t.LowerName). And("id!=?", t.ID). - Get(new(Team)) + Get(new(organization.Team)) if err != nil { return err } else if has { - return ErrTeamAlreadyExist{t.OrgID, t.LowerName} + return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName} } if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description", @@ -727,7 +362,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) { // Delete team-unit. if _, err := sess. Where("team_id=?", t.ID). - Delete(new(TeamUnit)); err != nil { + Delete(new(organization.TeamUnit)); err != nil { return err } if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil { @@ -737,7 +372,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) { // Update access for team members if needed. if authChanged { - if err = t.getRepositories(sess); err != nil { + if err = t.GetRepositoriesCtx(ctx); err != nil { return fmt.Errorf("getRepositories: %v", err) } @@ -750,7 +385,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) { // Add all repositories to the team if it has access to all of them. if includeAllChanged && t.IncludesAllRepositories { - err = t.addAllRepositories(ctx) + err = addAllRepositories(ctx, t) if err != nil { return fmt.Errorf("addAllRepositories: %v", err) } @@ -761,11 +396,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(&SearchOrgTeamOptions{}); err != nil { - return err - } - +func DeleteTeam(t *organization.Team) error { ctx, committer, err := db.TxContext() if err != nil { return err @@ -773,7 +404,11 @@ func DeleteTeam(t *Team) error { defer committer.Close() sess := db.GetEngine(ctx) - if err := t.getMembers(sess); err != nil { + if err := t.GetRepositoriesCtx(ctx); err != nil { + return err + } + + if err := t.GetMembersCtx(ctx); err != nil { return err } @@ -813,7 +448,7 @@ func DeleteTeam(t *Team) error { } if !t.IncludesAllRepositories { - if err := t.removeAllRepositories(ctx); err != nil { + if err := removeAllRepositories(ctx, t); err != nil { return err } } @@ -822,19 +457,19 @@ func DeleteTeam(t *Team) error { if _, err := sess. Where("org_id=?", t.OrgID). Where("team_id=?", t.ID). - Delete(new(TeamUser)); err != nil { + Delete(new(organization.TeamUser)); err != nil { return err } // Delete team-unit. if _, err := sess. Where("team_id=?", t.ID). - Delete(new(TeamUnit)); err != nil { + Delete(new(organization.TeamUnit)); err != nil { return err } // Delete team. - if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil { + if _, err := sess.ID(t.ID).Delete(new(organization.Team)); err != nil { return err } // Update organization number of teams. @@ -845,103 +480,15 @@ func DeleteTeam(t *Team) error { return committer.Commit() } -// ___________ ____ ___ -// \__ ___/___ _____ _____ | | \______ ___________ -// | |_/ __ \\__ \ / \| | / ___// __ \_ __ \ -// | |\ ___/ / __ \| Y Y \ | /\___ \\ ___/| | \/ -// |____| \___ >____ /__|_| /______//____ >\___ >__| -// \/ \/ \/ \/ \/ - -// TeamUser represents an team-user relation. -type TeamUser struct { - ID int64 `xorm:"pk autoincr"` - OrgID int64 `xorm:"INDEX"` - TeamID int64 `xorm:"UNIQUE(s)"` - UID int64 `xorm:"UNIQUE(s)"` -} - -func isTeamMember(e db.Engine, orgID, teamID, userID int64) (bool, error) { - return e. - Where("org_id=?", orgID). - And("team_id=?", teamID). - And("uid=?", userID). - Table("team_user"). - Exist() -} - -// IsTeamMember returns true if given user is a member of team. -func IsTeamMember(orgID, teamID, userID int64) (bool, error) { - return isTeamMember(db.GetEngine(db.DefaultContext), orgID, teamID, userID) -} - -func getTeamUsersByTeamID(e db.Engine, teamID int64) ([]*TeamUser, error) { - teamUsers := make([]*TeamUser, 0, 10) - return teamUsers, e. - Where("team_id=?", teamID). - Find(&teamUsers) -} - -func getTeamMembers(e db.Engine, teamID int64) (_ []*user_model.User, err error) { - teamUsers, err := getTeamUsersByTeamID(e, teamID) - if err != nil { - return nil, fmt.Errorf("get team-users: %v", err) - } - members := make([]*user_model.User, len(teamUsers)) - for i, teamUser := range teamUsers { - member, err := user_model.GetUserByIDEngine(e, teamUser.UID) - if err != nil { - return nil, fmt.Errorf("get user '%d': %v", teamUser.UID, err) - } - members[i] = member - } - sort.Slice(members, func(i, j int) bool { - return members[i].DisplayName() < members[j].DisplayName() - }) - return members, nil -} - -// GetTeamMembers returns all members in given team of organization. -func GetTeamMembers(teamID int64) ([]*user_model.User, error) { - return getTeamMembers(db.GetEngine(db.DefaultContext), teamID) -} - -func getUserOrgTeams(e db.Engine, orgID, userID int64) (teams []*Team, err error) { - return teams, e. - Join("INNER", "team_user", "team_user.team_id = team.id"). - Where("team.org_id = ?", orgID). - And("team_user.uid=?", userID). - Find(&teams) -} - -func getUserRepoTeams(e db.Engine, orgID, userID, repoID int64) (teams []*Team, err error) { - return teams, e. - Join("INNER", "team_user", "team_user.team_id = team.id"). - Join("INNER", "team_repo", "team_repo.team_id = team.id"). - Where("team.org_id = ?", orgID). - And("team_user.uid=?", userID). - And("team_repo.repo_id=?", repoID). - Find(&teams) -} - -// GetUserOrgTeams returns all teams that user belongs to in given organization. -func GetUserOrgTeams(orgID, userID int64) ([]*Team, error) { - return getUserOrgTeams(db.GetEngine(db.DefaultContext), orgID, userID) -} - // AddTeamMember adds new membership of given team to given organization, // the user will have membership to given organization automatically when needed. -func AddTeamMember(team *Team, userID int64) error { - isAlreadyMember, err := IsTeamMember(team.OrgID, team.ID, userID) +func AddTeamMember(team *organization.Team, userID int64) error { + isAlreadyMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, userID) if err != nil || isAlreadyMember { return err } - if err := AddOrgUser(team.OrgID, userID); err != nil { - return err - } - - // Get team and its repositories. - if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil { + if err := organization.AddOrgUser(team.OrgID, userID); err != nil { return err } @@ -951,15 +498,20 @@ func AddTeamMember(team *Team, userID int64) error { } defer committer.Close() + // Get team and its repositories. + if err := team.GetRepositoriesCtx(ctx); err != nil { + return err + } + sess := db.GetEngine(ctx) - if err := db.Insert(ctx, &TeamUser{ + if err := db.Insert(ctx, &organization.TeamUser{ UID: userID, OrgID: team.OrgID, TeamID: team.ID, }); err != nil { return err - } else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(Team)); err != nil { + } else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team)); err != nil { return err } @@ -980,25 +532,25 @@ func AddTeamMember(team *Team, userID int64) error { return committer.Commit() } -func removeTeamMember(ctx context.Context, team *Team, userID int64) error { +func removeTeamMember(ctx context.Context, team *organization.Team, userID int64) error { e := db.GetEngine(ctx) - isMember, err := isTeamMember(e, team.OrgID, team.ID, userID) + isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID) if err != nil || !isMember { return err } // Check if the user to delete is the last member in owner team. if team.IsOwnerTeam() && team.NumMembers == 1 { - return ErrLastOrgOwner{UID: userID} + return organization.ErrLastOrgOwner{UID: userID} } team.NumMembers-- - if err := team.getRepositories(e); err != nil { + if err := team.GetRepositoriesCtx(ctx); err != nil { return err } - if _, err := e.Delete(&TeamUser{ + if _, err := e.Delete(&organization.TeamUser{ UID: userID, OrgID: team.OrgID, TeamID: team.ID, @@ -1029,7 +581,7 @@ func removeTeamMember(ctx context.Context, team *Team, userID int64) error { } // Check if the user is a member of any team in the organization. - if count, err := e.Count(&TeamUser{ + if count, err := e.Count(&organization.TeamUser{ UID: userID, OrgID: team.OrgID, }); err != nil { @@ -1042,7 +594,7 @@ func removeTeamMember(ctx context.Context, team *Team, userID int64) error { } // RemoveTeamMember removes member from given team of given organization. -func RemoveTeamMember(team *Team, userID int64) error { +func RemoveTeamMember(team *organization.Team, userID int64) error { ctx, committer, err := db.TxContext() if err != nil { return err @@ -1053,125 +605,3 @@ func RemoveTeamMember(team *Team, userID int64) error { } return committer.Commit() } - -// IsUserInTeams returns if a user in some teams -func IsUserInTeams(userID int64, teamIDs []int64) (bool, error) { - return isUserInTeams(db.GetEngine(db.DefaultContext), userID, teamIDs) -} - -func isUserInTeams(e db.Engine, userID int64, teamIDs []int64) (bool, error) { - return e.Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser)) -} - -// UsersInTeamsCount counts the number of users which are in userIDs and teamIDs -func UsersInTeamsCount(userIDs, teamIDs []int64) (int64, error) { - var ids []int64 - if err := db.GetEngine(db.DefaultContext).In("uid", userIDs).In("team_id", teamIDs). - Table("team_user"). - Cols("uid").GroupBy("uid").Find(&ids); err != nil { - return 0, err - } - return int64(len(ids)), nil -} - -// ___________ __________ -// \__ ___/___ _____ _____\______ \ ____ ______ ____ -// | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \ -// | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> ) -// |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/ -// \/ \/ \/ \/ \/|__| - -// TeamRepo represents an team-repository relation. -type TeamRepo struct { - ID int64 `xorm:"pk autoincr"` - OrgID int64 `xorm:"INDEX"` - TeamID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` -} - -func hasTeamRepo(e db.Engine, orgID, teamID, repoID int64) bool { - has, _ := e. - Where("org_id=?", orgID). - And("team_id=?", teamID). - And("repo_id=?", repoID). - Get(new(TeamRepo)) - return has -} - -// HasTeamRepo returns true if given repository belongs to team. -func HasTeamRepo(orgID, teamID, repoID int64) bool { - return hasTeamRepo(db.GetEngine(db.DefaultContext), orgID, teamID, repoID) -} - -func addTeamRepo(e db.Engine, orgID, teamID, repoID int64) error { - _, err := e.InsertOne(&TeamRepo{ - OrgID: orgID, - TeamID: teamID, - RepoID: repoID, - }) - return err -} - -func removeTeamRepo(e db.Engine, teamID, repoID int64) error { - _, err := e.Delete(&TeamRepo{ - TeamID: teamID, - RepoID: repoID, - }) - return err -} - -// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository. -func GetTeamsWithAccessToRepo(orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) { - teams := make([]*Team, 0, 5) - return teams, db.GetEngine(db.DefaultContext).Where("team.authorize >= ?", mode). - Join("INNER", "team_repo", "team_repo.team_id = team.id"). - And("team_repo.org_id = ?", orgID). - And("team_repo.repo_id = ?", repoID). - Find(&teams) -} - -// ___________ ____ ___ .__ __ -// \__ ___/___ _____ _____ | | \____ |__|/ |_ -// | |_/ __ \\__ \ / \| | / \| \ __\ -// | |\ ___/ / __ \| Y Y \ | / | \ || | -// |____| \___ >____ /__|_| /______/|___| /__||__| -// \/ \/ \/ \/ - -// TeamUnit describes all units of a repository -type TeamUnit struct { - ID int64 `xorm:"pk autoincr"` - OrgID int64 `xorm:"INDEX"` - TeamID int64 `xorm:"UNIQUE(s)"` - Type unit.Type `xorm:"UNIQUE(s)"` - AccessMode perm.AccessMode -} - -// Unit returns Unit -func (t *TeamUnit) Unit() unit.Unit { - return unit.Units[t.Type] -} - -func getUnitsByTeamID(e db.Engine, teamID int64) (units []*TeamUnit, err error) { - return units, e.Where("team_id = ?", teamID).Find(&units) -} - -// UpdateTeamUnits updates a teams's units -func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) { - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - if _, err = db.GetEngine(ctx).Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil { - return err - } - - if len(units) > 0 { - if err = db.Insert(ctx, units); err != nil { - return err - } - } - - return committer.Commit() -} diff --git a/models/org_team_test.go b/models/org_team_test.go index cf3a797991..e125f3c65b 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -17,68 +18,14 @@ import ( "github.com/stretchr/testify/assert" ) -func TestTeam_IsOwnerTeam(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) - assert.True(t, team.IsOwnerTeam()) - - team = unittest.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) - assert.False(t, team.IsOwnerTeam()) -} - -func TestTeam_IsMember(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) - assert.True(t, team.IsMember(2)) - assert.False(t, team.IsMember(4)) - assert.False(t, team.IsMember(unittest.NonexistentID)) - - team = unittest.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) - assert.True(t, team.IsMember(2)) - assert.True(t, team.IsMember(4)) - assert.False(t, team.IsMember(unittest.NonexistentID)) -} - -func TestTeam_GetRepositories(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - test := func(teamID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - 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}) - } - } - test(1) - test(3) -} - -func TestTeam_GetMembers(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - test := func(teamID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - assert.NoError(t, team.GetMembers(&SearchMembersOptions{})) - assert.Len(t, team.Members, team.NumMembers) - for _, member := range team.Members { - unittest.AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) - } - } - test(1) - test(3) -} - func TestTeam_AddMember(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(teamID, userID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - assert.NoError(t, team.AddMember(userID)) - unittest.AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID}) - unittest.CheckConsistencyFor(t, &Team{ID: teamID}, &user_model.User{ID: team.OrgID}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) + assert.NoError(t, AddTeamMember(team, userID)) + unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID}) } test(1, 2) test(1, 4) @@ -89,27 +36,27 @@ func TestTeam_RemoveMember(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(teamID, userID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - assert.NoError(t, team.RemoveMember(userID)) - unittest.AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID}) - unittest.CheckConsistencyFor(t, &Team{ID: teamID}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) + assert.NoError(t, RemoveTeamMember(team, userID)) + unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}) } testSuccess(1, 4) testSuccess(2, 2) testSuccess(3, 2) testSuccess(3, unittest.NonexistentID) - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) - err := team.RemoveMember(2) - assert.True(t, IsErrLastOrgOwner(err)) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team) + err := RemoveTeamMember(team, 2) + assert.True(t, organization.IsErrLastOrgOwner(err)) } func TestTeam_HasRepository(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(teamID, repoID int64, expected bool) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - assert.Equal(t, expected, team.HasRepository(repoID)) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) + assert.Equal(t, expected, HasRepository(team, repoID)) } test(1, 1, false) test(1, 3, true) @@ -124,29 +71,29 @@ func TestTeam_AddRepository(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(teamID, repoID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository) - assert.NoError(t, team.AddRepository(repo)) - unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID}) - unittest.CheckConsistencyFor(t, &Team{ID: teamID}, &repo_model.Repository{ID: repoID}) + assert.NoError(t, AddRepository(team, repo)) + unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID}) } testSuccess(2, 3) testSuccess(2, 5) - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) - assert.Error(t, team.AddRepository(repo)) - unittest.CheckConsistencyFor(t, &Team{ID: 1}, &repo_model.Repository{ID: 1}) + assert.Error(t, AddRepository(team, repo)) + unittest.CheckConsistencyFor(t, &organization.Team{ID: 1}, &repo_model.Repository{ID: 1}) } func TestTeam_RemoveRepository(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(teamID, repoID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - assert.NoError(t, team.RemoveRepository(repoID)) - unittest.AssertNotExistsBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID}) - unittest.CheckConsistencyFor(t, &Team{ID: teamID}, &repo_model.Repository{ID: repoID}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) + assert.NoError(t, RemoveRepository(team, repoID)) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID}) } testSuccess(2, 3) testSuccess(2, 5) @@ -154,97 +101,62 @@ func TestTeam_RemoveRepository(t *testing.T) { } func TestIsUsableTeamName(t *testing.T) { - assert.NoError(t, IsUsableTeamName("usable")) - assert.True(t, db.IsErrNameReserved(IsUsableTeamName("new"))) + assert.NoError(t, organization.IsUsableTeamName("usable")) + assert.True(t, db.IsErrNameReserved(organization.IsUsableTeamName("new"))) } func TestNewTeam(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) const teamName = "newTeamName" - team := &Team{Name: teamName, OrgID: 3} + team := &organization.Team{Name: teamName, OrgID: 3} assert.NoError(t, NewTeam(team)) - unittest.AssertExistsAndLoadBean(t, &Team{Name: teamName}) - unittest.CheckConsistencyFor(t, &Team{}, &user_model.User{ID: team.OrgID}) -} - -func TestGetTeam(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - testSuccess := func(orgID int64, name string) { - team, err := GetTeam(orgID, name) - assert.NoError(t, err) - assert.EqualValues(t, orgID, team.OrgID) - assert.Equal(t, name, team.Name) - } - testSuccess(3, "Owners") - testSuccess(3, "team1") - - _, err := GetTeam(3, "nonexistent") - assert.Error(t, err) - _, err = GetTeam(unittest.NonexistentID, "Owners") - assert.Error(t, err) -} - -func TestGetTeamByID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - testSuccess := func(teamID int64) { - team, err := GetTeamByID(teamID) - assert.NoError(t, err) - assert.EqualValues(t, teamID, team.ID) - } - testSuccess(1) - testSuccess(2) - testSuccess(3) - testSuccess(4) - - _, err := GetTeamByID(unittest.NonexistentID) - assert.Error(t, err) + unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: teamName}) + unittest.CheckConsistencyFor(t, &organization.Team{}, &user_model.User{ID: team.OrgID}) } func TestUpdateTeam(t *testing.T) { // successful update assert.NoError(t, unittest.PrepareTestDatabase()) - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team) team.LowerName = "newname" team.Name = "newName" team.Description = strings.Repeat("A long description!", 100) team.AccessMode = perm.AccessModeAdmin assert.NoError(t, UpdateTeam(team, true, false)) - team = unittest.AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team) + team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"}).(*organization.Team) assert.True(t, strings.HasPrefix(team.Description, "A long description!")) access := unittest.AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: 3}).(*Access) assert.EqualValues(t, perm.AccessModeAdmin, access.Mode) - unittest.CheckConsistencyFor(t, &Team{ID: team.ID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) } func TestUpdateTeam2(t *testing.T) { // update to already-existing team assert.NoError(t, unittest.PrepareTestDatabase()) - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team) team.LowerName = "owners" team.Name = "Owners" team.Description = strings.Repeat("A long description!", 100) err := UpdateTeam(team, true, false) - assert.True(t, IsErrTeamAlreadyExist(err)) + assert.True(t, organization.IsErrTeamAlreadyExist(err)) - unittest.CheckConsistencyFor(t, &Team{ID: team.ID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) } func TestDeleteTeam(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team) assert.NoError(t, DeleteTeam(team)) - unittest.AssertNotExistsBean(t, &Team{ID: team.ID}) - unittest.AssertNotExistsBean(t, &TeamRepo{TeamID: team.ID}) - unittest.AssertNotExistsBean(t, &TeamUser{TeamID: team.ID}) + unittest.AssertNotExistsBean(t, &organization.Team{ID: team.ID}) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID}) + unittest.AssertNotExistsBean(t, &organization.TeamUser{TeamID: team.ID}) // check that team members don't have "leftover" access to repos user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User) @@ -254,78 +166,14 @@ func TestDeleteTeam(t *testing.T) { assert.True(t, accessMode < perm.AccessModeWrite) } -func TestIsTeamMember(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(orgID, teamID, userID int64, expected bool) { - isMember, err := IsTeamMember(orgID, teamID, userID) - assert.NoError(t, err) - assert.Equal(t, expected, isMember) - } - - test(3, 1, 2, true) - test(3, 1, 4, false) - test(3, 1, unittest.NonexistentID, false) - - test(3, 2, 2, true) - test(3, 2, 4, true) - - test(3, unittest.NonexistentID, unittest.NonexistentID, false) - test(unittest.NonexistentID, unittest.NonexistentID, unittest.NonexistentID, false) -} - -func TestGetTeamMembers(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - test := func(teamID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - members, err := GetTeamMembers(teamID) - assert.NoError(t, err) - assert.Len(t, members, team.NumMembers) - for _, member := range members { - unittest.AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) - } - } - test(1) - test(3) -} - -func TestGetUserTeams(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(userID int64) { - teams, _, err := GetUserTeams(&GetUserTeamOptions{UserID: userID}) - assert.NoError(t, err) - for _, team := range teams { - unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) - } - } - test(2) - test(5) - test(unittest.NonexistentID) -} - -func TestGetUserOrgTeams(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(orgID, userID int64) { - teams, err := GetUserOrgTeams(orgID, userID) - assert.NoError(t, err) - for _, team := range teams { - assert.EqualValues(t, orgID, team.OrgID) - unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) - } - } - test(3, 2) - test(3, 4) - test(3, unittest.NonexistentID) -} - func TestAddTeamMember(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(teamID, userID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) assert.NoError(t, AddTeamMember(team, userID)) - unittest.AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID}) - unittest.CheckConsistencyFor(t, &Team{ID: teamID}, &user_model.User{ID: team.OrgID}) + unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID}) } test(1, 2) test(1, 4) @@ -336,47 +184,17 @@ func TestRemoveTeamMember(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(teamID, userID int64) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) assert.NoError(t, RemoveTeamMember(team, userID)) - unittest.AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID}) - unittest.CheckConsistencyFor(t, &Team{ID: teamID}) + unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}) } testSuccess(1, 4) testSuccess(2, 2) testSuccess(3, 2) testSuccess(3, unittest.NonexistentID) - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team) err := RemoveTeamMember(team, 2) - assert.True(t, IsErrLastOrgOwner(err)) -} - -func TestHasTeamRepo(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - test := func(teamID, repoID int64, expected bool) { - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) - assert.Equal(t, expected, HasTeamRepo(team.OrgID, teamID, repoID)) - } - test(1, 1, false) - test(1, 3, true) - test(1, 5, true) - test(1, unittest.NonexistentID, false) - - test(2, 3, true) - test(2, 5, false) -} - -func TestUsersInTeamsCount(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - test := func(teamIDs, userIDs []int64, expected int64) { - count, err := UsersInTeamsCount(teamIDs, userIDs) - assert.NoError(t, err) - assert.Equal(t, expected, count) - } - - test([]int64{2}, []int64{1, 2, 3, 4}, 1) // only userid 2 - test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4 - test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5 + assert.True(t, organization.IsErrLastOrgOwner(err)) } diff --git a/models/org_test.go b/models/org_test.go index ec324cb71a..4d8831858c 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -8,176 +8,81 @@ import ( "testing" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) -func TestUser_IsOwnedBy(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - for _, testCase := range []struct { - OrgID int64 - UserID int64 - ExpectedOwner bool - }{ - {3, 2, true}, - {3, 1, false}, - {3, 3, false}, - {3, 4, false}, - {2, 2, false}, // user2 is not an organization - {2, 3, false}, - } { - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: testCase.OrgID}).(*Organization) - isOwner, err := org.IsOwnedBy(testCase.UserID) - assert.NoError(t, err) - assert.Equal(t, testCase.ExpectedOwner, isOwner) - } -} - -func TestUser_IsOrgMember(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - for _, testCase := range []struct { - OrgID int64 - UserID int64 - ExpectedMember bool - }{ - {3, 2, true}, - {3, 4, true}, - {3, 1, false}, - {3, 3, false}, - {2, 2, false}, // user2 is not an organization - {2, 3, false}, - } { - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: testCase.OrgID}).(*Organization) - isMember, err := org.IsOrgMember(testCase.UserID) - assert.NoError(t, err) - assert.Equal(t, testCase.ExpectedMember, isMember) - } -} - -func TestUser_GetTeam(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - team, err := org.GetTeam("team1") - assert.NoError(t, err) - assert.Equal(t, org.ID, team.OrgID) - assert.Equal(t, "team1", team.LowerName) - - _, err = org.GetTeam("does not exist") - assert.True(t, IsErrTeamNotExist(err)) - - nonOrg := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 2}).(*Organization) - _, err = nonOrg.GetTeam("team") - assert.True(t, IsErrTeamNotExist(err)) -} - -func TestUser_GetOwnerTeam(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - team, err := org.GetOwnerTeam() - assert.NoError(t, err) - assert.Equal(t, org.ID, team.OrgID) - - nonOrg := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 2}).(*Organization) - _, err = nonOrg.GetOwnerTeam() - assert.True(t, IsErrTeamNotExist(err)) -} - -func TestUser_GetTeams(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - teams, err := org.LoadTeams() - assert.NoError(t, err) - if assert.Len(t, teams, 4) { - assert.Equal(t, int64(1), teams[0].ID) - assert.Equal(t, int64(2), teams[1].ID) - assert.Equal(t, int64(12), teams[2].ID) - assert.Equal(t, int64(7), teams[3].ID) - } -} - -func TestUser_GetMembers(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - members, _, err := org.GetMembers() - assert.NoError(t, err) - if assert.Len(t, members, 3) { - assert.Equal(t, int64(2), members[0].ID) - assert.Equal(t, int64(28), members[1].ID) - assert.Equal(t, int64(4), members[2].ID) - } -} - -func TestUser_AddMember(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - - // add a user that is not a member - unittest.AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) - prevNumMembers := org.NumMembers - assert.NoError(t, org.AddMember(5)) - unittest.AssertExistsAndLoadBean(t, &OrgUser{UID: 5, OrgID: 3}) - org = unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - assert.Equal(t, prevNumMembers+1, org.NumMembers) - - // add a user that is already a member - unittest.AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) - prevNumMembers = org.NumMembers - assert.NoError(t, org.AddMember(4)) - unittest.AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) - org = unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - assert.Equal(t, prevNumMembers, org.NumMembers) - - unittest.CheckConsistencyFor(t, &user_model.User{}) -} - func TestUser_RemoveMember(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization) // remove a user that is a member - unittest.AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3}) + unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{UID: 4, OrgID: 3}) prevNumMembers := org.NumMembers - assert.NoError(t, org.RemoveMember(4)) - unittest.AssertNotExistsBean(t, &OrgUser{UID: 4, OrgID: 3}) - org = unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + assert.NoError(t, RemoveOrgUser(org.ID, 4)) + unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 4, OrgID: 3}) + org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization) assert.Equal(t, prevNumMembers-1, org.NumMembers) // remove a user that is not a member - unittest.AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) + unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3}) prevNumMembers = org.NumMembers - assert.NoError(t, org.RemoveMember(5)) - unittest.AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3}) - org = unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + assert.NoError(t, RemoveOrgUser(org.ID, 5)) + unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3}) + org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization) assert.Equal(t, prevNumMembers, org.NumMembers) - unittest.CheckConsistencyFor(t, &user_model.User{}, &Team{}) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) +} + +func TestRemoveOrgUser(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + testSuccess := func(orgID, userID int64) { + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User) + expectedNumMembers := org.NumMembers + if unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) { + expectedNumMembers-- + } + assert.NoError(t, RemoveOrgUser(orgID, userID)) + unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: orgID, UID: userID}) + org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User) + assert.EqualValues(t, expectedNumMembers, org.NumMembers) + } + testSuccess(3, 4) + testSuccess(3, 4) + + err := RemoveOrgUser(7, 5) + assert.Error(t, err) + assert.True(t, organization.IsErrLastOrgOwner(err)) + unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: 7, UID: 5}) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) } func TestUser_RemoveOrgRepo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: org.ID}).(*repo_model.Repository) // remove a repo that does belong to org - unittest.AssertExistsAndLoadBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) - assert.NoError(t, org.RemoveOrgRepo(repo.ID)) - unittest.AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) + unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{RepoID: repo.ID, OrgID: org.ID}) + assert.NoError(t, organization.RemoveOrgRepo(db.DefaultContext, org.ID, repo.ID)) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{RepoID: repo.ID, OrgID: org.ID}) unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) // repo should still exist // remove a repo that does not belong to org - assert.NoError(t, org.RemoveOrgRepo(repo.ID)) - unittest.AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID}) + assert.NoError(t, organization.RemoveOrgRepo(db.DefaultContext, org.ID, repo.ID)) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{RepoID: repo.ID, OrgID: org.ID}) - assert.NoError(t, org.RemoveOrgRepo(unittest.NonexistentID)) + assert.NoError(t, organization.RemoveOrgRepo(db.DefaultContext, org.ID, unittest.NonexistentID)) unittest.CheckConsistencyFor(t, &user_model.User{ID: org.ID}, - &Team{OrgID: org.ID}, + &organization.Team{OrgID: org.ID}, &repo_model.Repository{ID: repo.ID}) } @@ -187,18 +92,18 @@ func TestCreateOrganization(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) const newOrgName = "neworg" - org := &Organization{ + org := &organization.Organization{ Name: newOrgName, } unittest.AssertNotExistsBean(t, &user_model.User{Name: newOrgName, Type: user_model.UserTypeOrganization}) - assert.NoError(t, CreateOrganization(org, owner)) + assert.NoError(t, organization.CreateOrganization(org, owner)) org = unittest.AssertExistsAndLoadBean(t, - &Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}).(*Organization) + &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}).(*organization.Organization) ownerTeam := unittest.AssertExistsAndLoadBean(t, - &Team{Name: ownerTeamName, OrgID: org.ID}).(*Team) - unittest.AssertExistsAndLoadBean(t, &TeamUser{UID: owner.ID, TeamID: ownerTeam.ID}) - unittest.CheckConsistencyFor(t, &user_model.User{}, &Team{}) + &organization.Team{Name: organization.OwnerTeamName, OrgID: org.ID}).(*organization.Team) + unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: owner.ID, TeamID: ownerTeam.ID}) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) } func TestCreateOrganization2(t *testing.T) { @@ -207,16 +112,16 @@ func TestCreateOrganization2(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) const newOrgName = "neworg" - org := &Organization{ + org := &organization.Organization{ Name: newOrgName, } - unittest.AssertNotExistsBean(t, &Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) - err := CreateOrganization(org, owner) + unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) + err := organization.CreateOrganization(org, owner) assert.Error(t, err) - assert.True(t, IsErrUserNotAllowedCreateOrg(err)) - unittest.AssertNotExistsBean(t, &Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) - unittest.CheckConsistencyFor(t, &Organization{}, &Team{}) + assert.True(t, organization.IsErrUserNotAllowedCreateOrg(err)) + unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) + unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{}) } func TestCreateOrganization3(t *testing.T) { @@ -224,12 +129,12 @@ func TestCreateOrganization3(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - org := &Organization{Name: "user3"} // should already exist + org := &organization.Organization{Name: "user3"} // should already exist unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check - err := CreateOrganization(org, owner) + err := organization.CreateOrganization(org, owner) assert.Error(t, err) assert.True(t, user_model.IsErrUserAlreadyExist(err)) - unittest.CheckConsistencyFor(t, &user_model.User{}, &Team{}) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) } func TestCreateOrganization4(t *testing.T) { @@ -237,210 +142,10 @@ func TestCreateOrganization4(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - err := CreateOrganization(&Organization{Name: "assets"}, owner) + err := organization.CreateOrganization(&organization.Organization{Name: "assets"}, owner) assert.Error(t, err) assert.True(t, db.IsErrNameReserved(err)) - unittest.CheckConsistencyFor(t, &Organization{}, &Team{}) -} - -func TestGetOrgByName(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - org, err := GetOrgByName("user3") - assert.NoError(t, err) - assert.EqualValues(t, 3, org.ID) - assert.Equal(t, "user3", org.Name) - - _, err = GetOrgByName("user2") // user2 is an individual - assert.True(t, IsErrOrgNotExist(err)) - - _, err = GetOrgByName("") // corner case - assert.True(t, IsErrOrgNotExist(err)) -} - -func TestCountOrganizations(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&user_model.User{}) - assert.NoError(t, err) - assert.Equal(t, expected, CountOrganizations()) -} - -func TestIsOrganizationOwner(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(orgID, userID int64, expected bool) { - isOwner, err := IsOrganizationOwner(orgID, userID) - assert.NoError(t, err) - assert.EqualValues(t, expected, isOwner) - } - test(3, 2, true) - test(3, 3, false) - test(6, 5, true) - test(6, 4, false) - test(unittest.NonexistentID, unittest.NonexistentID, false) -} - -func TestIsOrganizationMember(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(orgID, userID int64, expected bool) { - isMember, err := IsOrganizationMember(orgID, userID) - assert.NoError(t, err) - assert.EqualValues(t, expected, isMember) - } - test(3, 2, true) - test(3, 3, false) - test(3, 4, true) - test(6, 5, true) - test(6, 4, false) - test(unittest.NonexistentID, unittest.NonexistentID, false) -} - -func TestIsPublicMembership(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(orgID, userID int64, expected bool) { - isMember, err := IsPublicMembership(orgID, userID) - assert.NoError(t, err) - assert.EqualValues(t, expected, isMember) - } - test(3, 2, true) - test(3, 3, false) - test(3, 4, false) - test(6, 5, true) - test(6, 4, false) - test(unittest.NonexistentID, unittest.NonexistentID, false) -} - -func TestFindOrgs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - orgs, err := FindOrgs(FindOrgOptions{ - UserID: 4, - IncludePrivate: true, - }) - assert.NoError(t, err) - if assert.Len(t, orgs, 1) { - assert.EqualValues(t, 3, orgs[0].ID) - } - - orgs, err = FindOrgs(FindOrgOptions{ - UserID: 4, - IncludePrivate: false, - }) - assert.NoError(t, err) - assert.Len(t, orgs, 0) - - total, err := CountOrgs(FindOrgOptions{ - UserID: 4, - IncludePrivate: true, - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, total) -} - -func TestGetOwnedOrgsByUserID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - orgs, err := GetOwnedOrgsByUserID(2) - assert.NoError(t, err) - if assert.Len(t, orgs, 1) { - assert.EqualValues(t, 3, orgs[0].ID) - } - - orgs, err = GetOwnedOrgsByUserID(4) - assert.NoError(t, err) - assert.Len(t, orgs, 0) -} - -func TestGetOwnedOrgsByUserIDDesc(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - orgs, err := GetOwnedOrgsByUserIDDesc(5, "id") - assert.NoError(t, err) - if assert.Len(t, orgs, 2) { - assert.EqualValues(t, 7, orgs[0].ID) - assert.EqualValues(t, 6, orgs[1].ID) - } - - orgs, err = GetOwnedOrgsByUserIDDesc(4, "id") - assert.NoError(t, err) - assert.Len(t, orgs, 0) -} - -func TestGetOrgUsersByUserID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - orgUsers, err := GetOrgUsersByUserID(5, &SearchOrganizationsOptions{All: true}) - assert.NoError(t, err) - if assert.Len(t, orgUsers, 2) { - assert.Equal(t, OrgUser{ - ID: orgUsers[0].ID, - OrgID: 6, - UID: 5, - IsPublic: true, - }, *orgUsers[0]) - assert.Equal(t, OrgUser{ - ID: orgUsers[1].ID, - OrgID: 7, - UID: 5, - IsPublic: false, - }, *orgUsers[1]) - } - - publicOrgUsers, err := GetOrgUsersByUserID(5, &SearchOrganizationsOptions{All: false}) - assert.NoError(t, err) - assert.Len(t, publicOrgUsers, 1) - assert.Equal(t, *orgUsers[0], *publicOrgUsers[0]) - - orgUsers, err = GetOrgUsersByUserID(1, &SearchOrganizationsOptions{All: true}) - assert.NoError(t, err) - assert.Len(t, orgUsers, 0) -} - -func TestGetOrgUsersByOrgID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - orgUsers, err := GetOrgUsersByOrgID(&FindOrgMembersOpts{ - ListOptions: db.ListOptions{}, - OrgID: 3, - PublicOnly: false, - }) - assert.NoError(t, err) - if assert.Len(t, orgUsers, 3) { - assert.Equal(t, OrgUser{ - ID: orgUsers[0].ID, - OrgID: 3, - UID: 2, - IsPublic: true, - }, *orgUsers[0]) - assert.Equal(t, OrgUser{ - ID: orgUsers[1].ID, - OrgID: 3, - UID: 4, - IsPublic: false, - }, *orgUsers[1]) - } - - orgUsers, err = GetOrgUsersByOrgID(&FindOrgMembersOpts{ - ListOptions: db.ListOptions{}, - OrgID: unittest.NonexistentID, - PublicOnly: false, - }) - assert.NoError(t, err) - assert.Len(t, orgUsers, 0) -} - -func TestChangeOrgUserStatus(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - testSuccess := func(orgID, userID int64, public bool) { - assert.NoError(t, ChangeOrgUserStatus(orgID, userID, public)) - orgUser := unittest.AssertExistsAndLoadBean(t, &OrgUser{OrgID: orgID, UID: userID}).(*OrgUser) - assert.Equal(t, public, orgUser.IsPublic) - } - - testSuccess(3, 2, false) - testSuccess(3, 2, false) - testSuccess(3, 4, true) - assert.NoError(t, ChangeOrgUserStatus(unittest.NonexistentID, unittest.NonexistentID, true)) + unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{}) } func TestAddOrgUser(t *testing.T) { @@ -448,11 +153,11 @@ func TestAddOrgUser(t *testing.T) { testSuccess := func(orgID, userID int64, isPublic bool) { org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User) expectedNumMembers := org.NumMembers - if !unittest.BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) { + if !unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) { expectedNumMembers++ } - assert.NoError(t, AddOrgUser(orgID, userID)) - ou := &OrgUser{OrgID: orgID, UID: userID} + assert.NoError(t, organization.AddOrgUser(orgID, userID)) + ou := &organization.OrgUser{OrgID: orgID, UID: userID} unittest.AssertExistsAndLoadBean(t, ou) assert.Equal(t, isPublic, ou.IsPublic) org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User) @@ -467,194 +172,5 @@ func TestAddOrgUser(t *testing.T) { setting.Service.DefaultOrgMemberVisible = true testSuccess(6, 3, true) - unittest.CheckConsistencyFor(t, &user_model.User{}, &Team{}) -} - -func TestRemoveOrgUser(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - testSuccess := func(orgID, userID int64) { - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User) - expectedNumMembers := org.NumMembers - if unittest.BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) { - expectedNumMembers-- - } - assert.NoError(t, RemoveOrgUser(orgID, userID)) - unittest.AssertNotExistsBean(t, &OrgUser{OrgID: orgID, UID: userID}) - org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User) - assert.EqualValues(t, expectedNumMembers, org.NumMembers) - } - testSuccess(3, 4) - testSuccess(3, 4) - - err := RemoveOrgUser(7, 5) - assert.Error(t, err) - assert.True(t, IsErrLastOrgOwner(err)) - unittest.AssertExistsAndLoadBean(t, &OrgUser{OrgID: 7, UID: 5}) - unittest.CheckConsistencyFor(t, &user_model.User{}, &Team{}) -} - -func TestUser_GetUserTeamIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - testSuccess := func(userID int64, expected []int64) { - teamIDs, err := org.GetUserTeamIDs(userID) - assert.NoError(t, err) - assert.Equal(t, expected, teamIDs) - } - testSuccess(2, []int64{1, 2}) - testSuccess(4, []int64{2}) - testSuccess(unittest.NonexistentID, []int64{}) -} - -func TestAccessibleReposEnv_CountRepos(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - testSuccess := func(userID, expectedCount int64) { - env, err := org.AccessibleReposEnv(userID) - assert.NoError(t, err) - count, err := env.CountRepos() - assert.NoError(t, err) - assert.EqualValues(t, expectedCount, count) - } - testSuccess(2, 3) - testSuccess(4, 2) -} - -func TestAccessibleReposEnv_RepoIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - testSuccess := func(userID, _, pageSize int64, expectedRepoIDs []int64) { - env, err := org.AccessibleReposEnv(userID) - assert.NoError(t, err) - repoIDs, err := env.RepoIDs(1, 100) - assert.NoError(t, err) - assert.Equal(t, expectedRepoIDs, repoIDs) - } - testSuccess(2, 1, 100, []int64{3, 5, 32}) - testSuccess(4, 0, 100, []int64{3, 32}) -} - -func TestAccessibleReposEnv_Repos(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - testSuccess := func(userID int64, expectedRepoIDs []int64) { - env, err := org.AccessibleReposEnv(userID) - assert.NoError(t, err) - repos, err := env.Repos(1, 100) - assert.NoError(t, err) - expectedRepos := make([]*repo_model.Repository, len(expectedRepoIDs)) - for i, repoID := range expectedRepoIDs { - expectedRepos[i] = unittest.AssertExistsAndLoadBean(t, - &repo_model.Repository{ID: repoID}).(*repo_model.Repository) - } - assert.Equal(t, expectedRepos, repos) - } - testSuccess(2, []int64{3, 5, 32}) - testSuccess(4, []int64{3, 32}) -} - -func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) - testSuccess := func(userID int64, expectedRepoIDs []int64) { - env, err := org.AccessibleReposEnv(userID) - assert.NoError(t, err) - repos, err := env.MirrorRepos() - assert.NoError(t, err) - expectedRepos := make([]*repo_model.Repository, len(expectedRepoIDs)) - for i, repoID := range expectedRepoIDs { - expectedRepos[i] = unittest.AssertExistsAndLoadBean(t, - &repo_model.Repository{ID: repoID}).(*repo_model.Repository) - } - assert.Equal(t, expectedRepos, repos) - } - testSuccess(2, []int64{5}) - testSuccess(4, []int64{}) -} - -func TestHasOrgVisibleTypePublic(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) - - const newOrgName = "test-org-public" - org := &Organization{ - Name: newOrgName, - Visibility: structs.VisibleTypePublic, - } - - unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, CreateOrganization(org, owner)) - org = unittest.AssertExistsAndLoadBean(t, - &Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*Organization) - test1 := HasOrgOrUserVisible(org.AsUser(), owner) - test2 := HasOrgOrUserVisible(org.AsUser(), user3) - test3 := HasOrgOrUserVisible(org.AsUser(), nil) - assert.True(t, test1) // owner of org - assert.True(t, test2) // user not a part of org - assert.True(t, test3) // logged out user -} - -func TestHasOrgVisibleTypeLimited(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) - - const newOrgName = "test-org-limited" - org := &Organization{ - Name: newOrgName, - Visibility: structs.VisibleTypeLimited, - } - - unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, CreateOrganization(org, owner)) - org = unittest.AssertExistsAndLoadBean(t, - &Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*Organization) - test1 := HasOrgOrUserVisible(org.AsUser(), owner) - test2 := HasOrgOrUserVisible(org.AsUser(), user3) - test3 := HasOrgOrUserVisible(org.AsUser(), nil) - assert.True(t, test1) // owner of org - assert.True(t, test2) // user not a part of org - assert.False(t, test3) // logged out user -} - -func TestHasOrgVisibleTypePrivate(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) - user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) - - const newOrgName = "test-org-private" - org := &Organization{ - Name: newOrgName, - Visibility: structs.VisibleTypePrivate, - } - - unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, CreateOrganization(org, owner)) - org = unittest.AssertExistsAndLoadBean(t, - &Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*Organization) - test1 := HasOrgOrUserVisible(org.AsUser(), owner) - test2 := HasOrgOrUserVisible(org.AsUser(), user3) - test3 := HasOrgOrUserVisible(org.AsUser(), nil) - assert.True(t, test1) // owner of org - assert.False(t, test2) // user not a part of org - assert.False(t, test3) // logged out user -} - -func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - users, err := GetUsersWhoCanCreateOrgRepo(3) - assert.NoError(t, err) - assert.Len(t, users, 2) - var ids []int64 - for i := range users { - ids = append(ids, users[i].ID) - } - assert.ElementsMatch(t, ids, []int64{2, 28}) - - users, err = GetUsersWhoCanCreateOrgRepo(7) - assert.NoError(t, err) - assert.Len(t, users, 1) - assert.EqualValues(t, 5, users[0].ID) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) } diff --git a/models/organization/main_test.go b/models/organization/main_test.go new file mode 100644 index 0000000000..a2cb911021 --- /dev/null +++ b/models/organization/main_test.go @@ -0,0 +1,24 @@ +// 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 organization + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, filepath.Join("..", ".."), + "user.yml", + "org_user.yml", + "team.yml", + "team_repo.yml", + "team_unit.yml", + "team_user.yml", + "repository.yml", + ) +} diff --git a/models/organization/org.go b/models/organization/org.go new file mode 100644 index 0000000000..9e71bbe80d --- /dev/null +++ b/models/organization/org.go @@ -0,0 +1,859 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 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 organization + +import ( + "context" + "fmt" + "strings" + + "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/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + + "xorm.io/builder" +) + +// ________ .__ __ .__ +// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____ +// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \ +// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \ +// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| / +// \/ /_____/ \/ \/ \/ \/ \/ + +// ErrOrgNotExist represents a "OrgNotExist" kind of error. +type ErrOrgNotExist struct { + ID int64 + Name string +} + +// IsErrOrgNotExist checks if an error is a ErrOrgNotExist. +func IsErrOrgNotExist(err error) bool { + _, ok := err.(ErrOrgNotExist) + return ok +} + +func (err ErrOrgNotExist) Error() string { + return fmt.Sprintf("org does not exist [id: %d, name: %s]", err.ID, err.Name) +} + +// ErrLastOrgOwner represents a "LastOrgOwner" kind of error. +type ErrLastOrgOwner struct { + UID int64 +} + +// IsErrLastOrgOwner checks if an error is a ErrLastOrgOwner. +func IsErrLastOrgOwner(err error) bool { + _, ok := err.(ErrLastOrgOwner) + return ok +} + +func (err ErrLastOrgOwner) Error() string { + return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID) +} + +// ErrUserNotAllowedCreateOrg represents a "UserNotAllowedCreateOrg" kind of error. +type ErrUserNotAllowedCreateOrg struct{} + +// IsErrUserNotAllowedCreateOrg checks if an error is an ErrUserNotAllowedCreateOrg. +func IsErrUserNotAllowedCreateOrg(err error) bool { + _, ok := err.(ErrUserNotAllowedCreateOrg) + return ok +} + +func (err ErrUserNotAllowedCreateOrg) Error() string { + return "user is not allowed to create organizations" +} + +// Organization represents an organization +type Organization user_model.User + +// OrgFromUser converts user to organization +func OrgFromUser(user *user_model.User) *Organization { + return (*Organization)(user) +} + +// TableName represents the real table name of Organization +func (Organization) TableName() string { + return "user" +} + +// IsOwnedBy returns true if given user is in the owner team. +func (org *Organization) IsOwnedBy(uid int64) (bool, error) { + return IsOrganizationOwner(db.DefaultContext, org.ID, uid) +} + +// IsOrgMember returns true if given user is member of organization. +func (org *Organization) IsOrgMember(uid int64) (bool, error) { + return IsOrganizationMember(db.DefaultContext, org.ID, uid) +} + +// CanCreateOrgRepo returns true if given user can create repo in organization +func (org *Organization) CanCreateOrgRepo(uid int64) (bool, error) { + return CanCreateOrgRepo(org.ID, uid) +} + +func (org *Organization) getTeam(ctx context.Context, name string) (*Team, error) { + return getTeam(ctx, org.ID, name) +} + +// GetTeam returns named team of organization. +func (org *Organization) GetTeam(name string) (*Team, error) { + return org.getTeam(db.DefaultContext, name) +} + +func (org *Organization) getOwnerTeam(ctx context.Context) (*Team, error) { + return org.getTeam(ctx, OwnerTeamName) +} + +// GetOwnerTeam returns owner team of organization. +func (org *Organization) GetOwnerTeam() (*Team, error) { + return org.getOwnerTeam(db.DefaultContext) +} + +// FindOrgTeams returns all teams of a given organization +func FindOrgTeams(ctx context.Context, orgID int64) ([]*Team, error) { + var teams []*Team + return teams, db.GetEngine(ctx). + Where("org_id=?", orgID). + OrderBy("CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END"). + Find(&teams) +} + +// LoadTeams load teams if not loaded. +func (org *Organization) LoadTeams() ([]*Team, error) { + return FindOrgTeams(db.DefaultContext, org.ID) +} + +// GetMembers returns all members of organization. +func (org *Organization) GetMembers() (user_model.UserList, map[int64]bool, error) { + return FindOrgMembers(&FindOrgMembersOpts{ + OrgID: org.ID, + }) +} + +// HasMemberWithUserID returns true if user with userID is part of the u organisation. +func (org *Organization) HasMemberWithUserID(userID int64) bool { + return org.hasMemberWithUserID(db.DefaultContext, userID) +} + +func (org *Organization) hasMemberWithUserID(ctx context.Context, userID int64) bool { + isMember, err := IsOrganizationMember(ctx, org.ID, userID) + if err != nil { + log.Error("IsOrganizationMember: %v", err) + return false + } + return isMember +} + +// AvatarLink returns the full avatar link with http host +func (org *Organization) AvatarLink() string { + return org.AsUser().AvatarLink() +} + +// HTMLURL returns the organization's full link. +func (org *Organization) HTMLURL() string { + return org.AsUser().HTMLURL() +} + +// OrganisationLink returns the organization sub page link. +func (org *Organization) OrganisationLink() string { + return org.AsUser().OrganisationLink() +} + +// ShortName ellipses username to length +func (org *Organization) ShortName(length int) string { + return org.AsUser().ShortName(length) +} + +// HomeLink returns the user or organization home page link. +func (org *Organization) HomeLink() string { + return org.AsUser().HomeLink() +} + +// CanCreateRepo returns if user login can create a repository +// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised +func (org *Organization) CanCreateRepo() bool { + return org.AsUser().CanCreateRepo() +} + +// FindOrgMembersOpts represensts find org members conditions +type FindOrgMembersOpts struct { + db.ListOptions + OrgID int64 + PublicOnly bool +} + +// CountOrgMembers counts the organization's members +func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) { + sess := db.GetEngine(db.DefaultContext).Where("org_id=?", opts.OrgID) + if opts.PublicOnly { + sess.And("is_public = ?", true) + } + return sess.Count(new(OrgUser)) +} + +// FindOrgMembers loads organization members according conditions +func FindOrgMembers(opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bool, error) { + ous, err := GetOrgUsersByOrgID(opts) + if err != nil { + return nil, nil, err + } + + ids := make([]int64, len(ous)) + idsIsPublic := make(map[int64]bool, len(ous)) + for i, ou := range ous { + ids[i] = ou.UID + idsIsPublic[ou.UID] = ou.IsPublic + } + + users, err := user_model.GetUsersByIDs(ids) + if err != nil { + return nil, nil, err + } + return users, idsIsPublic, nil +} + +// AsUser returns the org as user object +func (org *Organization) AsUser() *user_model.User { + return (*user_model.User)(org) +} + +// DisplayName returns full name if it's not empty, +// returns username otherwise. +func (org *Organization) DisplayName() string { + return org.AsUser().DisplayName() +} + +// CustomAvatarRelativePath returns user custom avatar relative path. +func (org *Organization) CustomAvatarRelativePath() string { + return org.Avatar +} + +// CreateOrganization creates record of a new organization. +func CreateOrganization(org *Organization, owner *user_model.User) (err error) { + if !owner.CanCreateOrganization() { + return ErrUserNotAllowedCreateOrg{} + } + + if err = user_model.IsUsableUsername(org.Name); err != nil { + return err + } + + isExist, err := user_model.IsUserExist(0, org.Name) + if err != nil { + return err + } else if isExist { + return user_model.ErrUserAlreadyExist{Name: org.Name} + } + + org.LowerName = strings.ToLower(org.Name) + if org.Rands, err = user_model.GetUserSalt(); err != nil { + return err + } + if org.Salt, err = user_model.GetUserSalt(); err != nil { + return err + } + org.UseCustomAvatar = true + org.MaxRepoCreation = -1 + org.NumTeams = 1 + org.NumMembers = 1 + org.Type = user_model.UserTypeOrganization + + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil { + return err + } + + if err = db.Insert(ctx, org); err != nil { + return fmt.Errorf("insert organization: %v", err) + } + if err = user_model.GenerateRandomAvatarCtx(ctx, org.AsUser()); err != nil { + return fmt.Errorf("generate random avatar: %v", err) + } + + // Add initial creator to organization and owner team. + if err = db.Insert(ctx, &OrgUser{ + UID: owner.ID, + OrgID: org.ID, + }); err != nil { + return fmt.Errorf("insert org-user relation: %v", err) + } + + // Create default owner team. + t := &Team{ + OrgID: org.ID, + LowerName: strings.ToLower(OwnerTeamName), + Name: OwnerTeamName, + AccessMode: perm.AccessModeOwner, + NumMembers: 1, + IncludesAllRepositories: true, + CanCreateOrgRepo: true, + } + if err = db.Insert(ctx, t); err != nil { + return fmt.Errorf("insert owner team: %v", err) + } + + // insert units for team + units := make([]TeamUnit, 0, len(unit.AllRepoUnitTypes)) + for _, tp := range unit.AllRepoUnitTypes { + units = append(units, TeamUnit{ + OrgID: org.ID, + TeamID: t.ID, + Type: tp, + }) + } + + if err = db.Insert(ctx, &units); err != nil { + return err + } + + if err = db.Insert(ctx, &TeamUser{ + UID: owner.ID, + OrgID: org.ID, + TeamID: t.ID, + }); err != nil { + return fmt.Errorf("insert team-user relation: %v", err) + } + + return committer.Commit() +} + +// GetOrgByName returns organization by given name. +func GetOrgByName(name string) (*Organization, error) { + if len(name) == 0 { + return nil, ErrOrgNotExist{0, name} + } + u := &Organization{ + LowerName: strings.ToLower(name), + Type: user_model.UserTypeOrganization, + } + has, err := db.GetEngine(db.DefaultContext).Get(u) + if err != nil { + return nil, err + } else if !has { + return nil, ErrOrgNotExist{0, name} + } + return u, nil +} + +// CountOrganizations returns number of organizations. +func CountOrganizations() int64 { + count, _ := db.GetEngine(db.DefaultContext). + Where("type=1"). + Count(new(Organization)) + return count +} + +// DeleteOrganization deletes models associated to an organization. +func DeleteOrganization(ctx context.Context, org *Organization) error { + if org.Type != user_model.UserTypeOrganization { + return fmt.Errorf("%s is a user not an organization", org.Name) + } + + if err := db.DeleteBeans(ctx, + &Team{OrgID: org.ID}, + &OrgUser{OrgID: org.ID}, + &TeamUser{OrgID: org.ID}, + &TeamUnit{OrgID: org.ID}, + ); err != nil { + return fmt.Errorf("deleteBeans: %v", err) + } + + if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil { + return fmt.Errorf("Delete: %v", err) + } + + return nil +} + +// GetOrgUserMaxAuthorizeLevel returns highest authorize level of user in an organization +func (org *Organization) GetOrgUserMaxAuthorizeLevel(uid int64) (perm.AccessMode, error) { + var authorize perm.AccessMode + _, err := db.GetEngine(db.DefaultContext). + Select("max(team.authorize)"). + Table("team"). + Join("INNER", "team_user", "team_user.team_id = team.id"). + Where("team_user.uid = ?", uid). + And("team_user.org_id = ?", org.ID). + Get(&authorize) + return authorize, err +} + +// GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization +func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) ([]*user_model.User, error) { + users := make([]*user_model.User, 0, 10) + return users, db.GetEngine(ctx). + Join("INNER", "`team_user`", "`team_user`.uid=`user`.id"). + Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). + Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": perm.AccessModeOwner})). + And("team_user.org_id = ?", orgID).Asc("`user`.name").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 { + cond := builder.NewCond() + 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 +} + +// FindOrgs returns a list of organizations according given conditions +func FindOrgs(opts FindOrgOptions) ([]*Organization, error) { + orgs := make([]*Organization, 0, 10) + sess := db.GetEngine(db.DefaultContext). + Where(opts.toConds()). + Asc("`user`.name") + if opts.Page > 0 && opts.PageSize > 0 { + sess.Limit(opts.PageSize, opts.PageSize*(opts.Page-1)) + } + return orgs, sess.Find(&orgs) +} + +// CountOrgs returns total count organizations according options +func CountOrgs(opts FindOrgOptions) (int64, error) { + return db.GetEngine(db.DefaultContext). + Where(opts.toConds()). + Count(new(user_model.User)) +} + +func getOwnedOrgsByUserID(sess db.Engine, userID int64) ([]*Organization, error) { + orgs := make([]*Organization, 0, 10) + return orgs, sess. + Join("INNER", "`team_user`", "`team_user`.org_id=`user`.id"). + Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). + Where("`team_user`.uid=?", userID). + And("`team`.authorize=?", perm.AccessModeOwner). + Asc("`user`.name"). + Find(&orgs) +} + +// HasOrgOrUserVisible tells if the given user can see the given org or user +func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool { + // Not SignedUser + if user == nil { + return orgOrUser.Visibility == structs.VisibleTypePublic + } + + if user.IsAdmin || orgOrUser.ID == user.ID { + return true + } + + if (orgOrUser.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !OrgFromUser(orgOrUser).hasMemberWithUserID(ctx, user.ID) { + return false + } + return true +} + +// HasOrgsVisible tells if the given user can see at least one of the orgs provided +func HasOrgsVisible(orgs []*Organization, user *user_model.User) bool { + if len(orgs) == 0 { + return false + } + + for _, org := range orgs { + if HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user) { + return true + } + } + return false +} + +// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID. +func GetOwnedOrgsByUserID(userID int64) ([]*Organization, error) { + return getOwnedOrgsByUserID(db.GetEngine(db.DefaultContext), userID) +} + +// GetOwnedOrgsByUserIDDesc returns a list of organizations are owned by +// given user ID, ordered descending by the given condition. +func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*Organization, error) { + return getOwnedOrgsByUserID(db.GetEngine(db.DefaultContext).Desc(desc), userID) +} + +// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID +// are allowed to create repos. +func GetOrgsCanCreateRepoByUserID(userID int64) ([]*Organization, error) { + orgs := make([]*Organization, 0, 10) + + return orgs, db.GetEngine(db.DefaultContext).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) +} + +// GetOrgUsersByUserID returns all organization-user relations by user ID. +func GetOrgUsersByUserID(uid int64, opts *SearchOrganizationsOptions) ([]*OrgUser, error) { + ous := make([]*OrgUser, 0, 10) + sess := db.GetEngine(db.DefaultContext). + Join("LEFT", "`user`", "`org_user`.org_id=`user`.id"). + Where("`org_user`.uid=?", uid) + if !opts.All { + // Only show public organizations + sess.And("is_public=?", true) + } + + if opts.PageSize != 0 { + sess = db.SetSessionPagination(sess, opts) + } + + err := sess. + Asc("`user`.name"). + Find(&ous) + return ous, err +} + +// GetOrgUsersByOrgID returns all organization-user relations by organization ID. +func GetOrgUsersByOrgID(opts *FindOrgMembersOpts) ([]*OrgUser, error) { + return getOrgUsersByOrgID(db.GetEngine(db.DefaultContext), opts) +} + +func getOrgUsersByOrgID(e db.Engine, opts *FindOrgMembersOpts) ([]*OrgUser, error) { + sess := e.Where("org_id=?", opts.OrgID) + if opts.PublicOnly { + sess.And("is_public = ?", true) + } + if opts.ListOptions.PageSize > 0 { + sess = db.SetSessionPagination(sess, opts) + + ous := make([]*OrgUser, 0, opts.PageSize) + return ous, sess.Find(&ous) + } + + var ous []*OrgUser + return ous, sess.Find(&ous) +} + +// ChangeOrgUserStatus changes public or private membership status. +func ChangeOrgUserStatus(orgID, uid int64, public bool) error { + ou := new(OrgUser) + has, err := db.GetEngine(db.DefaultContext). + Where("uid=?", uid). + And("org_id=?", orgID). + Get(ou) + if err != nil { + return err + } else if !has { + return nil + } + + ou.IsPublic = public + _, err = db.GetEngine(db.DefaultContext).ID(ou.ID).Cols("is_public").Update(ou) + return err +} + +// AddOrgUser adds new user to given organization. +func AddOrgUser(orgID, uid int64) error { + isAlreadyMember, err := IsOrganizationMember(db.DefaultContext, orgID, uid) + if err != nil || isAlreadyMember { + return err + } + + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + ou := &OrgUser{ + UID: uid, + OrgID: orgID, + IsPublic: setting.Service.DefaultOrgMemberVisible, + } + + if err := db.Insert(ctx, ou); err != nil { + return err + } else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil { + return err + } + + return committer.Commit() +} + +// GetOrgByIDCtx returns the user object by given ID if exists. +func GetOrgByIDCtx(ctx context.Context, id int64) (*Organization, error) { + u := new(Organization) + has, err := db.GetEngine(ctx).ID(id).Get(u) + if err != nil { + return nil, err + } else if !has { + return nil, user_model.ErrUserNotExist{ + UID: id, + Name: "", + KeyID: 0, + } + } + return u, nil +} + +// GetOrgByID returns the user object by given ID if exists. +func GetOrgByID(id int64) (*Organization, error) { + return GetOrgByIDCtx(db.DefaultContext, id) +} + +// RemoveOrgRepo removes all team-repository relations of organization. +func RemoveOrgRepo(ctx context.Context, orgID, repoID int64) error { + teamRepos := make([]*TeamRepo, 0, 10) + e := db.GetEngine(ctx) + if err := e.Find(&teamRepos, &TeamRepo{OrgID: orgID, RepoID: repoID}); err != nil { + return err + } + + if len(teamRepos) == 0 { + return nil + } + + if _, err := e.Delete(&TeamRepo{ + OrgID: orgID, + RepoID: repoID, + }); err != nil { + return err + } + + teamIDs := make([]int64, len(teamRepos)) + for i, teamRepo := range teamRepos { + teamIDs[i] = teamRepo.TeamID + } + + _, err := e.Decr("num_repos").In("id", teamIDs).Update(new(Team)) + return err +} + +func (org *Organization) getUserTeams(e db.Engine, userID int64, cols ...string) ([]*Team, error) { + teams := make([]*Team, 0, org.NumTeams) + return teams, e. + Where("`team_user`.org_id = ?", org.ID). + Join("INNER", "team_user", "`team_user`.team_id = team.id"). + Join("INNER", "`user`", "`user`.id=team_user.uid"). + And("`team_user`.uid = ?", userID). + Asc("`user`.name"). + Cols(cols...). + Find(&teams) +} + +func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]int64, error) { + teamIDs := make([]int64, 0, org.NumTeams) + return teamIDs, db.GetEngine(ctx). + Table("team"). + Cols("team.id"). + Where("`team_user`.org_id = ?", org.ID). + Join("INNER", "team_user", "`team_user`.team_id = team.id"). + And("`team_user`.uid = ?", userID). + Find(&teamIDs) +} + +// TeamsWithAccessToRepo returns all teams that have given access level to the repository. +func (org *Organization) TeamsWithAccessToRepo(repoID int64, mode perm.AccessMode) ([]*Team, error) { + return GetTeamsWithAccessToRepo(org.ID, repoID, mode) +} + +// GetUserTeamIDs returns of all team IDs of the organization that user is member of. +func (org *Organization) GetUserTeamIDs(userID int64) ([]int64, error) { + return org.getUserTeamIDs(db.DefaultContext, userID) +} + +// GetUserTeams returns all teams that belong to user, +// and that the user has joined. +func (org *Organization) GetUserTeams(userID int64) ([]*Team, error) { + return org.getUserTeams(db.GetEngine(db.DefaultContext), userID) +} + +// AccessibleReposEnvironment operations involving the repositories that are +// accessible to a particular user +type AccessibleReposEnvironment interface { + CountRepos() (int64, error) + RepoIDs(page, pageSize int) ([]int64, error) + Repos(page, pageSize int) ([]*repo_model.Repository, error) + MirrorRepos() ([]*repo_model.Repository, error) + AddKeyword(keyword string) + SetSort(db.SearchOrderBy) +} + +type accessibleReposEnv struct { + org *Organization + user *user_model.User + team *Team + teamIDs []int64 + e db.Engine + keyword string + orderBy db.SearchOrderBy +} + +// AccessibleReposEnv builds an AccessibleReposEnvironment for the repositories in `org` +// that are accessible to the specified user. +func AccessibleReposEnv(ctx context.Context, org *Organization, userID int64) (AccessibleReposEnvironment, error) { + var user *user_model.User + + if userID > 0 { + u, err := user_model.GetUserByIDCtx(ctx, userID) + if err != nil { + return nil, err + } + user = u + } + + teamIDs, err := org.getUserTeamIDs(ctx, userID) + if err != nil { + return nil, err + } + return &accessibleReposEnv{ + org: org, + user: user, + teamIDs: teamIDs, + e: db.GetEngine(ctx), + orderBy: db.SearchOrderByRecentUpdated, + }, nil +} + +// AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org` +// that are accessible to the specified team. +func (org *Organization) AccessibleTeamReposEnv(team *Team) AccessibleReposEnvironment { + return &accessibleReposEnv{ + org: org, + team: team, + e: db.GetEngine(db.DefaultContext), + orderBy: db.SearchOrderByRecentUpdated, + } +} + +func (env *accessibleReposEnv) cond() builder.Cond { + cond := builder.NewCond() + if env.team != nil { + cond = cond.And(builder.Eq{"team_repo.team_id": env.team.ID}) + } else { + if env.user == nil || !env.user.IsRestricted { + cond = cond.Or(builder.Eq{ + "`repository`.owner_id": env.org.ID, + "`repository`.is_private": false, + }) + } + if len(env.teamIDs) > 0 { + cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs)) + } + } + if env.keyword != "" { + cond = cond.And(builder.Like{"`repository`.lower_name", strings.ToLower(env.keyword)}) + } + return cond +} + +func (env *accessibleReposEnv) CountRepos() (int64, error) { + repoCount, err := env.e. + Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). + Where(env.cond()). + Distinct("`repository`.id"). + Count(&repo_model.Repository{}) + if err != nil { + return 0, fmt.Errorf("count user repositories in organization: %v", err) + } + return repoCount, nil +} + +func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) { + if page <= 0 { + page = 1 + } + + repoIDs := make([]int64, 0, pageSize) + return repoIDs, env.e. + Table("repository"). + Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). + Where(env.cond()). + GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]). + OrderBy(string(env.orderBy)). + Limit(pageSize, (page-1)*pageSize). + Cols("`repository`.id"). + Find(&repoIDs) +} + +func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*repo_model.Repository, error) { + repoIDs, err := env.RepoIDs(page, pageSize) + if err != nil { + return nil, fmt.Errorf("GetUserRepositoryIDs: %v", err) + } + + repos := make([]*repo_model.Repository, 0, len(repoIDs)) + if len(repoIDs) == 0 { + return repos, nil + } + + return repos, env.e. + In("`repository`.id", repoIDs). + OrderBy(string(env.orderBy)). + Find(&repos) +} + +func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { + repoIDs := make([]int64, 0, 10) + return repoIDs, env.e. + Table("repository"). + Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). + Where(env.cond()). + GroupBy("`repository`.id, `repository`.updated_unix"). + OrderBy(string(env.orderBy)). + Cols("`repository`.id"). + Find(&repoIDs) +} + +func (env *accessibleReposEnv) MirrorRepos() ([]*repo_model.Repository, error) { + repoIDs, err := env.MirrorRepoIDs() + if err != nil { + return nil, fmt.Errorf("MirrorRepoIDs: %v", err) + } + + repos := make([]*repo_model.Repository, 0, len(repoIDs)) + if len(repoIDs) == 0 { + return repos, nil + } + + return repos, env.e. + In("`repository`.id", repoIDs). + Find(&repos) +} + +func (env *accessibleReposEnv) AddKeyword(keyword string) { + env.keyword = keyword +} + +func (env *accessibleReposEnv) SetSort(orderBy db.SearchOrderBy) { + env.orderBy = orderBy +} diff --git a/models/organization/org_test.go b/models/organization/org_test.go new file mode 100644 index 0000000000..71cdbd869f --- /dev/null +++ b/models/organization/org_test.go @@ -0,0 +1,478 @@ +// Copyright 2017 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 organization + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestUser_IsOwnedBy(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + for _, testCase := range []struct { + OrgID int64 + UserID int64 + ExpectedOwner bool + }{ + {3, 2, true}, + {3, 1, false}, + {3, 3, false}, + {3, 4, false}, + {2, 2, false}, // user2 is not an organization + {2, 3, false}, + } { + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: testCase.OrgID}).(*Organization) + isOwner, err := org.IsOwnedBy(testCase.UserID) + assert.NoError(t, err) + assert.Equal(t, testCase.ExpectedOwner, isOwner) + } +} + +func TestUser_IsOrgMember(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + for _, testCase := range []struct { + OrgID int64 + UserID int64 + ExpectedMember bool + }{ + {3, 2, true}, + {3, 4, true}, + {3, 1, false}, + {3, 3, false}, + {2, 2, false}, // user2 is not an organization + {2, 3, false}, + } { + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: testCase.OrgID}).(*Organization) + isMember, err := org.IsOrgMember(testCase.UserID) + assert.NoError(t, err) + assert.Equal(t, testCase.ExpectedMember, isMember) + } +} + +func TestUser_GetTeam(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + team, err := org.GetTeam("team1") + assert.NoError(t, err) + assert.Equal(t, org.ID, team.OrgID) + assert.Equal(t, "team1", team.LowerName) + + _, err = org.GetTeam("does not exist") + assert.True(t, IsErrTeamNotExist(err)) + + nonOrg := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 2}).(*Organization) + _, err = nonOrg.GetTeam("team") + assert.True(t, IsErrTeamNotExist(err)) +} + +func TestUser_GetOwnerTeam(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + team, err := org.GetOwnerTeam() + assert.NoError(t, err) + assert.Equal(t, org.ID, team.OrgID) + + nonOrg := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 2}).(*Organization) + _, err = nonOrg.GetOwnerTeam() + assert.True(t, IsErrTeamNotExist(err)) +} + +func TestUser_GetTeams(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + teams, err := org.LoadTeams() + assert.NoError(t, err) + if assert.Len(t, teams, 4) { + assert.Equal(t, int64(1), teams[0].ID) + assert.Equal(t, int64(2), teams[1].ID) + assert.Equal(t, int64(12), teams[2].ID) + assert.Equal(t, int64(7), teams[3].ID) + } +} + +func TestUser_GetMembers(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + members, _, err := org.GetMembers() + assert.NoError(t, err) + if assert.Len(t, members, 3) { + assert.Equal(t, int64(2), members[0].ID) + assert.Equal(t, int64(28), members[1].ID) + assert.Equal(t, int64(4), members[2].ID) + } +} + +func TestGetOrgByName(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + org, err := GetOrgByName("user3") + assert.NoError(t, err) + assert.EqualValues(t, 3, org.ID) + assert.Equal(t, "user3", org.Name) + + _, err = GetOrgByName("user2") // user2 is an individual + assert.True(t, IsErrOrgNotExist(err)) + + _, err = GetOrgByName("") // corner case + assert.True(t, IsErrOrgNotExist(err)) +} + +func TestCountOrganizations(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&user_model.User{}) + assert.NoError(t, err) + assert.Equal(t, expected, CountOrganizations()) +} + +func TestIsOrganizationOwner(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + test := func(orgID, userID int64, expected bool) { + isOwner, err := IsOrganizationOwner(db.DefaultContext, orgID, userID) + assert.NoError(t, err) + assert.EqualValues(t, expected, isOwner) + } + test(3, 2, true) + test(3, 3, false) + test(6, 5, true) + test(6, 4, false) + test(unittest.NonexistentID, unittest.NonexistentID, false) +} + +func TestIsOrganizationMember(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + test := func(orgID, userID int64, expected bool) { + isMember, err := IsOrganizationMember(db.DefaultContext, orgID, userID) + assert.NoError(t, err) + assert.EqualValues(t, expected, isMember) + } + test(3, 2, true) + test(3, 3, false) + test(3, 4, true) + test(6, 5, true) + test(6, 4, false) + test(unittest.NonexistentID, unittest.NonexistentID, false) +} + +func TestIsPublicMembership(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + test := func(orgID, userID int64, expected bool) { + isMember, err := IsPublicMembership(orgID, userID) + assert.NoError(t, err) + assert.EqualValues(t, expected, isMember) + } + test(3, 2, true) + test(3, 3, false) + test(3, 4, false) + test(6, 5, true) + test(6, 4, false) + test(unittest.NonexistentID, unittest.NonexistentID, false) +} + +func TestFindOrgs(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + orgs, err := FindOrgs(FindOrgOptions{ + UserID: 4, + IncludePrivate: true, + }) + assert.NoError(t, err) + if assert.Len(t, orgs, 1) { + assert.EqualValues(t, 3, orgs[0].ID) + } + + orgs, err = FindOrgs(FindOrgOptions{ + UserID: 4, + IncludePrivate: false, + }) + assert.NoError(t, err) + assert.Len(t, orgs, 0) + + total, err := CountOrgs(FindOrgOptions{ + UserID: 4, + IncludePrivate: true, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, total) +} + +func TestGetOwnedOrgsByUserID(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + orgs, err := GetOwnedOrgsByUserID(2) + assert.NoError(t, err) + if assert.Len(t, orgs, 1) { + assert.EqualValues(t, 3, orgs[0].ID) + } + + orgs, err = GetOwnedOrgsByUserID(4) + assert.NoError(t, err) + assert.Len(t, orgs, 0) +} + +func TestGetOwnedOrgsByUserIDDesc(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + orgs, err := GetOwnedOrgsByUserIDDesc(5, "id") + assert.NoError(t, err) + if assert.Len(t, orgs, 2) { + assert.EqualValues(t, 7, orgs[0].ID) + assert.EqualValues(t, 6, orgs[1].ID) + } + + orgs, err = GetOwnedOrgsByUserIDDesc(4, "id") + assert.NoError(t, err) + assert.Len(t, orgs, 0) +} + +func TestGetOrgUsersByUserID(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + orgUsers, err := GetOrgUsersByUserID(5, &SearchOrganizationsOptions{All: true}) + assert.NoError(t, err) + if assert.Len(t, orgUsers, 2) { + assert.Equal(t, OrgUser{ + ID: orgUsers[0].ID, + OrgID: 6, + UID: 5, + IsPublic: true, + }, *orgUsers[0]) + assert.Equal(t, OrgUser{ + ID: orgUsers[1].ID, + OrgID: 7, + UID: 5, + IsPublic: false, + }, *orgUsers[1]) + } + + publicOrgUsers, err := GetOrgUsersByUserID(5, &SearchOrganizationsOptions{All: false}) + assert.NoError(t, err) + assert.Len(t, publicOrgUsers, 1) + assert.Equal(t, *orgUsers[0], *publicOrgUsers[0]) + + orgUsers, err = GetOrgUsersByUserID(1, &SearchOrganizationsOptions{All: true}) + assert.NoError(t, err) + assert.Len(t, orgUsers, 0) +} + +func TestGetOrgUsersByOrgID(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + orgUsers, err := GetOrgUsersByOrgID(&FindOrgMembersOpts{ + ListOptions: db.ListOptions{}, + OrgID: 3, + PublicOnly: false, + }) + assert.NoError(t, err) + if assert.Len(t, orgUsers, 3) { + assert.Equal(t, OrgUser{ + ID: orgUsers[0].ID, + OrgID: 3, + UID: 2, + IsPublic: true, + }, *orgUsers[0]) + assert.Equal(t, OrgUser{ + ID: orgUsers[1].ID, + OrgID: 3, + UID: 4, + IsPublic: false, + }, *orgUsers[1]) + } + + orgUsers, err = GetOrgUsersByOrgID(&FindOrgMembersOpts{ + ListOptions: db.ListOptions{}, + OrgID: unittest.NonexistentID, + PublicOnly: false, + }) + assert.NoError(t, err) + assert.Len(t, orgUsers, 0) +} + +func TestChangeOrgUserStatus(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testSuccess := func(orgID, userID int64, public bool) { + assert.NoError(t, ChangeOrgUserStatus(orgID, userID, public)) + orgUser := unittest.AssertExistsAndLoadBean(t, &OrgUser{OrgID: orgID, UID: userID}).(*OrgUser) + assert.Equal(t, public, orgUser.IsPublic) + } + + testSuccess(3, 2, false) + testSuccess(3, 2, false) + testSuccess(3, 4, true) + assert.NoError(t, ChangeOrgUserStatus(unittest.NonexistentID, unittest.NonexistentID, true)) +} + +func TestUser_GetUserTeamIDs(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + testSuccess := func(userID int64, expected []int64) { + teamIDs, err := org.GetUserTeamIDs(userID) + assert.NoError(t, err) + assert.Equal(t, expected, teamIDs) + } + testSuccess(2, []int64{1, 2}) + testSuccess(4, []int64{2}) + testSuccess(unittest.NonexistentID, []int64{}) +} + +func TestAccessibleReposEnv_CountRepos(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + testSuccess := func(userID, expectedCount int64) { + env, err := AccessibleReposEnv(db.DefaultContext, org, userID) + assert.NoError(t, err) + count, err := env.CountRepos() + assert.NoError(t, err) + assert.EqualValues(t, expectedCount, count) + } + testSuccess(2, 3) + testSuccess(4, 2) +} + +func TestAccessibleReposEnv_RepoIDs(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + testSuccess := func(userID, _, pageSize int64, expectedRepoIDs []int64) { + env, err := AccessibleReposEnv(db.DefaultContext, org, userID) + assert.NoError(t, err) + repoIDs, err := env.RepoIDs(1, 100) + assert.NoError(t, err) + assert.Equal(t, expectedRepoIDs, repoIDs) + } + testSuccess(2, 1, 100, []int64{3, 5, 32}) + testSuccess(4, 0, 100, []int64{3, 32}) +} + +func TestAccessibleReposEnv_Repos(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + testSuccess := func(userID int64, expectedRepoIDs []int64) { + env, err := AccessibleReposEnv(db.DefaultContext, org, userID) + assert.NoError(t, err) + repos, err := env.Repos(1, 100) + assert.NoError(t, err) + expectedRepos := make([]*repo_model.Repository, len(expectedRepoIDs)) + for i, repoID := range expectedRepoIDs { + expectedRepos[i] = unittest.AssertExistsAndLoadBean(t, + &repo_model.Repository{ID: repoID}).(*repo_model.Repository) + } + assert.Equal(t, expectedRepos, repos) + } + testSuccess(2, []int64{3, 5, 32}) + testSuccess(4, []int64{3, 32}) +} + +func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + org := unittest.AssertExistsAndLoadBean(t, &Organization{ID: 3}).(*Organization) + testSuccess := func(userID int64, expectedRepoIDs []int64) { + env, err := AccessibleReposEnv(db.DefaultContext, org, userID) + assert.NoError(t, err) + repos, err := env.MirrorRepos() + assert.NoError(t, err) + expectedRepos := make([]*repo_model.Repository, len(expectedRepoIDs)) + for i, repoID := range expectedRepoIDs { + expectedRepos[i] = unittest.AssertExistsAndLoadBean(t, + &repo_model.Repository{ID: repoID}).(*repo_model.Repository) + } + assert.Equal(t, expectedRepos, repos) + } + testSuccess(2, []int64{5}) + testSuccess(4, []int64{}) +} + +func TestHasOrgVisibleTypePublic(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) + + const newOrgName = "test-org-public" + org := &Organization{ + Name: newOrgName, + Visibility: structs.VisibleTypePublic, + } + + unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) + assert.NoError(t, CreateOrganization(org, owner)) + org = unittest.AssertExistsAndLoadBean(t, + &Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*Organization) + test1 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner) + test2 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3) + test3 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil) + assert.True(t, test1) // owner of org + assert.True(t, test2) // user not a part of org + assert.True(t, test3) // logged out user +} + +func TestHasOrgVisibleTypeLimited(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) + + const newOrgName = "test-org-limited" + org := &Organization{ + Name: newOrgName, + Visibility: structs.VisibleTypeLimited, + } + + unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) + assert.NoError(t, CreateOrganization(org, owner)) + org = unittest.AssertExistsAndLoadBean(t, + &Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*Organization) + test1 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner) + test2 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3) + test3 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil) + assert.True(t, test1) // owner of org + assert.True(t, test2) // user not a part of org + assert.False(t, test3) // logged out user +} + +func TestHasOrgVisibleTypePrivate(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) + + const newOrgName = "test-org-private" + org := &Organization{ + Name: newOrgName, + Visibility: structs.VisibleTypePrivate, + } + + unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) + assert.NoError(t, CreateOrganization(org, owner)) + org = unittest.AssertExistsAndLoadBean(t, + &Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*Organization) + test1 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner) + test2 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3) + test3 := HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil) + assert.True(t, test1) // owner of org + assert.False(t, test2) // user not a part of org + assert.False(t, test3) // logged out user +} + +func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + users, err := GetUsersWhoCanCreateOrgRepo(db.DefaultContext, 3) + assert.NoError(t, err) + assert.Len(t, users, 2) + var ids []int64 + for i := range users { + ids = append(ids, users[i].ID) + } + assert.ElementsMatch(t, ids, []int64{2, 28}) + + users, err = GetUsersWhoCanCreateOrgRepo(db.DefaultContext, 7) + assert.NoError(t, err) + assert.Len(t, users, 1) + assert.EqualValues(t, 5, users[0].ID) +} diff --git a/models/organization/org_user.go b/models/organization/org_user.go new file mode 100644 index 0000000000..b679246d0b --- /dev/null +++ b/models/organization/org_user.go @@ -0,0 +1,83 @@ +// Copyright 2022 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 organization + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + + "xorm.io/builder" +) + +// ________ ____ ___ +// \_____ \_______ ____ | | \______ ___________ +// / | \_ __ \/ ___\| | / ___// __ \_ __ \ +// / | \ | \/ /_/ > | /\___ \\ ___/| | \/ +// \_______ /__| \___ /|______//____ >\___ >__| +// \/ /_____/ \/ \/ + +// OrgUser represents an organization-user relation. +type OrgUser struct { + ID int64 `xorm:"pk autoincr"` + UID int64 `xorm:"INDEX UNIQUE(s)"` + OrgID int64 `xorm:"INDEX UNIQUE(s)"` + IsPublic bool `xorm:"INDEX"` +} + +func init() { + db.RegisterModel(new(OrgUser)) +} + +// GetOrganizationCount returns count of membership of organization of the user. +func GetOrganizationCount(ctx context.Context, u *user_model.User) (int64, error) { + return db.GetEngine(ctx). + Where("uid=?", u.ID). + Count(new(OrgUser)) +} + +// IsOrganizationOwner returns true if given user is in the owner team. +func IsOrganizationOwner(ctx context.Context, orgID, uid int64) (bool, error) { + ownerTeam, err := GetOwnerTeam(ctx, orgID) + if err != nil { + if IsErrTeamNotExist(err) { + log.Error("Organization does not have owner team: %d", orgID) + return false, nil + } + return false, err + } + return IsTeamMember(ctx, orgID, ownerTeam.ID, uid) +} + +// IsOrganizationMember returns true if given user is member of organization. +func IsOrganizationMember(ctx context.Context, orgID, uid int64) (bool, error) { + return db.GetEngine(ctx). + Where("uid=?", uid). + And("org_id=?", orgID). + Table("org_user"). + Exist() +} + +// IsPublicMembership returns true if the given user's membership of given org is public. +func IsPublicMembership(orgID, uid int64) (bool, error) { + return db.GetEngine(db.DefaultContext). + Where("uid=?", uid). + And("org_id=?", orgID). + And("is_public=?", true). + Table("org_user"). + Exist() +} + +// CanCreateOrgRepo returns true if user can create repo in organization +func CanCreateOrgRepo(orgID, uid int64) (bool, error) { + return db.GetEngine(db.DefaultContext). + Where(builder.Eq{"team.can_create_org_repo": true}). + Join("INNER", "team_user", "team_user.team_id = team.id"). + And("team_user.uid = ?", uid). + And("team_user.org_id = ?", orgID). + Exist(new(Team)) +} diff --git a/models/organization/org_user_test.go b/models/organization/org_user_test.go new file mode 100644 index 0000000000..b323002934 --- /dev/null +++ b/models/organization/org_user_test.go @@ -0,0 +1,72 @@ +// Copyright 2022 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 organization + +import ( + "fmt" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +func TestUserIsPublicMember(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + tt := []struct { + uid int64 + orgid int64 + expected bool + }{ + {2, 3, true}, + {4, 3, false}, + {5, 6, true}, + {5, 7, false}, + } + for _, v := range tt { + t.Run(fmt.Sprintf("UserId%dIsPublicMemberOf%d", v.uid, v.orgid), func(t *testing.T) { + testUserIsPublicMember(t, v.uid, v.orgid, v.expected) + }) + } +} + +func testUserIsPublicMember(t *testing.T, uid, orgID int64, expected bool) { + user, err := user_model.GetUserByID(uid) + assert.NoError(t, err) + is, err := IsPublicMembership(orgID, user.ID) + assert.NoError(t, err) + assert.Equal(t, expected, is) +} + +func TestIsUserOrgOwner(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + tt := []struct { + uid int64 + orgid int64 + expected bool + }{ + {2, 3, true}, + {4, 3, false}, + {5, 6, true}, + {5, 7, true}, + } + for _, v := range tt { + t.Run(fmt.Sprintf("UserId%dIsOrgOwnerOf%d", v.uid, v.orgid), func(t *testing.T) { + testIsUserOrgOwner(t, v.uid, v.orgid, v.expected) + }) + } +} + +func testIsUserOrgOwner(t *testing.T, uid, orgID int64, expected bool) { + user, err := user_model.GetUserByID(uid) + assert.NoError(t, err) + is, err := IsOrganizationOwner(db.DefaultContext, orgID, user.ID) + assert.NoError(t, err) + assert.Equal(t, expected, is) +} diff --git a/models/organization/team.go b/models/organization/team.go new file mode 100644 index 0000000000..1617005841 --- /dev/null +++ b/models/organization/team.go @@ -0,0 +1,359 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Copyright 2016 The Gogs 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 organization + +import ( + "context" + "fmt" + "strings" + + "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/log" + + "xorm.io/builder" +) + +// ___________ +// \__ ___/___ _____ _____ +// | |_/ __ \\__ \ / \ +// | |\ ___/ / __ \| Y Y \ +// |____| \___ >____ /__|_| / +// \/ \/ \/ + +// ErrTeamAlreadyExist represents a "TeamAlreadyExist" kind of error. +type ErrTeamAlreadyExist struct { + OrgID int64 + Name string +} + +// IsErrTeamAlreadyExist checks if an error is a ErrTeamAlreadyExist. +func IsErrTeamAlreadyExist(err error) bool { + _, ok := err.(ErrTeamAlreadyExist) + return ok +} + +func (err ErrTeamAlreadyExist) Error() string { + return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name) +} + +// ErrTeamNotExist represents a "TeamNotExist" error +type ErrTeamNotExist struct { + OrgID int64 + TeamID int64 + Name string +} + +// IsErrTeamNotExist checks if an error is a ErrTeamNotExist. +func IsErrTeamNotExist(err error) bool { + _, ok := err.(ErrTeamNotExist) + return ok +} + +func (err ErrTeamNotExist) Error() string { + return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name) +} + +// OwnerTeamName return the owner team name +const OwnerTeamName = "Owners" + +// Team represents a organization team. +type Team struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + LowerName string + Name string + Description string + AccessMode perm.AccessMode `xorm:"'authorize'"` + Repos []*repo_model.Repository `xorm:"-"` + Members []*user_model.User `xorm:"-"` + NumRepos int + NumMembers int + Units []*TeamUnit `xorm:"-"` + IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"` + CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"` +} + +func init() { + db.RegisterModel(new(Team)) + db.RegisterModel(new(TeamUser)) + db.RegisterModel(new(TeamRepo)) + db.RegisterModel(new(TeamUnit)) +} + +// SearchTeamOptions holds the search options +type SearchTeamOptions struct { + db.ListOptions + UserID int64 + Keyword string + OrgID int64 + IncludeDesc bool +} + +// SearchTeam search for teams. Caller is responsible to check permissions. +func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { + if opts.Page <= 0 { + opts.Page = 1 + } + if opts.PageSize == 0 { + // Default limit + opts.PageSize = 10 + } + + cond := builder.NewCond() + + if len(opts.Keyword) > 0 { + lowerKeyword := strings.ToLower(opts.Keyword) + var keywordCond builder.Cond = builder.Like{"lower_name", lowerKeyword} + if opts.IncludeDesc { + keywordCond = keywordCond.Or(builder.Like{"LOWER(description)", lowerKeyword}) + } + cond = cond.And(keywordCond) + } + + cond = cond.And(builder.Eq{"org_id": opts.OrgID}) + + sess := db.GetEngine(db.DefaultContext) + + count, err := sess. + Where(cond). + Count(new(Team)) + if err != nil { + return nil, 0, err + } + + sess = sess.Where(cond) + if opts.PageSize == -1 { + opts.PageSize = int(count) + } else { + sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + } + + teams := make([]*Team, 0, opts.PageSize) + if err = sess. + OrderBy("lower_name"). + Find(&teams); err != nil { + return nil, 0, err + } + + return teams, count, nil +} + +// ColorFormat provides a basic color format for a Team +func (t *Team) ColorFormat(s fmt.State) { + if t == nil { + log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v", + log.NewColoredIDValue(0), + "", + log.NewColoredIDValue(0), + 0) + return + } + log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v", + log.NewColoredIDValue(t.ID), + t.Name, + log.NewColoredIDValue(t.OrgID), + t.AccessMode) +} + +// GetUnits return a list of available units for a team +func (t *Team) GetUnits() error { + return t.getUnits(db.DefaultContext) +} + +func (t *Team) getUnits(ctx context.Context) (err error) { + if t.Units != nil { + return nil + } + + t.Units, err = getUnitsByTeamID(ctx, t.ID) + return err +} + +// GetUnitNames returns the team units names +func (t *Team) GetUnitNames() (res []string) { + if t.AccessMode >= perm.AccessModeAdmin { + return unit.AllUnitKeyNames() + } + + for _, u := range t.Units { + res = append(res, unit.Units[u.Type].NameKey) + } + return +} + +// GetUnitsMap returns the team units permissions +func (t *Team) GetUnitsMap() map[string]string { + m := make(map[string]string) + if t.AccessMode >= perm.AccessModeAdmin { + for _, u := range unit.Units { + m[u.NameKey] = t.AccessMode.String() + } + } else { + for _, u := range t.Units { + m[u.Unit().NameKey] = u.AccessMode.String() + } + } + return m +} + +// IsOwnerTeam returns true if team is owner team. +func (t *Team) IsOwnerTeam() bool { + return t.Name == OwnerTeamName +} + +// IsMember returns true if given user is a member of team. +func (t *Team) IsMember(userID int64) bool { + isMember, err := IsTeamMember(db.DefaultContext, t.OrgID, t.ID, userID) + if err != nil { + log.Error("IsMember: %v", err) + return false + } + return isMember +} + +// GetRepositoriesCtx returns paginated repositories in team of organization. +func (t *Team) GetRepositoriesCtx(ctx context.Context) (err error) { + if t.Repos != nil { + return nil + } + t.Repos, err = GetTeamRepositories(ctx, &SearchTeamRepoOptions{ + TeamID: t.ID, + }) + return +} + +// GetMembersCtx returns paginated members in team of organization. +func (t *Team) GetMembersCtx(ctx context.Context) (err error) { + t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{ + TeamID: t.ID, + }) + return err +} + +// UnitEnabled returns if the team has the given unit type enabled +func (t *Team) UnitEnabled(tp unit.Type) bool { + return t.unitEnabled(db.DefaultContext, tp) +} + +func (t *Team) unitEnabled(ctx context.Context, tp unit.Type) bool { + return t.UnitAccessMode(ctx, tp) > perm.AccessModeNone +} + +// UnitAccessMode returns if the team has the given unit type enabled +func (t *Team) UnitAccessMode(ctx context.Context, tp unit.Type) perm.AccessMode { + if err := t.getUnits(ctx); err != nil { + log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error()) + } + + for _, unit := range t.Units { + if unit.Type == tp { + return unit.AccessMode + } + } + return perm.AccessModeNone +} + +// IsUsableTeamName tests if a name could be as team name +func IsUsableTeamName(name string) error { + switch name { + case "new": + return db.ErrNameReserved{Name: name} + default: + return nil + } +} + +func getTeam(ctx context.Context, orgID int64, name string) (*Team, error) { + t := &Team{ + OrgID: orgID, + LowerName: strings.ToLower(name), + } + has, err := db.GetByBean(ctx, t) + if err != nil { + return nil, err + } else if !has { + return nil, ErrTeamNotExist{orgID, 0, name} + } + return t, nil +} + +// GetTeam returns team by given team name and organization. +func GetTeam(orgID int64, name string) (*Team, error) { + return getTeam(db.DefaultContext, orgID, name) +} + +// GetTeamIDsByNames returns a slice of team ids corresponds to names. +func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) { + ids := make([]int64, 0, len(names)) + for _, name := range names { + u, err := GetTeam(orgID, name) + if err != nil { + if ignoreNonExistent { + continue + } else { + return nil, err + } + } + ids = append(ids, u.ID) + } + return ids, nil +} + +// GetOwnerTeam returns team by given team name and organization. +func GetOwnerTeam(ctx context.Context, orgID int64) (*Team, error) { + return getTeam(ctx, orgID, OwnerTeamName) +} + +// GetTeamByIDCtx returns team by given ID. +func GetTeamByIDCtx(ctx context.Context, teamID int64) (*Team, error) { + t := new(Team) + has, err := db.GetEngine(ctx).ID(teamID).Get(t) + if err != nil { + return nil, err + } else if !has { + return nil, ErrTeamNotExist{0, teamID, ""} + } + return t, nil +} + +// GetTeamByID returns team by given ID. +func GetTeamByID(teamID int64) (*Team, error) { + return GetTeamByIDCtx(db.DefaultContext, teamID) +} + +// GetTeamNamesByID returns team's lower name from a list of team ids. +func GetTeamNamesByID(teamIDs []int64) ([]string, error) { + if len(teamIDs) == 0 { + return []string{}, nil + } + + var teamNames []string + err := db.GetEngine(db.DefaultContext).Table("team"). + Select("lower_name"). + In("id", teamIDs). + Asc("name"). + Find(&teamNames) + + return teamNames, err +} + +func getRepoTeams(e db.Engine, repo *repo_model.Repository) (teams []*Team, err error) { + return teams, e. + Join("INNER", "team_repo", "team_repo.team_id = team.id"). + Where("team.org_id = ?", repo.OwnerID). + And("team_repo.repo_id=?", repo.ID). + OrderBy("CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END"). + Find(&teams) +} + +// GetRepoTeams gets the list of teams that has access to the repository +func GetRepoTeams(repo *repo_model.Repository) ([]*Team, error) { + return getRepoTeams(db.GetEngine(db.DefaultContext), repo) +} diff --git a/models/organization/team_repo.go b/models/organization/team_repo.go new file mode 100644 index 0000000000..e6ac751829 --- /dev/null +++ b/models/organization/team_repo.go @@ -0,0 +1,84 @@ +// Copyright 2022 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 organization + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "xorm.io/builder" +) + +// TeamRepo represents an team-repository relation. +type TeamRepo struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` +} + +// HasTeamRepo returns true if given repository belongs to team. +func HasTeamRepo(ctx context.Context, orgID, teamID, repoID int64) bool { + has, _ := db.GetEngine(ctx). + Where("org_id=?", orgID). + And("team_id=?", teamID). + And("repo_id=?", repoID). + Get(new(TeamRepo)) + return has +} + +type SearchTeamRepoOptions struct { + db.ListOptions + TeamID int64 +} + +// GetRepositories returns paginated repositories in team of organization. +func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) ([]*repo_model.Repository, error) { + sess := db.GetEngine(ctx) + if opts.TeamID > 0 { + sess = sess.In("id", + builder.Select("repo_id"). + From("team_repo"). + Where(builder.Eq{"team_id": opts.TeamID}), + ) + } + if opts.PageSize > 0 { + sess.Limit(opts.PageSize, opts.Page*opts.PageSize) + } + var repos []*repo_model.Repository + return repos, sess.OrderBy("repository.name"). + Find(&repos) +} + +// AddTeamRepo addes a repo for an organization's team +func AddTeamRepo(ctx context.Context, orgID, teamID, repoID int64) error { + _, err := db.GetEngine(ctx).Insert(&TeamRepo{ + OrgID: orgID, + TeamID: teamID, + RepoID: repoID, + }) + return err +} + +// RemoveTeamRepo remove repository from team +func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error { + _, err := db.DeleteByBean(ctx, &TeamRepo{ + TeamID: teamID, + RepoID: repoID, + }) + return err +} + +// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository. +func GetTeamsWithAccessToRepo(orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) { + teams := make([]*Team, 0, 5) + return teams, db.GetEngine(db.DefaultContext).Where("team.authorize >= ?", mode). + Join("INNER", "team_repo", "team_repo.team_id = team.id"). + And("team_repo.org_id = ?", orgID). + And("team_repo.repo_id = ?", repoID). + Find(&teams) +} diff --git a/models/organization/team_test.go b/models/organization/team_test.go new file mode 100644 index 0000000000..bbf9f789f6 --- /dev/null +++ b/models/organization/team_test.go @@ -0,0 +1,199 @@ +// Copyright 2017 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 organization + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestTeam_IsOwnerTeam(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + assert.True(t, team.IsOwnerTeam()) + + team = unittest.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + assert.False(t, team.IsOwnerTeam()) +} + +func TestTeam_IsMember(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team) + assert.True(t, team.IsMember(2)) + assert.False(t, team.IsMember(4)) + assert.False(t, team.IsMember(unittest.NonexistentID)) + + team = unittest.AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team) + assert.True(t, team.IsMember(2)) + assert.True(t, team.IsMember(4)) + assert.False(t, team.IsMember(unittest.NonexistentID)) +} + +func TestTeam_GetRepositories(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + test := func(teamID int64) { + team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + assert.NoError(t, team.GetRepositoriesCtx(db.DefaultContext)) + assert.Len(t, team.Repos, team.NumRepos) + for _, repo := range team.Repos { + unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID}) + } + } + test(1) + test(3) +} + +func TestTeam_GetMembers(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + test := func(teamID int64) { + team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + assert.NoError(t, team.GetMembersCtx(db.DefaultContext)) + assert.Len(t, team.Members, team.NumMembers) + for _, member := range team.Members { + unittest.AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) + } + } + test(1) + test(3) +} + +func TestGetTeam(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testSuccess := func(orgID int64, name string) { + team, err := GetTeam(orgID, name) + assert.NoError(t, err) + assert.EqualValues(t, orgID, team.OrgID) + assert.Equal(t, name, team.Name) + } + testSuccess(3, "Owners") + testSuccess(3, "team1") + + _, err := GetTeam(3, "nonexistent") + assert.Error(t, err) + _, err = GetTeam(unittest.NonexistentID, "Owners") + assert.Error(t, err) +} + +func TestGetTeamByID(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testSuccess := func(teamID int64) { + team, err := GetTeamByID(teamID) + assert.NoError(t, err) + assert.EqualValues(t, teamID, team.ID) + } + testSuccess(1) + testSuccess(2) + testSuccess(3) + testSuccess(4) + + _, err := GetTeamByID(unittest.NonexistentID) + assert.Error(t, err) +} + +func TestIsTeamMember(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + test := func(orgID, teamID, userID int64, expected bool) { + isMember, err := IsTeamMember(db.DefaultContext, orgID, teamID, userID) + assert.NoError(t, err) + assert.Equal(t, expected, isMember) + } + + test(3, 1, 2, true) + test(3, 1, 4, false) + test(3, 1, unittest.NonexistentID, false) + + test(3, 2, 2, true) + test(3, 2, 4, true) + + test(3, unittest.NonexistentID, unittest.NonexistentID, false) + test(unittest.NonexistentID, unittest.NonexistentID, unittest.NonexistentID, false) +} + +func TestGetTeamMembers(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + test := func(teamID int64) { + team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + members, err := GetTeamMembers(db.DefaultContext, &SearchMembersOptions{ + TeamID: teamID, + }) + assert.NoError(t, err) + assert.Len(t, members, team.NumMembers) + for _, member := range members { + unittest.AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID}) + } + } + test(1) + test(3) +} + +func TestGetUserTeams(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + test := func(userID int64) { + teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID}) + assert.NoError(t, err) + for _, team := range teams { + unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) + } + } + test(2) + test(5) + test(unittest.NonexistentID) +} + +func TestGetUserOrgTeams(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + test := func(orgID, userID int64) { + teams, err := GetUserOrgTeams(db.DefaultContext, orgID, userID) + assert.NoError(t, err) + for _, team := range teams { + assert.EqualValues(t, orgID, team.OrgID) + unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) + } + } + test(3, 2) + test(3, 4) + test(3, unittest.NonexistentID) +} + +func TestHasTeamRepo(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + test := func(teamID, repoID int64, expected bool) { + team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) + assert.Equal(t, expected, HasTeamRepo(db.DefaultContext, team.OrgID, teamID, repoID)) + } + test(1, 1, false) + test(1, 3, true) + test(1, 5, true) + test(1, unittest.NonexistentID, false) + + test(2, 3, true) + test(2, 5, false) +} + +func TestUsersInTeamsCount(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + test := func(teamIDs, userIDs []int64, expected int64) { + count, err := UsersInTeamsCount(teamIDs, userIDs) + assert.NoError(t, err) + assert.Equal(t, expected, count) + } + + test([]int64{2}, []int64{1, 2, 3, 4}, 1) // only userid 2 + test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4 + test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5 +} diff --git a/models/organization/team_unit.go b/models/organization/team_unit.go new file mode 100644 index 0000000000..a712ddb2eb --- /dev/null +++ b/models/organization/team_unit.go @@ -0,0 +1,52 @@ +// Copyright 2022 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 organization + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" +) + +// TeamUnit describes all units of a repository +type TeamUnit struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + Type unit.Type `xorm:"UNIQUE(s)"` + AccessMode perm.AccessMode +} + +// Unit returns Unit +func (t *TeamUnit) Unit() unit.Unit { + return unit.Units[t.Type] +} + +func getUnitsByTeamID(ctx context.Context, teamID int64) (units []*TeamUnit, err error) { + return units, db.GetEngine(ctx).Where("team_id = ?", teamID).Find(&units) +} + +// UpdateTeamUnits updates a teams's units +func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) { + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if _, err = db.GetEngine(ctx).Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil { + return err + } + + if len(units) > 0 { + if err = db.Insert(ctx, units); err != nil { + return err + } + } + + return committer.Commit() +} diff --git a/models/organization/team_user.go b/models/organization/team_user.go new file mode 100644 index 0000000000..e972817f49 --- /dev/null +++ b/models/organization/team_user.go @@ -0,0 +1,109 @@ +// Copyright 2022 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 organization + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "xorm.io/builder" +) + +// TeamUser represents an team-user relation. +type TeamUser struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + UID int64 `xorm:"UNIQUE(s)"` +} + +// IsTeamMember returns true if given user is a member of team. +func IsTeamMember(ctx context.Context, orgID, teamID, userID int64) (bool, error) { + return db.GetEngine(ctx). + Where("org_id=?", orgID). + And("team_id=?", teamID). + And("uid=?", userID). + Table("team_user"). + Exist() +} + +// GetTeamUsersByTeamID returns team users for a team +func GetTeamUsersByTeamID(ctx context.Context, teamID int64) ([]*TeamUser, error) { + teamUsers := make([]*TeamUser, 0, 10) + return teamUsers, db.GetEngine(ctx). + Where("team_id=?", teamID). + Find(&teamUsers) +} + +// SearchMembersOptions holds the search options +type SearchMembersOptions struct { + db.ListOptions + TeamID int64 +} + +func (opts SearchMembersOptions) ToConds() builder.Cond { + cond := builder.NewCond() + if opts.TeamID > 0 { + cond = cond.And(builder.Eq{"": opts.TeamID}) + } + return cond +} + +// GetTeamMembers returns all members in given team of organization. +func GetTeamMembers(ctx context.Context, opts *SearchMembersOptions) ([]*user_model.User, error) { + var members []*user_model.User + sess := db.GetEngine(ctx) + if opts.TeamID > 0 { + sess = sess.In("id", + builder.Select("uid"). + From("team_user"). + Where(builder.Eq{"team_id": opts.TeamID}), + ) + } + if opts.PageSize > 0 && opts.Page > -1 { + sess = sess.Limit(opts.PageSize, opts.Page*opts.PageSize) + } + if err := sess.OrderBy("full_name, name").Find(&members); err != nil { + return nil, err + } + return members, nil +} + +// GetUserOrgTeams returns all teams that user belongs to in given organization. +func GetUserOrgTeams(ctx context.Context, orgID, userID int64) (teams []*Team, err error) { + return teams, db.GetEngine(ctx). + Join("INNER", "team_user", "team_user.team_id = team.id"). + Where("team.org_id = ?", orgID). + And("team_user.uid=?", userID). + Find(&teams) +} + +// GetUserRepoTeams returns user repo's teams +func GetUserRepoTeams(ctx context.Context, orgID, userID, repoID int64) (teams []*Team, err error) { + return teams, db.GetEngine(ctx). + Join("INNER", "team_user", "team_user.team_id = team.id"). + Join("INNER", "team_repo", "team_repo.team_id = team.id"). + Where("team.org_id = ?", orgID). + And("team_user.uid=?", userID). + And("team_repo.repo_id=?", repoID). + Find(&teams) +} + +// IsUserInTeams returns if a user in some teams +func IsUserInTeams(ctx context.Context, userID int64, teamIDs []int64) (bool, error) { + return db.GetEngine(ctx).Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser)) +} + +// UsersInTeamsCount counts the number of users which are in userIDs and teamIDs +func UsersInTeamsCount(userIDs, teamIDs []int64) (int64, error) { + var ids []int64 + if err := db.GetEngine(db.DefaultContext).In("uid", userIDs).In("team_id", teamIDs). + Table("team_user"). + Cols("uid").GroupBy("uid").Find(&ids); err != nil { + return 0, err + } + return int64(len(ids)), nil +} diff --git a/models/protected_tag.go b/models/protected_tag.go index c9cc0fa1ba..db6ff50462 100644 --- a/models/protected_tag.go +++ b/models/protected_tag.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/timeutil" @@ -83,7 +84,7 @@ func IsUserAllowedModifyTag(pt *ProtectedTag, userID int64) (bool, error) { return false, nil } - in, err := IsUserInTeams(userID, pt.AllowlistTeamIDs) + in, err := organization.IsUserInTeams(db.DefaultContext, userID, pt.AllowlistTeamIDs) if err != nil { return false, err } diff --git a/models/repo.go b/models/repo.go index d20e5f81d3..8e6f777f9b 100644 --- a/models/repo.go +++ b/models/repo.go @@ -20,6 +20,7 @@ import ( admin_model "code.gitea.io/gitea/models/admin" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -268,7 +269,7 @@ func GetReviewers(repo *repo_model.Repository, doerID, posterID int64) ([]*user_ } // GetReviewerTeams get all teams can be requested to review -func GetReviewerTeams(repo *repo_model.Repository) ([]*Team, error) { +func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) { if err := repo.GetOwner(db.DefaultContext); err != nil { return nil, err } @@ -276,7 +277,7 @@ func GetReviewerTeams(repo *repo_model.Repository) ([]*Team, error) { return nil, nil } - teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) + teams, err := organization.GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, perm.AccessModeRead) if err != nil { return nil, err } @@ -313,7 +314,7 @@ func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { return true, nil } - ownedOrgs, err := GetOrgsCanCreateRepoByUserID(user.ID) + ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(user.ID) if err != nil { return false, err } @@ -372,7 +373,7 @@ func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, er } if repo.Owner.IsOrganization() { - isOwner, err := OrgFromUser(repo.Owner).IsOwnedBy(user.ID) + isOwner, err := organization.OrgFromUser(repo.Owner).IsOwnedBy(user.ID) if err != nil { return false, err } else if isOwner { @@ -550,19 +551,19 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ // Give access to all members in teams with access to all repositories. if u.IsOrganization() { - teams, err := OrgFromUser(u).loadTeams(db.GetEngine(ctx)) + teams, err := organization.FindOrgTeams(ctx, u.ID) if err != nil { return fmt.Errorf("loadTeams: %v", err) } for _, t := range teams { if t.IncludesAllRepositories { - if err := t.addRepository(ctx, repo); err != nil { + if err := addRepository(ctx, t, repo); err != nil { return fmt.Errorf("addRepository: %v", err) } } } - if isAdmin, err := isUserRepoAdmin(db.GetEngine(ctx), repo, doer); err != nil { + if isAdmin, err := isUserRepoAdmin(ctx, repo, doer); err != nil { return fmt.Errorf("isUserRepoAdmin: %v", err) } else if !isAdmin { // Make creator repo admin if it wasn't assigned automatically @@ -768,14 +769,14 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error { } if org.IsOrganization() { - teams, err := OrgFromUser(org).loadTeams(sess) + teams, err := organization.FindOrgTeams(ctx, org.ID) if err != nil { return err } for _, t := range teams { - if !t.hasRepository(sess, repoID) { + if !hasRepository(ctx, t, repoID) { continue - } else if err = t.removeRepository(ctx, repo, false); err != nil { + } else if err = removeRepository(ctx, t, repo, false); err != nil { return err } } @@ -1326,7 +1327,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error if err != nil { return fmt.Errorf("GetRepositoryByID: %v", err) } - has, err := isUserRepoAdmin(sess, repo, doer) + has, err := isUserRepoAdmin(ctx, repo, doer) if err != nil { return fmt.Errorf("GetUserRepoPermission: %v", err) } else if !has { diff --git a/models/repo/repo.go b/models/repo/repo.go index 8cd055c67b..8dd772a4ec 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -751,37 +751,3 @@ func countRepositories(userID int64, private bool) int64 { func CountRepositories(private bool) int64 { return countRepositories(-1, private) } - -// CountUserRepositories returns number of repositories user owns. -// Argument private only takes effect when it is false, -// set it true to count all repositories. -func CountUserRepositories(userID int64, private bool) int64 { - return countRepositories(userID, private) -} - -func getRepositoryCount(e db.Engine, ownerID int64) (int64, error) { - return e.Count(&Repository{OwnerID: ownerID}) -} - -func getPublicRepositoryCount(e db.Engine, u *user_model.User) (int64, error) { - return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) -} - -func getPrivateRepositoryCount(e db.Engine, u *user_model.User) (int64, error) { - return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) -} - -// GetRepositoryCount returns the total number of repositories of user. -func GetRepositoryCount(ctx context.Context, ownerID int64) (int64, error) { - return getRepositoryCount(db.GetEngine(ctx), ownerID) -} - -// GetPublicRepositoryCount returns the total number of public repositories of user. -func GetPublicRepositoryCount(u *user_model.User) (int64, error) { - return getPublicRepositoryCount(db.GetEngine(db.DefaultContext), u) -} - -// GetPrivateRepositoryCount returns the total number of private repositories of user. -func GetPrivateRepositoryCount(u *user_model.User) (int64, error) { - return getPrivateRepositoryCount(db.GetEngine(db.DefaultContext), u) -} diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go new file mode 100644 index 0000000000..18a04f7267 --- /dev/null +++ b/models/repo/user_repo.go @@ -0,0 +1,87 @@ +// Copyright 2022 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 ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" +) + +// GetStarredRepos returns the repos starred by a particular user +func GetStarredRepos(userID int64, private bool, listOptions db.ListOptions) ([]*Repository, error) { + sess := db.GetEngine(db.DefaultContext).Where("star.uid=?", userID). + Join("LEFT", "star", "`repository`.id=`star`.repo_id") + if !private { + sess = sess.And("is_private=?", false) + } + + if listOptions.Page != 0 { + sess = db.SetSessionPagination(sess, &listOptions) + + repos := make([]*Repository, 0, listOptions.PageSize) + return repos, sess.Find(&repos) + } + + repos := make([]*Repository, 0, 10) + return repos, sess.Find(&repos) +} + +// GetWatchedRepos returns the repos watched by a particular user +func GetWatchedRepos(userID int64, private bool, listOptions db.ListOptions) ([]*Repository, int64, error) { + sess := db.GetEngine(db.DefaultContext).Where("watch.user_id=?", userID). + And("`watch`.mode<>?", WatchModeDont). + Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") + if !private { + sess = sess.And("is_private=?", false) + } + + if listOptions.Page != 0 { + sess = db.SetSessionPagination(sess, &listOptions) + + repos := make([]*Repository, 0, listOptions.PageSize) + total, err := sess.FindAndCount(&repos) + return repos, total, err + } + + repos := make([]*Repository, 0, 10) + total, err := sess.FindAndCount(&repos) + return repos, total, err +} + +// CountUserRepositories returns number of repositories user owns. +// Argument private only takes effect when it is false, +// set it true to count all repositories. +func CountUserRepositories(userID int64, private bool) int64 { + return countRepositories(userID, private) +} + +func getRepositoryCount(e db.Engine, ownerID int64) (int64, error) { + return e.Count(&Repository{OwnerID: ownerID}) +} + +func getPublicRepositoryCount(e db.Engine, u *user_model.User) (int64, error) { + return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) +} + +func getPrivateRepositoryCount(e db.Engine, u *user_model.User) (int64, error) { + return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) +} + +// GetRepositoryCount returns the total number of repositories of user. +func GetRepositoryCount(ctx context.Context, ownerID int64) (int64, error) { + return getRepositoryCount(db.GetEngine(ctx), ownerID) +} + +// GetPublicRepositoryCount returns the total number of public repositories of user. +func GetPublicRepositoryCount(u *user_model.User) (int64, error) { + return getPublicRepositoryCount(db.GetEngine(db.DefaultContext), u) +} + +// GetPrivateRepositoryCount returns the total number of private repositories of user. +func GetPrivateRepositoryCount(u *user_model.User) (int64, error) { + return getPrivateRepositoryCount(db.GetEngine(db.DefaultContext), u) +} diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index 3aca1023e6..d94b61b449 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -10,6 +10,7 @@ import ( "fmt" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -259,20 +260,6 @@ func reconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int return removeIssueWatchersByRepoID(db.GetEngine(ctx), uid, repo.ID) } -func getRepoTeams(e db.Engine, repo *repo_model.Repository) (teams []*Team, err error) { - return teams, e. - Join("INNER", "team_repo", "team_repo.team_id = team.id"). - Where("team.org_id = ?", repo.OwnerID). - And("team_repo.repo_id=?", repo.ID). - OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END"). - Find(&teams) -} - -// GetRepoTeams gets the list of teams that has access to the repository -func GetRepoTeams(repo *repo_model.Repository) ([]*Team, error) { - return getRepoTeams(db.GetEngine(db.DefaultContext), repo) -} - // IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository func IsOwnerMemberCollaborator(repo *repo_model.Repository, userID int64) (bool, error) { if repo.OwnerID == userID { @@ -282,7 +269,7 @@ func IsOwnerMemberCollaborator(repo *repo_model.Repository, userID int64) (bool, Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id"). Where("team_repo.repo_id = ?", repo.ID). And("team_unit.`type` = ?", unit.TypeCode). - And("team_user.uid = ?", userID).Table("team_user").Exist(&TeamUser{}) + And("team_user.uid = ?", userID).Table("team_user").Exist(&organization.TeamUser{}) if err != nil { return false, err } diff --git a/models/repo_permission.go b/models/repo_permission.go index 4e5cbfd558..4dfc63d4e1 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -9,6 +9,7 @@ import ( "fmt" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" perm_model "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -186,7 +187,7 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use // Prevent strangers from checking out public repo of private organization/users // Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself - if !hasOrgOrUserVisible(e, repo.Owner, user) && !is { + if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !is { perm.AccessMode = perm_model.AccessModeNone return } @@ -232,7 +233,7 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } // get units mode from teams - teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID) + teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID) if err != nil { return } @@ -249,7 +250,7 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use for _, u := range repo.Units { var found bool for _, team := range teams { - teamMode := team.unitAccessMode(e, u.Type) + teamMode := team.UnitAccessMode(ctx, u.Type) if teamMode > perm_model.AccessModeNone { m := perm.UnitsMode[u.Type] if m < teamMode { @@ -300,10 +301,10 @@ func IsUserRealRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bo // IsUserRepoAdmin return true if user has admin right of a repo func IsUserRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bool, error) { - return isUserRepoAdmin(db.GetEngine(db.DefaultContext), repo, user) + return isUserRepoAdmin(db.DefaultContext, repo, user) } -func isUserRepoAdmin(e db.Engine, repo *repo_model.Repository, user *user_model.User) (bool, error) { +func isUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) { if user == nil || repo == nil { return false, nil } @@ -311,6 +312,7 @@ func isUserRepoAdmin(e db.Engine, repo *repo_model.Repository, user *user_model. return true, nil } + e := db.GetEngine(ctx) mode, err := accessLevel(e, user, repo) if err != nil { return false, err @@ -319,7 +321,7 @@ func isUserRepoAdmin(e db.Engine, repo *repo_model.Repository, user *user_model. return true, nil } - teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID) + teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID) if err != nil { return false, err } diff --git a/models/repo_permission_test.go b/models/repo_permission_test.go index f2664d8101..c103a9363f 100644 --- a/models/repo_permission_test.go +++ b/models/repo_permission_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" perm_model "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -227,8 +228,8 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { } // update team information and then check permission - team := unittest.AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team) - err = UpdateTeamUnits(team, nil) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team) + err = organization.UpdateTeamUnits(team, nil) assert.NoError(t, err) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) diff --git a/models/repo_transfer.go b/models/repo_transfer.go index f7d5e20990..f9a758a20b 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -10,6 +10,7 @@ import ( "os" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -26,7 +27,7 @@ type RepoTransfer struct { Recipient *user_model.User `xorm:"-"` RepoID int64 TeamIDs []int64 - Teams []*Team `xorm:"-"` + Teams []*organization.Team `xorm:"-"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL updated"` @@ -49,7 +50,7 @@ func (r *RepoTransfer) LoadAttributes() error { if r.Recipient.IsOrganization() && len(r.TeamIDs) != len(r.Teams) { for _, v := range r.TeamIDs { - team, err := GetTeamByID(v) + team, err := organization.GetTeamByID(v) if err != nil { return err } @@ -87,7 +88,7 @@ func (r *RepoTransfer) CanUserAcceptTransfer(u *user_model.User) bool { return r.RecipientID == u.ID } - allowed, err := CanCreateOrgRepo(r.RecipientID, u.ID) + allowed, err := organization.CanCreateOrgRepo(r.RecipientID, u.ID) if err != nil { log.Error("CanCreateOrgRepo: %v", err) return false @@ -152,7 +153,7 @@ func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error { // CreatePendingRepositoryTransfer transfer a repo from one owner to a new one. // it marks the repository transfer as "pending" -func CreatePendingRepositoryTransfer(doer, newOwner *user_model.User, repoID int64, teams []*Team) error { +func CreatePendingRepositoryTransfer(doer, newOwner *user_model.User, repoID int64, teams []*organization.Team) error { ctx, committer, err := db.TxContext() if err != nil { return err @@ -296,7 +297,7 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo } if c.ID != newOwner.ID { - isMember, err := isOrganizationMember(sess, newOwner.ID, c.ID) + isMember, err := organization.IsOrganizationMember(ctx, newOwner.ID, c.ID) if err != nil { return fmt.Errorf("IsOrgMember: %v", err) } else if !isMember { @@ -312,19 +313,19 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo // Remove old team-repository relations. if oldOwner.IsOrganization() { - if err := OrgFromUser(oldOwner).removeOrgRepo(sess, repo.ID); err != nil { + if err := organization.RemoveOrgRepo(ctx, oldOwner.ID, repo.ID); err != nil { return fmt.Errorf("removeOrgRepo: %v", err) } } if newOwner.IsOrganization() { - teams, err := OrgFromUser(newOwner).loadTeams(sess) + teams, err := organization.FindOrgTeams(ctx, newOwner.ID) if err != nil { return fmt.Errorf("LoadTeams: %v", err) } for _, t := range teams { if t.IncludesAllRepositories { - if err := t.addRepository(ctx, repo); err != nil { + if err := addRepository(ctx, t, repo); err != nil { return fmt.Errorf("addRepository: %v", err) } } diff --git a/models/review.go b/models/review.go index 22c47486a1..6ef8d530b6 100644 --- a/models/review.go +++ b/models/review.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -58,10 +59,10 @@ func (rt ReviewType) Icon() string { type Review struct { ID int64 `xorm:"pk autoincr"` Type ReviewType - Reviewer *user_model.User `xorm:"-"` - ReviewerID int64 `xorm:"index"` - ReviewerTeamID int64 `xorm:"NOT NULL DEFAULT 0"` - ReviewerTeam *Team `xorm:"-"` + Reviewer *user_model.User `xorm:"-"` + ReviewerID int64 `xorm:"index"` + ReviewerTeamID int64 `xorm:"NOT NULL DEFAULT 0"` + ReviewerTeam *organization.Team `xorm:"-"` OriginalAuthor string OriginalAuthorID int64 Issue *Issue `xorm:"-"` @@ -114,12 +115,12 @@ func (r *Review) loadReviewer(e db.Engine) (err error) { return } -func (r *Review) loadReviewerTeam(e db.Engine) (err error) { +func (r *Review) loadReviewerTeam(ctx context.Context) (err error) { if r.ReviewerTeamID == 0 || r.ReviewerTeam != nil { return } - r.ReviewerTeam, err = getTeamByID(e, r.ReviewerTeamID) + r.ReviewerTeam, err = organization.GetTeamByIDCtx(ctx, r.ReviewerTeamID) return } @@ -130,7 +131,7 @@ func (r *Review) LoadReviewer() error { // LoadReviewerTeam loads reviewer team func (r *Review) LoadReviewerTeam() error { - return r.loadReviewerTeam(db.GetEngine(db.DefaultContext)) + return r.loadReviewerTeam(db.DefaultContext) } // LoadAttributes loads all attributes except CodeComments @@ -145,7 +146,7 @@ func (r *Review) LoadAttributes(ctx context.Context) (err error) { if err = r.loadReviewer(e); err != nil { return } - if err = r.loadReviewerTeam(e); err != nil { + if err = r.loadReviewerTeam(ctx); err != nil { return } return @@ -221,7 +222,7 @@ type CreateReviewOptions struct { Type ReviewType Issue *Issue Reviewer *user_model.User - ReviewerTeam *Team + ReviewerTeam *organization.Team Official bool CommitID string Stale bool @@ -255,11 +256,11 @@ func isOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_mo } // IsOfficialReviewerTeam check if reviewer in this team can make official reviews in issue (counts towards required approvals) -func IsOfficialReviewerTeam(issue *Issue, team *Team) (bool, error) { +func IsOfficialReviewerTeam(issue *Issue, team *organization.Team) (bool, error) { return isOfficialReviewerTeam(db.DefaultContext, issue, team) } -func isOfficialReviewerTeam(ctx context.Context, issue *Issue, team *Team) (bool, error) { +func isOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organization.Team) (bool, error) { pr, err := getPullRequestByIssueID(db.GetEngine(ctx), issue.ID) if err != nil { return false, err @@ -272,7 +273,7 @@ func isOfficialReviewerTeam(ctx context.Context, issue *Issue, team *Team) (bool } if !pr.ProtectedBranch.EnableApprovalsWhitelist { - return team.UnitAccessMode(unit.TypeCode) >= perm.AccessModeWrite, nil + return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil } return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil @@ -447,7 +448,7 @@ func SubmitReview(doer *user_model.User, issue *Issue, reviewType ReviewType, co } for _, teamReviewRequest := range teamReviewRequests { - ok, err := isTeamMember(sess, issue.Repo.OwnerID, teamReviewRequest.ReviewerTeamID, doer.ID) + ok, err := organization.IsTeamMember(ctx, issue.Repo.OwnerID, teamReviewRequest.ReviewerTeamID, doer.ID) if err != nil { return nil, nil, err } else if !ok { @@ -732,7 +733,7 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen } // AddTeamReviewRequest add a review request from one team -func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *user_model.User) (*Comment, error) { +func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) { ctx, committer, err := db.TxContext() if err != nil { return nil, err @@ -792,7 +793,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *user_model.User) ( } // RemoveTeamReviewRequest remove a review request from one team -func RemoveTeamReviewRequest(issue *Issue, reviewer *Team, doer *user_model.User) (*Comment, error) { +func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) { ctx, committer, err := db.TxContext() if err != nil { return nil, err diff --git a/models/statistic.go b/models/statistic.go index dfe543d063..67be4c5d42 100644 --- a/models/statistic.go +++ b/models/statistic.go @@ -8,6 +8,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" @@ -47,7 +48,7 @@ type IssueByRepositoryCount struct { func GetStatistic() (stats Statistic) { e := db.GetEngine(db.DefaultContext) stats.Counter.User = user_model.CountUsers() - stats.Counter.Org = CountOrganizations() + stats.Counter.Org = organization.CountOrganizations() stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) stats.Counter.Repo = repo_model.CountRepositories(true) stats.Counter.Watch, _ = e.Count(new(repo_model.Watch)) @@ -103,7 +104,7 @@ func GetStatistic() (stats Statistic) { stats.Counter.Milestone, _ = e.Count(new(Milestone)) stats.Counter.Label, _ = e.Count(new(Label)) stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask)) - stats.Counter.Team, _ = e.Count(new(Team)) + stats.Counter.Team, _ = e.Count(new(organization.Team)) stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment)) stats.Counter.Project, _ = e.Count(new(Project)) stats.Counter.ProjectBoard, _ = e.Count(new(ProjectBoard)) diff --git a/models/user.go b/models/user.go index 1dbc025155..7744222809 100644 --- a/models/user.go +++ b/models/user.go @@ -14,22 +14,13 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" - - "xorm.io/builder" ) -// GetOrganizationCount returns count of membership of organization of the user. -func GetOrganizationCount(ctx context.Context, u *user_model.User) (int64, error) { - return db.GetEngine(ctx). - Where("uid=?", u.ID). - Count(new(OrgUser)) -} - // DeleteUser deletes models associated to an user. func DeleteUser(ctx context.Context, u *user_model.User) (err error) { e := db.GetEngine(ctx) @@ -86,7 +77,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) { &user_model.EmailAddress{UID: u.ID}, &user_model.UserOpenID{UID: u.ID}, &Reaction{UserID: u.ID}, - &TeamUser{UID: u.ID}, + &organization.TeamUser{UID: u.ID}, &Collaboration{UserID: u.ID}, &Stopwatch{UserID: u.ID}, &user_model.Setting{UserID: u.ID}, @@ -204,100 +195,3 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) { return nil } - -// GetStarredRepos returns the repos starred by a particular user -func GetStarredRepos(userID int64, private bool, listOptions db.ListOptions) ([]*repo_model.Repository, error) { - sess := db.GetEngine(db.DefaultContext).Where("star.uid=?", userID). - Join("LEFT", "star", "`repository`.id=`star`.repo_id") - if !private { - sess = sess.And("is_private=?", false) - } - - if listOptions.Page != 0 { - sess = db.SetSessionPagination(sess, &listOptions) - - repos := make([]*repo_model.Repository, 0, listOptions.PageSize) - return repos, sess.Find(&repos) - } - - repos := make([]*repo_model.Repository, 0, 10) - return repos, sess.Find(&repos) -} - -// GetWatchedRepos returns the repos watched by a particular user -func GetWatchedRepos(userID int64, private bool, listOptions db.ListOptions) ([]*repo_model.Repository, int64, error) { - sess := db.GetEngine(db.DefaultContext).Where("watch.user_id=?", userID). - And("`watch`.mode<>?", repo_model.WatchModeDont). - Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") - if !private { - sess = sess.And("is_private=?", false) - } - - if listOptions.Page != 0 { - sess = db.SetSessionPagination(sess, &listOptions) - - repos := make([]*repo_model.Repository, 0, listOptions.PageSize) - total, err := sess.FindAndCount(&repos) - return repos, total, err - } - - repos := make([]*repo_model.Repository, 0, 10) - total, err := sess.FindAndCount(&repos) - return repos, total, err -} - -// IsUserVisibleToViewer check if viewer is able to see user profile -func IsUserVisibleToViewer(u, viewer *user_model.User) bool { - return isUserVisibleToViewer(db.GetEngine(db.DefaultContext), u, viewer) -} - -func isUserVisibleToViewer(e db.Engine, u, viewer *user_model.User) bool { - if viewer != nil && viewer.IsAdmin { - return true - } - - switch u.Visibility { - case structs.VisibleTypePublic: - return true - case structs.VisibleTypeLimited: - if viewer == nil || viewer.IsRestricted { - return false - } - return true - case structs.VisibleTypePrivate: - if viewer == nil || viewer.IsRestricted { - return false - } - - // If they follow - they see each over - follower := user_model.IsFollowing(u.ID, viewer.ID) - if follower { - return true - } - - // Now we need to check if they in some organization together - count, err := e.Table("team_user"). - Where( - builder.And( - builder.Eq{"uid": viewer.ID}, - builder.Or( - builder.Eq{"org_id": u.ID}, - builder.In("org_id", - builder.Select("org_id"). - From("team_user", "t2"). - Where(builder.Eq{"uid": u.ID}))))). - Count(new(TeamUser)) - if err != nil { - return false - } - - if count < 0 { - // No common organization - return false - } - - // they are in an organization together - return true - } - return false -} diff --git a/models/user/user.go b/models/user/user.go index a3094a13ce..0e51cf955c 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1211,3 +1211,59 @@ func GetAdminUser() (*User, error) { return &admin, nil } + +// IsUserVisibleToViewer check if viewer is able to see user profile +func IsUserVisibleToViewer(u, viewer *User) bool { + return isUserVisibleToViewer(db.GetEngine(db.DefaultContext), u, viewer) +} + +func isUserVisibleToViewer(e db.Engine, u, viewer *User) bool { + if viewer != nil && viewer.IsAdmin { + return true + } + + switch u.Visibility { + case structs.VisibleTypePublic: + return true + case structs.VisibleTypeLimited: + if viewer == nil || viewer.IsRestricted { + return false + } + return true + case structs.VisibleTypePrivate: + if viewer == nil || viewer.IsRestricted { + return false + } + + // If they follow - they see each over + follower := IsFollowing(u.ID, viewer.ID) + if follower { + return true + } + + // Now we need to check if they in some organization together + count, err := e.Table("team_user"). + Where( + builder.And( + builder.Eq{"uid": viewer.ID}, + builder.Or( + builder.Eq{"org_id": u.ID}, + builder.In("org_id", + builder.Select("org_id"). + From("team_user", "t2"). + Where(builder.Eq{"uid": u.ID}))))). + Count() + if err != nil { + return false + } + + if count < 0 { + // No common organization + return false + } + + // they are in an organization together + return true + } + return false +} diff --git a/models/user_heatmap.go b/models/user_heatmap.go index f331a0c16b..e908837ae8 100644 --- a/models/user_heatmap.go +++ b/models/user_heatmap.go @@ -6,6 +6,7 @@ package models import ( "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -23,11 +24,11 @@ func GetUserHeatmapDataByUser(user, doer *user_model.User) ([]*UserHeatmapData, } // GetUserHeatmapDataByUserTeam returns an array of UserHeatmapData -func GetUserHeatmapDataByUserTeam(user *user_model.User, team *Team, doer *user_model.User) ([]*UserHeatmapData, error) { +func GetUserHeatmapDataByUserTeam(user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { return getUserHeatmapData(user, team, doer) } -func getUserHeatmapData(user *user_model.User, team *Team, doer *user_model.User) ([]*UserHeatmapData, error) { +func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { hdata := make([]*UserHeatmapData, 0) if !activityReadable(user, doer) { diff --git a/models/user_test.go b/models/user_test.go index 83201ff4cb..bf6f71895e 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -5,7 +5,6 @@ package models import ( - "fmt" "testing" "code.gitea.io/gitea/models/unittest" @@ -42,59 +41,3 @@ func TestUnfollowUser(t *testing.T) { unittest.CheckConsistencyFor(t, &user_model.User{}) } - -func TestUserIsPublicMember(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - tt := []struct { - uid int64 - orgid int64 - expected bool - }{ - {2, 3, true}, - {4, 3, false}, - {5, 6, true}, - {5, 7, false}, - } - for _, v := range tt { - t.Run(fmt.Sprintf("UserId%dIsPublicMemberOf%d", v.uid, v.orgid), func(t *testing.T) { - testUserIsPublicMember(t, v.uid, v.orgid, v.expected) - }) - } -} - -func testUserIsPublicMember(t *testing.T, uid, orgID int64, expected bool) { - user, err := user_model.GetUserByID(uid) - assert.NoError(t, err) - is, err := IsPublicMembership(orgID, user.ID) - assert.NoError(t, err) - assert.Equal(t, expected, is) -} - -func TestIsUserOrgOwner(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - tt := []struct { - uid int64 - orgid int64 - expected bool - }{ - {2, 3, true}, - {4, 3, false}, - {5, 6, true}, - {5, 7, true}, - } - for _, v := range tt { - t.Run(fmt.Sprintf("UserId%dIsOrgOwnerOf%d", v.uid, v.orgid), func(t *testing.T) { - testIsUserOrgOwner(t, v.uid, v.orgid, v.expected) - }) - } -} - -func testIsUserOrgOwner(t *testing.T, uid, orgID int64, expected bool) { - user, err := user_model.GetUserByID(uid) - assert.NoError(t, err) - is, err := IsOrganizationOwner(orgID, user.ID) - assert.NoError(t, err) - assert.Equal(t, expected, is) -} diff --git a/models/userlist.go b/models/userlist.go index 102c587dfe..fbe1995b40 100644 --- a/models/userlist.go +++ b/models/userlist.go @@ -5,9 +5,11 @@ package models import ( + "context" "fmt" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" ) @@ -18,7 +20,7 @@ func IsUserOrgOwner(users user_model.UserList, orgID int64) map[int64]bool { for _, user := range users { results[user.ID] = false // Set default to false } - ownerMaps, err := loadOrganizationOwners(db.GetEngine(db.DefaultContext), users, orgID) + ownerMaps, err := loadOrganizationOwners(db.DefaultContext, users, orgID) if err == nil { for _, owner := range ownerMaps { results[owner.UID] = true @@ -27,13 +29,13 @@ func IsUserOrgOwner(users user_model.UserList, orgID int64) map[int64]bool { return results } -func loadOrganizationOwners(e db.Engine, users user_model.UserList, orgID int64) (map[int64]*TeamUser, error) { +func loadOrganizationOwners(ctx context.Context, users user_model.UserList, orgID int64) (map[int64]*organization.TeamUser, error) { if len(users) == 0 { return nil, nil } - ownerTeam, err := getOwnerTeam(e, orgID) + ownerTeam, err := organization.GetOwnerTeam(ctx, orgID) if err != nil { - if IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { log.Error("Organization does not have owner team: %d", orgID) return nil, nil } @@ -41,8 +43,8 @@ func loadOrganizationOwners(e db.Engine, users user_model.UserList, orgID int64) } userIDs := users.GetUserIDs() - ownerMaps := make(map[int64]*TeamUser) - err = e.In("uid", userIDs). + ownerMaps := make(map[int64]*organization.TeamUser) + err = db.GetEngine(ctx).In("uid", userIDs). And("org_id=?", orgID). And("team_id=?", ownerTeam.ID). Find(&ownerMaps) diff --git a/models/userlist_test.go b/models/userlist_test.go index 6776850b60..9b3c796e64 100644 --- a/models/userlist_test.go +++ b/models/userlist_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -33,7 +34,7 @@ func TestUserListIsPublicMember(t *testing.T) { } func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) { - org, err := GetOrgByID(orgID) + org, err := organization.GetOrgByID(orgID) assert.NoError(t, err) _, membersIsPublic, err := org.GetMembers() assert.NoError(t, err) @@ -60,7 +61,7 @@ func TestUserListIsUserOrgOwner(t *testing.T) { } func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) { - org, err := GetOrgByID(orgID) + org, err := organization.GetOrgByID(orgID) assert.NoError(t, err) members, _, err := org.GetMembers() assert.NoError(t, err) diff --git a/modules/context/api_org.go b/modules/context/api_org.go index 6d86fa6ed2..2231677d42 100644 --- a/modules/context/api_org.go +++ b/modules/context/api_org.go @@ -4,12 +4,10 @@ package context -import ( - "code.gitea.io/gitea/models" -) +import "code.gitea.io/gitea/models/organization" // APIOrganization contains organization and team type APIOrganization struct { - Organization *models.Organization - Team *models.Team + Organization *organization.Organization + Team *organization.Team } diff --git a/modules/context/org.go b/modules/context/org.go index 8e292fa1c5..f8607bd29f 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -8,7 +8,7 @@ package context import ( "strings" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" user_model "code.gitea.io/gitea/models/user" ) @@ -19,12 +19,12 @@ type Organization struct { IsMember bool IsTeamMember bool // Is member of team. IsTeamAdmin bool // In owner team or team that has admin permission level. - Organization *models.Organization + Organization *organization.Organization OrgLink string CanCreateOrgRepo bool - Team *models.Team - Teams []*models.Team + Team *organization.Team + Teams []*organization.Team } // HandleOrgAssignment handles organization assignment @@ -51,9 +51,9 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { orgName := ctx.Params(":org") var err error - ctx.Org.Organization, err = models.GetOrgByName(orgName) + ctx.Org.Organization, err = organization.GetOrgByName(orgName) if err != nil { - if models.IsErrOrgNotExist(err) { + if organization.IsErrOrgNotExist(err) { redirectUserID, err := user_model.LookupUserRedirect(orgName) if err == nil { RedirectToUser(ctx, orgName, redirectUserID) @@ -120,7 +120,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember ctx.Data["IsPublicMember"] = func(uid int64) bool { - is, _ := models.IsPublicMembership(ctx.Org.Organization.ID, uid) + is, _ := organization.IsPublicMembership(ctx.Org.Organization.ID, uid) return is } ctx.Data["CanCreateOrgRepo"] = ctx.Org.CanCreateOrgRepo diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 41a044c6d7..43300f603e 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -98,15 +99,15 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection { if err != nil { log.Error("GetUserNamesByIDs (ApprovalsWhitelistUserIDs): %v", err) } - pushWhitelistTeams, err := models.GetTeamNamesByID(bp.WhitelistTeamIDs) + pushWhitelistTeams, err := organization.GetTeamNamesByID(bp.WhitelistTeamIDs) if err != nil { log.Error("GetTeamNamesByID (WhitelistTeamIDs): %v", err) } - mergeWhitelistTeams, err := models.GetTeamNamesByID(bp.MergeWhitelistTeamIDs) + mergeWhitelistTeams, err := organization.GetTeamNamesByID(bp.MergeWhitelistTeamIDs) if err != nil { log.Error("GetTeamNamesByID (MergeWhitelistTeamIDs): %v", err) } - approvalsWhitelistTeams, err := models.GetTeamNamesByID(bp.ApprovalsWhitelistTeamIDs) + approvalsWhitelistTeams, err := organization.GetTeamNamesByID(bp.ApprovalsWhitelistTeamIDs) if err != nil { log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err) } @@ -280,7 +281,7 @@ func ToDeployKey(apiLink string, key *asymkey_model.DeployKey) *api.DeployKey { } // ToOrganization convert user_model.User to api.Organization -func ToOrganization(org *models.Organization) *api.Organization { +func ToOrganization(org *organization.Organization) *api.Organization { return &api.Organization{ ID: org.ID, AvatarURL: org.AsUser().AvatarLink(), @@ -294,8 +295,8 @@ func ToOrganization(org *models.Organization) *api.Organization { } } -// ToTeam convert models.Team to api.Team -func ToTeam(team *models.Team) *api.Team { +// ToTeam convert organization.Team to api.Team +func ToTeam(team *organization.Team) *api.Team { if team == nil { return nil } diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go index 4e232a8609..b6a89a7ed6 100644 --- a/modules/repository/create_test.go +++ b/modules/repository/create_test.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -22,13 +23,13 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) testTeamRepositories := func(teamID int64, repoIds []int64) { - team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) - assert.NoError(t, team.GetRepositories(&models.SearchOrgTeamOptions{}), "%s: GetRepositories", team.Name) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team) + assert.NoError(t, team.GetRepositoriesCtx(db.DefaultContext), "%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 { if rid > 0 { - assert.True(t, team.HasRepository(rid), "%s: HasRepository(%d) %d", rid, i) + assert.True(t, models.HasRepository(team, rid), "%s: HasRepository(%d) %d", rid, i) } } } @@ -38,13 +39,13 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, err, "GetUserByID") // Create org. - org := &models.Organization{ + org := &organization.Organization{ Name: "All_repo", IsActive: true, Type: user_model.UserTypeOrganization, Visibility: structs.VisibleTypePublic, } - assert.NoError(t, models.CreateOrganization(org, user), "CreateOrganization") + assert.NoError(t, organization.CreateOrganization(org, user), "CreateOrganization") // Check Owner team. ownerTeam, err := org.GetOwnerTeam() @@ -65,7 +66,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, err, "GetOwnerTeam") // Create teams and check repositories. - teams := []*models.Team{ + teams := []*organization.Team{ ownerTeam, { OrgID: org.ID, @@ -144,5 +145,5 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i) } } - assert.NoError(t, models.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") + assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") } diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 98388bbd7b..dbf4b527bf 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" @@ -55,7 +56,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repoPath := repo_model.RepoPath(u.Name, opts.RepoName) if u.IsOrganization() { - t, err := models.OrgFromUser(u).GetOwnerTeam() + t, err := organization.OrgFromUser(u).GetOwnerTeam() if err != nil { return nil, err } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 2973b2c938..80ad7066a7 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/avatars" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -569,7 +570,7 @@ func Avatar(item interface{}, others ...interface{}) template.HTML { if src != "" { return AvatarHTML(src, size, class, t.DisplayName()) } - case *models.Organization: + case *organization.Organization: src := t.AsUser().AvatarLinkWithSize(size * setting.Avatar.RenderedSizeFactor) if src != "" { return AvatarHTML(src, size, class, t.AsUser().DisplayName()) diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index e4850ac494..727f3193cf 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -8,8 +8,8 @@ package admin import ( "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" @@ -52,7 +52,7 @@ func CreateOrg(ctx *context.APIContext) { visibility = api.VisibilityModes[form.Visibility] } - org := &models.Organization{ + org := &organization.Organization{ Name: form.UserName, FullName: form.FullName, Description: form.Description, @@ -63,7 +63,7 @@ func CreateOrg(ctx *context.APIContext) { Visibility: visibility, } - if err := models.CreateOrganization(org, ctx.ContextUser); err != nil { + if err := organization.CreateOrganization(org, ctx.ContextUser); err != nil { if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNameCharsNotAllowed(err) || @@ -115,7 +115,7 @@ func GetAllOrgs(ctx *context.APIContext) { } orgs := make([]*api.Organization, len(users)) for i := range users { - orgs[i] = convert.ToOrganization(models.OrgFromUser(users[i])) + orgs[i] = convert.ToOrganization(organization.OrgFromUser(users[i])) } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 3debf58a17..1fed95284b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -71,6 +71,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -309,7 +310,7 @@ func reqOrgOwnership() func(ctx *context.APIContext) { return } - isOwner, err := models.IsOrganizationOwner(orgID, ctx.Doer.ID) + isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err) return @@ -336,7 +337,7 @@ func reqTeamMembership() func(ctx *context.APIContext) { } orgID := ctx.Org.Team.OrgID - isOwner, err := models.IsOrganizationOwner(orgID, ctx.Doer.ID) + isOwner, err := organization.IsOrganizationOwner(ctx, orgID, ctx.Doer.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsOrganizationOwner", err) return @@ -344,11 +345,11 @@ func reqTeamMembership() func(ctx *context.APIContext) { return } - if isTeamMember, err := models.IsTeamMember(orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil { + if isTeamMember, err := organization.IsTeamMember(ctx, orgID, ctx.Org.Team.ID, ctx.Doer.ID); err != nil { ctx.Error(http.StatusInternalServerError, "IsTeamMember", err) return } else if !isTeamMember { - isOrgMember, err := models.IsOrganizationMember(orgID, ctx.Doer.ID) + isOrgMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err) } else if isOrgMember { @@ -378,7 +379,7 @@ func reqOrgMembership() func(ctx *context.APIContext) { return } - if isMember, err := models.IsOrganizationMember(orgID, ctx.Doer.ID); err != nil { + if isMember, err := organization.IsOrganizationMember(ctx, orgID, ctx.Doer.ID); err != nil { ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err) return } else if !isMember { @@ -427,9 +428,9 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) { var err error if assignOrg { - ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":org")) + ctx.Org.Organization, err = organization.GetOrgByName(ctx.Params(":org")) if err != nil { - if models.IsErrOrgNotExist(err) { + if organization.IsErrOrgNotExist(err) { redirectUserID, err := user_model.LookupUserRedirect(ctx.Params(":org")) if err == nil { context.RedirectToUser(ctx.Context, ctx.Params(":org"), redirectUserID) @@ -447,9 +448,9 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) { } if assignTeam { - ctx.Org.Team, err = models.GetTeamByID(ctx.ParamsInt64(":teamid")) + ctx.Org.Team, err = organization.GetTeamByID(ctx.ParamsInt64(":teamid")) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.NotFound() } else { ctx.Error(http.StatusInternalServerError, "GetTeamById", err) diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 9dae15462f..85fe2ded4d 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -9,6 +9,7 @@ import ( "net/url" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/setting" @@ -19,19 +20,19 @@ import ( // listMembers list an organization's members func listMembers(ctx *context.APIContext, publicOnly bool) { - opts := &models.FindOrgMembersOpts{ + opts := &organization.FindOrgMembersOpts{ OrgID: ctx.Org.Organization.ID, PublicOnly: publicOnly, ListOptions: utils.GetListOptions(ctx), } - count, err := models.CountOrgMembers(opts) + count, err := organization.CountOrgMembers(opts) if err != nil { ctx.InternalServerError(err) return } - members, _, err := models.FindOrgMembers(opts) + members, _, err := organization.FindOrgMembers(opts) if err != nil { ctx.InternalServerError(err) return @@ -190,7 +191,7 @@ func IsPublicMember(ctx *context.APIContext) { if ctx.Written() { return } - is, err := models.IsPublicMembership(ctx.Org.Organization.ID, userToCheck.ID) + is, err := organization.IsPublicMembership(ctx.Org.Organization.ID, userToCheck.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsPublicMembership", err) return @@ -234,7 +235,7 @@ func PublicizeMember(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "", "Cannot publicize another member") return } - err := models.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToPublicize.ID, true) + err := organization.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToPublicize.ID, true) if err != nil { ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", err) return @@ -274,7 +275,7 @@ func ConcealMember(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "", "Cannot conceal another member") return } - err := models.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToConceal.ID, false) + err := organization.ChangeOrgUserStatus(ctx.Org.Organization.ID, userToConceal.ID, false) if err != nil { ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", err) return @@ -308,8 +309,8 @@ func DeleteMember(ctx *context.APIContext) { if ctx.Written() { return } - if err := ctx.Org.Organization.RemoveMember(member.ID); err != nil { - ctx.Error(http.StatusInternalServerError, "RemoveMember", err) + if err := models.RemoveOrgUser(ctx.Org.Organization.ID, member.ID); err != nil { + ctx.Error(http.StatusInternalServerError, "RemoveOrgUser", err) } ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index fcc98f4b78..d55a4a4514 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -8,8 +8,8 @@ package org import ( "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" @@ -25,17 +25,17 @@ func listUserOrgs(ctx *context.APIContext, u *user_model.User) { listOptions := utils.GetListOptions(ctx) showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID) - opts := models.FindOrgOptions{ + opts := organization.FindOrgOptions{ ListOptions: listOptions, UserID: u.ID, IncludePrivate: showPrivate, } - orgs, err := models.FindOrgs(opts) + orgs, err := organization.FindOrgs(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "FindOrgs", err) return } - maxResults, err := models.CountOrgs(opts) + maxResults, err := organization.CountOrgs(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "CountOrgs", err) return @@ -135,12 +135,12 @@ func GetUserOrgsPermissions(ctx *context.APIContext) { op := api.OrganizationPermissions{} - if !models.HasOrgOrUserVisible(o, ctx.ContextUser) { + if !organization.HasOrgOrUserVisible(ctx, o, ctx.ContextUser) { ctx.NotFound("HasOrgOrUserVisible", nil) return } - org := models.OrgFromUser(o) + org := organization.OrgFromUser(o) authorizeLevel, err := org.GetOrgUserMaxAuthorizeLevel(ctx.ContextUser.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "GetOrgUserAuthorizeLevel", err) @@ -212,7 +212,7 @@ func GetAll(ctx *context.APIContext) { } orgs := make([]*api.Organization, len(publicOrgs)) for i := range publicOrgs { - orgs[i] = convert.ToOrganization(models.OrgFromUser(publicOrgs[i])) + orgs[i] = convert.ToOrganization(organization.OrgFromUser(publicOrgs[i])) } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) @@ -252,7 +252,7 @@ func Create(ctx *context.APIContext) { visibility = api.VisibilityModes[form.Visibility] } - org := &models.Organization{ + org := &organization.Organization{ Name: form.UserName, FullName: form.FullName, Description: form.Description, @@ -263,7 +263,7 @@ func Create(ctx *context.APIContext) { Visibility: visibility, RepoAdminChangeTeamAccess: form.RepoAdminChangeTeamAccess, } - if err := models.CreateOrganization(org, ctx.Doer); err != nil { + if err := organization.CreateOrganization(org, ctx.Doer); err != nil { if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNameCharsNotAllowed(err) || @@ -295,7 +295,7 @@ func Get(ctx *context.APIContext) { // "200": // "$ref": "#/responses/Organization" - if !models.HasOrgOrUserVisible(ctx.Org.Organization.AsUser(), ctx.Doer) { + if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) { ctx.NotFound("HasOrgOrUserVisible", nil) return } diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index ca8cda269a..206012530f 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -10,6 +10,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" @@ -47,7 +48,7 @@ func ListTeams(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TeamList" - teams, count, err := models.SearchOrgTeams(&models.SearchOrgTeamOptions{ + teams, count, err := organization.SearchTeam(&organization.SearchTeamOptions{ ListOptions: utils.GetListOptions(ctx), OrgID: ctx.Org.Organization.ID, }) @@ -90,7 +91,7 @@ func ListUserTeams(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TeamList" - teams, count, err := models.GetUserTeams(&models.GetUserTeamOptions{ + teams, count, err := organization.SearchTeam(&organization.SearchTeamOptions{ ListOptions: utils.GetListOptions(ctx), UserID: ctx.Doer.ID, }) @@ -104,7 +105,7 @@ func ListUserTeams(ctx *context.APIContext) { for i := range teams { apiOrg, ok := cache[teams[i].OrgID] if !ok { - org, err := models.GetOrgByID(teams[i].OrgID) + org, err := organization.GetOrgByID(teams[i].OrgID) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserByID", err) return @@ -150,11 +151,11 @@ func GetTeam(ctx *context.APIContext) { ctx.JSON(http.StatusOK, convert.ToTeam(ctx.Org.Team)) } -func attachTeamUnits(team *models.Team, units []string) { +func attachTeamUnits(team *organization.Team, units []string) { unitTypes := unit_model.FindUnitTypes(units...) - team.Units = make([]*models.TeamUnit, 0, len(units)) + team.Units = make([]*organization.TeamUnit, 0, len(units)) for _, tp := range unitTypes { - team.Units = append(team.Units, &models.TeamUnit{ + team.Units = append(team.Units, &organization.TeamUnit{ OrgID: team.OrgID, Type: tp, AccessMode: team.AccessMode, @@ -170,10 +171,10 @@ func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.Access return res } -func attachTeamUnitsMap(team *models.Team, unitsMap map[string]string) { - team.Units = make([]*models.TeamUnit, 0, len(unitsMap)) +func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) { + team.Units = make([]*organization.TeamUnit, 0, len(unitsMap)) for unitKey, p := range unitsMap { - team.Units = append(team.Units, &models.TeamUnit{ + team.Units = append(team.Units, &organization.TeamUnit{ OrgID: team.OrgID, Type: unit_model.TypeFromKey(unitKey), AccessMode: perm.ParseAccessMode(p), @@ -210,7 +211,7 @@ func CreateTeam(ctx *context.APIContext) { if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 { p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap)) } - team := &models.Team{ + team := &organization.Team{ OrgID: ctx.Org.Organization.ID, Name: form.Name, Description: form.Description, @@ -231,7 +232,7 @@ func CreateTeam(ctx *context.APIContext) { } if err := models.NewTeam(team); err != nil { - if models.IsErrTeamAlreadyExist(err) { + if organization.IsErrTeamAlreadyExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { ctx.Error(http.StatusInternalServerError, "NewTeam", err) @@ -368,7 +369,7 @@ func GetTeamMembers(ctx *context.APIContext) { // "200": // "$ref": "#/responses/UserList" - isMember, err := models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.Doer.ID) + isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Team.OrgID, ctx.Doer.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err) return @@ -377,14 +378,17 @@ func GetTeamMembers(ctx *context.APIContext) { return } - if err := ctx.Org.Team.GetMembers(&models.SearchMembersOptions{ + teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ ListOptions: utils.GetListOptions(ctx), - }); err != nil { + TeamID: ctx.Org.Team.ID, + }) + if err != nil { ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err) return } + members := make([]*api.User, len(ctx.Org.Team.Members)) - for i, member := range ctx.Org.Team.Members { + for i, member := range teamMembers { members[i] = convert.ToUser(member, ctx.Doer) } @@ -422,7 +426,7 @@ func GetTeamMember(ctx *context.APIContext) { return } teamID := ctx.ParamsInt64("teamid") - isTeamMember, err := models.IsUserInTeams(u.ID, []int64{teamID}) + isTeamMember, err := organization.IsUserInTeams(ctx, u.ID, []int64{teamID}) if err != nil { ctx.Error(http.StatusInternalServerError, "IsUserInTeams", err) return @@ -462,7 +466,7 @@ func AddTeamMember(ctx *context.APIContext) { if ctx.Written() { return } - if err := ctx.Org.Team.AddMember(u.ID); err != nil { + if err := models.AddTeamMember(ctx.Org.Team, u.ID); err != nil { ctx.Error(http.StatusInternalServerError, "AddMember", err) return } @@ -499,8 +503,8 @@ func RemoveTeamMember(ctx *context.APIContext) { return } - if err := ctx.Org.Team.RemoveMember(u.ID); err != nil { - ctx.Error(http.StatusInternalServerError, "RemoveMember", err) + if err := models.RemoveTeamMember(ctx.Org.Team, u.ID); err != nil { + ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err) return } ctx.Status(http.StatusNoContent) @@ -533,13 +537,16 @@ func GetTeamRepos(ctx *context.APIContext) { // "$ref": "#/responses/RepositoryList" team := ctx.Org.Team - if err := team.GetRepositories(&models.SearchOrgTeamOptions{ + teamRepos, err := organization.GetTeamRepositories(ctx, &organization.SearchTeamRepoOptions{ ListOptions: utils.GetListOptions(ctx), - }); err != nil { + TeamID: team.ID, + }) + if err != nil { ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err) + return } repos := make([]*api.Repository, len(team.Repos)) - for i, repo := range team.Repos { + for i, repo := range teamRepos { access, err := models.AccessLevel(ctx.Doer, repo) if err != nil { ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err) @@ -606,7 +613,7 @@ func AddTeamRepository(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository") return } - if err := ctx.Org.Team.AddRepository(repo); err != nil { + if err := models.AddRepository(ctx.Org.Team, repo); err != nil { ctx.Error(http.StatusInternalServerError, "AddRepository", err) return } @@ -656,7 +663,7 @@ func RemoveTeamRepository(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository") return } - if err := ctx.Org.Team.RemoveRepository(repo.ID); err != nil { + if err := models.RemoveRepository(ctx.Org.Team, repo.ID); err != nil { ctx.Error(http.StatusInternalServerError, "RemoveRepository", err) return } @@ -707,14 +714,15 @@ func SearchTeam(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) - opts := &models.SearchOrgTeamOptions{ + opts := &organization.SearchTeamOptions{ + UserID: ctx.Doer.ID, Keyword: ctx.FormTrim("q"), OrgID: ctx.Org.Organization.ID, IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"), ListOptions: listOptions, } - teams, maxResults, err := models.SearchOrgTeams(opts) + teams, maxResults, err := organization.SearchTeam(opts) if err != nil { log.Error("SearchTeam failed: %v", err) ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 065bad2708..f22bed813e 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -11,6 +11,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" @@ -448,27 +449,27 @@ func CreateBranchProtection(ctx *context.APIContext) { } var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64 if repo.Owner.IsOrganization() { - whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) + whitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) return } ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) return } - mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) + mergeWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) return } ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err) return } - approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) + approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) return } @@ -692,9 +693,9 @@ func EditBranchProtection(ctx *context.APIContext) { var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64 if repo.Owner.IsOrganization() { if form.PushWhitelistTeams != nil { - whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) + whitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) return } @@ -705,9 +706,9 @@ func EditBranchProtection(ctx *context.APIContext) { whitelistTeams = protectBranch.WhitelistTeamIDs } if form.MergeWhitelistTeams != nil { - mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) + mergeWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) return } @@ -718,9 +719,9 @@ func EditBranchProtection(ctx *context.APIContext) { mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs } if form.ApprovalsWhitelistTeams != nil { - approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) + approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err) return } diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 6cf51b2467..aaff9107cf 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -10,6 +10,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -108,9 +109,9 @@ func CreateFork(ctx *context.APIContext) { if form.Organization == nil { forker = ctx.Doer } else { - org, err := models.GetOrgByName(*form.Organization) + org, err := organization.GetOrgByName(*form.Organization) if err != nil { - if models.IsErrOrgNotExist(err) { + if organization.IsErrOrgNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { ctx.Error(http.StatusInternalServerError, "GetOrgByName", err) diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index ef8c2d008e..05c9c27144 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" @@ -161,9 +162,9 @@ func SearchIssues(ctx *context.APIContext) { ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team") return } - team, err := models.GetTeam(opts.OwnerID, ctx.FormString("team")) + team, err := organization.GetTeam(opts.OwnerID, ctx.FormString("team")) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusBadRequest, "Team not found", err) } else { ctx.Error(http.StatusInternalServerError, "GetUserByName", err) diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index 1461c2a145..1adf5bbe86 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -90,7 +91,7 @@ func Migrate(ctx *context.APIContext) { if repoOwner.IsOrganization() { // Check ownership of organization. - isOwner, err := models.OrgFromUser(repoOwner).IsOwnedBy(ctx.Doer.ID) + isOwner, err := organization.OrgFromUser(repoOwner).IsOwnedBy(ctx.Doer.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err) return diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index b4a484f68c..331b78e1d8 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" @@ -722,12 +723,12 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 { - teamReviewers := make([]*models.Team, 0, len(opts.TeamReviewers)) + teamReviewers := make([]*organization.Team, 0, len(opts.TeamReviewers)) for _, t := range opts.TeamReviewers { - var teamReviewer *models.Team - teamReviewer, err = models.GetTeam(ctx.Repo.Owner.ID, t) + var teamReviewer *organization.Team + teamReviewer, err = organization.GetTeam(ctx.Repo.Owner.ID, t) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.NotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t)) return } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 7e386d2e5b..2d979235a0 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" @@ -398,7 +399,7 @@ func Generate(ctx *context.APIContext) { } if !ctx.Doer.IsAdmin { - canCreate, err := models.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx.Doer.ID) + canCreate, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx.Doer.ID) if err != nil { ctx.ServerError("CanCreateOrgRepo", err) return @@ -484,9 +485,9 @@ func CreateOrgRepo(ctx *context.APIContext) { // "403": // "$ref": "#/responses/forbidden" opt := web.GetForm(ctx).(*api.CreateRepoOption) - org, err := models.GetOrgByName(ctx.Params(":org")) + org, err := organization.GetOrgByName(ctx.Params(":org")) if err != nil { - if models.IsErrOrgNotExist(err) { + if organization.IsErrOrgNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { ctx.Error(http.StatusInternalServerError, "GetOrgByName", err) @@ -494,7 +495,7 @@ func CreateOrgRepo(ctx *context.APIContext) { return } - if !models.HasOrgOrUserVisible(org.AsUser(), ctx.Doer) { + if !organization.HasOrgOrUserVisible(ctx, org.AsUser(), ctx.Doer) { ctx.NotFound("HasOrgOrUserVisible", nil) return } diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go index 024224b5a3..1e3ea326d3 100644 --- a/routers/api/v1/repo/teams.go +++ b/routers/api/v1/repo/teams.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -41,7 +42,7 @@ func ListTeams(ctx *context.APIContext) { return } - teams, err := models.GetRepoTeams(ctx.Repo.Repository) + teams, err := organization.GetRepoTeams(ctx.Repo.Repository) if err != nil { ctx.InternalServerError(err) return @@ -101,7 +102,7 @@ func IsTeam(ctx *context.APIContext) { return } - if team.HasRepository(ctx.Repo.Repository.ID) { + if models.HasRepository(team, ctx.Repo.Repository.ID) { if err := team.GetUnits(); err != nil { ctx.Error(http.StatusInternalServerError, "GetUnits", err) return @@ -196,20 +197,20 @@ func changeRepoTeam(ctx *context.APIContext, add bool) { return } - repoHasTeam := team.HasRepository(ctx.Repo.Repository.ID) + repoHasTeam := models.HasRepository(team, ctx.Repo.Repository.ID) var err error if add { if repoHasTeam { ctx.Error(http.StatusUnprocessableEntity, "alreadyAdded", fmt.Errorf("team '%s' is already added to repo", team.Name)) return } - err = team.AddRepository(ctx.Repo.Repository) + err = models.AddRepository(team, ctx.Repo.Repository) } else { if !repoHasTeam { ctx.Error(http.StatusUnprocessableEntity, "notAdded", fmt.Errorf("team '%s' was not added to repo", team.Name)) return } - err = team.RemoveRepository(ctx.Repo.Repository.ID) + err = models.RemoveRepository(team, ctx.Repo.Repository.ID) } if err != nil { ctx.InternalServerError(err) @@ -219,10 +220,10 @@ func changeRepoTeam(ctx *context.APIContext, add bool) { ctx.Status(http.StatusNoContent) } -func getTeamByParam(ctx *context.APIContext) *models.Team { - team, err := models.GetTeam(ctx.Repo.Owner.ID, ctx.Params(":team")) +func getTeamByParam(ctx *context.APIContext) *organization.Team { + team, err := organization.GetTeam(ctx.Repo.Owner.ID, ctx.Params(":team")) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Error(http.StatusNotFound, "TeamNotExit", err) return nil } diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index e149f9c8f0..7578fbd187 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -67,23 +68,23 @@ func Transfer(ctx *context.APIContext) { } if newOwner.Type == user_model.UserTypeOrganization { - if !ctx.Doer.IsAdmin && newOwner.Visibility == api.VisibleTypePrivate && !models.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) { + if !ctx.Doer.IsAdmin && newOwner.Visibility == api.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) { // The user shouldn't know about this organization ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found") return } } - var teams []*models.Team + var teams []*organization.Team if opts.TeamIDs != nil { if !newOwner.IsOrganization() { ctx.Error(http.StatusUnprocessableEntity, "repoTransfer", "Teams can only be added to organization-owned repositories") return } - org := convert.ToOrganization(models.OrgFromUser(newOwner)) + org := convert.ToOrganization(organization.OrgFromUser(newOwner)) for _, tID := range *opts.TeamIDs { - team, err := models.GetTeamByID(tID) + team, err := organization.GetTeamByID(tID) if err != nil { ctx.Error(http.StatusUnprocessableEntity, "team", fmt.Errorf("team %d not found", tID)) return diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go index 83f97f8f01..cdbc35471b 100644 --- a/routers/api/v1/user/star.go +++ b/routers/api/v1/user/star.go @@ -21,7 +21,7 @@ import ( // getStarredRepos returns the repos that the user with the specified userID has // starred func getStarredRepos(user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, error) { - starredRepos, err := models.GetStarredRepos(user.ID, private, listOptions) + starredRepos, err := repo_model.GetStarredRepos(user.ID, private, listOptions) if err != nil { return nil, err } diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index e9d871c5c1..018f75762f 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -98,7 +98,7 @@ func GetInfo(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if !models.IsUserVisibleToViewer(ctx.ContextUser, ctx.Doer) { + if !user_model.IsUserVisibleToViewer(ctx.ContextUser, ctx.Doer) { // fake ErrUserNotExist error message to not leak information about existence ctx.NotFound("GetUserByName", user_model.ErrUserNotExist{Name: ctx.Params(":username")}) return diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go index 22e76f5ebc..e7c6837cb8 100644 --- a/routers/api/v1/user/watch.go +++ b/routers/api/v1/user/watch.go @@ -19,7 +19,7 @@ import ( // getWatchedRepos returns the repos that the user with the specified userID is watching func getWatchedRepos(user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, int64, error) { - watchedRepos, total, err := models.GetWatchedRepos(user.ID, private, listOptions) + watchedRepos, total, err := repo_model.GetWatchedRepos(user.ID, private, listOptions) if err != nil { return nil, 0, err } diff --git a/routers/web/org/home.go b/routers/web/org/home.go index 1466731b9e..24a0f13b54 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -39,7 +40,7 @@ func Home(ctx *context.Context) { org := ctx.Org.Organization - if !models.HasOrgOrUserVisible(org.AsUser(), ctx.Doer) { + if !organization.HasOrgOrUserVisible(ctx, org.AsUser(), ctx.Doer) { ctx.NotFound("HasOrgOrUserVisible", nil) return } @@ -122,7 +123,7 @@ func Home(ctx *context.Context) { return } - opts := &models.FindOrgMembersOpts{ + opts := &organization.FindOrgMembersOpts{ OrgID: org.ID, PublicOnly: true, ListOptions: db.ListOptions{Page: 1, PageSize: 25}, @@ -137,13 +138,13 @@ func Home(ctx *context.Context) { opts.PublicOnly = !isMember && !ctx.Doer.IsAdmin } - members, _, err := models.FindOrgMembers(opts) + members, _, err := organization.FindOrgMembers(opts) if err != nil { ctx.ServerError("FindOrgMembers", err) return } - membersCount, err := models.CountOrgMembers(opts) + membersCount, err := organization.CountOrgMembers(opts) if err != nil { ctx.ServerError("CountOrgMembers", err) return diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 9e65e61404..add8e724bd 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -31,7 +32,7 @@ func Members(ctx *context.Context) { page = 1 } - opts := &models.FindOrgMembersOpts{ + opts := &organization.FindOrgMembersOpts{ OrgID: org.ID, PublicOnly: true, } @@ -45,7 +46,7 @@ func Members(ctx *context.Context) { opts.PublicOnly = !isMember && !ctx.Doer.IsAdmin } - total, err := models.CountOrgMembers(opts) + total, err := organization.CountOrgMembers(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "CountOrgMembers") return @@ -54,7 +55,7 @@ func Members(ctx *context.Context) { pager := context.NewPagination(int(total), setting.UI.MembersPagingNum, page, 5) opts.ListOptions.Page = page opts.ListOptions.PageSize = setting.UI.MembersPagingNum - members, membersIsPublic, err := models.FindOrgMembers(opts) + members, membersIsPublic, err := organization.FindOrgMembers(opts) if err != nil { ctx.ServerError("GetMembers", err) return @@ -84,20 +85,20 @@ func MembersAction(ctx *context.Context) { ctx.Error(http.StatusNotFound) return } - err = models.ChangeOrgUserStatus(org.ID, uid, false) + err = organization.ChangeOrgUserStatus(org.ID, uid, false) case "public": if ctx.Doer.ID != uid && !ctx.Org.IsOwner { ctx.Error(http.StatusNotFound) return } - err = models.ChangeOrgUserStatus(org.ID, uid, true) + err = organization.ChangeOrgUserStatus(org.ID, uid, true) case "remove": if !ctx.Org.IsOwner { ctx.Error(http.StatusNotFound) return } - err = org.RemoveMember(uid) - if models.IsErrLastOrgOwner(err) { + err = models.RemoveOrgUser(org.ID, uid) + if organization.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": ctx.Org.OrgLink + "/members", @@ -105,8 +106,8 @@ func MembersAction(ctx *context.Context) { return } case "leave": - err = org.RemoveMember(ctx.Doer.ID) - if models.IsErrLastOrgOwner(err) { + err = models.RemoveOrgUser(org.ID, ctx.Doer.ID) + if organization.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": ctx.Org.OrgLink + "/members", diff --git a/routers/web/org/org.go b/routers/web/org/org.go index b641f768b2..32d8787995 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -9,8 +9,8 @@ import ( "errors" "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -51,7 +51,7 @@ func CreatePost(ctx *context.Context) { return } - org := &models.Organization{ + org := &organization.Organization{ Name: form.OrgName, IsActive: true, Type: user_model.UserTypeOrganization, @@ -59,7 +59,7 @@ func CreatePost(ctx *context.Context) { RepoAdminChangeTeamAccess: form.RepoAdminChangeTeamAccess, } - if err := models.CreateOrganization(org, ctx.Doer); err != nil { + if err := organization.CreateOrganization(org, ctx.Doer); err != nil { ctx.Data["Err_OrgName"] = true switch { case user_model.IsErrUserAlreadyExist(err): @@ -68,7 +68,7 @@ func CreatePost(ctx *context.Context) { ctx.RenderWithErr(ctx.Tr("org.form.name_reserved", err.(db.ErrNameReserved).Name), tplCreateOrg, &form) case db.IsErrNamePatternNotAllowed(err): ctx.RenderWithErr(ctx.Tr("org.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplCreateOrg, &form) - case models.IsErrUserNotAllowedCreateOrg(err): + case organization.IsErrUserNotAllowedCreateOrg(err): ctx.RenderWithErr(ctx.Tr("org.form.create_org_not_allowed"), tplCreateOrg, &form) default: ctx.ServerError("CreateOrganization", err) diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index d13c4e854d..034a8ce978 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" @@ -43,7 +44,7 @@ func Teams(ctx *context.Context) { ctx.Data["PageIsOrgTeams"] = true for _, t := range ctx.Org.Teams { - if err := t.GetMembers(&models.SearchMembersOptions{}); err != nil { + if err := t.GetMembersCtx(ctx); err != nil { ctx.ServerError("GetMembers", err) return } @@ -69,11 +70,11 @@ func TeamsAction(ctx *context.Context) { ctx.Error(http.StatusNotFound) return } - err = ctx.Org.Team.AddMember(ctx.Doer.ID) + err = models.AddTeamMember(ctx.Org.Team, ctx.Doer.ID) case "leave": - err = ctx.Org.Team.RemoveMember(ctx.Doer.ID) + err = models.RemoveTeamMember(ctx.Org.Team, ctx.Doer.ID) if err != nil { - if models.IsErrLastOrgOwner(err) { + if organization.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) } else { log.Error("Action(%s): %v", ctx.Params(":action"), err) @@ -94,9 +95,9 @@ func TeamsAction(ctx *context.Context) { ctx.Error(http.StatusNotFound) return } - err = ctx.Org.Team.RemoveMember(uid) + err = models.RemoveTeamMember(ctx.Org.Team, uid) if err != nil { - if models.IsErrLastOrgOwner(err) { + if organization.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) } else { log.Error("Action(%s): %v", ctx.Params(":action"), err) @@ -139,14 +140,14 @@ func TeamsAction(ctx *context.Context) { if ctx.Org.Team.IsMember(u.ID) { ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users")) } else { - err = ctx.Org.Team.AddMember(u.ID) + err = models.AddTeamMember(ctx.Org.Team, u.ID) } page = "team" } if err != nil { - if models.IsErrLastOrgOwner(err) { + if organization.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) } else { log.Error("Action(%s): %v", ctx.Params(":action"), err) @@ -191,13 +192,13 @@ func TeamsRepoAction(ctx *context.Context) { ctx.ServerError("GetRepositoryByName", err) return } - err = ctx.Org.Team.AddRepository(repo) + err = models.AddRepository(ctx.Org.Team, repo) case "remove": - err = ctx.Org.Team.RemoveRepository(ctx.FormInt64("repoid")) + err = models.RemoveRepository(ctx.Org.Team, ctx.FormInt64("repoid")) case "addall": - err = ctx.Org.Team.AddAllRepositories() + err = models.AddAllRepositories(ctx.Org.Team) case "removeall": - err = ctx.Org.Team.RemoveAllRepositories() + err = models.RemoveAllRepositories(ctx.Org.Team) } if err != nil { @@ -220,7 +221,7 @@ func NewTeam(ctx *context.Context) { ctx.Data["Title"] = ctx.Org.Organization.FullName ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeamsNew"] = true - ctx.Data["Team"] = &models.Team{} + ctx.Data["Team"] = &organization.Team{} ctx.Data["Units"] = unit_model.Units ctx.HTML(http.StatusOK, tplTeamNew) } @@ -251,7 +252,7 @@ func NewTeamPost(ctx *context.Context) { p = unit_model.MinUnitAccessMode(unitPerms) } - t := &models.Team{ + t := &organization.Team{ OrgID: ctx.Org.Organization.ID, Name: form.TeamName, Description: form.Description, @@ -261,9 +262,9 @@ func NewTeamPost(ctx *context.Context) { } if t.AccessMode < perm.AccessModeAdmin { - units := make([]*models.TeamUnit, 0, len(unitPerms)) + units := make([]*organization.TeamUnit, 0, len(unitPerms)) for tp, perm := range unitPerms { - units = append(units, &models.TeamUnit{ + units = append(units, &organization.TeamUnit{ OrgID: ctx.Org.Organization.ID, Type: tp, AccessMode: perm, @@ -291,7 +292,7 @@ func NewTeamPost(ctx *context.Context) { if err := models.NewTeam(t); err != nil { ctx.Data["Err_TeamName"] = true switch { - case models.IsErrTeamAlreadyExist(err): + case organization.IsErrTeamAlreadyExist(err): ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form) default: ctx.ServerError("NewTeam", err) @@ -307,7 +308,7 @@ func TeamMembers(ctx *context.Context) { ctx.Data["Title"] = ctx.Org.Team.Name ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeamMembers"] = true - if err := ctx.Org.Team.GetMembers(&models.SearchMembersOptions{}); err != nil { + if err := ctx.Org.Team.GetMembersCtx(ctx); err != nil { ctx.ServerError("GetMembers", err) return } @@ -320,7 +321,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.SearchOrgTeamOptions{}); err != nil { + if err := ctx.Org.Team.GetRepositoriesCtx(ctx); err != nil { ctx.ServerError("GetRepositories", err) return } @@ -374,17 +375,17 @@ func EditTeamPost(ctx *context.Context) { } t.Description = form.Description if t.AccessMode < perm.AccessModeAdmin { - units := make([]models.TeamUnit, 0, len(unitPerms)) + units := make([]organization.TeamUnit, 0, len(unitPerms)) for tp, perm := range unitPerms { - units = append(units, models.TeamUnit{ + units = append(units, organization.TeamUnit{ OrgID: t.OrgID, TeamID: t.ID, Type: tp, AccessMode: perm, }) } - if err := models.UpdateTeamUnits(t, units); err != nil { - ctx.Error(http.StatusInternalServerError, "LoadIssue", err.Error()) + if err := organization.UpdateTeamUnits(t, units); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateTeamUnits", err.Error()) return } } @@ -403,7 +404,7 @@ func EditTeamPost(ctx *context.Context) { if err := models.UpdateTeam(t, isAuthChanged, isIncludeAllChanged); err != nil { ctx.Data["Err_TeamName"] = true switch { - case models.IsErrTeamAlreadyExist(err): + case organization.IsErrTeamAlreadyExist(err): ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form) default: ctx.ServerError("UpdateTeam", err) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index aeb1f0a020..ee4738c970 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -471,7 +472,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { // repoReviewerSelection items to bee shown type repoReviewerSelection struct { IsTeam bool - Team *models.Team + Team *organization.Team User *user_model.User Review *models.Review CanChange bool @@ -504,7 +505,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is pullReviews []*repoReviewerSelection reviewersResult []*repoReviewerSelection teamReviewersResult []*repoReviewerSelection - teamReviewers []*models.Team + teamReviewers []*organization.Team reviewers []*user_model.User ) @@ -586,7 +587,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is item.User = item.Review.Reviewer } else if item.Review.ReviewerTeamID > 0 { if err = item.Review.LoadReviewerTeam(); err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { continue } ctx.ServerError("LoadReviewerTeam", err) @@ -1965,9 +1966,9 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - team, err := models.GetTeamByID(-reviewID) + team, err := organization.GetTeamByID(-reviewID) if err != nil { - ctx.ServerError("models.GetTeamByID", err) + ctx.ServerError("GetTeamByID", err) return } @@ -2702,8 +2703,8 @@ func handleTeamMentions(ctx *context.Context) { var isAdmin bool var err error - var teams []*models.Team - org := models.OrgFromUser(ctx.Repo.Owner) + var teams []*organization.Team + org := organization.OrgFromUser(ctx.Repo.Owner) // Admin has super access. if ctx.Doer.IsAdmin { isAdmin = true diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index ec04fa1660..a8866fc072 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -77,7 +78,7 @@ func RetrieveLabels(ctx *context.Context) { } ctx.Data["OrgLabels"] = orgLabels - org, err := models.GetOrgByName(ctx.Repo.Owner.LowerName) + org, err := organization.GetOrgByName(ctx.Repo.Owner.LowerName) if err != nil { ctx.ServerError("GetOrgByName", err) return diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index a4edfd9ee4..af99f1b275 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -111,12 +112,12 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { ctx.Data["ForkRepo"] = forkRepo - ownedOrgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.Doer.ID) + ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx.Doer.ID) if err != nil { ctx.ServerError("GetOrgsCanCreateRepoByUserID", err) return nil } - var orgs []*models.Organization + var orgs []*organization.Organization for _, org := range ownedOrgs { if forkRepo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, forkRepo.ID) { orgs = append(orgs, org) @@ -216,7 +217,7 @@ func ForkPost(ctx *context.Context) { // Check if user is allowed to create repo's on the organization. if ctxUser.IsOrganization() { - isAllowedToFork, err := models.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx.Doer.ID) + isAllowedToFork, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx.Doer.ID) if err != nil { ctx.ServerError("CanCreateOrgRepo", err) return diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 4e07d47203..8b03133611 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -57,14 +58,14 @@ func MustBeAbleToUpload(ctx *context.Context) { } func checkContextUser(ctx *context.Context, uid int64) *user_model.User { - orgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.Doer.ID) + orgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx.Doer.ID) if err != nil { ctx.ServerError("GetOrgsCanCreateRepoByUserID", err) return nil } if !ctx.Doer.IsAdmin { - orgsAvailable := []*models.Organization{} + orgsAvailable := []*organization.Organization{} for i := 0; i < len(orgs); i++ { if orgs[i].CanCreateRepo() { orgsAvailable = append(orgsAvailable, orgs[i]) @@ -96,7 +97,7 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User { return nil } if !ctx.Doer.IsAdmin { - canCreate, err := models.OrgFromUser(org).CanCreateOrgRepo(ctx.Doer.ID) + canCreate, err := organization.OrgFromUser(org).CanCreateOrgRepo(ctx.Doer.ID) if err != nil { ctx.ServerError("CanCreateOrgRepo", err) return nil diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 75db7ca3e2..ef99eee15a 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" @@ -648,7 +649,7 @@ func SettingsPost(ctx *context.Context) { } if newOwner.Type == user_model.UserTypeOrganization { - if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !models.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) { + if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) { // The user shouldn't know about this organization ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) return @@ -835,7 +836,7 @@ func Collaboration(ctx *context.Context) { } ctx.Data["Collaborators"] = users - teams, err := models.GetRepoTeams(ctx.Repo.Repository) + teams, err := organization.GetRepoTeams(ctx.Repo.Repository) if err != nil { ctx.ServerError("GetRepoTeams", err) return @@ -938,9 +939,9 @@ func AddTeamPost(ctx *context.Context) { return } - team, err := models.OrgFromUser(ctx.Repo.Owner).GetTeam(name) + team, err := organization.OrgFromUser(ctx.Repo.Owner).GetTeam(name) if err != nil { - if models.IsErrTeamNotExist(err) { + if organization.IsErrTeamNotExist(err) { ctx.Flash.Error(ctx.Tr("form.team_not_exist")) ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") } else { @@ -955,13 +956,13 @@ func AddTeamPost(ctx *context.Context) { return } - if models.HasTeamRepo(ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) { + if organization.HasTeamRepo(ctx, ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) { ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate")) ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") return } - if err = team.AddRepository(ctx.Repo.Repository); err != nil { + if err = models.AddRepository(team, ctx.Repo.Repository); err != nil { ctx.ServerError("team.AddRepository", err) return } @@ -978,13 +979,13 @@ func DeleteTeam(ctx *context.Context) { return } - team, err := models.GetTeamByID(ctx.FormInt64("id")) + team, err := organization.GetTeamByID(ctx.FormInt64("id")) if err != nil { ctx.ServerError("GetTeamByID", err) return } - if err = team.RemoveRepository(ctx.Repo.Repository.ID); err != nil { + if err = models.RemoveRepository(team, ctx.Repo.Repository.ID); err != nil { ctx.ServerError("team.RemoveRepositorys", err) return } diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index dae618a758..a26a0a620a 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" @@ -158,7 +159,7 @@ func SettingsProtectedBranch(c *context.Context) { } if c.Repo.Owner.IsOrganization() { - teams, err := models.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c.Repo.Repository.ID, perm.AccessModeRead) + teams, err := organization.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c.Repo.Repository.ID, perm.AccessModeRead) if err != nil { c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err) return diff --git a/routers/web/repo/settings_test.go b/routers/web/repo/settings_test.go index db1e905869..36d02de273 100644 --- a/routers/web/repo/settings_test.go +++ b/routers/web/repo/settings_test.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -231,7 +232,7 @@ func TestAddTeamPost(t *testing.T) { Type: user_model.UserTypeOrganization, } - team := &models.Team{ + team := &organization.Team{ ID: 11, OrgID: 26, } @@ -255,7 +256,7 @@ func TestAddTeamPost(t *testing.T) { AddTeamPost(ctx) - assert.True(t, team.HasRepository(re.ID)) + assert.True(t, models.HasRepository(team, re.ID)) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assert.Empty(t, ctx.Flash.ErrorMsg) } @@ -271,7 +272,7 @@ func TestAddTeamPost_NotAllowed(t *testing.T) { Type: user_model.UserTypeOrganization, } - team := &models.Team{ + team := &organization.Team{ ID: 11, OrgID: 26, } @@ -295,7 +296,7 @@ func TestAddTeamPost_NotAllowed(t *testing.T) { AddTeamPost(ctx) - assert.False(t, team.HasRepository(re.ID)) + assert.False(t, models.HasRepository(team, re.ID)) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -311,7 +312,7 @@ func TestAddTeamPost_AddTeamTwice(t *testing.T) { Type: user_model.UserTypeOrganization, } - team := &models.Team{ + team := &organization.Team{ ID: 11, OrgID: 26, } @@ -336,7 +337,7 @@ func TestAddTeamPost_AddTeamTwice(t *testing.T) { AddTeamPost(ctx) AddTeamPost(ctx) - assert.True(t, team.HasRepository(re.ID)) + assert.True(t, models.HasRepository(team, re.ID)) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -385,7 +386,7 @@ func TestDeleteTeam(t *testing.T) { Type: user_model.UserTypeOrganization, } - team := &models.Team{ + team := &organization.Team{ ID: 2, OrgID: 3, } @@ -409,5 +410,5 @@ func TestDeleteTeam(t *testing.T) { DeleteTeam(ctx) - assert.False(t, team.HasRepository(re.ID)) + assert.False(t, models.HasRepository(team, re.ID)) } diff --git a/routers/web/repo/tag.go b/routers/web/repo/tag.go index 0114704f82..7da1e36c81 100644 --- a/routers/web/repo/tag.go +++ b/routers/web/repo/tag.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -150,7 +151,7 @@ func setTagsContext(ctx *context.Context) error { ctx.Data["Users"] = users if ctx.Repo.Owner.IsOrganization() { - teams, err := models.OrgFromUser(ctx.Repo.Owner).TeamsWithAccessToRepo(ctx.Repo.Repository.ID, perm.AccessModeRead) + teams, err := organization.OrgFromUser(ctx.Repo.Owner).TeamsWithAccessToRepo(ctx.Repo.Repository.ID, perm.AccessModeRead) if err != nil { ctx.ServerError("Repo.Owner.TeamsWithAccessToRepo", err) return err diff --git a/routers/web/user/home.go b/routers/web/user/home.go index e0beb0cbee..156cf0fa64 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -74,7 +75,7 @@ func Dashboard(ctx *context.Context) { ctx.Data["Title"] = ctxUser.DisplayName() + " - " + ctx.Tr("dashboard") ctx.Data["PageIsDashboard"] = true ctx.Data["PageIsNews"] = true - cnt, _ := models.GetOrganizationCount(ctx, ctxUser) + cnt, _ := organization.GetOrganizationCount(ctx, ctxUser) ctx.Data["UserOrgsCount"] = cnt var uid int64 @@ -99,11 +100,11 @@ func Dashboard(ctx *context.Context) { var err error var mirrors []*repo_model.Repository if ctxUser.IsOrganization() { - var env models.AccessibleReposEnvironment + var env organization.AccessibleReposEnvironment if ctx.Org.Team != nil { - env = models.OrgFromUser(ctxUser).AccessibleTeamReposEnv(ctx.Org.Team) + env = organization.OrgFromUser(ctxUser).AccessibleTeamReposEnv(ctx.Org.Team) } else { - env, err = models.OrgFromUser(ctxUser).AccessibleReposEnv(ctx.Doer.ID) + env, err = organization.AccessibleReposEnv(ctx, organization.OrgFromUser(ctxUser), ctx.Doer.ID) if err != nil { ctx.ServerError("AccessibleReposEnv", err) return @@ -404,8 +405,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // -------------------------------------------------------------------------- // Get repository IDs where User/Org/Team has access. - var team *models.Team - var org *models.Organization + var team *organization.Team + var org *organization.Organization if ctx.Org != nil { org = ctx.Org.Organization team = ctx.Org.Team diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index db2660af61..99198e8866 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" @@ -40,7 +41,7 @@ func Profile(ctx *context.Context) { } // check view permissions - if !models.IsUserVisibleToViewer(ctx.ContextUser, ctx.Doer) { + if !user_model.IsUserVisibleToViewer(ctx.ContextUser, ctx.Doer) { ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name)) return } @@ -91,7 +92,7 @@ func Profile(ctx *context.Context) { showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) - orgs, err := models.FindOrgs(models.FindOrgOptions{ + orgs, err := organization.FindOrgs(organization.FindOrgOptions{ UserID: ctx.ContextUser.ID, IncludePrivate: showPrivate, }) @@ -101,7 +102,7 @@ func Profile(ctx *context.Context) { } ctx.Data["Orgs"] = orgs - ctx.Data["HasOrgsVisible"] = models.HasOrgsVisible(orgs, ctx.Doer) + ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(orgs, ctx.Doer) tab := ctx.FormString("tab") ctx.Data["TabName"] = tab diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 09871ef763..7a875e38cb 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -218,7 +219,7 @@ func Organization(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsOrganization"] = true - opts := models.FindOrgOptions{ + opts := organization.FindOrgOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.Admin.UserPagingNum, Page: ctx.FormInt("page"), @@ -231,12 +232,12 @@ func Organization(ctx *context.Context) { opts.Page = 1 } - orgs, err := models.FindOrgs(opts) + orgs, err := organization.FindOrgs(opts) if err != nil { ctx.ServerError("FindOrgs", err) return } - total, err := models.CountOrgs(opts) + total, err := organization.CountOrgs(opts) if err != nil { ctx.ServerError("CountOrgs", err) return diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 2c1bcc29cc..ddd70627ed 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -8,10 +8,10 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/mailer" user_service "code.gitea.io/gitea/services/user" @@ -65,8 +65,8 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str if user != nil { if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - orgCache := make(map[string]*models.Organization) - teamCache := make(map[string]*models.Team) + orgCache := make(map[string]*organization.Organization) + teamCache := make(map[string]*organization.Team) source.SyncLdapGroupsToTeams(user, sr.LdapTeamAdd, sr.LdapTeamRemove, orgCache, teamCache) } if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(user, source.authSource, sr.SSHPublicKey) { @@ -111,8 +111,8 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str _ = user_service.UploadAvatar(user, sr.Avatar) } if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - orgCache := make(map[string]*models.Organization) - teamCache := make(map[string]*models.Team) + orgCache := make(map[string]*organization.Organization) + teamCache := make(map[string]*organization.Team) source.SyncLdapGroupsToTeams(user, sr.LdapTeamAdd, sr.LdapTeamRemove, orgCache, teamCache) } diff --git a/services/auth/source/ldap/source_group_sync.go b/services/auth/source/ldap/source_group_sync.go index 7c62af705e..e797e015b2 100644 --- a/services/auth/source/ldap/source_group_sync.go +++ b/services/auth/source/ldap/source_group_sync.go @@ -6,12 +6,14 @@ package ldap import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" ) // SyncLdapGroupsToTeams maps LDAP groups to organization and team memberships -func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, ldapTeamRemove map[string][]string, orgCache map[string]*models.Organization, teamCache map[string]*models.Team) { +func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, ldapTeamRemove map[string][]string, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) { var err error if source.GroupsEnabled && source.GroupTeamMapRemoval { // when the user is not a member of configs LDAP group, remove mapped organizations/teams memberships @@ -20,7 +22,7 @@ func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, for orgName, teamNames := range ldapTeamAdd { org, ok := orgCache[orgName] if !ok { - org, err = models.GetOrgByName(orgName) + org, err = organization.GetOrgByName(orgName) if err != nil { // organization must be created before LDAP group sync log.Warn("LDAP group sync: Could not find organisation %s: %v", orgName, err) @@ -28,14 +30,7 @@ func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, } orgCache[orgName] = org } - if isMember, err := models.IsOrganizationMember(org.ID, user.ID); !isMember && err == nil { - log.Trace("LDAP group sync: adding user [%s] to organization [%s]", user.Name, org.Name) - err = org.AddMember(user.ID) - if err != nil { - log.Error("LDAP group sync: Could not add user to organization: %v", err) - continue - } - } + for _, teamName := range teamNames { team, ok := teamCache[orgName+teamName] if !ok { @@ -47,12 +42,12 @@ func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, } teamCache[orgName+teamName] = team } - if isMember, err := models.IsTeamMember(org.ID, team.ID, user.ID); !isMember && err == nil { + if isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID); !isMember && err == nil { log.Trace("LDAP group sync: adding user [%s] to team [%s]", user.Name, org.Name) } else { continue } - err := team.AddMember(user.ID) + err := models.AddTeamMember(team, user.ID) if err != nil { log.Error("LDAP group sync: Could not add user to team: %v", err) } @@ -63,12 +58,12 @@ func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, // remove membership to organizations/teams if user is not member of corresponding LDAP group // e.g. lets assume user is member of LDAP group "x", but LDAP group team map contains LDAP groups "x" and "y" // then users membership gets removed for all organizations/teams mapped by LDAP group "y" -func removeMappedMemberships(user *user_model.User, ldapTeamRemove map[string][]string, orgCache map[string]*models.Organization, teamCache map[string]*models.Team) { +func removeMappedMemberships(user *user_model.User, ldapTeamRemove map[string][]string, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) { var err error for orgName, teamNames := range ldapTeamRemove { org, ok := orgCache[orgName] if !ok { - org, err = models.GetOrgByName(orgName) + org, err = organization.GetOrgByName(orgName) if err != nil { // organization must be created before LDAP group sync log.Warn("LDAP group sync: Could not find organisation %s: %v", orgName, err) @@ -86,12 +81,12 @@ func removeMappedMemberships(user *user_model.User, ldapTeamRemove map[string][] continue } } - if isMember, err := models.IsTeamMember(org.ID, team.ID, user.ID); isMember && err == nil { + if isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID); isMember && err == nil { log.Trace("LDAP group sync: removing user [%s] from team [%s]", user.Name, org.Name) } else { continue } - err = team.RemoveMember(user.ID) + err = models.RemoveTeamMember(team, user.ID) if err != nil { log.Error("LDAP group sync: Could not remove user from team: %v", err) } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index b7ea1f0584..65efed78c1 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -10,9 +10,9 @@ import ( "sort" "strings" - "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" user_service "code.gitea.io/gitea/services/user" @@ -62,8 +62,8 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { }) userPos := 0 - orgCache := make(map[string]*models.Organization) - teamCache := make(map[string]*models.Team) + orgCache := make(map[string]*organization.Organization) + teamCache := make(map[string]*organization.Team) for _, su := range sr { select { diff --git a/services/issue/assignee.go b/services/issue/assignee.go index f09c51293b..86d653dfa6 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -6,6 +6,8 @@ package issue import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -166,7 +168,7 @@ func IsValidReviewRequest(reviewer, doer *user_model.User, isAdd bool, issue *mo } // IsValidTeamReviewRequest Check permission for ReviewRequest Team -func IsValidTeamReviewRequest(reviewer *models.Team, doer *user_model.User, isAdd bool, issue *models.Issue) error { +func IsValidTeamReviewRequest(reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *models.Issue) error { if doer.IsOrganization() { return models.ErrNotValidReviewRequest{ Reason: "Organization can't be doer to add reviewer", @@ -183,7 +185,7 @@ func IsValidTeamReviewRequest(reviewer *models.Team, doer *user_model.User, isAd if isAdd { if issue.Repo.IsPrivate { - hasTeam := models.HasTeamRepo(reviewer.OrgID, reviewer.ID, issue.RepoID) + hasTeam := organization.HasTeamRepo(db.DefaultContext, reviewer.OrgID, reviewer.ID, issue.RepoID) if !hasTeam { return models.ErrNotValidReviewRequest{ @@ -221,7 +223,7 @@ func IsValidTeamReviewRequest(reviewer *models.Team, doer *user_model.User, isAd } // TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it. -func TeamReviewRequest(issue *models.Issue, doer *user_model.User, reviewer *models.Team, isAdd bool) (comment *models.Comment, err error) { +func TeamReviewRequest(issue *models.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *models.Comment, err error) { if isAdd { comment, err = models.AddTeamReviewRequest(issue, reviewer, doer) } else { @@ -241,11 +243,14 @@ func TeamReviewRequest(issue *models.Issue, doer *user_model.User, reviewer *mod return } - if err = reviewer.GetMembers(&models.SearchMembersOptions{}); err != nil { + members, err := organization.GetTeamMembers(db.DefaultContext, &organization.SearchMembersOptions{ + TeamID: reviewer.ID, + }) + if err != nil { return } - for _, member := range reviewer.Members { + for _, member := range members { if member.ID == comment.Issue.PosterID { continue } diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 0abc666f1a..7f856f2d40 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -8,7 +8,8 @@ import ( "bytes" "fmt" - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" @@ -24,7 +25,7 @@ func SendRepoTransferNotifyMail(doer, newOwner *user_model.User, repo *repo_mode } if newOwner.IsOrganization() { - users, err := models.GetUsersWhoCanCreateOrgRepo(newOwner.ID) + users, err := organization.GetUsersWhoCanCreateOrgRepo(db.DefaultContext, newOwner.ID) if err != nil { return err } diff --git a/services/org/org.go b/services/org/org.go index 4c0cfe87fc..da7a71fec5 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/storage" @@ -16,7 +17,7 @@ import ( ) // DeleteOrganization completely and permanently deletes everything of organization. -func DeleteOrganization(org *models.Organization) error { +func DeleteOrganization(org *organization.Organization) error { ctx, commiter, err := db.TxContext() if err != nil { return err @@ -31,7 +32,7 @@ func DeleteOrganization(org *models.Organization) error { return models.ErrUserOwnRepos{UID: org.ID} } - if err := models.DeleteOrganization(ctx, org); err != nil { + if err := organization.DeleteOrganization(ctx, org); err != nil { return fmt.Errorf("DeleteOrganization: %v", err) } diff --git a/services/org/org_test.go b/services/org/org_test.go index aaa2756bb5..b30bfddbb4 100644 --- a/services/org/org_test.go +++ b/services/org/org_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -21,18 +22,18 @@ func TestMain(m *testing.M) { func TestDeleteOrganization(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &models.Organization{ID: 6}).(*models.Organization) + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}).(*organization.Organization) assert.NoError(t, DeleteOrganization(org)) - unittest.AssertNotExistsBean(t, &models.Organization{ID: 6}) - unittest.AssertNotExistsBean(t, &models.OrgUser{OrgID: 6}) - unittest.AssertNotExistsBean(t, &models.Team{OrgID: 6}) + unittest.AssertNotExistsBean(t, &organization.Organization{ID: 6}) + unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: 6}) + unittest.AssertNotExistsBean(t, &organization.Team{OrgID: 6}) - org = unittest.AssertExistsAndLoadBean(t, &models.Organization{ID: 3}).(*models.Organization) + org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization) err := DeleteOrganization(org) assert.Error(t, err) assert.True(t, models.IsErrUserOwnRepos(err)) - user := unittest.AssertExistsAndLoadBean(t, &models.Organization{ID: 5}).(*models.Organization) + user := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 5}).(*organization.Organization) assert.Error(t, DeleteOrganization(user)) - unittest.CheckConsistencyFor(t, &user_model.User{}, &models.Team{}) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) } diff --git a/services/repository/repository.go b/services/repository/repository.go index c3ca867187..5886b96ab8 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -9,6 +9,7 @@ import ( "fmt" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -50,7 +51,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod func PushCreateRepo(authUser, owner *user_model.User, repoName string) (*repo_model.Repository, error) { if !authUser.IsAdmin { if owner.IsOrganization() { - if ok, err := models.CanCreateOrgRepo(owner.ID, authUser.ID); err != nil { + if ok, err := organization.CanCreateOrgRepo(owner.ID, authUser.ID); err != nil { return nil, err } else if !ok { return nil, fmt.Errorf("cannot push-create repository for org") diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 1e8b78dfc6..0abb03a88d 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -21,7 +22,7 @@ import ( var repoWorkingPool = sync.NewExclusivePool() // TransferOwnership transfers all corresponding setting from old user to new one. -func TransferOwnership(doer, newOwner *user_model.User, repo *repo_model.Repository, teams []*models.Team) error { +func TransferOwnership(doer, newOwner *user_model.User, repo *repo_model.Repository, teams []*organization.Team) error { if err := repo.GetOwner(db.DefaultContext); err != nil { return err } @@ -46,7 +47,7 @@ func TransferOwnership(doer, newOwner *user_model.User, repo *repo_model.Reposit } for _, team := range teams { - if err := team.AddRepository(newRepo); err != nil { + if err := models.AddRepository(team, newRepo); err != nil { return err } } @@ -81,7 +82,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *repo_model.Repository, ne // StartRepositoryTransfer transfer a repo from one owner to a new one. // it make repository into pending transfer state, if doer can not create repo for new owner. -func StartRepositoryTransfer(doer, newOwner *user_model.User, repo *repo_model.Repository, teams []*models.Team) error { +func StartRepositoryTransfer(doer, newOwner *user_model.User, repo *repo_model.Repository, teams []*organization.Team) error { if err := models.TestRepositoryReadyForTransfer(repo.Status); err != nil { return err } @@ -93,7 +94,7 @@ func StartRepositoryTransfer(doer, newOwner *user_model.User, repo *repo_model.R // If new owner is an org and user can create repos he can transfer directly too if newOwner.IsOrganization() { - allowed, err := models.CanCreateOrgRepo(newOwner.ID, doer.ID) + allowed, err := organization.CanCreateOrgRepo(newOwner.ID, doer.ID) if err != nil { return err } diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index d4fbe95834..1081c76c7e 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -53,7 +54,7 @@ func TestTransferOwnership(t *testing.T) { Content: "user3/repo3", }) - unittest.CheckConsistencyFor(t, &repo_model.Repository{}, &user_model.User{}, &models.Team{}) + unittest.CheckConsistencyFor(t, &repo_model.Repository{}, &user_model.User{}, &organization.Team{}) } func TestStartRepositoryTransferSetPermission(t *testing.T) { @@ -74,5 +75,5 @@ func TestStartRepositoryTransferSetPermission(t *testing.T) { assert.NoError(t, err) assert.True(t, hasAccess) - unittest.CheckConsistencyFor(t, &repo_model.Repository{}, &user_model.User{}, &models.Team{}) + unittest.CheckConsistencyFor(t, &repo_model.Repository{}, &user_model.User{}, &organization.Team{}) } diff --git a/services/user/user.go b/services/user/user.go index e31b174c0c..f88c0df93d 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -16,6 +16,7 @@ import ( admin_model "code.gitea.io/gitea/models/admin" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/avatar" @@ -50,7 +51,7 @@ func DeleteUser(u *user_model.User) error { } // Check membership of organization. - count, err = models.GetOrganizationCount(ctx, u) + count, err = organization.GetOrganizationCount(ctx, u) if err != nil { return fmt.Errorf("GetOrganizationCount: %v", err) } else if count > 0 { diff --git a/services/user/user_test.go b/services/user/user_test.go index 5b35db790c..7835530394 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -36,11 +37,11 @@ func TestDeleteUser(t *testing.T) { return } - orgUsers := make([]*models.OrgUser, 0, 10) - assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &models.OrgUser{UID: userID})) + orgUsers := make([]*organization.OrgUser, 0, 10) + assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &organization.OrgUser{UID: userID})) for _, orgUser := range orgUsers { if err := models.RemoveOrgUser(orgUser.OrgID, orgUser.UID); err != nil { - assert.True(t, models.IsErrLastOrgOwner(err)) + assert.True(t, organization.IsErrLastOrgOwner(err)) return } }