Skip to content

Commit

Permalink
Support org/user level projects (#22235)
Browse files Browse the repository at this point in the history
Fix #13405

<img width="1151" alt="image"
src="https://user-images.githubusercontent.com/81045/209442911-7baa3924-c389-47b6-b63b-a740803e640e.png">

Co-authored-by: 6543 <6543@obermui.de>
  • Loading branch information
lunny and 6543 committed Jan 20, 2023
1 parent 0c048e5 commit 6fe3c8b
Show file tree
Hide file tree
Showing 30 changed files with 1,556 additions and 176 deletions.
9 changes: 9 additions & 0 deletions models/fixtures/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,12 @@
creator_id: 5
board_type: 1
type: 2

-
id: 4
title: project on user2
owner_id: 2
is_closed: false
creator_id: 2
board_type: 1
type: 2
8 changes: 8 additions & 0 deletions models/fixtures/project_board.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@
creator_id: 2
created_unix: 1588117528
updated_unix: 1588117528

-
id: 4
project_id: 4
title: Done
creator_id: 2
created_unix: 1588117528
updated_unix: 1588117528
2 changes: 1 addition & 1 deletion models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1098,7 +1098,7 @@ func GetIssueWithAttrsByID(id int64) (*Issue, error) {
}

// GetIssuesByIDs return issues with the given IDs.
func GetIssuesByIDs(ctx context.Context, issueIDs []int64) ([]*Issue, error) {
func GetIssuesByIDs(ctx context.Context, issueIDs []int64) (IssueList, error) {
issues := make([]*Issue, 0, 10)
return issues, db.GetEngine(ctx).In("id", issueIDs).Find(&issues)
}
Expand Down
10 changes: 5 additions & 5 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,17 @@ func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
oldProjectID := issue.projectID(ctx)

if err := issue.LoadRepo(ctx); err != nil {
return err
}

// Only check if we add a new project and not remove it.
if newProjectID > 0 {
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
if err != nil {
return err
}
if newProject.RepoID != issue.RepoID {
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
return fmt.Errorf("issue's repository is not the same as project's repository")
}
}
Expand All @@ -140,10 +144,6 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
return err
}

if err := issue.LoadRepo(ctx); err != nil {
return err
}

if oldProjectID > 0 || newProjectID > 0 {
if _, err := CreateComment(ctx, &CreateCommentOptions{
Type: CommentTypeProject,
Expand Down
65 changes: 0 additions & 65 deletions models/organization/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
)

// ___________
Expand Down Expand Up @@ -96,59 +94,6 @@ func init() {
db.RegisterModel(new(TeamInvite))
}

// SearchTeamOptions holds the search options
type SearchTeamOptions struct {
db.ListOptions
UserID int64
Keyword string
OrgID int64
IncludeDesc bool
}

func (opts *SearchTeamOptions) toCond() builder.Cond {
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)
}

if opts.OrgID > 0 {
cond = cond.And(builder.Eq{"`team`.org_id": opts.OrgID})
}

if opts.UserID > 0 {
cond = cond.And(builder.Eq{"team_user.uid": opts.UserID})
}

return cond
}

// SearchTeam search for teams. Caller is responsible to check permissions.
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
sess := db.GetEngine(db.DefaultContext)

opts.SetDefaultValues()
cond := opts.toCond()

if opts.UserID > 0 {
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
}
sess = db.SetSessionPagination(sess, opts)

teams := make([]*Team, 0, opts.PageSize)
count, err := sess.Where(cond).OrderBy("lower_name").FindAndCount(&teams)
if 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 {
Expand Down Expand Up @@ -335,16 +280,6 @@ func GetTeamNamesByID(teamIDs []int64) ([]string, error) {
return teamNames, err
}

// GetRepoTeams gets the list of teams that has access to the repository
func GetRepoTeams(ctx context.Context, repo *repo_model.Repository) (teams []*Team, err error) {
return teams, db.GetEngine(ctx).
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)
}

// IncrTeamRepoNum increases the number of repos for the given team by 1
func IncrTeamRepoNum(ctx context.Context, teamID int64) error {
_, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team))
Expand Down
128 changes: 128 additions & 0 deletions models/organization/team_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package organization

import (
"context"
"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"

"xorm.io/builder"
)

type TeamList []*Team

func (t TeamList) LoadUnits(ctx context.Context) error {
for _, team := range t {
if err := team.getUnits(ctx); err != nil {
return err
}
}
return nil
}

func (t TeamList) UnitMaxAccess(tp unit.Type) perm.AccessMode {
maxAccess := perm.AccessModeNone
for _, team := range t {
if team.IsOwnerTeam() {
return perm.AccessModeOwner
}
for _, teamUnit := range team.Units {
if teamUnit.Type != tp {
continue
}
if teamUnit.AccessMode > maxAccess {
maxAccess = teamUnit.AccessMode
}
}
}
return maxAccess
}

// SearchTeamOptions holds the search options
type SearchTeamOptions struct {
db.ListOptions
UserID int64
Keyword string
OrgID int64
IncludeDesc bool
}

func (opts *SearchTeamOptions) toCond() builder.Cond {
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)
}

if opts.OrgID > 0 {
cond = cond.And(builder.Eq{"`team`.org_id": opts.OrgID})
}

if opts.UserID > 0 {
cond = cond.And(builder.Eq{"team_user.uid": opts.UserID})
}

return cond
}

// SearchTeam search for teams. Caller is responsible to check permissions.
func SearchTeam(opts *SearchTeamOptions) (TeamList, int64, error) {
sess := db.GetEngine(db.DefaultContext)

opts.SetDefaultValues()
cond := opts.toCond()

if opts.UserID > 0 {
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
}
sess = db.SetSessionPagination(sess, opts)

teams := make([]*Team, 0, opts.PageSize)
count, err := sess.Where(cond).OrderBy("lower_name").FindAndCount(&teams)
if err != nil {
return nil, 0, err
}

return teams, count, nil
}

// GetRepoTeams gets the list of teams that has access to the repository
func GetRepoTeams(ctx context.Context, repo *repo_model.Repository) (teams TeamList, err error) {
return teams, db.GetEngine(ctx).
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)
}

// GetUserOrgTeams returns all teams that user belongs to in given organization.
func GetUserOrgTeams(ctx context.Context, orgID, userID int64) (teams TeamList, 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 TeamList, 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)
}
20 changes: 0 additions & 20 deletions models/organization/team_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,6 @@ func GetTeamMembers(ctx context.Context, opts *SearchMembersOptions) ([]*user_mo
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))
Expand Down
Loading

0 comments on commit 6fe3c8b

Please sign in to comment.