mirror of https://github.com/go-gitea/gitea.git
Move team related functions to service layer (#32537)
There are still some functions under `models` after last big refactor about `models`. This change will move all team related functions to service layer with no code change.pull/32661/head
parent
5a50b271e7
commit
f62f68cbdd
@ -1,27 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package org
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TeamAddRepository adds new repository to team of organization.
|
|
||||||
func TeamAddRepository(ctx context.Context, 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 organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
||||||
return models.AddRepository(ctx, t, repo)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package models
|
package org
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -1,148 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIncludesAllRepositoriesTeams(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
testTeamRepositories := func(teamID int64, repoIDs []int64) {
|
|
||||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
|
||||||
assert.NoError(t, team.LoadRepositories(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, HasRepository(db.DefaultContext, team, rid), "%s: HasRepository(%d) %d", rid, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an admin user.
|
|
||||||
user, err := user_model.GetUserByID(db.DefaultContext, 1)
|
|
||||||
assert.NoError(t, err, "GetUserByID")
|
|
||||||
|
|
||||||
// Create org.
|
|
||||||
org := &organization.Organization{
|
|
||||||
Name: "All_repo",
|
|
||||||
IsActive: true,
|
|
||||||
Type: user_model.UserTypeOrganization,
|
|
||||||
Visibility: structs.VisibleTypePublic,
|
|
||||||
}
|
|
||||||
assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, user), "CreateOrganization")
|
|
||||||
|
|
||||||
// Check Owner team.
|
|
||||||
ownerTeam, err := org.GetOwnerTeam(db.DefaultContext)
|
|
||||||
assert.NoError(t, err, "GetOwnerTeam")
|
|
||||||
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
|
|
||||||
|
|
||||||
// Create repos.
|
|
||||||
repoIDs := make([]int64, 0)
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
r, err := CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
|
|
||||||
assert.NoError(t, err, "CreateRepository %d", i)
|
|
||||||
if r != nil {
|
|
||||||
repoIDs = append(repoIDs, r.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get fresh copy of Owner team after creating repos.
|
|
||||||
ownerTeam, err = org.GetOwnerTeam(db.DefaultContext)
|
|
||||||
assert.NoError(t, err, "GetOwnerTeam")
|
|
||||||
|
|
||||||
// Create teams and check repositories.
|
|
||||||
teams := []*organization.Team{
|
|
||||||
ownerTeam,
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team one",
|
|
||||||
AccessMode: perm.AccessModeRead,
|
|
||||||
IncludesAllRepositories: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team 2",
|
|
||||||
AccessMode: perm.AccessModeRead,
|
|
||||||
IncludesAllRepositories: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team three",
|
|
||||||
AccessMode: perm.AccessModeWrite,
|
|
||||||
IncludesAllRepositories: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team 4",
|
|
||||||
AccessMode: perm.AccessModeWrite,
|
|
||||||
IncludesAllRepositories: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
teamRepos := [][]int64{
|
|
||||||
repoIDs,
|
|
||||||
repoIDs,
|
|
||||||
{},
|
|
||||||
repoIDs,
|
|
||||||
{},
|
|
||||||
}
|
|
||||||
for i, team := range teams {
|
|
||||||
if i > 0 { // first team is Owner.
|
|
||||||
assert.NoError(t, models.NewTeam(db.DefaultContext, team), "%s: NewTeam", team.Name)
|
|
||||||
}
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update teams and check repositories.
|
|
||||||
teams[3].IncludesAllRepositories = false
|
|
||||||
teams[4].IncludesAllRepositories = true
|
|
||||||
teamRepos[4] = repoIDs
|
|
||||||
for i, team := range teams {
|
|
||||||
assert.NoError(t, models.UpdateTeam(db.DefaultContext, team, false, true), "%s: UpdateTeam", team.Name)
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create repo and check teams repositories.
|
|
||||||
r, err := CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), CreateRepoOptions{Name: "repo-last"})
|
|
||||||
assert.NoError(t, err, "CreateRepository last")
|
|
||||||
if r != nil {
|
|
||||||
repoIDs = append(repoIDs, r.ID)
|
|
||||||
}
|
|
||||||
teamRepos[0] = repoIDs
|
|
||||||
teamRepos[1] = repoIDs
|
|
||||||
teamRepos[4] = repoIDs
|
|
||||||
for i, team := range teams {
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove repo and check teams repositories.
|
|
||||||
assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, repoIDs[0]), "DeleteRepository")
|
|
||||||
teamRepos[0] = repoIDs[1:]
|
|
||||||
teamRepos[1] = repoIDs[1:]
|
|
||||||
teamRepos[3] = repoIDs[1:3]
|
|
||||||
teamRepos[4] = repoIDs[1:]
|
|
||||||
for i, team := range teams {
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wipe created items.
|
|
||||||
for i, rid := range repoIDs {
|
|
||||||
if i > 0 { // first repo already deleted.
|
|
||||||
assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, rid), "DeleteRepository %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization")
|
|
||||||
}
|
|
||||||
@ -0,0 +1,226 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TeamAddRepository adds new repository to team of organization.
|
||||||
|
func TeamAddRepository(ctx context.Context, 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 organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
return addRepositoryToTeam(ctx, t, repo)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRepositoryToTeam(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 = organization.IncrTeamRepoNum(ctx, t.ID); err != nil {
|
||||||
|
return fmt.Errorf("update team: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumRepos++
|
||||||
|
|
||||||
|
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
|
||||||
|
return fmt.Errorf("recalculateAccesses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make all team members watch this repo if enabled in global settings
|
||||||
|
if setting.Service.AutoWatchNewRepos {
|
||||||
|
if err = t.LoadMembers(ctx); err != nil {
|
||||||
|
return fmt.Errorf("getMembers: %w", err)
|
||||||
|
}
|
||||||
|
for _, u := range t.Members {
|
||||||
|
if err = repo_model.WatchRepo(ctx, u, repo, true); err != nil {
|
||||||
|
return fmt.Errorf("watchRepo: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAllRepositoriesToTeam adds all repositories to the team.
|
||||||
|
// If the team already has some repositories they will be left unchanged.
|
||||||
|
func AddAllRepositoriesToTeam(ctx context.Context, t *organization.Team) error {
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
orgRepos, err := organization.GetOrgRepositories(ctx, t.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get org repos: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range orgRepos {
|
||||||
|
if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
|
||||||
|
if err := addRepositoryToTeam(ctx, t, repo); err != nil {
|
||||||
|
return fmt.Errorf("AddRepository: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllRepositoriesFromTeam removes all repositories from team and recalculates access
|
||||||
|
func RemoveAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
if err = removeAllRepositoriesFromTeam(ctx, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeAllRepositoriesFromTeam removes all repositories from team and recalculates access
|
||||||
|
// Note: Shall not be called if team includes all repositories
|
||||||
|
func removeAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||||
|
e := db.GetEngine(ctx)
|
||||||
|
// Delete all accesses.
|
||||||
|
for _, repo := range t.Repos {
|
||||||
|
if err := access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove watches from all users and now unaccessible repos
|
||||||
|
for _, user := range t.Members {
|
||||||
|
has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err = issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete team-repo
|
||||||
|
if _, err := e.
|
||||||
|
Where("team_id=?", t.ID).
|
||||||
|
Delete(new(organization.TeamRepo)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumRepos = 0
|
||||||
|
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRepositoryFromTeam removes repository from team of organization.
|
||||||
|
// If the team shall include all repositories the request is ignored.
|
||||||
|
func RemoveRepositoryFromTeam(ctx context.Context, t *organization.Team, repoID int64) error {
|
||||||
|
if !HasRepository(ctx, t, repoID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
if err = removeRepositoryFromTeam(ctx, t, repo, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeRepositoryFromTeam 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 removeRepositoryFromTeam(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) {
|
||||||
|
e := db.GetEngine(ctx)
|
||||||
|
if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumRepos--
|
||||||
|
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't need to recalculate when delete a repository from organization.
|
||||||
|
if recalculate {
|
||||||
|
if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
|
||||||
|
TeamID: t.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetTeamMembers: %w", err)
|
||||||
|
}
|
||||||
|
for _, member := range teamMembers {
|
||||||
|
has, err := access_model.HasAnyUnitAccess(ctx, member.ID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = repo_model.WatchRepo(ctx, member, repo, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err := issues_model.RemoveIssueWatchersByRepoID(ctx, member.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasRepository returns true if given repository belong to team.
|
||||||
|
func HasRepository(ctx context.Context, t *organization.Team, repoID int64) bool {
|
||||||
|
return organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID)
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package org
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
Loading…
Reference in New Issue