mirror of https://github.com/go-gitea/gitea
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.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package models |
||||
package org |
||||
|
||||
import ( |
||||
"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.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org |
||||
package repository |
||||
|
||||
import ( |
||||
"testing" |
Loading…
Reference in new issue