@@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) { | |||||
expectedResults | expectedResults | ||||
}{ | }{ | ||||
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{ | {name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{ | ||||
nil: {count: 15}, | |||||
user: {count: 15}, | |||||
user2: {count: 15}}, | |||||
nil: {count: 16}, | |||||
user: {count: 16}, | |||||
user2: {count: 16}}, | |||||
}, | }, | ||||
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{ | {name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{ | ||||
nil: {count: 10}, | nil: {count: 10}, | ||||
@@ -22,8 +22,12 @@ func TestAccessLevel(t *testing.T) { | |||||
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | ||||
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository) | |||||
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository) | |||||
// A public repository owned by User 2 | |||||
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||||
assert.False(t, repo1.IsPrivate) | |||||
// A private repository owned by Org 3 | |||||
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | |||||
assert.True(t, repo2.IsPrivate) | |||||
level, err := AccessLevel(user1.ID, repo1) | level, err := AccessLevel(user1.ID, repo1) | ||||
assert.NoError(t, err) | assert.NoError(t, err) | ||||
@@ -47,8 +51,12 @@ func TestHasAccess(t *testing.T) { | |||||
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | ||||
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository) | |||||
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository) | |||||
// A public repository owned by User 2 | |||||
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||||
assert.False(t, repo1.IsPrivate) | |||||
// A private repository owned by Org 3 | |||||
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | |||||
assert.True(t, repo2.IsPrivate) | |||||
for _, accessMode := range accessModes { | for _, accessMode := range accessModes { | ||||
has, err := HasAccess(user1.ID, repo1, accessMode) | has, err := HasAccess(user1.ID, repo1, accessMode) | ||||
@@ -351,7 +351,7 @@ | |||||
is_mirror: true | is_mirror: true | ||||
num_forks: 1 | num_forks: 1 | ||||
is_fork: false | is_fork: false | ||||
- | - | ||||
id: 29 | id: 29 | ||||
fork_id: 27 | fork_id: 27 | ||||
@@ -365,7 +365,7 @@ | |||||
num_closed_pulls: 0 | num_closed_pulls: 0 | ||||
is_mirror: false | is_mirror: false | ||||
is_fork: true | is_fork: true | ||||
- | - | ||||
id: 30 | id: 30 | ||||
fork_id: 28 | fork_id: 28 | ||||
@@ -389,3 +389,14 @@ | |||||
num_forks: 0 | num_forks: 0 | ||||
num_issues: 0 | num_issues: 0 | ||||
is_mirror: false | is_mirror: false | ||||
- | |||||
id: 32 | |||||
owner_id: 3 | |||||
lower_name: repo21 | |||||
name: repo21 | |||||
is_private: false | |||||
num_stars: 0 | |||||
num_forks: 0 | |||||
num_issues: 0 | |||||
is_mirror: false |
@@ -4,9 +4,8 @@ | |||||
lower_name: owners | lower_name: owners | ||||
name: Owners | name: Owners | ||||
authorize: 4 # owner | authorize: 4 # owner | ||||
num_repos: 2 | |||||
num_repos: 3 | |||||
num_members: 1 | num_members: 1 | ||||
unit_types: '[1,2,3,4,5,6,7]' | |||||
- | - | ||||
id: 2 | id: 2 | ||||
@@ -16,7 +15,6 @@ | |||||
authorize: 2 # write | authorize: 2 # write | ||||
num_repos: 1 | num_repos: 1 | ||||
num_members: 2 | num_members: 2 | ||||
unit_types: '[1,2,3,4,5,6,7]' | |||||
- | - | ||||
id: 3 | id: 3 | ||||
@@ -26,7 +24,6 @@ | |||||
authorize: 4 # owner | authorize: 4 # owner | ||||
num_repos: 0 | num_repos: 0 | ||||
num_members: 1 | num_members: 1 | ||||
unit_types: '[1,2,3,4,5,6,7]' | |||||
- | - | ||||
id: 4 | id: 4 | ||||
@@ -36,7 +33,6 @@ | |||||
authorize: 4 # owner | authorize: 4 # owner | ||||
num_repos: 0 | num_repos: 0 | ||||
num_members: 1 | num_members: 1 | ||||
unit_types: '[1,2,3,4,5,6,7]' | |||||
- | - | ||||
id: 5 | id: 5 | ||||
@@ -46,7 +42,6 @@ | |||||
authorize: 4 # owner | authorize: 4 # owner | ||||
num_repos: 2 | num_repos: 2 | ||||
num_members: 2 | num_members: 2 | ||||
unit_types: '[1,2,3,4,5,6,7]' | |||||
- | - | ||||
id: 6 | id: 6 | ||||
@@ -56,4 +51,3 @@ | |||||
authorize: 4 # owner | authorize: 4 # owner | ||||
num_repos: 2 | num_repos: 2 | ||||
num_members: 1 | num_members: 1 | ||||
unit_types: '[1,2,3,4,5,6,7]' |
@@ -33,9 +33,15 @@ | |||||
org_id: 19 | org_id: 19 | ||||
team_id: 6 | team_id: 6 | ||||
repo_id: 27 | repo_id: 27 | ||||
- | - | ||||
id: 7 | id: 7 | ||||
org_id: 19 | org_id: 19 | ||||
team_id: 6 | team_id: 6 | ||||
repo_id: 28 | |||||
repo_id: 28 | |||||
- | |||||
id: 8 | |||||
org_id: 3 | |||||
team_id: 1 | |||||
repo_id: 32 |
@@ -0,0 +1,209 @@ | |||||
- | |||||
id: 1 | |||||
team_id: 1 | |||||
type: 1 | |||||
- | |||||
id: 2 | |||||
team_id: 1 | |||||
type: 2 | |||||
- | |||||
id: 3 | |||||
team_id: 1 | |||||
type: 3 | |||||
- | |||||
id: 4 | |||||
team_id: 1 | |||||
type: 4 | |||||
- | |||||
id: 5 | |||||
team_id: 1 | |||||
type: 5 | |||||
- | |||||
id: 6 | |||||
team_id: 1 | |||||
type: 6 | |||||
- | |||||
id: 7 | |||||
team_id: 1 | |||||
type: 7 | |||||
- | |||||
id: 8 | |||||
team_id: 2 | |||||
type: 1 | |||||
- | |||||
id: 9 | |||||
team_id: 2 | |||||
type: 2 | |||||
- | |||||
id: 10 | |||||
team_id: 2 | |||||
type: 3 | |||||
- | |||||
id: 11 | |||||
team_id: 2 | |||||
type: 4 | |||||
- | |||||
id: 12 | |||||
team_id: 2 | |||||
type: 5 | |||||
- | |||||
id: 13 | |||||
team_id: 2 | |||||
type: 6 | |||||
- | |||||
id: 14 | |||||
team_id: 2 | |||||
type: 7 | |||||
- | |||||
id: 15 | |||||
team_id: 3 | |||||
type: 1 | |||||
- | |||||
id: 16 | |||||
team_id: 3 | |||||
type: 2 | |||||
- | |||||
id: 17 | |||||
team_id: 3 | |||||
type: 3 | |||||
- | |||||
id: 18 | |||||
team_id: 3 | |||||
type: 4 | |||||
- | |||||
id: 19 | |||||
team_id: 3 | |||||
type: 5 | |||||
- | |||||
id: 20 | |||||
team_id: 3 | |||||
type: 6 | |||||
- | |||||
id: 21 | |||||
team_id: 3 | |||||
type: 7 | |||||
- | |||||
id: 22 | |||||
team_id: 4 | |||||
type: 1 | |||||
- | |||||
id: 23 | |||||
team_id: 4 | |||||
type: 2 | |||||
- | |||||
id: 24 | |||||
team_id: 4 | |||||
type: 3 | |||||
- | |||||
id: 25 | |||||
team_id: 4 | |||||
type: 4 | |||||
- | |||||
id: 26 | |||||
team_id: 4 | |||||
type: 5 | |||||
- | |||||
id: 27 | |||||
team_id: 4 | |||||
type: 6 | |||||
- | |||||
id: 28 | |||||
team_id: 4 | |||||
type: 7 | |||||
- | |||||
id: 29 | |||||
team_id: 5 | |||||
type: 1 | |||||
- | |||||
id: 30 | |||||
team_id: 5 | |||||
type: 2 | |||||
- | |||||
id: 31 | |||||
team_id: 5 | |||||
type: 3 | |||||
- | |||||
id: 32 | |||||
team_id: 5 | |||||
type: 4 | |||||
- | |||||
id: 33 | |||||
team_id: 5 | |||||
type: 5 | |||||
- | |||||
id: 34 | |||||
team_id: 5 | |||||
type: 6 | |||||
- | |||||
id: 35 | |||||
team_id: 5 | |||||
type: 7 | |||||
- | |||||
id: 36 | |||||
team_id: 6 | |||||
type: 1 | |||||
- | |||||
id: 37 | |||||
team_id: 6 | |||||
type: 2 | |||||
- | |||||
id: 38 | |||||
team_id: 6 | |||||
type: 3 | |||||
- | |||||
id: 39 | |||||
team_id: 6 | |||||
type: 4 | |||||
- | |||||
id: 40 | |||||
team_id: 6 | |||||
type: 5 | |||||
- | |||||
id: 41 | |||||
team_id: 6 | |||||
type: 6 | |||||
- | |||||
id: 42 | |||||
team_id: 6 | |||||
type: 7 |
@@ -45,7 +45,7 @@ | |||||
is_admin: false | is_admin: false | ||||
avatar: avatar3 | avatar: avatar3 | ||||
avatar_email: user3@example.com | avatar_email: user3@example.com | ||||
num_repos: 2 | |||||
num_repos: 3 | |||||
num_members: 2 | num_members: 2 | ||||
num_teams: 2 | num_teams: 2 | ||||
@@ -190,6 +190,8 @@ var migrations = []Migration{ | |||||
NewMigration("remove stale watches", removeStaleWatches), | NewMigration("remove stale watches", removeStaleWatches), | ||||
// v68 -> V69 | // v68 -> V69 | ||||
NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics), | NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics), | ||||
// v69 -> v70 | |||||
NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), | |||||
} | } | ||||
// Migrate database to current version | // Migrate database to current version | ||||
@@ -25,10 +25,15 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) { | |||||
Created time.Time `xorm:"-"` | Created time.Time `xorm:"-"` | ||||
} | } | ||||
type Team struct { | |||||
ID int64 | |||||
UnitTypes []int `xorm:"json"` | |||||
} | |||||
// Update team unit types | // Update team unit types | ||||
const batchSize = 100 | const batchSize = 100 | ||||
for start := 0; ; start += batchSize { | for start := 0; ; start += batchSize { | ||||
teams := make([]*models.Team, 0, batchSize) | |||||
teams := make([]*Team, 0, batchSize) | |||||
if err := x.Limit(batchSize, start).Find(&teams); err != nil { | if err := x.Limit(batchSize, start).Find(&teams); err != nil { | ||||
return err | return err | ||||
} | } | ||||
@@ -36,7 +41,7 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) { | |||||
break | break | ||||
} | } | ||||
for _, team := range teams { | for _, team := range teams { | ||||
ut := make([]models.UnitType, 0, len(team.UnitTypes)) | |||||
ut := make([]int, 0, len(team.UnitTypes)) | |||||
for _, u := range team.UnitTypes { | for _, u := range team.UnitTypes { | ||||
if u < V16UnitTypeCommits { | if u < V16UnitTypeCommits { | ||||
ut = append(ut, u) | ut = append(ut, u) | ||||
@@ -0,0 +1,80 @@ | |||||
// Copyright 2018 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 migrations | |||||
import ( | |||||
"fmt" | |||||
"github.com/go-xorm/xorm" | |||||
) | |||||
func moveTeamUnitsToTeamUnitTable(x *xorm.Engine) error { | |||||
// Team see models/team.go | |||||
type Team struct { | |||||
ID int64 | |||||
OrgID int64 | |||||
UnitTypes []int `xorm:"json"` | |||||
} | |||||
// TeamUnit see models/org_team.go | |||||
type TeamUnit struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
OrgID int64 `xorm:"INDEX"` | |||||
TeamID int64 `xorm:"UNIQUE(s)"` | |||||
Type int `xorm:"UNIQUE(s)"` | |||||
} | |||||
if err := x.Sync2(new(TeamUnit)); err != nil { | |||||
return fmt.Errorf("Sync2: %v", err) | |||||
} | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
if err := sess.Begin(); err != nil { | |||||
return err | |||||
} | |||||
// Update team unit types | |||||
const batchSize = 100 | |||||
for start := 0; ; start += batchSize { | |||||
teams := make([]*Team, 0, batchSize) | |||||
if err := x.Limit(batchSize, start).Find(&teams); err != nil { | |||||
return err | |||||
} | |||||
if len(teams) == 0 { | |||||
break | |||||
} | |||||
for _, team := range teams { | |||||
var unitTypes []int | |||||
if len(team.UnitTypes) == 0 { | |||||
unitTypes = allUnitTypes | |||||
} else { | |||||
unitTypes = team.UnitTypes | |||||
} | |||||
// insert units for team | |||||
var units = make([]TeamUnit, 0, len(unitTypes)) | |||||
for _, tp := range unitTypes { | |||||
units = append(units, TeamUnit{ | |||||
OrgID: team.OrgID, | |||||
TeamID: team.ID, | |||||
Type: tp, | |||||
}) | |||||
} | |||||
if _, err := sess.Insert(&units); err != nil { | |||||
return fmt.Errorf("Insert team units: %v", err) | |||||
} | |||||
} | |||||
} | |||||
if err := dropTableColumns(sess, "team", "unit_types"); err != nil { | |||||
return err | |||||
} | |||||
return sess.Commit() | |||||
} |
@@ -122,6 +122,7 @@ func init() { | |||||
new(Reaction), | new(Reaction), | ||||
new(IssueAssignees), | new(IssueAssignees), | ||||
new(U2FRegistration), | new(U2FRegistration), | ||||
new(TeamUnit), | |||||
) | ) | ||||
gonicNames := []string{"SSL", "UID"} | gonicNames := []string{"SSL", "UID"} | ||||
@@ -119,7 +119,17 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor | |||||
} | } | ||||
} | } | ||||
issue.loadRepo(e) | |||||
for _, watch := range watches { | for _, watch := range watches { | ||||
issue.Repo.Units = nil | |||||
if issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypePullRequests) { | |||||
continue | |||||
} | |||||
if !issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypeIssues) { | |||||
continue | |||||
} | |||||
if err := notifyUser(watch.UserID); err != nil { | if err := notifyUser(watch.UserID); err != nil { | ||||
return err | return err | ||||
} | } | ||||
@@ -154,12 +154,26 @@ func CreateOrganization(org, owner *User) (err error) { | |||||
Name: ownerTeamName, | Name: ownerTeamName, | ||||
Authorize: AccessModeOwner, | Authorize: AccessModeOwner, | ||||
NumMembers: 1, | NumMembers: 1, | ||||
UnitTypes: allRepUnitTypes, | |||||
} | } | ||||
if _, err = sess.Insert(t); err != nil { | if _, err = sess.Insert(t); err != nil { | ||||
return fmt.Errorf("insert owner team: %v", err) | return fmt.Errorf("insert owner team: %v", err) | ||||
} | } | ||||
// insert units for team | |||||
var units = make([]TeamUnit, 0, len(allRepUnitTypes)) | |||||
for _, tp := range allRepUnitTypes { | |||||
units = append(units, TeamUnit{ | |||||
OrgID: org.ID, | |||||
TeamID: t.ID, | |||||
Type: tp, | |||||
}) | |||||
} | |||||
if _, err = sess.Insert(&units); err != nil { | |||||
sess.Rollback() | |||||
return err | |||||
} | |||||
if _, err = sess.Insert(&TeamUser{ | if _, err = sess.Insert(&TeamUser{ | ||||
UID: owner.ID, | UID: owner.ID, | ||||
OrgID: org.ID, | OrgID: org.ID, | ||||
@@ -238,6 +252,7 @@ func deleteOrg(e *xorm.Session, u *User) error { | |||||
&Team{OrgID: u.ID}, | &Team{OrgID: u.ID}, | ||||
&OrgUser{OrgID: u.ID}, | &OrgUser{OrgID: u.ID}, | ||||
&TeamUser{OrgID: u.ID}, | &TeamUser{OrgID: u.ID}, | ||||
&TeamUnit{OrgID: u.ID}, | |||||
); err != nil { | ); err != nil { | ||||
return fmt.Errorf("deleteBeans: %v", err) | return fmt.Errorf("deleteBeans: %v", err) | ||||
} | } | ||||
@@ -1,3 +1,4 @@ | |||||
// Copyright 2018 The Gitea Authors. All rights reserved. | |||||
// Copyright 2016 The Gogs Authors. All rights reserved. | // Copyright 2016 The Gogs Authors. All rights reserved. | ||||
// Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||
// license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||
@@ -10,7 +11,6 @@ import ( | |||||
"strings" | "strings" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"github.com/go-xorm/xorm" | "github.com/go-xorm/xorm" | ||||
) | ) | ||||
@@ -28,15 +28,16 @@ type Team struct { | |||||
Members []*User `xorm:"-"` | Members []*User `xorm:"-"` | ||||
NumRepos int | NumRepos int | ||||
NumMembers int | NumMembers int | ||||
UnitTypes []UnitType `xorm:"json"` | |||||
Units []*TeamUnit `xorm:"-"` | |||||
} | } | ||||
// GetUnitTypes returns unit types the team owned, empty means all the unit types | |||||
func (t *Team) GetUnitTypes() []UnitType { | |||||
if len(t.UnitTypes) == 0 { | |||||
return allRepUnitTypes | |||||
func (t *Team) getUnits(e Engine) (err error) { | |||||
if t.Units != nil { | |||||
return nil | |||||
} | } | ||||
return t.UnitTypes | |||||
t.Units, err = getUnitsByTeamID(e, t.ID) | |||||
return err | |||||
} | } | ||||
// HasWriteAccess returns true if team has at least write level access mode. | // HasWriteAccess returns true if team has at least write level access mode. | ||||
@@ -214,11 +215,12 @@ func (t *Team) RemoveRepository(repoID int64) error { | |||||
// UnitEnabled returns if the team has the given unit type enabled | // UnitEnabled returns if the team has the given unit type enabled | ||||
func (t *Team) UnitEnabled(tp UnitType) bool { | func (t *Team) UnitEnabled(tp UnitType) bool { | ||||
if len(t.UnitTypes) == 0 { | |||||
return true | |||||
if err := t.getUnits(x); err != nil { | |||||
log.Warn("Error loading repository (ID: %d) units: %s", t.ID, err.Error()) | |||||
} | } | ||||
for _, u := range t.UnitTypes { | |||||
if u == tp { | |||||
for _, unit := range t.Units { | |||||
if unit.Type == tp { | |||||
return true | return true | ||||
} | } | ||||
} | } | ||||
@@ -275,6 +277,17 @@ func NewTeam(t *Team) (err error) { | |||||
return err | return err | ||||
} | } | ||||
// insert units for team | |||||
if len(t.Units) > 0 { | |||||
for _, unit := range t.Units { | |||||
unit.TeamID = t.ID | |||||
} | |||||
if _, err = sess.Insert(&t.Units); err != nil { | |||||
sess.Rollback() | |||||
return err | |||||
} | |||||
} | |||||
// Update organization number of teams. | // Update organization number of teams. | ||||
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { | if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil { | ||||
sess.Rollback() | sess.Rollback() | ||||
@@ -424,6 +437,13 @@ func DeleteTeam(t *Team) error { | |||||
return err | return err | ||||
} | } | ||||
// Delete team-unit. | |||||
if _, err := sess. | |||||
Where("team_id=?", t.ID). | |||||
Delete(new(TeamUnit)); err != nil { | |||||
return err | |||||
} | |||||
// Delete team. | // Delete team. | ||||
if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil { | if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil { | ||||
return err | return err | ||||
@@ -695,3 +715,47 @@ func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, er | |||||
And("team_repo.repo_id = ?", repoID). | And("team_repo.repo_id = ?", repoID). | ||||
Find(&teams) | 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 UnitType `xorm:"UNIQUE(s)"` | |||||
} | |||||
// Unit returns Unit | |||||
func (t *TeamUnit) Unit() Unit { | |||||
return Units[t.Type] | |||||
} | |||||
func getUnitsByTeamID(e 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) { | |||||
sess := x.NewSession() | |||||
defer sess.Close() | |||||
if err = sess.Begin(); err != nil { | |||||
return err | |||||
} | |||||
if _, err = sess.Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil { | |||||
return err | |||||
} | |||||
if _, err = sess.Insert(units); err != nil { | |||||
sess.Rollback() | |||||
return err | |||||
} | |||||
return sess.Commit() | |||||
} |
@@ -489,8 +489,8 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) { | |||||
assert.NoError(t, err) | assert.NoError(t, err) | ||||
assert.EqualValues(t, expectedCount, count) | assert.EqualValues(t, expectedCount, count) | ||||
} | } | ||||
testSuccess(2, 2) | |||||
testSuccess(4, 1) | |||||
testSuccess(2, 3) | |||||
testSuccess(4, 2) | |||||
} | } | ||||
func TestAccessibleReposEnv_RepoIDs(t *testing.T) { | func TestAccessibleReposEnv_RepoIDs(t *testing.T) { | ||||
@@ -503,8 +503,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) { | |||||
assert.NoError(t, err) | assert.NoError(t, err) | ||||
assert.Equal(t, expectedRepoIDs, repoIDs) | assert.Equal(t, expectedRepoIDs, repoIDs) | ||||
} | } | ||||
testSuccess(2, 1, 100, []int64{3, 5}) | |||||
testSuccess(4, 0, 100, []int64{3}) | |||||
testSuccess(2, 1, 100, []int64{3, 5, 32}) | |||||
testSuccess(4, 0, 100, []int64{3, 32}) | |||||
} | } | ||||
func TestAccessibleReposEnv_Repos(t *testing.T) { | func TestAccessibleReposEnv_Repos(t *testing.T) { | ||||
@@ -522,8 +522,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) { | |||||
} | } | ||||
assert.Equal(t, expectedRepos, repos) | assert.Equal(t, expectedRepos, repos) | ||||
} | } | ||||
testSuccess(2, []int64{3, 5}) | |||||
testSuccess(4, []int64{3}) | |||||
testSuccess(2, []int64{3, 5, 32}) | |||||
testSuccess(4, []int64{3, 32}) | |||||
} | } | ||||
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { | func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { | ||||
@@ -365,22 +365,14 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) ( | |||||
return err | return err | ||||
} | } | ||||
var allTypes = make(map[UnitType]struct{}, len(allRepUnitTypes)) | |||||
for _, team := range teams { | |||||
// Administrators can not be limited | |||||
if team.Authorize >= AccessModeAdmin { | |||||
return nil | |||||
} | |||||
for _, unitType := range team.UnitTypes { | |||||
allTypes[unitType] = struct{}{} | |||||
} | |||||
} | |||||
// unique | // unique | ||||
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units)) | var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units)) | ||||
for _, u := range repo.Units { | for _, u := range repo.Units { | ||||
if _, ok := allTypes[u.Type]; ok { | |||||
newRepoUnits = append(newRepoUnits, u) | |||||
for _, team := range teams { | |||||
if team.UnitEnabled(u.Type) { | |||||
newRepoUnits = append(newRepoUnits, u) | |||||
break | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) { | |||||
count: 14}, | count: 14}, | ||||
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", | {name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", | ||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true}, | opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true}, | ||||
count: 15}, | |||||
count: 16}, | |||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", | {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", | ||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, | opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, | ||||
count: 19}, | |||||
count: 20}, | |||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", | {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", | ||||
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, | opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, | ||||
count: 13}, | count: 13}, | ||||
@@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) { | |||||
count: 11}, | count: 11}, | ||||
{name: "AllPublic/PublicRepositoriesOfOrganization", | {name: "AllPublic/PublicRepositoriesOfOrganization", | ||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse}, | opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse}, | ||||
count: 15}, | |||||
count: 16}, | |||||
} | } | ||||
for _, testCase := range testCases { | for _, testCase := range testCases { | ||||
@@ -109,6 +109,23 @@ func notifyWatchers(e Engine, act *Action) error { | |||||
act.ID = 0 | act.ID = 0 | ||||
act.UserID = watches[i].UserID | act.UserID = watches[i].UserID | ||||
act.Repo.Units = nil | |||||
switch act.OpType { | |||||
case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch: | |||||
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeCode) { | |||||
continue | |||||
} | |||||
case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue: | |||||
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeIssues) { | |||||
continue | |||||
} | |||||
case ActionCreatePullRequest, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest: | |||||
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypePullRequests) { | |||||
continue | |||||
} | |||||
} | |||||
if _, err = e.InsertOne(act); err != nil { | if _, err = e.InsertOne(act); err != nil { | ||||
return fmt.Errorf("insert new action: %v", err) | return fmt.Errorf("insert new action: %v", err) | ||||
} | } | ||||
@@ -546,28 +546,46 @@ func (u *User) GetRepositories(page, pageSize int) (err error) { | |||||
return err | return err | ||||
} | } | ||||
// GetRepositoryIDs returns repositories IDs where user owned | |||||
func (u *User) GetRepositoryIDs() ([]int64, error) { | |||||
// GetRepositoryIDs returns repositories IDs where user owned and has unittypes | |||||
func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) { | |||||
var ids []int64 | var ids []int64 | ||||
return ids, x.Table("repository").Cols("id").Where("owner_id = ?", u.ID).Find(&ids) | |||||
sess := x.Table("repository").Cols("repository.id") | |||||
if len(units) > 0 { | |||||
sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id") | |||||
sess = sess.In("repo_unit.type", units) | |||||
} | |||||
return ids, sess.Where("owner_id = ?", u.ID).Find(&ids) | |||||
} | } | ||||
// GetOrgRepositoryIDs returns repositories IDs where user's team owned | |||||
func (u *User) GetOrgRepositoryIDs() ([]int64, error) { | |||||
// GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes | |||||
func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) { | |||||
var ids []int64 | var ids []int64 | ||||
return ids, x.Table("repository"). | |||||
sess := x.Table("repository"). | |||||
Cols("repository.id"). | Cols("repository.id"). | ||||
Join("INNER", "team_user", "repository.owner_id = team_user.org_id AND team_user.uid = ?", u.ID). | |||||
Join("INNER", "team_user", "repository.owner_id = team_user.org_id"). | |||||
Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true) | |||||
if len(units) > 0 { | |||||
sess = sess.Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id") | |||||
sess = sess.In("team_unit.type", units) | |||||
} | |||||
return ids, sess. | |||||
Where("team_user.uid = ?", u.ID). | |||||
GroupBy("repository.id").Find(&ids) | GroupBy("repository.id").Find(&ids) | ||||
} | } | ||||
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations | // GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations | ||||
func (u *User) GetAccessRepoIDs() ([]int64, error) { | |||||
ids, err := u.GetRepositoryIDs() | |||||
func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) { | |||||
ids, err := u.GetRepositoryIDs(units...) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
ids2, err := u.GetOrgRepositoryIDs() | |||||
ids2, err := u.GetOrgRepositoryIDs(units...) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
@@ -159,3 +159,25 @@ func BenchmarkHashPassword(b *testing.B) { | |||||
u.HashPassword(pass) | u.HashPassword(pass) | ||||
} | } | ||||
} | } | ||||
func TestGetOrgRepositoryIDs(t *testing.T) { | |||||
assert.NoError(t, PrepareTestDatabase()) | |||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | |||||
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) | |||||
user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | |||||
accessibleRepos, err := user2.GetOrgRepositoryIDs() | |||||
assert.NoError(t, err) | |||||
// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization | |||||
assert.Equal(t, []int64{3, 5, 32}, accessibleRepos) | |||||
accessibleRepos, err = user4.GetOrgRepositoryIDs() | |||||
assert.NoError(t, err) | |||||
// User 4's team has access to private repo 3, repo 32 is a public repo of the organization | |||||
assert.Equal(t, []int64{3, 32}, accessibleRepos) | |||||
accessibleRepos, err = user5.GetOrgRepositoryIDs() | |||||
assert.NoError(t, err) | |||||
// User 5's team has no access to any repo | |||||
assert.Len(t, accessibleRepos, 0) | |||||
} |
@@ -182,7 +182,14 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) { | |||||
Authorize: models.ParseAccessMode(form.Permission), | Authorize: models.ParseAccessMode(form.Permission), | ||||
} | } | ||||
if t.Authorize < models.AccessModeAdmin { | if t.Authorize < models.AccessModeAdmin { | ||||
t.UnitTypes = form.Units | |||||
var units = make([]*models.TeamUnit, 0, len(form.Units)) | |||||
for _, tp := range form.Units { | |||||
units = append(units, &models.TeamUnit{ | |||||
OrgID: ctx.Org.Organization.ID, | |||||
Type: tp, | |||||
}) | |||||
} | |||||
t.Units = units | |||||
} | } | ||||
ctx.Data["Team"] = t | ctx.Data["Team"] = t | ||||
@@ -264,9 +271,17 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) { | |||||
} | } | ||||
t.Description = form.Description | t.Description = form.Description | ||||
if t.Authorize < models.AccessModeAdmin { | if t.Authorize < models.AccessModeAdmin { | ||||
t.UnitTypes = form.Units | |||||
var units = make([]models.TeamUnit, 0, len(form.Units)) | |||||
for _, tp := range form.Units { | |||||
units = append(units, models.TeamUnit{ | |||||
OrgID: t.OrgID, | |||||
TeamID: t.ID, | |||||
Type: tp, | |||||
}) | |||||
} | |||||
models.UpdateTeamUnits(t, units) | |||||
} else { | } else { | ||||
t.UnitTypes = nil | |||||
models.UpdateTeamUnits(t, nil) | |||||
} | } | ||||
if ctx.HasError() { | if ctx.HasError() { | ||||
@@ -203,7 +203,11 @@ func Issues(ctx *context.Context) { | |||||
return | return | ||||
} | } | ||||
} else { | } else { | ||||
userRepoIDs, err = ctxUser.GetAccessRepoIDs() | |||||
unitType := models.UnitTypeIssues | |||||
if isPullList { | |||||
unitType = models.UnitTypePullRequests | |||||
} | |||||
userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType) | |||||
if err != nil { | if err != nil { | ||||
ctx.ServerError("ctxUser.GetAccessRepoIDs", err) | ctx.ServerError("ctxUser.GetAccessRepoIDs", err) | ||||
return | return | ||||
@@ -26,8 +26,8 @@ func TestIssues(t *testing.T) { | |||||
Issues(ctx) | Issues(ctx) | ||||
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) | assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) | ||||
assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"]) | |||||
assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"]) | |||||
assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) | assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) | ||||
assert.Len(t, ctx.Data["Issues"], 1) | assert.Len(t, ctx.Data["Issues"], 1) | ||||
assert.Len(t, ctx.Data["Repos"], 2) | |||||
assert.Len(t, ctx.Data["Repos"], 1) | |||||
} | } |
@@ -57,7 +57,7 @@ | |||||
{{range $t, $unit := $.Units}} | {{range $t, $unit := $.Units}} | ||||
<div class="field"> | <div class="field"> | ||||
<div class="ui toggle checkbox"> | <div class="ui toggle checkbox"> | ||||
<input type="checkbox" class="hidden" name="units" value="{{$unit.Type}}"{{if $.Team.UnitEnabled $unit.Type}} checked{{end}}> | |||||
<input type="checkbox" class="hidden" name="units" value="{{$unit.Type}}"{{if or (eq $.Team.ID 0) ($.Team.UnitEnabled $unit.Type)}} checked{{end}}> | |||||
<label>{{$.i18n.Tr $unit.NameKey}}</label> | <label>{{$.i18n.Tr $unit.NameKey}}</label> | ||||
<span class="help">{{$.i18n.Tr $unit.DescKey}}</span> | <span class="help">{{$.i18n.Tr $unit.DescKey}}</span> | ||||
</div> | </div> | ||||