diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c9feee4e24f..dae14e83721 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -61,7 +61,7 @@ jobs: name: Test strategy: matrix: - go-version: [ 1.20.x, 1.21.x ] + go-version: [ 1.21.x ] platform: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.platform }} steps: @@ -101,7 +101,7 @@ jobs: name: Test Windows strategy: matrix: - go-version: [ 1.20.x, 1.21.x ] + go-version: [ 1.21.x ] platform: [ windows-latest ] runs-on: ${{ matrix.platform }} steps: @@ -139,7 +139,7 @@ jobs: name: Postgres strategy: matrix: - go-version: [ 1.20.x, 1.21.x ] + go-version: [ 1.21.x ] platform: [ ubuntu-latest ] runs-on: ${{ matrix.platform }} services: @@ -175,7 +175,7 @@ jobs: name: MySQL strategy: matrix: - go-version: [ 1.20.x, 1.21.x ] + go-version: [ 1.21.x ] platform: [ ubuntu-20.04 ] runs-on: ${{ matrix.platform }} steps: @@ -200,7 +200,7 @@ jobs: name: SQLite - Go strategy: matrix: - go-version: [ 1.20.x, 1.21.x ] + go-version: [ 1.21.x ] platform: [ ubuntu-latest ] runs-on: ${{ matrix.platform }} steps: diff --git a/docs/dev/database_schema.md b/docs/dev/database_schema.md index b49cca3e611..613f1831ebe 100644 --- a/docs/dev/database_schema.md +++ b/docs/dev/database_schema.md @@ -129,3 +129,22 @@ Primary keys: id Primary keys: id ``` +# Table "org_user" + +``` + FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3 +-----------+-----------+--------------------------------+--------------------------------+--------------------------------- + ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER + UserID | uid | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL + OrgID | org_id | BIGINT NOT NULL | BIGINT NOT NULL | INTEGER NOT NULL + IsPublic | is_public | BOOLEAN NOT NULL DEFAULT FALSE | BOOLEAN NOT NULL DEFAULT FALSE | NUMERIC NOT NULL DEFAULT FALSE + IsOwner | is_owner | BOOLEAN NOT NULL DEFAULT FALSE | BOOLEAN NOT NULL DEFAULT FALSE | NUMERIC NOT NULL DEFAULT FALSE + NumTeams | num_teams | BIGINT NOT NULL DEFAULT 0 | BIGINT NOT NULL DEFAULT 0 | INTEGER NOT NULL DEFAULT 0 + +Primary keys: id +Indexes: + "idx_org_user_org_id" (org_id) + "idx_org_user_user_id" (uid) + "org_user_user_org_unique" UNIQUE (uid, org_id) +``` + diff --git a/go.mod b/go.mod index 9f9e0eba9e9..7d8531f922c 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,6 @@ require ( gorm.io/gorm v1.25.5 modernc.org/sqlite v1.27.0 unknwon.dev/clog/v2 v2.2.0 - xorm.io/builder v0.3.6 xorm.io/core v0.7.2 xorm.io/xorm v0.8.0 ) @@ -137,6 +136,7 @@ require ( modernc.org/opt v0.1.3 // indirect modernc.org/strutil v1.1.3 // indirect modernc.org/token v1.0.1 // indirect + xorm.io/builder v0.3.6 // indirect ) // +heroku goVersion go1.20 diff --git a/internal/context/go_get.go b/internal/context/go_get.go index b83eda83b5d..e05dcbc394e 100644 --- a/internal/context/go_get.go +++ b/internal/context/go_get.go @@ -29,7 +29,7 @@ func ServeGoGet() macaron.Handler { owner, err := db.Users.GetByUsername(c.Req.Context(), ownerName) if err == nil { - repo, err := db.Repos.GetByName(c.Req.Context(), owner.ID, repoName) + repo, err := db.Repositories.GetByName(c.Req.Context(), owner.ID, repoName) if err == nil && repo.DefaultBranch != "" { branchName = repo.DefaultBranch } diff --git a/internal/context/org.go b/internal/context/org.go index 389677af915..1f2cdc515f3 100644 --- a/internal/context/org.go +++ b/internal/context/org.go @@ -73,8 +73,8 @@ func HandleOrgAssignment(c *Context, args ...bool) { c.Org.IsMember = true c.Org.IsTeamMember = true c.Org.IsTeamAdmin = true - } else if org.IsOrgMember(c.User.ID) { - c.Org.IsMember = true + } else { + c.Org.IsMember, _ = db.Organizations.HasMember(c.Req.Context(), org.ID, c.User.ID) } } else { // Fake data. diff --git a/internal/context/repo.go b/internal/context/repo.go index 3c3462f0e47..6e0c0b8b521 100644 --- a/internal/context/repo.go +++ b/internal/context/repo.go @@ -403,7 +403,7 @@ func RepoRef() macaron.Handler { c.Data["IsViewCommit"] = c.Repo.IsViewCommit // People who have push access or have forked repository can propose a new pull request. - if c.Repo.IsWriter() || (c.IsLogged && db.Repos.HasForkedBy(c.Req.Context(), c.Repo.Repository.ID, c.User.ID)) { + if c.Repo.IsWriter() || (c.IsLogged && db.Repositories.HasForkedBy(c.Req.Context(), c.Repo.Repository.ID, c.User.ID)) { // Pull request is allowed if this is a fork repository // and base repository accepts pull requests. if c.Repo.Repository.BaseRepo != nil { diff --git a/internal/db/access_tokens.go b/internal/db/access_tokens.go index 825cfa87a04..f9b63e15462 100644 --- a/internal/db/access_tokens.go +++ b/internal/db/access_tokens.go @@ -154,7 +154,7 @@ func (db *accessTokens) GetBySHA1(ctx context.Context, sha1 string) (*AccessToke token := new(AccessToken) err := db.WithContext(ctx).Where("sha256 = ?", sha256).First(token).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha1}} } return nil, err diff --git a/internal/db/actions.go b/internal/db/actions.go index 48d080b3d7e..02e4c93e660 100644 --- a/internal/db/actions.go +++ b/internal/db/actions.go @@ -86,19 +86,20 @@ func (db *actions) listByOrganization(ctx context.Context, orgID, actorID, after /* Equivalent SQL for PostgreSQL: - SELECT * FROM "action" + WHERE user_id = @userID AND (@skipAfter OR id < @afterID) @@ -153,8 +154,8 @@ func (db *actions) listByUser(ctx context.Context, userID, actorID, afterID int6 Where("?", !isProfile || actorID == userID). Or("is_private = ? AND act_user_id = ?", false, userID), ). - Limit(conf.UI.User.NewsFeedPagingNum). - Order("id DESC") + Order("id DESC"). + Limit(conf.UI.User.NewsFeedPagingNum) } func (db *actions) ListByUser(ctx context.Context, userID, actorID, afterID int64, isProfile bool) ([]*Action, error) { @@ -164,7 +165,7 @@ func (db *actions) ListByUser(ctx context.Context, userID, actorID, afterID int6 // notifyWatchers creates rows in action table for watchers who are able to see the action. func (db *actions) notifyWatchers(ctx context.Context, act *Action) error { - watches, err := NewReposStore(db.DB).ListWatches(ctx, act.RepoID) + watches, err := NewRepositoriesStore(db.DB).ListWatches(ctx, act.RepoID) if err != nil { return errors.Wrap(err, "list watches") } @@ -488,7 +489,7 @@ type CommitRepoOptions struct { } func (db *actions) CommitRepo(ctx context.Context, opts CommitRepoOptions) error { - err := NewReposStore(db.DB).Touch(ctx, opts.Repo.ID) + err := NewRepositoriesStore(db.DB).Touch(ctx, opts.Repo.ID) if err != nil { return errors.Wrap(err, "touch repository") } @@ -632,7 +633,7 @@ type PushTagOptions struct { } func (db *actions) PushTag(ctx context.Context, opts PushTagOptions) error { - err := NewReposStore(db.DB).Touch(ctx, opts.Repo.ID) + err := NewRepositoriesStore(db.DB).Touch(ctx, opts.Repo.ID) if err != nil { return errors.Wrap(err, "touch repository") } diff --git a/internal/db/actions_test.go b/internal/db/actions_test.go index 8bf20f697ae..03b547b7c7a 100644 --- a/internal/db/actions_test.go +++ b/internal/db/actions_test.go @@ -137,7 +137,7 @@ func TestActions(t *testing.T) { func actionsCommitRepo(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -440,7 +440,7 @@ func actionsListByUser(t *testing.T, ctx context.Context, db *actions) { func actionsMergePullRequest(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -485,7 +485,7 @@ func actionsMergePullRequest(t *testing.T, ctx context.Context, db *actions) { func actionsMirrorSyncCreate(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -526,7 +526,7 @@ func actionsMirrorSyncCreate(t *testing.T, ctx context.Context, db *actions) { func actionsMirrorSyncDelete(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -567,7 +567,7 @@ func actionsMirrorSyncDelete(t *testing.T, ctx context.Context, db *actions) { func actionsMirrorSyncPush(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -632,7 +632,7 @@ func actionsMirrorSyncPush(t *testing.T, ctx context.Context, db *actions) { func actionsNewRepo(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -711,7 +711,7 @@ func actionsPushTag(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -803,7 +803,7 @@ func actionsPushTag(t *testing.T, ctx context.Context, db *actions) { func actionsRenameRepo(t *testing.T, ctx context.Context, db *actions) { alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", @@ -842,7 +842,7 @@ func actionsTransferRepo(t *testing.T, ctx context.Context, db *actions) { require.NoError(t, err) bob, err := NewUsersStore(db.DB).Create(ctx, "bob", "bob@example.com", CreateUserOptions{}) require.NoError(t, err) - repo, err := NewReposStore(db.DB).Create(ctx, + repo, err := NewRepositoriesStore(db.DB).Create(ctx, alice.ID, CreateRepoOptions{ Name: "example", diff --git a/internal/db/backup_test.go b/internal/db/backup_test.go index 9d2d9403a40..391af791a7a 100644 --- a/internal/db/backup_test.go +++ b/internal/db/backup_test.go @@ -31,7 +31,7 @@ func TestDumpAndImport(t *testing.T) { } t.Parallel() - const wantTables = 8 + const wantTables = 9 if len(Tables) != wantTables { t.Fatalf("New table has added (want %d got %d), please add new tests for the table and update this check", wantTables, len(Tables)) } @@ -197,6 +197,23 @@ func setupDBToDump(t *testing.T, db *gorm.DB) { Description: "This is a notice", CreatedUnix: 1588568886, }, + + &OrgUser{ + ID: 1, + UserID: 1, + OrgID: 11, + IsPublic: true, + IsOwner: true, + NumTeams: 3, + }, + &OrgUser{ + ID: 2, + UserID: 2, + OrgID: 11, + IsPublic: false, + IsOwner: false, + NumTeams: 0, + }, } for _, val := range vals { err := db.Create(val).Error diff --git a/internal/db/db.go b/internal/db/db.go index 9878032d3d9..00f3984795f 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -46,6 +46,7 @@ var Tables = []any{ new(Follow), new(LFSObject), new(LoginSource), new(Notice), + new(OrgUser), } // Init initializes the database with given logger. @@ -126,9 +127,9 @@ func Init(w logger.Writer) (*gorm.DB, error) { LoginSources = &loginSources{DB: db, files: sourceFiles} LFS = &lfs{DB: db} Notices = NewNoticesStore(db) - Orgs = NewOrgsStore(db) + Organizations = NewOrganizationsStore(db) Perms = NewPermsStore(db) - Repos = NewReposStore(db) + Repositories = NewRepositoriesStore(db) TwoFactors = &twoFactors{DB: db} Users = NewUsersStore(db) diff --git a/internal/db/error.go b/internal/db/error.go index 8436aa99e08..1c831cf86de 100644 --- a/internal/db/error.go +++ b/internal/db/error.go @@ -133,26 +133,6 @@ func (err ErrDeployKeyNameAlreadyUsed) Error() string { return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name) } -// ________ .__ __ .__ -// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____ -// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \ -// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \ -// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| / -// \/ /_____/ \/ \/ \/ \/ \/ - -type ErrLastOrgOwner struct { - UID int64 -} - -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) -} - // __________ .__ __ // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | diff --git a/internal/db/issue.go b/internal/db/issue.go index 53c8132dea9..869a57b5699 100644 --- a/internal/db/issue.go +++ b/internal/db/issue.go @@ -1180,12 +1180,13 @@ func updateIssueMentions(e Engine, issueID int64, mentions []string) error { continue } - memberIDs := make([]int64, 0, user.NumMembers) - orgUsers, err := getOrgUsersByOrgID(e, user.ID, 0) + orgUsers := make([]*OrgUser, 0, 10) + err := e.Where("org_id=?", user.ID).Find(&orgUsers) if err != nil { return fmt.Errorf("getOrgUsersByOrgID [%d]: %v", user.ID, err) } + memberIDs := make([]int64, 0, user.NumMembers) for _, orgUser := range orgUsers { memberIDs = append(memberIDs, orgUser.ID) } diff --git a/internal/db/lfs.go b/internal/db/lfs.go index bff18efd15d..6f911661404 100644 --- a/internal/db/lfs.go +++ b/internal/db/lfs.go @@ -9,6 +9,7 @@ import ( "fmt" "time" + "github.com/pkg/errors" "gorm.io/gorm" "gogs.io/gogs/internal/errutil" @@ -75,7 +76,7 @@ func (db *lfs) GetObjectByOID(ctx context.Context, repoID int64, oid lfsutil.OID object := new(LFSObject) err := db.WithContext(ctx).Where("repo_id = ? AND oid = ?", repoID, oid).First(object).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrLFSObjectNotExist{args: errutil.Args{"repoID": repoID, "oid": oid}} } return nil, err diff --git a/internal/db/login_sources.go b/internal/db/login_sources.go index 9469a3f080e..c5a7b974dfd 100644 --- a/internal/db/login_sources.go +++ b/internal/db/login_sources.go @@ -264,7 +264,7 @@ func (db *loginSources) GetByID(ctx context.Context, id int64) (*LoginSource, er source := new(LoginSource) err := db.WithContext(ctx).Where("id = ?", id).First(source).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return db.files.GetByID(id) } return nil, err diff --git a/internal/db/migrations/migrations.go b/internal/db/migrations/migrations.go index 04c1e36364d..d6cfe6f9404 100644 --- a/internal/db/migrations/migrations.go +++ b/internal/db/migrations/migrations.go @@ -80,7 +80,7 @@ func Migrate(db *gorm.DB) error { var current Version err := db.Where("id = ?", 1).First(¤t).Error - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { err = db.Create( &Version{ ID: 1, diff --git a/internal/db/mocks.go b/internal/db/mocks.go index 89c3ed9c5e3..44aa01300c4 100644 --- a/internal/db/mocks.go +++ b/internal/db/mocks.go @@ -48,11 +48,11 @@ func SetMockPermsStore(t *testing.T, mock PermsStore) { }) } -func SetMockReposStore(t *testing.T, mock ReposStore) { - before := Repos - Repos = mock +func SetMockReposStore(t *testing.T, mock RepositoriesStore) { + before := Repositories + Repositories = mock t.Cleanup(func() { - Repos = before + Repositories = before }) } diff --git a/internal/db/models.go b/internal/db/models.go index 1e3b8d9b719..f2082ee9dd9 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -57,7 +57,7 @@ func init() { new(Label), new(IssueLabel), new(Milestone), new(Mirror), new(Release), new(Webhook), new(HookTask), new(ProtectBranch), new(ProtectBranchWhitelist), - new(Team), new(OrgUser), new(TeamUser), new(TeamRepo), + new(Team), new(TeamUser), new(TeamRepo), ) gonicNames := []string{"SSL"} @@ -211,7 +211,7 @@ type Statistic struct { func GetStatistic(ctx context.Context) (stats Statistic) { stats.Counter.User = Users.Count(ctx) - stats.Counter.Org = CountOrganizations() + stats.Counter.Org = Organizations.Count(ctx) stats.Counter.PublicKey, _ = x.Count(new(PublicKey)) stats.Counter.Repo = CountRepositories(true) stats.Counter.Watch, _ = x.Count(new(Watch)) diff --git a/internal/db/org.go b/internal/db/org.go deleted file mode 100644 index 8bc16701e4f..00000000000 --- a/internal/db/org.go +++ /dev/null @@ -1,561 +0,0 @@ -// Copyright 2014 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 db - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - - "xorm.io/builder" - "xorm.io/xorm" - - "gogs.io/gogs/internal/errutil" - "gogs.io/gogs/internal/repoutil" - "gogs.io/gogs/internal/userutil" -) - -var ErrOrgNotExist = errors.New("Organization does not exist") - -// IsOwnedBy returns true if given user is in the owner team. -func (org *User) IsOwnedBy(userID int64) bool { - return IsOrganizationOwner(org.ID, userID) -} - -// IsOrgMember returns true if given user is member of organization. -func (org *User) IsOrgMember(uid int64) bool { - return org.IsOrganization() && IsOrganizationMember(org.ID, uid) -} - -func (org *User) getTeam(e Engine, name string) (*Team, error) { - return getTeamOfOrgByName(e, org.ID, name) -} - -// GetTeamOfOrgByName returns named team of organization. -func (org *User) GetTeam(name string) (*Team, error) { - return org.getTeam(x, name) -} - -func (org *User) getOwnerTeam(e Engine) (*Team, error) { - return org.getTeam(e, OWNER_TEAM) -} - -// GetOwnerTeam returns owner team of organization. -func (org *User) GetOwnerTeam() (*Team, error) { - return org.getOwnerTeam(x) -} - -func (org *User) getTeams(e Engine) (err error) { - org.Teams, err = getTeamsByOrgID(e, org.ID) - return err -} - -// GetTeams returns all teams that belong to organization. -func (org *User) GetTeams() error { - return org.getTeams(x) -} - -// TeamsHaveAccessToRepo returns all teams that have given access level to the repository. -func (org *User) TeamsHaveAccessToRepo(repoID int64, mode AccessMode) ([]*Team, error) { - return GetTeamsHaveAccessToRepo(org.ID, repoID, mode) -} - -// GetMembers returns all members of organization. -func (org *User) GetMembers(limit int) error { - ous, err := GetOrgUsersByOrgID(org.ID, limit) - if err != nil { - return err - } - - org.Members = make([]*User, len(ous)) - for i, ou := range ous { - org.Members[i], err = Users.GetByID(context.TODO(), ou.Uid) - if err != nil { - return err - } - } - return nil -} - -// AddMember adds new member to organization. -func (org *User) AddMember(uid int64) error { - return AddOrgUser(org.ID, uid) -} - -// RemoveMember removes member from organization. -func (org *User) RemoveMember(uid int64) error { - return RemoveOrgUser(org.ID, uid) -} - -func (org *User) removeOrgRepo(e Engine, repoID int64) error { - return removeOrgRepo(e, org.ID, repoID) -} - -// RemoveOrgRepo removes all team-repository relations of organization. -func (org *User) RemoveOrgRepo(repoID int64) error { - return org.removeOrgRepo(x, repoID) -} - -// CreateOrganization creates record of a new organization. -func CreateOrganization(org, owner *User) (err error) { - if err = isUsernameAllowed(org.Name); err != nil { - return err - } - - if Users.IsUsernameUsed(context.TODO(), org.Name, 0) { - return ErrUserAlreadyExist{ - args: errutil.Args{ - "name": org.Name, - }, - } - } - - org.LowerName = strings.ToLower(org.Name) - if org.Rands, err = userutil.RandomSalt(); err != nil { - return err - } - if org.Salt, err = userutil.RandomSalt(); err != nil { - return err - } - org.UseCustomAvatar = true - org.MaxRepoCreation = -1 - org.NumTeams = 1 - org.NumMembers = 1 - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(org); err != nil { - return fmt.Errorf("insert organization: %v", err) - } - _ = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email) - - // Add initial creator to organization and owner team. - if _, err = sess.Insert(&OrgUser{ - Uid: owner.ID, - OrgID: org.ID, - IsOwner: true, - NumTeams: 1, - }); err != nil { - return fmt.Errorf("insert org-user relation: %v", err) - } - - // Create default owner team. - t := &Team{ - OrgID: org.ID, - LowerName: strings.ToLower(OWNER_TEAM), - Name: OWNER_TEAM, - Authorize: AccessModeOwner, - NumMembers: 1, - } - if _, err = sess.Insert(t); err != nil { - return fmt.Errorf("insert owner team: %v", err) - } - - if _, err = sess.Insert(&TeamUser{ - UID: owner.ID, - OrgID: org.ID, - TeamID: t.ID, - }); err != nil { - return fmt.Errorf("insert team-user relation: %v", err) - } - - if err = os.MkdirAll(repoutil.UserPath(org.Name), os.ModePerm); err != nil { - return fmt.Errorf("create directory: %v", err) - } - - return sess.Commit() -} - -// GetOrgByName returns organization by given name. -func GetOrgByName(name string) (*User, error) { - if name == "" { - return nil, ErrOrgNotExist - } - u := &User{ - LowerName: strings.ToLower(name), - Type: UserTypeOrganization, - } - has, err := x.Get(u) - if err != nil { - return nil, err - } else if !has { - return nil, ErrOrgNotExist - } - return u, nil -} - -// CountOrganizations returns number of organizations. -func CountOrganizations() int64 { - count, _ := x.Where("type=1").Count(new(User)) - return count -} - -// Organizations returns number of organizations in given page. -func Organizations(page, pageSize int) ([]*User, error) { - orgs := make([]*User, 0, pageSize) - return orgs, x.Limit(pageSize, (page-1)*pageSize).Where("type=1").Asc("id").Find(&orgs) -} - -// deleteBeans deletes all given beans, beans should contain delete conditions. -func deleteBeans(e Engine, beans ...any) (err error) { - for i := range beans { - if _, err = e.Delete(beans[i]); err != nil { - return err - } - } - return nil -} - -// DeleteOrganization completely and permanently deletes everything of organization. -func DeleteOrganization(org *User) error { - err := Users.DeleteByID(context.TODO(), org.ID, false) - if err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = deleteBeans(sess, - &Team{OrgID: org.ID}, - &OrgUser{OrgID: org.ID}, - &TeamUser{OrgID: org.ID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %v", err) - } - return sess.Commit() -} - -// ________ ____ ___ -// \_____ \_______ ____ | | \______ ___________ -// / | \_ __ \/ ___\| | / ___// __ \_ __ \ -// / | \ | \/ /_/ > | /\___ \\ ___/| | \/ -// \_______ /__| \___ /|______//____ >\___ >__| -// \/ /_____/ \/ \/ - -// OrgUser represents relations of organizations and their members. -type OrgUser struct { - ID int64 `gorm:"primaryKey"` - Uid int64 `xorm:"INDEX UNIQUE(s)" gorm:"uniqueIndex:org_user_user_org_unique;index;not null"` - OrgID int64 `xorm:"INDEX UNIQUE(s)" gorm:"uniqueIndex:org_user_user_org_unique;index;not null"` - IsPublic bool `gorm:"not null;default:FALSE"` - IsOwner bool `gorm:"not null;default:FALSE"` - NumTeams int `gorm:"not null;default:0"` -} - -// IsOrganizationOwner returns true if given user is in the owner team. -func IsOrganizationOwner(orgID, userID int64) bool { - has, _ := x.Where("is_owner = ?", true).And("uid = ?", userID).And("org_id = ?", orgID).Get(new(OrgUser)) - return has -} - -// IsOrganizationMember returns true if given user is member of organization. -func IsOrganizationMember(orgId, uid int64) bool { - has, _ := x.Where("uid=?", uid).And("org_id=?", orgId).Get(new(OrgUser)) - return has -} - -// IsPublicMembership returns true if given user public his/her membership. -func IsPublicMembership(orgId, uid int64) bool { - has, _ := x.Where("uid=?", uid).And("org_id=?", orgId).And("is_public=?", true).Get(new(OrgUser)) - return has -} - -func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) { - orgs := make([]*User, 0, 10) - if !showAll { - sess.And("`org_user`.is_public=?", true) - } - return orgs, sess.And("`org_user`.uid=?", userID). - Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs) -} - -// GetOrgsByUserID returns a list of organizations that the given user ID -// has joined. -func GetOrgsByUserID(userID int64, showAll bool) ([]*User, error) { - return getOrgsByUserID(x.NewSession(), userID, showAll) -} - -func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) { - orgs := make([]*User, 0, 10) - return orgs, sess.Where("`org_user`.uid=?", userID).And("`org_user`.is_owner=?", true). - Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs) -} - -// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID. -func GetOwnedOrgsByUserID(userID int64) ([]*User, error) { - sess := x.NewSession() - return getOwnedOrgsByUserID(sess, userID) -} - -// GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by -// given user ID, ordered descending by the given condition. -func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) { - sess := x.NewSession() - return getOwnedOrgsByUserID(sess.Desc(desc), userID) -} - -func getOrgUsersByOrgID(e Engine, orgID int64, limit int) ([]*OrgUser, error) { - orgUsers := make([]*OrgUser, 0, 10) - - sess := e.Where("org_id=?", orgID) - if limit > 0 { - sess = sess.Limit(limit) - } - return orgUsers, sess.Find(&orgUsers) -} - -// GetOrgUsersByOrgID returns all organization-user relations by organization ID. -func GetOrgUsersByOrgID(orgID int64, limit int) ([]*OrgUser, error) { - return getOrgUsersByOrgID(x, orgID, limit) -} - -// ChangeOrgUserStatus changes public or private membership status. -func ChangeOrgUserStatus(orgID, uid int64, public bool) error { - ou := new(OrgUser) - has, err := x.Where("uid=?", uid).And("org_id=?", orgID).Get(ou) - if err != nil { - return err - } else if !has { - return nil - } - - ou.IsPublic = public - _, err = x.Id(ou.ID).AllCols().Update(ou) - return err -} - -// AddOrgUser adds new user to given organization. -func AddOrgUser(orgID, uid int64) error { - if IsOrganizationMember(orgID, uid) { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - ou := &OrgUser{ - Uid: uid, - OrgID: orgID, - } - - if _, err := sess.Insert(ou); err != nil { - return err - } else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil { - return err - } - - return sess.Commit() -} - -// RemoveOrgUser removes user from given organization. -func RemoveOrgUser(orgID, userID int64) error { - ou := new(OrgUser) - - has, err := x.Where("uid=?", userID).And("org_id=?", orgID).Get(ou) - if err != nil { - return fmt.Errorf("get org-user: %v", err) - } else if !has { - return nil - } - - user, err := Users.GetByID(context.TODO(), userID) - if err != nil { - return fmt.Errorf("GetUserByID [%d]: %v", userID, err) - } - org, err := Users.GetByID(context.TODO(), orgID) - if err != nil { - return fmt.Errorf("GetUserByID [%d]: %v", orgID, err) - } - - // FIXME: only need to get IDs here, not all fields of repository. - repos, _, err := org.GetUserRepositories(user.ID, 1, org.NumRepos) - if err != nil { - return fmt.Errorf("GetUserRepositories [%d]: %v", user.ID, err) - } - - // Check if the user to delete is the last member in owner team. - if IsOrganizationOwner(orgID, userID) { - t, err := org.GetOwnerTeam() - if err != nil { - return err - } - if t.NumMembers == 1 { - return ErrLastOrgOwner{UID: userID} - } - } - - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - if _, err := sess.ID(ou.ID).Delete(ou); err != nil { - return err - } else if _, err = sess.Exec("UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil { - return err - } - - // Delete all repository accesses and unwatch them. - repoIDs := make([]int64, len(repos)) - for i := range repos { - repoIDs = append(repoIDs, repos[i].ID) - if err = watchRepo(sess, user.ID, repos[i].ID, false); err != nil { - return err - } - } - - if len(repoIDs) > 0 { - if _, err = sess.Where("user_id = ?", user.ID).In("repo_id", repoIDs).Delete(new(Access)); err != nil { - return err - } - } - - // Delete member in his/her teams. - teams, err := getUserTeams(sess, org.ID, user.ID) - if err != nil { - return err - } - for _, t := range teams { - if err = removeTeamMember(sess, org.ID, t.ID, user.ID); err != nil { - return err - } - } - - return sess.Commit() -} - -func removeOrgRepo(e Engine, orgID, repoID int64) error { - _, err := e.Delete(&TeamRepo{ - OrgID: orgID, - RepoID: repoID, - }) - return err -} - -// RemoveOrgRepo removes all team-repository relations of given organization. -func RemoveOrgRepo(orgID, repoID int64) error { - return removeOrgRepo(x, orgID, repoID) -} - -func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, error) { - teams := make([]*Team, 0, org.NumTeams) - return teams, e.Where("team_user.org_id = ?", org.ID). - And("team_user.uid = ?", userID). - Join("INNER", "team_user", "team_user.team_id = team.id"). - Cols(cols...).Find(&teams) -} - -// GetUserTeamIDs returns of all team IDs of the organization that user is member of. -func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) { - teams, err := org.getUserTeams(x, userID, "team.id") - if err != nil { - return nil, fmt.Errorf("getUserTeams [%d]: %v", userID, err) - } - - teamIDs := make([]int64, len(teams)) - for i := range teams { - teamIDs[i] = teams[i].ID - } - return teamIDs, nil -} - -// GetTeams returns all teams that belong to organization, -// and that the user has joined. -func (org *User) GetUserTeams(userID int64) ([]*Team, error) { - return org.getUserTeams(x, userID) -} - -// GetUserRepositories returns a range of repositories in organization which the user has access to, -// and total number of records based on given condition. -func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repository, int64, error) { - teamIDs, err := org.GetUserTeamIDs(userID) - if err != nil { - return nil, 0, fmt.Errorf("GetUserTeamIDs: %v", err) - } - if len(teamIDs) == 0 { - // user has no team but "IN ()" is invalid SQL - teamIDs = []int64{-1} // there is no team with id=-1 - } - - var teamRepoIDs []int64 - if err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs); err != nil { - return nil, 0, fmt.Errorf("get team repository IDs: %v", err) - } - if len(teamRepoIDs) == 0 { - // team has no repo but "IN ()" is invalid SQL - teamRepoIDs = []int64{-1} // there is no repo with id=-1 - } - - if page <= 0 { - page = 1 - } - repos := make([]*Repository, 0, pageSize) - if err = x.Where("owner_id = ?", org.ID). - And(builder.Or( - builder.And(builder.Expr("is_private = ?", false), builder.Expr("is_unlisted = ?", false)), - builder.In("id", teamRepoIDs))). - Desc("updated_unix"). - Limit(pageSize, (page-1)*pageSize). - Find(&repos); err != nil { - return nil, 0, fmt.Errorf("get user repositories: %v", err) - } - - repoCount, err := x.Where("owner_id = ?", org.ID). - And(builder.Or( - builder.Expr("is_private = ?", false), - builder.In("id", teamRepoIDs))). - Count(new(Repository)) - if err != nil { - return nil, 0, fmt.Errorf("count user repositories: %v", err) - } - - return repos, repoCount, nil -} - -// GetUserMirrorRepositories returns mirror repositories of the organization which the user has access to. -func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error) { - teamIDs, err := org.GetUserTeamIDs(userID) - if err != nil { - return nil, fmt.Errorf("GetUserTeamIDs: %v", err) - } - if len(teamIDs) == 0 { - teamIDs = []int64{-1} - } - - var teamRepoIDs []int64 - err = x.Table("team_repo").In("team_id", teamIDs).Distinct("repo_id").Find(&teamRepoIDs) - if err != nil { - return nil, fmt.Errorf("get team repository ids: %v", err) - } - if len(teamRepoIDs) == 0 { - // team has no repo but "IN ()" is invalid SQL - teamRepoIDs = []int64{-1} // there is no repo with id=-1 - } - - repos := make([]*Repository, 0, 10) - if err = x.Where("owner_id = ?", org.ID). - And("is_private = ?", false). - Or(builder.In("id", teamRepoIDs)). - And("is_mirror = ?", true). // Don't move up because it's an independent condition - Desc("updated_unix"). - Find(&repos); err != nil { - return nil, fmt.Errorf("get user repositories: %v", err) - } - return repos, nil -} diff --git a/internal/db/org_team.go b/internal/db/org_team.go index d737167e463..280e72556a2 100644 --- a/internal/db/org_team.go +++ b/internal/db/org_team.go @@ -9,24 +9,24 @@ import ( "fmt" "strings" + "github.com/pkg/errors" "xorm.io/xorm" - "gogs.io/gogs/internal/db/errors" - "gogs.io/gogs/internal/errutil" + dberrors "gogs.io/gogs/internal/db/errors" ) -const OWNER_TEAM = "Owners" +const TeamNameOwners = "Owners" // Team represents a organization team. type Team struct { - ID int64 - OrgID int64 `xorm:"INDEX"` + ID int64 `gorm:"primaryKey"` + OrgID int64 `xorm:"INDEX" gorm:"index"` LowerName string Name string Description string Authorize AccessMode - Repos []*Repository `xorm:"-" json:"-"` - Members []*User `xorm:"-" json:"-"` + Repos []*Repository `xorm:"-" json:"-" gorm:"-"` + Members []*User `xorm:"-" json:"-" gorm:"-"` NumRepos int NumMembers int } @@ -43,7 +43,7 @@ func (t *Team) AfterSet(colName string, _ xorm.Cell) { // IsOwnerTeam returns true if team is owner team. func (t *Team) IsOwnerTeam() bool { - return t.Name == OWNER_TEAM + return t.Name == TeamNameOwners } // HasWriteAccess returns true if team has at least write level access mode. @@ -136,7 +136,7 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) { // AddRepository adds new repository to team of organization. func (t *Team) AddRepository(repo *Repository) (err error) { if repo.OwnerID != t.OrgID { - return errors.New("Repository does not belong to organization") + return dberrors.New("Repository does not belong to organization") } else if t.HasRepository(repo.ID) { return nil } @@ -259,9 +259,9 @@ func IsUsableTeamName(name string) error { // It's caller's responsibility to assign organization ID. func NewTeam(t *Team) error { if t.Name == "" { - return errors.New("empty team name") + return dberrors.New("empty team name") } else if t.OrgID == 0 { - return errors.New("OrgID is not assigned") + return dberrors.New("OrgID is not assigned") } if err := IsUsableTeamName(t.Name); err != nil { @@ -272,7 +272,7 @@ func NewTeam(t *Team) error { if err != nil { return err } else if !has { - return ErrOrgNotExist + return errors.New("organization does not exist") } t.LowerName = strings.ToLower(t.Name) @@ -301,44 +301,6 @@ func NewTeam(t *Team) error { return sess.Commit() } -var _ errutil.NotFound = (*ErrTeamNotExist)(nil) - -type ErrTeamNotExist struct { - args map[string]any -} - -func IsErrTeamNotExist(err error) bool { - _, ok := err.(ErrTeamNotExist) - return ok -} - -func (err ErrTeamNotExist) Error() string { - return fmt.Sprintf("team does not exist: %v", err.args) -} - -func (ErrTeamNotExist) NotFound() bool { - return true -} - -func getTeamOfOrgByName(e 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{args: map[string]any{"orgID": orgID, "name": name}} - } - return t, nil -} - -// GetTeamOfOrgByName returns team by given team name and organization. -func GetTeamOfOrgByName(orgID int64, name string) (*Team, error) { - return getTeamOfOrgByName(x, orgID, name) -} - func getTeamByID(e Engine, teamID int64) (*Team, error) { t := new(Team) has, err := e.ID(teamID).Get(t) @@ -355,20 +317,16 @@ func GetTeamByID(teamID int64) (*Team, error) { return getTeamByID(x, teamID) } +// getTeamsByOrgID returns all teams belong to given organization. func getTeamsByOrgID(e Engine, orgID int64) ([]*Team, error) { teams := make([]*Team, 0, 3) return teams, e.Where("org_id = ?", orgID).Find(&teams) } -// GetTeamsByOrgID returns all teams belong to given organization. -func GetTeamsByOrgID(orgID int64) ([]*Team, error) { - return getTeamsByOrgID(x, orgID) -} - // UpdateTeam updates information of team. func UpdateTeam(t *Team, authChanged bool) (err error) { if t.Name == "" { - return errors.New("empty team name") + return dberrors.New("empty team name") } if len(t.Description) > 255 { @@ -462,10 +420,10 @@ func DeleteTeam(t *Team) error { // TeamUser represents an team-user relation. type TeamUser struct { - ID int64 - OrgID int64 `xorm:"INDEX"` - TeamID int64 `xorm:"UNIQUE(s)"` - UID int64 `xorm:"UNIQUE(s)"` + ID int64 `gorm:"primaryKey"` + OrgID int64 `xorm:"INDEX" gorm:"index"` + TeamID int64 `xorm:"UNIQUE(s)" gorm:"uniqueIndex:team_user_unique"` + UID int64 `xorm:"UNIQUE(s)" gorm:"uniqueIndex:team_user_unique"` } func isTeamMember(e Engine, orgID, teamID, uid int64) bool { @@ -528,7 +486,7 @@ func AddTeamMember(orgID, teamID, userID int64) error { return nil } - if err := AddOrgUser(orgID, userID); err != nil { + if err := Organizations.AddMember(context.TODO(), orgID, userID); err != nil { return err } @@ -583,8 +541,8 @@ func AddTeamMember(orgID, teamID, userID int64) error { return sess.Commit() } -func removeTeamMember(e Engine, orgID, teamID, uid int64) error { - if !isTeamMember(e, orgID, teamID, uid) { +func removeTeamMember(e Engine, orgID, teamID, userID int64) error { + if !isTeamMember(e, orgID, teamID, userID) { return nil } @@ -596,7 +554,7 @@ func removeTeamMember(e Engine, orgID, teamID, uid int64) error { // Check if the user to delete is the last member in owner team. if t.IsOwnerTeam() && t.NumMembers == 1 { - return ErrLastOrgOwner{UID: uid} + return ErrLastOrgOwner{args: map[string]any{"orgID": orgID, "userID": userID}} } t.NumMembers-- @@ -612,7 +570,7 @@ func removeTeamMember(e Engine, orgID, teamID, uid int64) error { } tu := &TeamUser{ - UID: uid, + UID: userID, OrgID: orgID, TeamID: teamID, } @@ -631,7 +589,7 @@ func removeTeamMember(e Engine, orgID, teamID, uid int64) error { // This must exist. ou := new(OrgUser) - _, err = e.Where("uid = ?", uid).And("org_id = ?", org.ID).Get(ou) + _, err = e.Where("uid = ?", userID).And("org_id = ?", org.ID).Get(ou) if err != nil { return err } @@ -673,16 +631,13 @@ type TeamRepo struct { RepoID int64 `xorm:"UNIQUE(s)"` } +// hasTeamRepo returns true if given team has access to the repository of the organization. func hasTeamRepo(e 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 team has access to the repository of the organization. -func HasTeamRepo(orgID, teamID, repoID int64) bool { - return hasTeamRepo(x, orgID, teamID, repoID) -} - +// addTeamRepo adds new repository relation to team. func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { _, err := e.InsertOne(&TeamRepo{ OrgID: orgID, @@ -692,11 +647,7 @@ func addTeamRepo(e Engine, orgID, teamID, repoID int64) error { return err } -// AddTeamRepo adds new repository relation to team. -func AddTeamRepo(orgID, teamID, repoID int64) error { - return addTeamRepo(x, orgID, teamID, repoID) -} - +// removeTeamRepo deletes repository relation to team. func removeTeamRepo(e Engine, teamID, repoID int64) error { _, err := e.Delete(&TeamRepo{ TeamID: teamID, @@ -705,11 +656,6 @@ func removeTeamRepo(e Engine, teamID, repoID int64) error { return err } -// RemoveTeamRepo deletes repository relation to team. -func RemoveTeamRepo(teamID, repoID int64) error { - return removeTeamRepo(x, teamID, repoID) -} - // GetTeamsHaveAccessToRepo returns all teams in an organization that have given access level to the repository. func GetTeamsHaveAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, error) { teams := make([]*Team, 0, 5) @@ -719,3 +665,32 @@ func GetTeamsHaveAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, er And("team_repo.repo_id = ?", repoID). Find(&teams) } + +func (u *User) getTeams(e Engine) (err error) { + u.Teams, err = getTeamsByOrgID(e, u.ID) + return err +} + +// GetTeams returns all teams that belong to organization. +func (u *User) GetTeams() error { + return u.getTeams(x) +} + +// TeamsHaveAccessToRepo returns all teams that have given access level to the repository. +func (u *User) TeamsHaveAccessToRepo(repoID int64, mode AccessMode) ([]*Team, error) { + return GetTeamsHaveAccessToRepo(u.ID, repoID, mode) +} + +func (u *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, error) { + teams := make([]*Team, 0, u.NumTeams) + return teams, e.Where("team_user.org_id = ?", u.ID). + And("team_user.uid = ?", userID). + Join("INNER", "team_user", "team_user.team_id = team.id"). + Cols(cols...).Find(&teams) +} + +// GetTeams returns all teams that belong to organization, +// and that the user has joined. +func (u *User) GetUserTeams(userID int64) ([]*Team, error) { + return u.getUserTeams(x, userID) +} diff --git a/internal/db/organizations.go b/internal/db/organizations.go new file mode 100644 index 00000000000..c93d0038736 --- /dev/null +++ b/internal/db/organizations.go @@ -0,0 +1,697 @@ +// Copyright 2022 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 db + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/pkg/errors" + "gorm.io/gorm" + + "gogs.io/gogs/internal/dbutil" + "gogs.io/gogs/internal/errutil" + "gogs.io/gogs/internal/repoutil" + "gogs.io/gogs/internal/userutil" +) + +// OrganizationsStore is the persistent interface for organizations. +type OrganizationsStore interface { + // Create creates a new organization with the initial owner and persists to + // database. It returns ErrNameNotAllowed if the given name or pattern of the + // name is not allowed as an organization name, or ErrOrganizationAlreadyExist + // when a user or an organization with same name already exists. + Create(ctx context.Context, name string, ownerID int64, opts CreateOrganizationOptions) (*Organization, error) + // GetByName returns the organization with given name. It returns + // ErrOrganizationNotExist when not found. + GetByName(ctx context.Context, name string) (*Organization, error) + // SearchByName returns a list of organizations whose username or full name + // matches the given keyword case-insensitively. Results are paginated by given + // page and page size, and sorted by the given order (e.g. "id DESC"). A total + // count of all results is also returned. If the order is not given, it's up to + // the database to decide. + SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error) + // List returns a list of organizations filtered by options. + List(ctx context.Context, opts ListOrganizationsOptions) ([]*Organization, error) + // CountByUser returns the number of organizations the user is a member of. + CountByUser(ctx context.Context, userID int64) (int64, error) + // Count returns the total number of organizations. + Count(ctx context.Context) int64 + // DeleteByID deletes the given organization and all their resources. It returns + // ErrOrganizationOwnRepos when the user still has repository ownership. + DeleteByID(ctx context.Context, orgID int64) error + + // AddMember adds a new member to the given organization. + AddMember(ctx context.Context, orgID, userID int64) error + // RemoveMember removes a member from the given organization. + RemoveMember(ctx context.Context, orgID, userID int64) error + // HasMember returns whether the given user is a member of the organization + // (first), and whether the organization membership is public (second). + HasMember(ctx context.Context, orgID, userID int64) (bool, bool) + // ListMembers returns all members of the given organization, and sorted by the + // given order (e.g. "id ASC"). + ListMembers(ctx context.Context, orgID int64, opts ListOrgMembersOptions) ([]*User, error) + // IsOwnedBy returns true if the given user is an owner of the organization. + IsOwnedBy(ctx context.Context, orgID, userID int64) bool + // SetMemberVisibility sets the visibility of the given user in the organization. + SetMemberVisibility(ctx context.Context, orgID, userID int64, public bool) error + + // GetTeamByName returns the team with given name under the given organization. + // It returns ErrTeamNotExist whe not found. + GetTeamByName(ctx context.Context, orgID int64, name string) (*Team, error) + + // AccessibleRepositoriesByUser returns a range of repositories in the + // organization that the user has access to and the total number of it. Results + // are paginated by given page and page size, and OrderByUpdatedDesc is used. + AccessibleRepositoriesByUser(ctx context.Context, orgID, userID int64, page, pageSize int, opts AccessibleRepositoriesByUserOptions) ([]*Repository, int64, error) + // MirrorRepositoriesByUser returns a list of mirror repositories of the + // organization which the user has access to. + MirrorRepositoriesByUser(ctx context.Context, orgID, userID int64) ([]*Repository, error) +} + +var Organizations OrganizationsStore + +var _ OrganizationsStore = (*organizations)(nil) + +type organizations struct { + *gorm.DB +} + +// NewOrganizationsStore returns a persistent interface for orgs with given +// database connection. +func NewOrganizationsStore(db *gorm.DB) OrganizationsStore { + return &organizations{DB: db} +} + +func (*organizations) recountMembers(tx *gorm.DB, orgID int64) error { + /* + Equivalent SQL for PostgreSQL: + + UPDATE "user" + SET num_members = ( + SELECT COUNT(*) FROM org_user WHERE org_id = @orgID + ) + WHERE id = @orgID + */ + err := tx.Model(&User{}). + Where("id = ?", orgID). + Update( + "num_members", + tx.Model(&OrgUser{}).Select("COUNT(*)").Where("org_id = ?", orgID), + ). + Error + if err != nil { + return errors.Wrap(err, `update "user.num_members"`) + } + return nil +} + +func (db *organizations) AddMember(ctx context.Context, orgID, userID int64) error { + return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + ou := &OrgUser{ + UserID: userID, + OrgID: orgID, + } + result := tx.FirstOrCreate(ou, ou) + if result.Error != nil { + return errors.Wrap(result.Error, "upsert") + } else if result.RowsAffected <= 0 { + return nil // Relation already exists + } + return db.recountMembers(tx, orgID) + }) +} + +type ErrLastOrgOwner struct { + args map[string]any +} + +func IsErrLastOrgOwner(err error) bool { + return errors.As(err, &ErrLastOrgOwner{}) +} + +func (err ErrLastOrgOwner) Error() string { + return fmt.Sprintf("user is the last owner of the organization: %v", err.args) +} + +func (db *organizations) RemoveMember(ctx context.Context, orgID, userID int64) error { + ou, err := db.getOrgUser(ctx, orgID, userID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil // Not a member + } + return errors.Wrap(err, "check organization membership") + } + + // Check if the member to remove is the last owner. + if ou.IsOwner { + t, err := db.GetTeamByName(ctx, orgID, TeamNameOwners) + if err != nil { + return errors.Wrap(err, "get owners team") + } else if t.NumMembers == 1 { + return ErrLastOrgOwner{args: errutil.Args{"orgID": orgID, "userID": userID}} + } + } + + return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + repoIDsConds := db.accessibleRepositoriesByUser(tx, orgID, userID, accessibleRepositoriesByUserOptions{}).Select("repository.id") + + err := tx.Where("user_id = ? AND repo_id IN (?)", userID, repoIDsConds).Delete(&Watch{}).Error + if err != nil { + return errors.Wrap(err, "unwatch repositories") + } + + err = tx. + Table("repository"). + Where("id IN (?)", repoIDsConds). + UpdateColumn("num_watches", gorm.Expr("num_watches - 1")). + Error + if err != nil { + return errors.Wrap(err, `decrease "repository.num_watches"`) + } + + err = tx.Where("user_id = ? AND repo_id IN (?)", userID, repoIDsConds).Delete(&Access{}).Error + if err != nil { + return errors.Wrap(err, "delete repository accesses") + } + + err = tx.Where("user_id = ? AND repo_id IN (?)", userID, repoIDsConds).Delete(&Collaboration{}).Error + if err != nil { + return errors.Wrap(err, "delete repository collaborations") + } + + /* + Equivalent SQL for PostgreSQL: + + UPDATE "team" + SET num_members = num_members - 1 + WHERE id IN ( + SELECT team_id FROM "team_user" + WHERE team_user.org_id = @orgID AND uid = @userID) + ) + */ + err = tx. + Table("team"). + Where("id IN (?)", tx. + Select("team_id"). + Table("team_user"). + Where("org_id = ? AND uid = ?", orgID, userID), + ). + UpdateColumn("num_members", gorm.Expr("num_members - 1")). + Error + if err != nil { + return errors.Wrap(err, `decrease "team.num_members"`) + } + + err = tx.Where("uid = ? AND org_id = ?", userID, orgID).Delete(&TeamUser{}).Error + if err != nil { + return errors.Wrap(err, "delete team membership") + } + + err = tx.Where("uid = ? AND org_id = ?", userID, orgID).Delete(&OrgUser{}).Error + if err != nil { + return errors.Wrap(err, "delete organization membership") + } + return db.recountMembers(tx, orgID) + }) +} + +type OrderBy int + +const ( + OrderByIDAsc OrderBy = iota + 1 + OrderByUpdatedDesc +) + +type accessibleRepositoriesByUserOptions struct { + orderBy OrderBy + page int + pageSize int +} + +func (*organizations) accessibleRepositoriesByUser(tx *gorm.DB, orgID, userID int64, opts accessibleRepositoriesByUserOptions) *gorm.DB { + /* + Equivalent SQL for PostgreSQL: + + SELECT * FROM "repository" + WHERE + owner_id = @orgID + AND ( + id IN ( + SELECT repo_id + FROM "team_repo" + JOIN "team_user" ON team_user.org_id = team_repo.org_id + WHERE team_repo.org_id = @orgID AND team_user.uid = @userID + ) + OR (is_private = FALSE AND is_unlisted = FALSE) + ) + [ORDER BY updated_unix DESC] + [LIMIT @limit OFFSET @offset] + */ + conds := tx. + Table("repository"). + Where("owner_id = ? AND (id IN (?) OR (is_private = ? AND is_unlisted = ?))", + orgID, + tx.Select("repo_id"). + Table("team_repo"). + Joins("JOIN team_user ON team_user.org_id = team_repo.org_id"). + Where("team_repo.org_id = ? AND team_user.uid = ?", orgID, userID), + false, + false, + ) + if opts.orderBy == OrderByUpdatedDesc { + conds.Order("updated_unix DESC") + } + if opts.page > 0 && opts.pageSize > 0 { + conds.Limit(opts.pageSize).Offset((opts.page - 1) * opts.pageSize) + } + return conds +} + +type AccessibleRepositoriesByUserOptions struct { + // Whether to skip counting the total number of repositories. + SkipCount bool +} + +func (db *organizations) AccessibleRepositoriesByUser(ctx context.Context, orgID, userID int64, page, pageSize int, opts AccessibleRepositoriesByUserOptions) ([]*Repository, int64, error) { + conds := db.accessibleRepositoriesByUser( + db.DB, + orgID, + userID, + accessibleRepositoriesByUserOptions{ + orderBy: OrderByUpdatedDesc, + page: page, + pageSize: pageSize, + }, + ).WithContext(ctx) + + repos := make([]*Repository, 0, pageSize) + err := conds.Find(&repos).Error + if err != nil { + return nil, 0, errors.Wrap(err, "list repositories") + } + + if opts.SkipCount { + return repos, 0, nil + } + var count int64 + err = conds.Count(&count).Error + if err != nil { + return nil, 0, errors.Wrap(err, "count repositories") + } + return repos, count, nil +} + +func (db *organizations) MirrorRepositoriesByUser(ctx context.Context, orgID, userID int64) ([]*Repository, error) { + /* + Equivalent SQL for PostgreSQL: + + SELECT * FROM "repository" + JOIN team_repo ON repository.id = team_repo.repo_id + WHERE + owner_id = @orgID + AND repository.is_mirror = TRUE + AND ( + team_repo.team_id IN ( + SELECT team_id FROM "team_user" + WHERE team_user.org_id = @orgID AND uid = @userID) + ) + OR repository.is_private = FALSE + ) + ORDER BY updated_unix DESC + */ + var repos []*Repository + return repos, db.WithContext(ctx). + Joins("JOIN team_repo ON repository.id = team_repo.repo_id"). + Where("owner_id = ? AND repository.is_mirror = ? AND (?)", orgID, true, db. + Where("team_repo.team_id IN (?)", db. + Select("team_id"). + Table("team_user"). + Where("team_user.org_id = ? AND uid = ?", orgID, userID), + ). + Or("repository.is_private = ?", false), + ). + Order("updated_unix DESC"). + Find(&repos). + Error +} + +func (db *organizations) getOrgUser(ctx context.Context, orgID, userID int64) (*OrgUser, error) { + var ou OrgUser + return &ou, db.WithContext(ctx).Where("org_id = ? AND uid = ?", orgID, userID).First(&ou).Error +} + +func (db *organizations) IsOwnedBy(ctx context.Context, orgID, userID int64) bool { + ou, err := db.getOrgUser(ctx, orgID, userID) + return err == nil && ou.IsOwner +} + +func (db *organizations) SetMemberVisibility(ctx context.Context, orgID, userID int64, public bool) error { + return db.Table("org_user").Where("org_id = ? AND uid = ?", orgID, userID).UpdateColumn("is_public", public).Error +} + +func (db *organizations) HasMember(ctx context.Context, orgID, userID int64) (bool, bool) { + ou, err := db.getOrgUser(ctx, orgID, userID) + return err == nil, ou != nil && ou.IsPublic +} + +type ListOrgMembersOptions struct { + // The maximum number of members to return. + Limit int +} + +func (db *organizations) ListMembers(ctx context.Context, orgID int64, opts ListOrgMembersOptions) ([]*User, error) { + /* + Equivalent SQL for PostgreSQL: + + SELECT * FROM "user" + JOIN org_user ON org_user.uid = user.id + WHERE + org_user.org_id = @orgID + ORDER BY user.id ASC + [LIMIT @limit] + */ + conds := db.WithContext(ctx). + Joins(dbutil.Quote("JOIN org_user ON org_user.uid = %s.id", "user")). + Where("org_user.org_id = ?", orgID). + Order(dbutil.Quote("%s.id ASC", "user")) + if opts.Limit > 0 { + conds.Limit(opts.Limit) + } + var users []*User + return users, conds.Find(&users).Error +} + +type ListOrganizationsOptions struct { + // Filter by the membership with the given user ID. It cannot be set when the + // OwnerID is also set. + MemberID int64 + // Filter by the ownership with the given user ID. It cannot be set when the + // MemberID is also set. + OwnerID int64 + // Whether to include private memberships. + IncludePrivateMembers bool + + // Order by the given field and direction. Default is OrderByIDAsc. + OrderBy OrderBy + + // 1-based page number. + Page int + // Number of results per page. + PageSize int +} + +func (db *organizations) List(ctx context.Context, opts ListOrganizationsOptions) ([]*Organization, error) { + if opts.MemberID > 0 && opts.OwnerID > 0 { + return nil, errors.New("cannot filter by both MemberID and OwnerID") + } + + /* + Equivalent SQL for PostgreSQL: + + SELECT * FROM "user" + [JOIN org_user ON org_user.org_id = user.id] + WHERE + type = @type + [AND org_user.uid = (@memberID | @ownerID) + AND org_user.is_public = @includePrivateMembers + AND org_user.is_owner = @ownerID > 0] + ORDER BY (user.id ASC | user.updated_unix DESC) + [LIMIT @limit OFFSET @offset] + */ + conds := db.WithContext(ctx).Where("type = ?", UserTypeOrganization) + + if opts.MemberID > 0 || opts.OwnerID > 0 { + conds.Joins(dbutil.Quote("JOIN org_user ON org_user.org_id = %s.id", "user")) + } + if opts.MemberID > 0 { + conds.Where("org_user.uid = ?", opts.MemberID) + } else if opts.OwnerID > 0 { + conds.Where("org_user.uid = ? AND org_user.is_owner = ?", opts.OwnerID, true) + } + if (opts.MemberID > 0 || opts.OwnerID > 0) && !opts.IncludePrivateMembers { + conds.Where("org_user.is_public = ?", true) + } + + if opts.OrderBy == OrderByUpdatedDesc { + conds.Order(dbutil.Quote("%s.updated_unix DESC", "user")) + } else { + conds.Order(dbutil.Quote("%s.id ASC", "user")) + } + + if opts.Page > 0 && opts.PageSize > 0 { + conds.Limit(opts.PageSize).Offset((opts.Page - 1) * opts.PageSize) + } + + var orgs []*Organization + return orgs, conds.Find(&orgs).Error +} + +type CreateOrganizationOptions struct { + FullName string + Email string + Location string + Website string + Description string +} + +type ErrOrganizationAlreadyExist struct { + args errutil.Args +} + +// IsErrOrganizationAlreadyExist returns true if the underlying error has the +// type ErrOrganizationAlreadyExist. +func IsErrOrganizationAlreadyExist(err error) bool { + return errors.As(err, &ErrOrganizationAlreadyExist{}) +} + +func (err ErrOrganizationAlreadyExist) Error() string { + return fmt.Sprintf("organization already exists: %v", err.args) +} + +func (db *organizations) Create(ctx context.Context, name string, ownerID int64, opts CreateOrganizationOptions) (*Organization, error) { + err := isUsernameAllowed(name) + if err != nil { + return nil, err + } + + if NewUsersStore(db.DB).IsUsernameUsed(ctx, name, 0) { + return nil, ErrOrganizationAlreadyExist{ + args: errutil.Args{ + "name": name, + }, + } + } + + org := &Organization{ + LowerName: strings.ToLower(name), + Name: name, + FullName: opts.FullName, + Email: opts.Email, + Type: UserTypeOrganization, + Location: opts.Location, + Website: opts.Website, + MaxRepoCreation: -1, + IsActive: true, + UseCustomAvatar: true, + Description: opts.Description, + NumTeams: 1, // The default "owners" team + NumMembers: 1, // The initial owner + } + + org.Rands, err = userutil.RandomSalt() + if err != nil { + return nil, err + } + org.Salt, err = userutil.RandomSalt() + if err != nil { + return nil, err + } + + return org, db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + err := tx.Create(org).Error + if err != nil { + return errors.Wrap(err, "create organization") + } + + err = tx.Create(&OrgUser{ + UserID: ownerID, + OrgID: org.ID, + IsOwner: true, + NumTeams: 1, + }).Error + if err != nil { + return errors.Wrap(err, "create org-user relation") + } + + team := &Team{ + OrgID: org.ID, + LowerName: strings.ToLower(TeamNameOwners), + Name: TeamNameOwners, + Authorize: AccessModeOwner, + NumMembers: 1, + } + err = tx.Create(team).Error + if err != nil { + return errors.Wrap(err, "create owner team") + } + + err = tx.Create(&TeamUser{ + UID: ownerID, + OrgID: org.ID, + TeamID: team.ID, + }).Error + if err != nil { + return errors.Wrap(err, "create team-user relation") + } + + err = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email) + if err != nil { + return errors.Wrap(err, "generate organization avatar") + } + + err = os.MkdirAll(repoutil.UserPath(org.Name), os.ModePerm) + if err != nil { + return errors.Wrap(err, "create organization directory") + } + return nil + }) +} + +var _ errutil.NotFound = (*ErrUserNotExist)(nil) + +type ErrOrganizationNotExist struct { + args errutil.Args +} + +// IsErrOrganizationNotExist returns true if the underlying error has the type +// ErrOrganizationNotExist. +func IsErrOrganizationNotExist(err error) bool { + return errors.As(err, &ErrOrganizationNotExist{}) +} + +func (err ErrOrganizationNotExist) Error() string { + return fmt.Sprintf("organization does not exist: %v", err.args) +} + +func (ErrOrganizationNotExist) NotFound() bool { + return true +} + +func (db *organizations) GetByName(ctx context.Context, name string) (*Organization, error) { + org, err := getUserByUsername(ctx, db.DB, UserTypeOrganization, name) + if err != nil { + if IsErrUserNotExist(err) { + return nil, ErrOrganizationNotExist{args: map[string]any{"name": name}} + } + return nil, errors.Wrap(err, "get organization by name") + } + return org, nil +} + +func (db *organizations) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error) { + return searchUserByName(ctx, db.DB, UserTypeOrganization, keyword, page, pageSize, orderBy) +} + +func (db *organizations) CountByUser(ctx context.Context, userID int64) (int64, error) { + var count int64 + return count, db.WithContext(ctx).Model(&OrgUser{}).Where("uid = ?", userID).Count(&count).Error +} + +func (db *organizations) Count(ctx context.Context) int64 { + var count int64 + db.WithContext(ctx).Model(&User{}).Where("type = ?", UserTypeOrganization).Count(&count) + return count +} + +type ErrOrganizationOwnRepos struct { + args errutil.Args +} + +// IsErrOrganizationOwnRepos returns true if the underlying error has the type +// ErrOrganizationOwnRepos. +func IsErrOrganizationOwnRepos(err error) bool { + return errors.As(errors.Cause(err), &ErrOrganizationOwnRepos{}) +} + +func (err ErrOrganizationOwnRepos) Error() string { + return fmt.Sprintf("organization still has repository ownership: %v", err.args) +} + +func (db *organizations) DeleteByID(ctx context.Context, orgID int64) error { + return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + for _, t := range []any{&Team{}, &OrgUser{}, &TeamUser{}} { + err := tx.Where("org_id = ?", orgID).Delete(t).Error + if err != nil { + return errors.Wrapf(err, "clean up table %T", t) + } + } + + err := NewUsersStore(tx).DeleteByID(ctx, orgID, false) + if err != nil { + if IsErrUserOwnRepos(err) { + return ErrOrganizationOwnRepos{args: map[string]any{"orgID": orgID}} + } + return errors.Wrap(err, "delete organization") + } + return nil + }) +} + +var _ errutil.NotFound = (*ErrTeamNotExist)(nil) + +type ErrTeamNotExist struct { + args map[string]any +} + +func IsErrTeamNotExist(err error) bool { + return errors.As(err, &ErrTeamNotExist{}) +} + +func (err ErrTeamNotExist) Error() string { + return fmt.Sprintf("team does not exist: %v", err.args) +} + +func (ErrTeamNotExist) NotFound() bool { + return true +} + +func (db *organizations) GetTeamByName(ctx context.Context, orgID int64, name string) (*Team, error) { + var team Team + err := db.WithContext(ctx).Where("org_id = ? AND lower_name = ?", orgID, strings.ToLower(name)).First(&team).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrTeamNotExist{args: map[string]any{"orgID": orgID, "name": name}} + } + return nil, errors.Wrap(err, "get team by name") + } + return &team, nil +} + +type Organization = User + +func (u *Organization) TableName() string { + return "user" +} + +// IsOwnedBy returns true if the given user is an owner of the organization. +// +// TODO(unknwon): This is also used in templates, which should be fixed by +// having a dedicated type `template.Organization`. +func (u *Organization) IsOwnedBy(userID int64) bool { + return Organizations.IsOwnedBy(context.TODO(), u.ID, userID) +} + +// OrgUser represents relations of organizations and their members. +type OrgUser struct { + ID int64 `gorm:"primaryKey"` + UserID int64 `xorm:"uid INDEX UNIQUE(s)" gorm:"column:uid;uniqueIndex:org_user_user_org_unique;index;not null" json:"Uid"` + OrgID int64 `xorm:"INDEX UNIQUE(s)" gorm:"uniqueIndex:org_user_user_org_unique;index;not null"` + IsPublic bool `gorm:"not null;default:FALSE"` + IsOwner bool `gorm:"not null;default:FALSE"` + NumTeams int `gorm:"not null;default:0"` +} diff --git a/internal/db/organizations_test.go b/internal/db/organizations_test.go new file mode 100644 index 00000000000..27b6d2aa343 --- /dev/null +++ b/internal/db/organizations_test.go @@ -0,0 +1,686 @@ +// Copyright 2022 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 db + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gogs.io/gogs/internal/conf" + "gogs.io/gogs/internal/dbtest" + "gogs.io/gogs/internal/errutil" +) + +func TestOrganizations(t *testing.T) { + if testing.Short() { + t.Skip() + } + t.Parallel() + + ctx := context.Background() + tables := []any{ + new(User), new(EmailAddress), new(OrgUser), new(Team), new(TeamUser), new(Repository), new(Watch), new(Star), + new(Follow), new(Issue), new(PublicKey), new(AccessToken), new(Collaboration), new(Access), new(Action), + new(IssueUser), new(TeamRepo), + } + db := &organizations{ + DB: dbtest.NewDB(t, "orgs", tables...), + } + + for _, tc := range []struct { + name string + test func(t *testing.T, ctx context.Context, db *organizations) + }{ + {"Create", orgsCreate}, + {"GetByName", orgsGetByName}, + {"SearchByName", orgsSearchByName}, + {"List", orgsList}, + {"CountByUser", orgsCountByUser}, + {"Count", orgsCount}, + {"DeleteByID", orgsDeleteByID}, + {"AddMember", orgsAddMember}, + {"RemoveMember", orgsRemoveMember}, + {"HasMember", orgsHasMember}, + {"ListMembers", orgsListMembers}, + {"IsOwnedBy", orgsIsOwnedBy}, + {"SetMemberVisibility", orgsSetMemberVisibility}, + {"GetTeamByName", orgsGetTeamByName}, + {"AccessibleRepositoriesByUser", orgsAccessibleRepositoriesByUser}, + {"MirrorRepositoriesByUser", orgsMirrorRepositoriesByUser}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Cleanup(func() { + err := clearTables(t, db.DB, tables...) + require.NoError(t, err) + }) + tc.test(t, ctx, db) + }) + if t.Failed() { + break + } + } +} + +func orgsCreate(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + + t.Run("name not allowed", func(t *testing.T) { + _, err := db.Create(ctx, "-", alice.ID, CreateOrganizationOptions{}) + wantErr := ErrNameNotAllowed{ + args: errutil.Args{ + "reason": "reserved", + "name": "-", + }, + } + assert.Equal(t, wantErr, err) + }) + + // Users and organizations share the same namespace for names. + t.Run("name already exists", func(t *testing.T) { + _, err := db.Create(ctx, alice.Name, alice.ID, CreateOrganizationOptions{}) + wantErr := ErrOrganizationAlreadyExist{ + args: errutil.Args{ + "name": alice.Name, + }, + } + assert.Equal(t, wantErr, err) + }) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsCreate-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org, err := db.Create( + ctx, + "acme", + alice.ID, + CreateOrganizationOptions{ + FullName: "Acme Corp", + Email: "admin@acme.com", + Location: "Earth", + Website: "acme.com", + Description: "A popcorn company", + }, + ) + require.NoError(t, err) + + got, err := db.GetByName(ctx, org.Name) + require.NoError(t, err) + assert.Equal(t, org.Name, got.Name) + assert.Equal(t, org.FullName, got.FullName) + assert.Equal(t, org.Email, got.Email) + assert.Equal(t, org.Location, got.Location) + assert.Equal(t, org.Website, got.Website) + assert.Equal(t, org.Description, got.Description) + assert.Equal(t, -1, got.MaxRepoCreation) + assert.Equal(t, 1, got.NumTeams) + assert.Equal(t, 1, got.NumMembers) + assert.Equal(t, db.NowFunc().Format(time.RFC3339), got.Created.UTC().Format(time.RFC3339)) + assert.Equal(t, db.NowFunc().Format(time.RFC3339), got.Updated.UTC().Format(time.RFC3339)) +} + +func orgsGetByName(t *testing.T, ctx context.Context, db *organizations) { + t.Run("correct user type", func(t *testing.T) { + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersGetByUsername-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", 1, CreateOrganizationOptions{}) + require.NoError(t, err) + + got, err := db.GetByName(ctx, org1.Name) + require.NoError(t, err) + assert.Equal(t, org1.Name, got.Name) + + _, err = db.GetByName(ctx, "bad_name") + wantErr := ErrOrganizationNotExist{args: errutil.Args{"name": "bad_name"}} + assert.Equal(t, wantErr, err) + }) + + t.Run("wrong user type", func(t *testing.T) { + alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + + _, err = db.GetByName(ctx, alice.Name) + wantErr := ErrOrganizationNotExist{args: errutil.Args{"name": alice.Name}} + assert.Equal(t, wantErr, err) + }) +} + +func orgsList(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + bob, err := usersStore.Create(ctx, "bob", "bob@example.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsList-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1-alice-owned", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + org2, err := db.Create(ctx, "org2-alice-owned", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + err = db.SetMemberVisibility(ctx, org2.ID, alice.ID, true) + require.NoError(t, err) + err = db.AddMember(ctx, org2.ID, bob.ID) + require.NoError(t, err) + + org3, err := db.Create(ctx, "org3-bob-owned", bob.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + tests := []struct { + name string + opts ListOrganizationsOptions + wantOrgNames []string + }{ + { + name: "only public memberships for a user", + opts: ListOrganizationsOptions{ + MemberID: alice.ID, + IncludePrivateMembers: false, + }, + wantOrgNames: []string{org2.Name}, + }, + { + name: "all memberships for a user", + opts: ListOrganizationsOptions{ + MemberID: alice.ID, + IncludePrivateMembers: true, + }, + wantOrgNames: []string{org1.Name, org2.Name}, + }, + { + name: "only public ownership for a user", + opts: ListOrganizationsOptions{ + OwnerID: alice.ID, + IncludePrivateMembers: false, + }, + wantOrgNames: []string{org2.Name}, + }, + { + name: "all ownership for a user", + opts: ListOrganizationsOptions{ + OwnerID: alice.ID, + IncludePrivateMembers: true, + }, + wantOrgNames: []string{org1.Name, org2.Name}, + }, + { + name: "no membership for a non-existent user", + opts: ListOrganizationsOptions{ + MemberID: 404, + IncludePrivateMembers: true, + }, + wantOrgNames: []string{}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := db.List(ctx, test.opts) + require.NoError(t, err) + + gotOrgNames := make([]string, len(got)) + for i := range got { + gotOrgNames[i] = got[i].Name + } + assert.Equal(t, test.wantOrgNames, gotOrgNames) + }) + } + + t.Run("pagination", func(t *testing.T) { + got, err := db.List(ctx, ListOrganizationsOptions{Page: 1, PageSize: 1}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, org1.ID, got[0].ID) + + got, err = db.List(ctx, ListOrganizationsOptions{Page: 2, PageSize: 1}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, org2.ID, got[0].ID) + + got, err = db.List(ctx, ListOrganizationsOptions{Page: 1, PageSize: 4}) + require.NoError(t, err) + require.Len(t, got, 3) + assert.Equal(t, org1.ID, got[0].ID) + assert.Equal(t, org2.ID, got[1].ID) + assert.Equal(t, org3.ID, got[2].ID) + }) +} + +func orgsSearchByName(t *testing.T, ctx context.Context, db *organizations) { + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsSearchByName-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + tempRepositoryRoot := filepath.Join(os.TempDir(), "orgsSearchByName-tempRepositoryRoot") + conf.SetMockRepository(t, conf.RepositoryOpts{Root: tempRepositoryRoot}) + + org1, err := db.Create(ctx, "org1", 1, CreateOrganizationOptions{FullName: "Acme Corp"}) + require.NoError(t, err) + org2, err := db.Create(ctx, "org2", 1, CreateOrganizationOptions{FullName: "Acme Corp 2"}) + require.NoError(t, err) + + t.Run("search for username org1", func(t *testing.T) { + orgs, count, err := db.SearchByName(ctx, "G1", 1, 1, "") + require.NoError(t, err) + require.Len(t, orgs, int(count)) + assert.Equal(t, int64(1), count) + assert.Equal(t, org1.ID, orgs[0].ID) + }) + + t.Run("search for username org2", func(t *testing.T) { + orgs, count, err := db.SearchByName(ctx, "G2", 1, 1, "") + require.NoError(t, err) + require.Len(t, orgs, int(count)) + assert.Equal(t, int64(1), count) + assert.Equal(t, org2.ID, orgs[0].ID) + }) + + t.Run("search for full name acme", func(t *testing.T) { + orgs, count, err := db.SearchByName(ctx, "ACME", 1, 10, "") + require.NoError(t, err) + require.Len(t, orgs, int(count)) + assert.Equal(t, int64(2), count) + }) + + t.Run("search for full name acme ORDER BY id DESC LIMIT 1", func(t *testing.T) { + orgs, count, err := db.SearchByName(ctx, "ACME", 1, 1, "id DESC") + require.NoError(t, err) + require.Len(t, orgs, 1) + assert.Equal(t, int64(2), count) + assert.Equal(t, org2.ID, orgs[0].ID) + }) +} + +func orgsCountByUser(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + bob, err := usersStore.Create(ctx, "bob", "bob@example.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsCountByUser-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + err = db.AddMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + + got, err := db.CountByUser(ctx, alice.ID) + require.NoError(t, err) + assert.Equal(t, int64(1), got) + + got, err = db.CountByUser(ctx, 404) + require.NoError(t, err) + assert.Equal(t, int64(0), got) +} + +func orgsCount(t *testing.T, ctx context.Context, db *organizations) { + // Has no organization initially + got := db.Count(ctx) + assert.Equal(t, int64(0), got) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersCount-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + _, err := db.Create(ctx, "org1", 1, CreateOrganizationOptions{}) + require.NoError(t, err) + + // Create a user shouldn't count + _, err = NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + got = db.Count(ctx) + assert.Equal(t, int64(1), got) +} + +func orgsDeleteByID(t *testing.T, ctx context.Context, db *organizations) { + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsDeleteByID-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + t.Run("organization still has repository ownership", func(t *testing.T) { + org1, err := db.Create(ctx, "org1", 404, CreateOrganizationOptions{}) + require.NoError(t, err) + + _, err = NewRepositoriesStore(db.DB).Create(ctx, org1.ID, CreateRepoOptions{Name: "repo1"}) + require.NoError(t, err) + + err = db.DeleteByID(ctx, org1.ID) + wantErr := ErrOrganizationOwnRepos{errutil.Args{"orgID": org1.ID}} + assert.Equal(t, wantErr, err) + }) + + alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + org2, err := db.Create(ctx, "org2", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + // Mock team membership + // TODO: Use Organizations.CreateTeam to replace SQL hack when the method is available. + team1 := &Team{ + OrgID: org2.ID, + LowerName: "team1", + Name: "team1", + NumMembers: 1, + } + err = db.DB.Create(team1).Error + require.NoError(t, err) + // TODO: Use Organizations.AddTeamMember to replace SQL hack when the method is available. + err = db.DB.Create( + &TeamUser{ + OrgID: org2.ID, + TeamID: team1.ID, + UID: alice.ID, + }, + ).Error + require.NoError(t, err) + + // Pull the trigger + err = db.DeleteByID(ctx, org2.ID) + require.NoError(t, err) + + // Verify after-the-fact data + for _, table := range []any{ + &Team{OrgID: org2.ID}, + &TeamUser{OrgID: org2.ID}, + } { + var count int64 + err = db.DB.Model(table).Where(table).Count(&count).Error + require.NoError(t, err, "table for %T", table) + assert.Equal(t, int64(0), count, "table for %T", table) + } + + _, err = db.GetByName(ctx, org2.Name) + wantErr := ErrOrganizationNotExist{errutil.Args{"name": org2.Name}} + assert.Equal(t, wantErr, err) +} + +func orgsAddMember(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + bob, err := usersStore.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsAddMember-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + // Not yet a member + gotOrgs, err := db.List(ctx, ListOrganizationsOptions{MemberID: bob.ID, IncludePrivateMembers: true}) + require.NoError(t, err) + assert.Len(t, gotOrgs, 0) + + // Add member + err = db.AddMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + + // Now a member + gotOrgs, err = db.List(ctx, ListOrganizationsOptions{MemberID: bob.ID, IncludePrivateMembers: true}) + require.NoError(t, err) + assert.Len(t, gotOrgs, 1) + assert.Equal(t, org1.ID, gotOrgs[0].ID) + + // Add member again shouldn't fail + err = db.AddMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + + gotOrg, err := db.GetByName(ctx, org1.Name) + require.NoError(t, err) + assert.Equal(t, 2, gotOrg.NumMembers) +} + +func orgsRemoveMember(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + bob, err := usersStore.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsRemoveMember-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + t.Run("remove non-existent member", func(t *testing.T) { + err = db.RemoveMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + }) + + t.Run("remove last owner", func(t *testing.T) { + err = db.RemoveMember(ctx, org1.ID, alice.ID) + wantErr := ErrLastOrgOwner{errutil.Args{"orgID": org1.ID, "userID": alice.ID}} + assert.Equal(t, wantErr, err) + }) + + err = db.AddMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + + // Mock repository, watches and collaborations + reposStore := NewRepositoriesStore(db.DB) + repo1, err := reposStore.Create(ctx, org1.ID, CreateRepoOptions{Name: "repo1", Private: true}) + require.NoError(t, err) + // TODO: Use Repositories.AddCollaborator to replace SQL hack when the method is available. + err = db.DB.Create( + &Collaboration{ + UserID: bob.ID, + RepoID: repo1.ID, + Mode: AccessModeRead, + }, + ).Error + require.NoError(t, err) + + // Mock team membership + // TODO: Use Organizations.CreateTeam to replace SQL hack when the method is available. + team1 := &Team{ + OrgID: org1.ID, + LowerName: "team1", + Name: "team1", + NumMembers: 1, + } + err = db.DB.Create(team1).Error + require.NoError(t, err) + // TODO: Use Organizations.AddTeamMember to replace SQL hack when the method is available. + err = db.DB.Create( + &TeamUser{ + OrgID: org1.ID, + TeamID: team1.ID, + UID: bob.ID, + }, + ).Error + require.NoError(t, err) + // TODO: Use Organizations.AddTeamRepository to replace SQL hack when the method is available. + err = db.DB.Create( + &TeamRepo{ + OrgID: org1.ID, + TeamID: team1.ID, + RepoID: repo1.ID, + }, + ).Error + require.NoError(t, err) + + // Mock accesses + permsStore := NewPermsStore(db.DB) + err = permsStore.SetRepoPerms(ctx, repo1.ID, map[int64]AccessMode{bob.ID: AccessModeRead}) + require.NoError(t, err) + err = reposStore.Watch( + ctx, + WatchRepositoryOptions{ + UserID: bob.ID, + RepoID: repo1.ID, + RepoOwnerID: repo1.OwnerID, + RepoIsPrivate: repo1.IsPrivate, + }, + ) + require.NoError(t, err) + + // Pull the trigger + err = db.RemoveMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + + // Verify after-the-fact data + gotRepo, err := reposStore.GetByID(ctx, repo1.ID) + require.NoError(t, err) + assert.Equal(t, 1, gotRepo.NumWatches) + + gotAccessMode := permsStore.AccessMode(ctx, repo1.ID, bob.ID, AccessModeOptions{Private: repo1.IsPrivate}) + assert.Equal(t, AccessModeNone, gotAccessMode) + + // TODO: Use Repositories.ListCollaborators to replace SQL hack when the method is available. + var count int64 + err = db.DB.Model(&Collaboration{}).Where(&Collaboration{RepoID: repo1.ID}).Count(&count).Error + require.NoError(t, err) + assert.Equal(t, int64(0), count) + + gotTeam, err := db.GetTeamByName(ctx, org1.ID, team1.Name) + require.NoError(t, err) + assert.Equal(t, 0, gotTeam.NumMembers) + + gotOrg, err := db.GetByName(ctx, org1.Name) + require.NoError(t, err) + assert.Equal(t, 1, gotOrg.NumMembers) +} + +func orgsHasMember(t *testing.T, ctx context.Context, db *organizations) { + got, _ := db.HasMember(ctx, 1, 1) + assert.False(t, got) + + err := db.AddMember(ctx, 1, 1) + require.NoError(t, err) + + got, _ = db.HasMember(ctx, 1, 1) + assert.True(t, got) +} + +func orgsListMembers(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + bob, err := usersStore.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsListMembers-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + err = db.AddMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + + got, err := db.ListMembers(ctx, org1.ID, ListOrgMembersOptions{Limit: 1}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, alice.ID, got[0].ID) + + got, err = db.ListMembers(ctx, org1.ID, ListOrgMembersOptions{}) + require.NoError(t, err) + require.Len(t, got, 2) + assert.Equal(t, alice.ID, got[0].ID) + assert.Equal(t, bob.ID, got[1].ID) +} + +func orgsIsOwnedBy(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + bob, err := usersStore.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) + require.NoError(t, err) + cindy, err := usersStore.Create(ctx, "cindy", "cindy@exmaple.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsIsOwnedBy-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + err = db.AddMember(ctx, org1.ID, bob.ID) + require.NoError(t, err) + + got := db.IsOwnedBy(ctx, org1.ID, alice.ID) + assert.True(t, got) + got = db.IsOwnedBy(ctx, org1.ID, bob.ID) + assert.False(t, got) + got = db.IsOwnedBy(ctx, org1.ID, cindy.ID) + assert.False(t, got) +} + +func orgsSetMemberVisibility(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsSetMemberVisibility-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + got, err := db.List(ctx, ListOrganizationsOptions{MemberID: alice.ID}) + require.NoError(t, err) + assert.Len(t, got, 0) + + err = db.SetMemberVisibility(ctx, org1.ID, alice.ID, true) + require.NoError(t, err) + got, err = db.List(ctx, ListOrganizationsOptions{MemberID: alice.ID}) + require.NoError(t, err) + assert.Len(t, got, 1) +} + +func orgsGetTeamByName(t *testing.T, ctx context.Context, db *organizations) { + usersStore := NewUsersStore(db.DB) + alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) + require.NoError(t, err) + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "orgsGetTeamByName-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := db.Create(ctx, "org1", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + t.Run("non-existent team", func(t *testing.T) { + _, err := db.GetTeamByName(ctx, org1.ID, "non-existent") + wantErr := ErrTeamNotExist{errutil.Args{"orgID": org1.ID, "name": "non-existent"}} + assert.Equal(t, wantErr, err) + }) + + t.Run("team of another organization", func(t *testing.T) { + org2, err := db.Create(ctx, "org2", alice.ID, CreateOrganizationOptions{}) + require.NoError(t, err) + + // TODO: Use Organizations.CreateTeam to replace SQL hack when the method is available. + team1 := &Team{ + OrgID: org2.ID, + LowerName: "team1", + Name: "team1", + NumMembers: 1, + } + err = db.DB.Create(team1).Error + require.NoError(t, err) + + _, err = db.GetTeamByName(ctx, org1.ID, team1.Name) + wantErr := ErrTeamNotExist{errutil.Args{"orgID": org1.ID, "name": team1.Name}} + assert.Equal(t, wantErr, err) + }) + + _, err = db.GetTeamByName(ctx, org1.ID, TeamNameOwners) + require.NoError(t, err) +} + +func orgsAccessibleRepositoriesByUser(t *testing.T, ctx context.Context, db *organizations) { + // todo +} + +func orgsMirrorRepositoriesByUser(t *testing.T, ctx context.Context, db *organizations) { + // todo +} diff --git a/internal/db/orgs.go b/internal/db/orgs.go deleted file mode 100644 index 753d81209a5..00000000000 --- a/internal/db/orgs.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2022 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 db - -import ( - "context" - - "github.com/pkg/errors" - "gorm.io/gorm" - - "gogs.io/gogs/internal/dbutil" -) - -// OrgsStore is the persistent interface for organizations. -type OrgsStore interface { - // List returns a list of organizations filtered by options. - List(ctx context.Context, opts ListOrgsOptions) ([]*Organization, error) - // SearchByName returns a list of organizations whose username or full name - // matches the given keyword case-insensitively. Results are paginated by given - // page and page size, and sorted by the given order (e.g. "id DESC"). A total - // count of all results is also returned. If the order is not given, it's up to - // the database to decide. - SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error) - - // CountByUser returns the number of organizations the user is a member of. - CountByUser(ctx context.Context, userID int64) (int64, error) -} - -var Orgs OrgsStore - -var _ OrgsStore = (*orgs)(nil) - -type orgs struct { - *gorm.DB -} - -// NewOrgsStore returns a persistent interface for orgs with given database -// connection. -func NewOrgsStore(db *gorm.DB) OrgsStore { - return &orgs{DB: db} -} - -type ListOrgsOptions struct { - // Filter by the membership with the given user ID. - MemberID int64 - // Whether to include private memberships. - IncludePrivateMembers bool -} - -func (db *orgs) List(ctx context.Context, opts ListOrgsOptions) ([]*Organization, error) { - if opts.MemberID <= 0 { - return nil, errors.New("MemberID must be greater than 0") - } - - /* - Equivalent SQL for PostgreSQL: - - SELECT * FROM "org" - JOIN org_user ON org_user.org_id = org.id - WHERE - org_user.uid = @memberID - [AND org_user.is_public = @includePrivateMembers] - ORDER BY org.id ASC - */ - tx := db.WithContext(ctx). - Joins(dbutil.Quote("JOIN org_user ON org_user.org_id = %s.id", "user")). - Where("org_user.uid = ?", opts.MemberID). - Order(dbutil.Quote("%s.id ASC", "user")) - if !opts.IncludePrivateMembers { - tx = tx.Where("org_user.is_public = ?", true) - } - - var orgs []*Organization - return orgs, tx.Find(&orgs).Error -} - -func (db *orgs) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error) { - return searchUserByName(ctx, db.DB, UserTypeOrganization, keyword, page, pageSize, orderBy) -} - -func (db *orgs) CountByUser(ctx context.Context, userID int64) (int64, error) { - var count int64 - return count, db.WithContext(ctx).Model(&OrgUser{}).Where("uid = ?", userID).Count(&count).Error -} - -type Organization = User - -func (o *Organization) TableName() string { - return "user" -} diff --git a/internal/db/orgs_test.go b/internal/db/orgs_test.go deleted file mode 100644 index 64831c8a784..00000000000 --- a/internal/db/orgs_test.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2022 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 db - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "gogs.io/gogs/internal/dbtest" - "gogs.io/gogs/internal/dbutil" -) - -func TestOrgs(t *testing.T) { - if testing.Short() { - t.Skip() - } - t.Parallel() - - ctx := context.Background() - tables := []any{new(User), new(EmailAddress), new(OrgUser)} - db := &orgs{ - DB: dbtest.NewDB(t, "orgs", tables...), - } - - for _, tc := range []struct { - name string - test func(t *testing.T, ctx context.Context, db *orgs) - }{ - {"List", orgsList}, - {"SearchByName", orgsSearchByName}, - {"CountByUser", orgsCountByUser}, - } { - t.Run(tc.name, func(t *testing.T) { - t.Cleanup(func() { - err := clearTables(t, db.DB, tables...) - require.NoError(t, err) - }) - tc.test(t, ctx, db) - }) - if t.Failed() { - break - } - } -} - -func orgsList(t *testing.T, ctx context.Context, db *orgs) { - usersStore := NewUsersStore(db.DB) - alice, err := usersStore.Create(ctx, "alice", "alice@example.com", CreateUserOptions{}) - require.NoError(t, err) - bob, err := usersStore.Create(ctx, "bob", "bob@example.com", CreateUserOptions{}) - require.NoError(t, err) - - // TODO: Use Orgs.Create to replace SQL hack when the method is available. - org1, err := usersStore.Create(ctx, "org1", "org1@example.com", CreateUserOptions{}) - require.NoError(t, err) - org2, err := usersStore.Create(ctx, "org2", "org2@example.com", CreateUserOptions{}) - require.NoError(t, err) - err = db.Exec( - dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?, ?)", "user"), - UserTypeOrganization, org1.ID, org2.ID, - ).Error - require.NoError(t, err) - - // TODO: Use Orgs.Join to replace SQL hack when the method is available. - err = db.Exec(`INSERT INTO org_user (uid, org_id, is_public) VALUES (?, ?, ?)`, alice.ID, org1.ID, false).Error - require.NoError(t, err) - err = db.Exec(`INSERT INTO org_user (uid, org_id, is_public) VALUES (?, ?, ?)`, alice.ID, org2.ID, true).Error - require.NoError(t, err) - err = db.Exec(`INSERT INTO org_user (uid, org_id, is_public) VALUES (?, ?, ?)`, bob.ID, org2.ID, true).Error - require.NoError(t, err) - - tests := []struct { - name string - opts ListOrgsOptions - wantOrgNames []string - }{ - { - name: "only public memberships for a user", - opts: ListOrgsOptions{ - MemberID: alice.ID, - IncludePrivateMembers: false, - }, - wantOrgNames: []string{org2.Name}, - }, - { - name: "all memberships for a user", - opts: ListOrgsOptions{ - MemberID: alice.ID, - IncludePrivateMembers: true, - }, - wantOrgNames: []string{org1.Name, org2.Name}, - }, - { - name: "no membership for a non-existent user", - opts: ListOrgsOptions{ - MemberID: 404, - IncludePrivateMembers: true, - }, - wantOrgNames: []string{}, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - got, err := db.List(ctx, test.opts) - require.NoError(t, err) - - gotOrgNames := make([]string, len(got)) - for i := range got { - gotOrgNames[i] = got[i].Name - } - assert.Equal(t, test.wantOrgNames, gotOrgNames) - }) - } -} - -func orgsSearchByName(t *testing.T, ctx context.Context, db *orgs) { - // TODO: Use Orgs.Create to replace SQL hack when the method is available. - usersStore := NewUsersStore(db.DB) - org1, err := usersStore.Create(ctx, "org1", "org1@example.com", CreateUserOptions{FullName: "Acme Corp"}) - require.NoError(t, err) - org2, err := usersStore.Create(ctx, "org2", "org2@example.com", CreateUserOptions{FullName: "Acme Corp 2"}) - require.NoError(t, err) - err = db.Exec( - dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?, ?)", "user"), - UserTypeOrganization, org1.ID, org2.ID, - ).Error - require.NoError(t, err) - - t.Run("search for username org1", func(t *testing.T) { - orgs, count, err := db.SearchByName(ctx, "G1", 1, 1, "") - require.NoError(t, err) - require.Len(t, orgs, int(count)) - assert.Equal(t, int64(1), count) - assert.Equal(t, org1.ID, orgs[0].ID) - }) - - t.Run("search for username org2", func(t *testing.T) { - orgs, count, err := db.SearchByName(ctx, "G2", 1, 1, "") - require.NoError(t, err) - require.Len(t, orgs, int(count)) - assert.Equal(t, int64(1), count) - assert.Equal(t, org2.ID, orgs[0].ID) - }) - - t.Run("search for full name acme", func(t *testing.T) { - orgs, count, err := db.SearchByName(ctx, "ACME", 1, 10, "") - require.NoError(t, err) - require.Len(t, orgs, int(count)) - assert.Equal(t, int64(2), count) - }) - - t.Run("search for full name acme ORDER BY id DESC LIMIT 1", func(t *testing.T) { - orgs, count, err := db.SearchByName(ctx, "ACME", 1, 1, "id DESC") - require.NoError(t, err) - require.Len(t, orgs, 1) - assert.Equal(t, int64(2), count) - assert.Equal(t, org2.ID, orgs[0].ID) - }) -} - -func orgsCountByUser(t *testing.T, ctx context.Context, db *orgs) { - // TODO: Use Orgs.Join to replace SQL hack when the method is available. - err := db.Exec(`INSERT INTO org_user (uid, org_id) VALUES (?, ?)`, 1, 1).Error - require.NoError(t, err) - err = db.Exec(`INSERT INTO org_user (uid, org_id) VALUES (?, ?)`, 2, 1).Error - require.NoError(t, err) - - got, err := db.CountByUser(ctx, 1) - require.NoError(t, err) - assert.Equal(t, int64(1), got) - - got, err = db.CountByUser(ctx, 404) - require.NoError(t, err) - assert.Equal(t, int64(0), got) -} diff --git a/internal/db/repo.go b/internal/db/repo.go index 8f15ea2b96e..a878344e032 100644 --- a/internal/db/repo.go +++ b/internal/db/repo.go @@ -798,7 +798,7 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository, wikiPath := WikiPath(owner.Name, opts.Name) if owner.IsOrganization() { - t, err := owner.GetOwnerTeam() + t, err := Organizations.GetTeamByName(context.TODO(), owner.ID, TeamNameOwners) if err != nil { return nil, err } @@ -1135,7 +1135,7 @@ func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{args: errutil.Args{"ownerID": owner.ID, "name": repo.Name}} + return ErrRepositoryAlreadyExist{args: errutil.Args{"ownerID": owner.ID, "name": repo.Name}} } if _, err = e.Insert(repo); err != nil { @@ -1149,7 +1149,24 @@ func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err // Give access to all members in owner team. if owner.IsOrganization() { - t, err := owner.getOwnerTeam(e) + // FIXME: This is identical to Orgs.GetTeamByName but we are not yet able to + // wrap transaction with different ORM objects, should delete this once migrated + // to GORM for this part of logic. + getTeamOfOrgByName := func(e 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{args: map[string]any{"orgID": orgID, "name": name}} + } + return t, nil + } + + t, err := getTeamOfOrgByName(e, owner.ID, TeamNameOwners) if err != nil { return fmt.Errorf("getOwnerTeam: %v", err) } else if err = t.addRepository(e, repo); err != nil { @@ -1294,14 +1311,14 @@ func CountUserRepositories(userID int64, private bool) int64 { return countRepositories(userID, private) } -func Repositories(page, pageSize int) (_ []*Repository, err error) { +func ListRepositories(page, pageSize int) (_ []*Repository, err error) { repos := make([]*Repository, 0, pageSize) return repos, x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos) } // RepositoriesWithUsers returns number of repos in given page. func RepositoriesWithUsers(page, pageSize int) (_ []*Repository, err error) { - repos, err := Repositories(page, pageSize) + repos, err := ListRepositories(page, pageSize) if err != nil { return nil, fmt.Errorf("Repositories: %v", err) } @@ -1362,7 +1379,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{args: errutil.Args{"ownerName": newOwnerName, "name": repo.Name}} + return ErrRepositoryAlreadyExist{args: errutil.Args{"ownerName": newOwnerName, "name": repo.Name}} } sess := x.NewSession() @@ -1393,7 +1410,8 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error collaboration := &Collaboration{RepoID: repo.ID} for _, c := range collaborators { collaboration.UserID = c.ID - if c.ID == newOwner.ID || newOwner.IsOrgMember(c.ID) { + if c.ID == newOwner.ID || + (newOwner.IsOrganization() && func() bool { member, _ := Organizations.HasMember(context.TODO(), newOwner.ID, c.ID); return member }()) { if _, err = sess.Delete(collaboration); err != nil { return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) } @@ -1416,13 +1434,34 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error } } - if err = owner.removeOrgRepo(sess, repo.ID); err != nil { + _, err := sess.Delete(&TeamRepo{ + OrgID: owner.ID, + RepoID: repo.ID, + }) + if err != nil { return fmt.Errorf("removeOrgRepo: %v", err) } } if newOwner.IsOrganization() { - t, err := newOwner.getOwnerTeam(sess) + // FIXME: This is identical to Orgs.GetTeamByName but we are not yet able to + // wrap transaction with different ORM objects, should delete this once migrated + // to GORM for this part of logic. + getTeamOfOrgByName := func(e 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{args: map[string]any{"orgID": orgID, "name": name}} + } + return t, nil + } + + t, err := getTeamOfOrgByName(sess, owner.ID, TeamNameOwners) if err != nil { return fmt.Errorf("getOwnerTeam: %v", err) } else if err = t.addRepository(sess, repo); err != nil { @@ -1513,7 +1552,7 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{args: errutil.Args{"ownerID": u.ID, "name": newRepoName}} + return ErrRepositoryAlreadyExist{args: errutil.Args{"ownerID": u.ID, "name": newRepoName}} } repo, err := GetRepositoryByName(u.ID, oldRepoName) @@ -1752,6 +1791,16 @@ func DeleteRepository(ownerID, repoID int64) error { return nil } +// deleteBeans deletes all given beans, beans should contain delete conditions. +func deleteBeans(e Engine, beans ...any) (err error) { + for i := range beans { + if _, err = e.Delete(beans[i]); err != nil { + return err + } + } + return nil +} + // GetRepositoryByRef returns a Repository specified by a GFM reference. // See https://help.github.com/articles/writing-on-github#references for more information on the syntax. func GetRepositoryByRef(ref string) (*Repository, error) { diff --git a/internal/db/repos.go b/internal/db/repositories.go similarity index 77% rename from internal/db/repos.go rename to internal/db/repositories.go index 28a38148c74..3c03034b6fe 100644 --- a/internal/db/repos.go +++ b/internal/db/repositories.go @@ -18,11 +18,11 @@ import ( "gogs.io/gogs/internal/repoutil" ) -// ReposStore is the persistent interface for repositories. -type ReposStore interface { +// RepositoriesStore is the persistent interface for repositories. +type RepositoriesStore interface { // Create creates a new repository record in the database. It returns // ErrNameNotAllowed when the repository name is not allowed, or - // ErrRepoAlreadyExist when a repository with same name already exists for the + // ErrRepositoryAlreadyExist when a repository with same name already exists for the // owner. Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error) // GetByCollaboratorID returns a list of repositories that the given @@ -50,13 +50,13 @@ type ReposStore interface { // ListWatches returns all watches of the given repository. ListWatches(ctx context.Context, repoID int64) ([]*Watch, error) // Watch marks the user to watch the repository. - Watch(ctx context.Context, userID, repoID int64) error + Watch(ctx context.Context, opts WatchRepositoryOptions) error // HasForkedBy returns true if the given repository has forked by the given user. HasForkedBy(ctx context.Context, repoID, userID int64) bool } -var Repos ReposStore +var Repositories RepositoriesStore // BeforeCreate implements the GORM create hook. func (r *Repository) BeforeCreate(tx *gorm.DB) error { @@ -119,28 +119,27 @@ func (r *Repository) APIFormat(owner *User, opts ...RepositoryAPIFormatOptions) } } -var _ ReposStore = (*repos)(nil) +var _ RepositoriesStore = (*repositories)(nil) -type repos struct { +type repositories struct { *gorm.DB } -// NewReposStore returns a persistent interface for repositories with given +// NewRepositoriesStore returns a persistent interface for repositories with given // database connection. -func NewReposStore(db *gorm.DB) ReposStore { - return &repos{DB: db} +func NewRepositoriesStore(db *gorm.DB) RepositoriesStore { + return &repositories{DB: db} } -type ErrRepoAlreadyExist struct { +type ErrRepositoryAlreadyExist struct { args errutil.Args } func IsErrRepoAlreadyExist(err error) bool { - _, ok := err.(ErrRepoAlreadyExist) - return ok + return errors.As(err, &ErrRepositoryAlreadyExist{}) } -func (err ErrRepoAlreadyExist) Error() string { +func (err ErrRepositoryAlreadyExist) Error() string { return fmt.Sprintf("repository already exists: %v", err.args) } @@ -157,7 +156,7 @@ type CreateRepoOptions struct { ForkID int64 } -func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error) { +func (db *repositories) Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error) { err := isRepoNameAllowed(opts.Name) if err != nil { return nil, err @@ -165,7 +164,7 @@ func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptio _, err = db.GetByName(ctx, ownerID, opts.Name) if err == nil { - return nil, ErrRepoAlreadyExist{ + return nil, ErrRepositoryAlreadyExist{ args: errutil.Args{ "ownerID": ownerID, "name": opts.Name, @@ -195,7 +194,15 @@ func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptio return errors.Wrap(err, "create") } - err = NewReposStore(tx).Watch(ctx, ownerID, repo.ID) + err = NewRepositoriesStore(tx).Watch( + ctx, + WatchRepositoryOptions{ + UserID: ownerID, + RepoID: repo.ID, + RepoOwnerID: ownerID, + RepoIsPrivate: repo.IsPrivate, + }, + ) if err != nil { return errors.Wrap(err, "watch") } @@ -203,7 +210,7 @@ func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptio }) } -func (db *repos) GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error) { +func (db *repositories) GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error) { /* Equivalent SQL for PostgreSQL: @@ -223,7 +230,7 @@ func (db *repos) GetByCollaboratorID(ctx context.Context, collaboratorID int64, Error } -func (db *repos) GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error) { +func (db *repositories) GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error) { /* Equivalent SQL for PostgreSQL: @@ -263,8 +270,7 @@ type ErrRepoNotExist struct { } func IsErrRepoNotExist(err error) bool { - _, ok := err.(ErrRepoNotExist) - return ok + return errors.As(err, &ErrRepoNotExist{}) } func (err ErrRepoNotExist) Error() string { @@ -275,11 +281,11 @@ func (ErrRepoNotExist) NotFound() bool { return true } -func (db *repos) GetByID(ctx context.Context, id int64) (*Repository, error) { +func (db *repositories) GetByID(ctx context.Context, id int64) (*Repository, error) { repo := new(Repository) err := db.WithContext(ctx).Where("id = ?", id).First(repo).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrRepoNotExist{errutil.Args{"repoID": id}} } return nil, err @@ -287,14 +293,14 @@ func (db *repos) GetByID(ctx context.Context, id int64) (*Repository, error) { return repo, nil } -func (db *repos) GetByName(ctx context.Context, ownerID int64, name string) (*Repository, error) { +func (db *repositories) GetByName(ctx context.Context, ownerID int64, name string) (*Repository, error) { repo := new(Repository) err := db.WithContext(ctx). Where("owner_id = ? AND lower_name = ?", ownerID, strings.ToLower(name)). First(repo). Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrRepoNotExist{ args: errutil.Args{ "ownerID": ownerID, @@ -307,7 +313,7 @@ func (db *repos) GetByName(ctx context.Context, ownerID int64, name string) (*Re return repo, nil } -func (db *repos) recountStars(tx *gorm.DB, userID, repoID int64) error { +func (db *repositories) recountStars(tx *gorm.DB, userID, repoID int64) error { /* Equivalent SQL for PostgreSQL: @@ -350,7 +356,7 @@ func (db *repos) recountStars(tx *gorm.DB, userID, repoID int64) error { return nil } -func (db *repos) Star(ctx context.Context, userID, repoID int64) error { +func (db *repositories) Star(ctx context.Context, userID, repoID int64) error { return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { s := &Star{ UserID: userID, @@ -367,7 +373,7 @@ func (db *repos) Star(ctx context.Context, userID, repoID int64) error { }) } -func (db *repos) Touch(ctx context.Context, id int64) error { +func (db *repositories) Touch(ctx context.Context, id int64) error { return db.WithContext(ctx). Model(new(Repository)). Where("id = ?", id). @@ -378,12 +384,12 @@ func (db *repos) Touch(ctx context.Context, id int64) error { Error } -func (db *repos) ListWatches(ctx context.Context, repoID int64) ([]*Watch, error) { +func (db *repositories) ListWatches(ctx context.Context, repoID int64) ([]*Watch, error) { var watches []*Watch return watches, db.WithContext(ctx).Where("repo_id = ?", repoID).Find(&watches).Error } -func (db *repos) recountWatches(tx *gorm.DB, repoID int64) error { +func (db *repositories) recountWatches(tx *gorm.DB, repoID int64) error { /* Equivalent SQL for PostgreSQL: @@ -402,11 +408,34 @@ func (db *repos) recountWatches(tx *gorm.DB, repoID int64) error { Error } -func (db *repos) Watch(ctx context.Context, userID, repoID int64) error { +type WatchRepositoryOptions struct { + UserID int64 + RepoID int64 + RepoOwnerID int64 + RepoIsPrivate bool +} + +func (db *repositories) Watch(ctx context.Context, opts WatchRepositoryOptions) error { + // Make sure the user has access to the private repository + if opts.RepoIsPrivate && + opts.UserID != opts.RepoOwnerID && + !NewPermsStore(db.DB).Authorize( + ctx, + opts.UserID, + opts.RepoID, + AccessModeRead, + AccessModeOptions{ + OwnerID: opts.RepoOwnerID, + Private: true, + }, + ) { + return errors.New("user does not have access to the repository") + } + return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { w := &Watch{ - UserID: userID, - RepoID: repoID, + UserID: opts.UserID, + RepoID: opts.RepoID, } result := tx.FirstOrCreate(w, w) if result.Error != nil { @@ -415,11 +444,11 @@ func (db *repos) Watch(ctx context.Context, userID, repoID int64) error { return nil // Relation already exists } - return db.recountWatches(tx, repoID) + return db.recountWatches(tx, opts.RepoID) }) } -func (db *repos) HasForkedBy(ctx context.Context, repoID, userID int64) bool { +func (db *repositories) HasForkedBy(ctx context.Context, repoID, userID int64) bool { var count int64 db.WithContext(ctx).Model(new(Repository)).Where("owner_id = ? AND fork_id = ?", userID, repoID).Count(&count) return count > 0 diff --git a/internal/db/repos_test.go b/internal/db/repositories_test.go similarity index 78% rename from internal/db/repos_test.go rename to internal/db/repositories_test.go index 7bb74f88ae8..687b916ee95 100644 --- a/internal/db/repos_test.go +++ b/internal/db/repositories_test.go @@ -87,13 +87,13 @@ func TestRepos(t *testing.T) { ctx := context.Background() tables := []any{new(Repository), new(Access), new(Watch), new(User), new(EmailAddress), new(Star)} - db := &repos{ + db := &repositories{ DB: dbtest.NewDB(t, "repos", tables...), } for _, tc := range []struct { name string - test func(t *testing.T, ctx context.Context, db *repos) + test func(t *testing.T, ctx context.Context, db *repositories) }{ {"Create", reposCreate}, {"GetByCollaboratorID", reposGetByCollaboratorID}, @@ -119,7 +119,7 @@ func TestRepos(t *testing.T) { } } -func reposCreate(t *testing.T, ctx context.Context, db *repos) { +func reposCreate(t *testing.T, ctx context.Context, db *repositories) { t.Run("name not allowed", func(t *testing.T) { _, err := db.Create(ctx, 1, @@ -144,7 +144,7 @@ func reposCreate(t *testing.T, ctx context.Context, db *repos) { Name: "repo1", }, ) - wantErr := ErrRepoAlreadyExist{args: errutil.Args{"ownerID": int64(2), "name": "repo1"}} + wantErr := ErrRepositoryAlreadyExist{args: errutil.Args{"ownerID": int64(2), "name": "repo1"}} assert.Equal(t, wantErr, err) }) @@ -161,7 +161,7 @@ func reposCreate(t *testing.T, ctx context.Context, db *repos) { assert.Equal(t, 1, repo.NumWatches) // The owner is watching the repo by default. } -func reposGetByCollaboratorID(t *testing.T, ctx context.Context, db *repos) { +func reposGetByCollaboratorID(t *testing.T, ctx context.Context, db *repositories) { repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"}) require.NoError(t, err) repo2, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo2"}) @@ -187,7 +187,7 @@ func reposGetByCollaboratorID(t *testing.T, ctx context.Context, db *repos) { }) } -func reposGetByCollaboratorIDWithAccessMode(t *testing.T, ctx context.Context, db *repos) { +func reposGetByCollaboratorIDWithAccessMode(t *testing.T, ctx context.Context, db *repositories) { repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"}) require.NoError(t, err) repo2, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo2"}) @@ -215,7 +215,7 @@ func reposGetByCollaboratorIDWithAccessMode(t *testing.T, ctx context.Context, d assert.Equal(t, AccessModeAdmin, accessModes[repo2.ID]) } -func reposGetByID(t *testing.T, ctx context.Context, db *repos) { +func reposGetByID(t *testing.T, ctx context.Context, db *repositories) { repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"}) require.NoError(t, err) @@ -228,7 +228,7 @@ func reposGetByID(t *testing.T, ctx context.Context, db *repos) { assert.Equal(t, wantErr, err) } -func reposGetByName(t *testing.T, ctx context.Context, db *repos) { +func reposGetByName(t *testing.T, ctx context.Context, db *repositories) { repo, err := db.Create(ctx, 1, CreateRepoOptions{ Name: "repo1", @@ -244,7 +244,7 @@ func reposGetByName(t *testing.T, ctx context.Context, db *repos) { assert.Equal(t, wantErr, err) } -func reposStar(t *testing.T, ctx context.Context, db *repos) { +func reposStar(t *testing.T, ctx context.Context, db *repositories) { repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"}) require.NoError(t, err) usersStore := NewUsersStore(db.DB) @@ -263,7 +263,7 @@ func reposStar(t *testing.T, ctx context.Context, db *repos) { assert.Equal(t, 1, alice.NumStars) } -func reposTouch(t *testing.T, ctx context.Context, db *repos) { +func reposTouch(t *testing.T, ctx context.Context, db *repositories) { repo, err := db.Create(ctx, 1, CreateRepoOptions{ Name: "repo1", @@ -289,12 +289,21 @@ func reposTouch(t *testing.T, ctx context.Context, db *repos) { assert.False(t, got.IsBare) } -func reposListWatches(t *testing.T, ctx context.Context, db *repos) { - err := db.Watch(ctx, 1, 1) +func reposListWatches(t *testing.T, ctx context.Context, db *repositories) { + repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"}) require.NoError(t, err) - err = db.Watch(ctx, 2, 1) + _, err = db.Create(ctx, 2, CreateRepoOptions{Name: "repo2"}) require.NoError(t, err) - err = db.Watch(ctx, 2, 2) + + err = db.Watch( + ctx, + WatchRepositoryOptions{ + UserID: 2, + RepoID: repo1.ID, + RepoOwnerID: repo1.OwnerID, + RepoIsPrivate: repo1.IsPrivate, + }, + ) require.NoError(t, err) got, err := db.ListWatches(ctx, 1) @@ -310,28 +319,61 @@ func reposListWatches(t *testing.T, ctx context.Context, db *repos) { assert.Equal(t, want, got) } -func reposWatch(t *testing.T, ctx context.Context, db *repos) { - reposStore := NewReposStore(db.DB) - repo1, err := reposStore.Create(ctx, 1, CreateRepoOptions{Name: "repo1"}) - require.NoError(t, err) +func reposWatch(t *testing.T, ctx context.Context, db *repositories) { + t.Run("user does not have access to the repository", func(t *testing.T) { + repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1", Private: true}) + require.NoError(t, err) - err = db.Watch(ctx, 2, repo1.ID) - require.NoError(t, err) + err = db.Watch( + ctx, + WatchRepositoryOptions{ + UserID: 2, + RepoID: repo1.ID, + RepoOwnerID: repo1.OwnerID, + RepoIsPrivate: repo1.IsPrivate, + }, + ) + require.Error(t, err) + }) - // It is OK to watch multiple times and just be noop. - err = db.Watch(ctx, 2, repo1.ID) - require.NoError(t, err) + t.Run("user has access to the repository", func(t *testing.T) { + repo2, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo2"}) + require.NoError(t, err) - repo1, err = reposStore.GetByID(ctx, repo1.ID) - require.NoError(t, err) - assert.Equal(t, 2, repo1.NumWatches) // The owner is watching the repo by default. + err = db.Watch( + ctx, + WatchRepositoryOptions{ + UserID: 2, + RepoID: repo2.ID, + RepoOwnerID: repo2.OwnerID, + RepoIsPrivate: repo2.IsPrivate, + }, + ) + require.NoError(t, err) + + // It is OK to watch multiple times and just be noop. + err = db.Watch( + ctx, + WatchRepositoryOptions{ + UserID: 2, + RepoID: repo2.ID, + RepoOwnerID: repo2.OwnerID, + RepoIsPrivate: repo2.IsPrivate, + }, + ) + require.NoError(t, err) + + repo2, err = db.GetByID(ctx, repo2.ID) + require.NoError(t, err) + assert.Equal(t, 2, repo2.NumWatches) // The owner is watching the repo by default. + }) } -func reposHasForkedBy(t *testing.T, ctx context.Context, db *repos) { +func reposHasForkedBy(t *testing.T, ctx context.Context, db *repositories) { has := db.HasForkedBy(ctx, 1, 2) assert.False(t, has) - _, err := NewReposStore(db.DB).Create( + _, err := NewRepositoriesStore(db.DB).Create( ctx, 2, CreateRepoOptions{ diff --git a/internal/db/testdata/backup/OrgUser.golden.json b/internal/db/testdata/backup/OrgUser.golden.json new file mode 100644 index 00000000000..d27c3cee4f7 --- /dev/null +++ b/internal/db/testdata/backup/OrgUser.golden.json @@ -0,0 +1,2 @@ +{"ID":1,"Uid":1,"OrgID":11,"IsPublic":true,"IsOwner":true,"NumTeams":3} +{"ID":2,"Uid":2,"OrgID":11,"IsPublic":false,"IsOwner":false,"NumTeams":0} diff --git a/internal/db/two_factors.go b/internal/db/two_factors.go index 741a2ff7968..901191576a4 100644 --- a/internal/db/two_factors.go +++ b/internal/db/two_factors.go @@ -104,7 +104,7 @@ func (db *twoFactors) GetByUserID(ctx context.Context, userID int64) (*TwoFactor tf := new(TwoFactor) err := db.WithContext(ctx).Where("user_id = ?", userID).First(tf).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrTwoFactorNotFound{args: errutil.Args{"userID": userID}} } return nil, err diff --git a/internal/db/users.go b/internal/db/users.go index c39f9f39cb4..81623928d8b 100644 --- a/internal/db/users.go +++ b/internal/db/users.go @@ -48,8 +48,9 @@ type UsersStore interface { Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error) // Create creates a new user and persists to database. It returns // ErrNameNotAllowed if the given name or pattern of the name is not allowed as - // a username, or ErrUserAlreadyExist when a user with same name already exists, - // or ErrEmailAlreadyUsed if the email has been verified by another user. + // a username, or ErrUserAlreadyExist when a user or an organization with same + // name already exists, or ErrEmailAlreadyUsed if the email has been verified by + // another user. Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error) // GetByEmail returns the user (not organization) with given email. It ignores @@ -362,8 +363,7 @@ type ErrUserAlreadyExist struct { // IsErrUserAlreadyExist returns true if the underlying error has the type // ErrUserAlreadyExist. func IsErrUserAlreadyExist(err error) bool { - _, ok := errors.Cause(err).(ErrUserAlreadyExist) - return ok + return errors.As(err, &ErrUserAlreadyExist{}) } func (err ErrUserAlreadyExist) Error() string { @@ -377,8 +377,7 @@ type ErrEmailAlreadyUsed struct { // IsErrEmailAlreadyUsed returns true if the underlying error has the type // ErrEmailAlreadyUsed. func IsErrEmailAlreadyUsed(err error) bool { - _, ok := errors.Cause(err).(ErrEmailAlreadyUsed) - return ok + return errors.As(err, &ErrEmailAlreadyUsed{}) } func (err ErrEmailAlreadyUsed) Email() string { @@ -468,8 +467,7 @@ type ErrUserOwnRepos struct { // IsErrUserOwnRepos returns true if the underlying error has the type // ErrUserOwnRepos. func IsErrUserOwnRepos(err error) bool { - _, ok := errors.Cause(err).(ErrUserOwnRepos) - return ok + return errors.As(errors.Cause(err), &ErrUserOwnRepos{}) } func (err ErrUserOwnRepos) Error() string { @@ -528,7 +526,8 @@ func (db *users) DeleteByID(ctx context.Context, userID int64, skipRewriteAuthor SELECT repo_id FROM watch WHERE user_id = @userID ) */ - err = tx.Table("repository"). + err = tx. + Table("repository"). Where("id IN (?)", tx. Select("repo_id"). Table("watch"). @@ -549,7 +548,8 @@ func (db *users) DeleteByID(ctx context.Context, userID int64, skipRewriteAuthor SELECT repo_id FROM star WHERE uid = @userID ) */ - err = tx.Table("repository"). + err = tx. + Table("repository"). Where("id IN (?)", tx. Select("repo_id"). Table("star"). @@ -570,7 +570,8 @@ func (db *users) DeleteByID(ctx context.Context, userID int64, skipRewriteAuthor SELECT follow_id FROM follow WHERE user_id = @userID ) */ - err = tx.Table("user"). + err = tx. + Table("user"). Where("id IN (?)", tx. Select("follow_id"). Table("follow"). @@ -591,7 +592,8 @@ func (db *users) DeleteByID(ctx context.Context, userID int64, skipRewriteAuthor SELECT user_id FROM follow WHERE follow_id = @userID ) */ - err = tx.Table("user"). + err = tx. + Table("user"). Where("id IN (?)", tx. Select("user_id"). Table("follow"). @@ -738,7 +740,6 @@ func (db *users) Follow(ctx context.Context, userID, followID int64) error { } else if result.RowsAffected <= 0 { return nil // Relation already exists } - return db.recountFollows(tx, userID, followID) }) } @@ -770,8 +771,7 @@ type ErrUserNotExist struct { // IsErrUserNotExist returns true if the underlying error has the type // ErrUserNotExist. func IsErrUserNotExist(err error) bool { - _, ok := errors.Cause(err).(ErrUserNotExist) - return ok + return errors.As(err, &ErrUserNotExist{}) } func (err ErrUserNotExist) Error() string { @@ -811,7 +811,7 @@ func (db *users) GetByEmail(ctx context.Context, email string) (*User, error) { First(&user). Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrUserNotExist{args: errutil.Args{"email": email}} } return nil, err @@ -823,7 +823,7 @@ func (db *users) GetByID(ctx context.Context, id int64) (*User, error) { user := new(User) err := db.WithContext(ctx).Where("id = ?", id).First(user).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrUserNotExist{args: errutil.Args{"userID": id}} } return nil, err @@ -831,11 +831,17 @@ func (db *users) GetByID(ctx context.Context, id int64) (*User, error) { return user, nil } -func (db *users) GetByUsername(ctx context.Context, username string) (*User, error) { +func getUserByUsername(ctx context.Context, db *gorm.DB, userType UserType, username string) (*User, error) { + if username == "" { + return nil, ErrUserNotExist{args: errutil.Args{"name": username}} + } user := new(User) - err := db.WithContext(ctx).Where("lower_name = ?", strings.ToLower(username)).First(user).Error + err := db.WithContext(ctx). + Where("type = ? AND lower_name = ?", userType, strings.ToLower(username)). + First(user). + Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrUserNotExist{args: errutil.Args{"name": username}} } return nil, err @@ -843,6 +849,10 @@ func (db *users) GetByUsername(ctx context.Context, username string) (*User, err return user, nil } +func (db *users) GetByUsername(ctx context.Context, username string) (*User, error) { + return getUserByUsername(ctx, db.DB, UserTypeIndividual, username) +} + func (db *users) GetByKeyID(ctx context.Context, keyID int64) (*User, error) { user := new(User) err := db.WithContext(ctx). @@ -851,7 +861,7 @@ func (db *users) GetByKeyID(ctx context.Context, keyID int64) (*User, error) { First(user). Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrUserNotExist{args: errutil.Args{"keyID": keyID}} } return nil, err @@ -872,11 +882,13 @@ func (db *users) IsUsernameUsed(ctx context.Context, username string, excludeUse if username == "" { return false } - return db.WithContext(ctx). + + err := db.WithContext(ctx). Select("id"). Where("lower_name = ? AND id != ?", strings.ToLower(username), excludeUserId). First(&User{}). - Error != gorm.ErrRecordNotFound + Error + return !errors.Is(err, gorm.ErrRecordNotFound) } func (db *users) List(ctx context.Context, page, pageSize int) ([]*User, error) { @@ -1126,15 +1138,15 @@ func (ErrEmailNotExist) NotFound() bool { } func (db *users) GetEmail(ctx context.Context, userID int64, email string, needsActivated bool) (*EmailAddress, error) { - tx := db.WithContext(ctx).Where("uid = ? AND email = ?", userID, email) + conds := db.WithContext(ctx).Where("uid = ? AND email = ?", userID, email) if needsActivated { - tx = tx.Where("is_activated = ?", true) + conds.Where("is_activated = ?", true) } emailAddress := new(EmailAddress) - err := tx.First(emailAddress).Error + err := conds.First(emailAddress).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrEmailNotExist{ args: errutil.Args{ "email": email, @@ -1213,7 +1225,7 @@ func (db *users) MarkEmailPrimary(ctx context.Context, userID int64, email strin var emailAddress EmailAddress err := db.WithContext(ctx).Where("uid = ? AND email = ?", userID, email).First(&emailAddress).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return ErrEmailNotExist{args: errutil.Args{"email": email}} } return errors.Wrap(err, "get email address") @@ -1317,7 +1329,6 @@ type User struct { NumTeams int NumMembers int Teams []*Team `xorm:"-" gorm:"-" json:"-"` - Members []*User `xorm:"-" gorm:"-" json:"-"` } // BeforeCreate implements the GORM create hook. @@ -1467,13 +1478,12 @@ func (u *User) IsFollowing(followID int64) bool { return Users.IsFollowing(context.TODO(), u.ID, followID) } -// IsUserOrgOwner returns true if the user is in the owner team of the given -// organization. +// IsUserOrgOwner returns true if the user is an owner of the organization. // // TODO(unknwon): This is also used in templates, which should be fixed by // having a dedicated type `template.User`. -func (u *User) IsUserOrgOwner(orgId int64) bool { - return IsOrganizationOwner(orgId, u.ID) +func (u *User) IsUserOrgOwner(orgID int64) bool { + return Organizations.IsOwnedBy(context.TODO(), orgID, u.ID) } // IsPublicMember returns true if the user has public membership of the given @@ -1481,8 +1491,9 @@ func (u *User) IsUserOrgOwner(orgId int64) bool { // // TODO(unknwon): This is also used in templates, which should be fixed by // having a dedicated type `template.User`. -func (u *User) IsPublicMember(orgId int64) bool { - return IsPublicMembership(orgId, u.ID) +func (u *User) IsPublicMember(orgID int64) bool { + _, public := Organizations.HasMember(context.TODO(), orgID, u.ID) + return public } // GetOrganizationCount returns the count of organization membership that the @@ -1491,7 +1502,7 @@ func (u *User) IsPublicMember(orgId int64) bool { // TODO(unknwon): This is also used in templates, which should be fixed by // having a dedicated type `template.User`. func (u *User) GetOrganizationCount() (int64, error) { - return Orgs.CountByUser(context.TODO(), u.ID) + return Organizations.CountByUser(context.TODO(), u.ID) } // ShortName truncates and returns the username at most in given length. diff --git a/internal/db/users_test.go b/internal/db/users_test.go index bb0273a5810..4a5d65907a7 100644 --- a/internal/db/users_test.go +++ b/internal/db/users_test.go @@ -20,7 +20,6 @@ import ( "gogs.io/gogs/internal/auth" "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/dbtest" - "gogs.io/gogs/internal/dbutil" "gogs.io/gogs/internal/errutil" "gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/repoutil" @@ -88,7 +87,7 @@ func TestUsers(t *testing.T) { tables := []any{ new(User), new(EmailAddress), new(Repository), new(Follow), new(PullRequest), new(PublicKey), new(OrgUser), new(Watch), new(Star), new(Issue), new(AccessToken), new(Collaboration), new(Action), new(IssueUser), - new(Access), + new(Access), new(Team), new(TeamUser), } db := &users{ DB: dbtest.NewDB(t, "users", tables...), @@ -278,12 +277,7 @@ func usersChangeUsername(t *testing.T, ctx context.Context, db *users) { }) tempRepositoryRoot := filepath.Join(os.TempDir(), "usersChangeUsername-tempRepositoryRoot") - conf.SetMockRepository( - t, - conf.RepositoryOpts{ - Root: tempRepositoryRoot, - }, - ) + conf.SetMockRepository(t, conf.RepositoryOpts{Root: tempRepositoryRoot}) err = os.RemoveAll(tempRepositoryRoot) require.NoError(t, err) defer func() { _ = os.RemoveAll(tempRepositoryRoot) }() @@ -299,7 +293,7 @@ func usersChangeUsername(t *testing.T, ctx context.Context, db *users) { require.NoError(t, err) defer func() { _ = os.RemoveAll(tempServerAppDataPath) }() - repo, err := NewReposStore(db.DB).Create( + repo, err := NewRepositoriesStore(db.DB).Create( ctx, alice.ID, CreateRepoOptions{ @@ -375,15 +369,13 @@ func usersCount(t *testing.T, ctx context.Context, db *users) { got = db.Count(ctx) assert.Equal(t, int64(1), got) + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersCount-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + // Create an organization shouldn't count - // TODO: Use Orgs.Create to replace SQL hack when the method is available. - org1, err := db.Create(ctx, "org1", "org1@example.com", CreateUserOptions{}) - require.NoError(t, err) - err = db.Exec( - dbutil.Quote("UPDATE %s SET type = ? WHERE id = ?", "user"), - UserTypeOrganization, org1.ID, - ).Error + _, err = NewOrganizationsStore(db.DB).Create(ctx, "org1", 1, CreateOrganizationOptions{}) require.NoError(t, err) + got = db.Count(ctx) assert.Equal(t, int64(1), got) } @@ -394,6 +386,9 @@ func usersCreate(t *testing.T, ctx context.Context, db *users) { "alice", "alice@example.com", CreateUserOptions{ + FullName: "Alice Jones", + Location: "Earth", + Website: "alice@example.com", Activated: true, }, ) @@ -430,10 +425,14 @@ func usersCreate(t *testing.T, ctx context.Context, db *users) { assert.Equal(t, wantErr, err) }) - user, err := db.GetByUsername(ctx, alice.Name) + got, err := db.GetByUsername(ctx, alice.Name) require.NoError(t, err) - assert.Equal(t, db.NowFunc().Format(time.RFC3339), user.Created.UTC().Format(time.RFC3339)) - assert.Equal(t, db.NowFunc().Format(time.RFC3339), user.Updated.UTC().Format(time.RFC3339)) + assert.Equal(t, alice.Name, got.Name) + assert.Equal(t, alice.FullName, got.FullName) + assert.Equal(t, alice.Location, got.Location) + assert.Equal(t, alice.Website, got.Website) + assert.Equal(t, db.NowFunc().Format(time.RFC3339), got.Created.UTC().Format(time.RFC3339)) + assert.Equal(t, db.NowFunc().Format(time.RFC3339), got.Updated.UTC().Format(time.RFC3339)) } func usersDeleteCustomAvatar(t *testing.T, ctx context.Context, db *users) { @@ -443,6 +442,9 @@ func usersDeleteCustomAvatar(t *testing.T, ctx context.Context, db *users) { avatar, err := public.Files.ReadFile("img/avatar_default.png") require.NoError(t, err) + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersDeleteCustomAvatar-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + avatarPath := userutil.CustomAvatarPath(alice.ID) _ = os.Remove(avatarPath) defer func() { _ = os.Remove(avatarPath) }() @@ -471,7 +473,7 @@ func usersDeleteCustomAvatar(t *testing.T, ctx context.Context, db *users) { } func usersDeleteByID(t *testing.T, ctx context.Context, db *users) { - reposStore := NewReposStore(db.DB) + reposStore := NewRepositoriesStore(db.DB) t.Run("user still has repository ownership", func(t *testing.T) { alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) @@ -485,21 +487,14 @@ func usersDeleteByID(t *testing.T, ctx context.Context, db *users) { assert.Equal(t, wantErr, err) }) + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersDeleteByID-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + t.Run("user still has organization membership", func(t *testing.T) { bob, err := db.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) require.NoError(t, err) - // TODO: Use Orgs.Create to replace SQL hack when the method is available. - org1, err := db.Create(ctx, "org1", "org1@example.com", CreateUserOptions{}) - require.NoError(t, err) - err = db.Exec( - dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?)", "user"), - UserTypeOrganization, org1.ID, - ).Error - require.NoError(t, err) - - // TODO: Use Orgs.Join to replace SQL hack when the method is available. - err = db.Exec(`INSERT INTO org_user (uid, org_id) VALUES (?, ?)`, bob.ID, org1.ID).Error + _, err = NewOrganizationsStore(db.DB).Create(ctx, "org1", bob.ID, CreateOrganizationOptions{}) require.NoError(t, err) err = db.DeleteByID(ctx, bob.ID, false) @@ -518,7 +513,15 @@ func usersDeleteByID(t *testing.T, ctx context.Context, db *users) { require.NoError(t, err) // Mock watches, stars and follows - err = reposStore.Watch(ctx, testUser.ID, repo2.ID) + err = reposStore.Watch( + ctx, + WatchRepositoryOptions{ + UserID: testUser.ID, + RepoID: repo2.ID, + RepoOwnerID: repo2.OwnerID, + RepoIsPrivate: repo2.IsPrivate, + }, + ) require.NoError(t, err) err = reposStore.Star(ctx, testUser.ID, repo2.ID) require.NoError(t, err) @@ -575,8 +578,6 @@ func usersDeleteByID(t *testing.T, ctx context.Context, db *users) { require.NoError(t, err) // Mock user custom avatar - tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersDeleteByID-tempPictureAvatarUploadPath") - conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) err = os.MkdirAll(tempPictureAvatarUploadPath, os.ModePerm) require.NoError(t, err) tempCustomAvatarPath := userutil.CustomAvatarPath(testUser.ID) @@ -684,23 +685,18 @@ func usersDeleteInactivated(t *testing.T, ctx context.Context, db *users) { // User with repository ownership should be skipped alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) require.NoError(t, err) - reposStore := NewReposStore(db.DB) + reposStore := NewRepositoriesStore(db.DB) _, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"}) require.NoError(t, err) // User with organization membership should be skipped bob, err := db.Create(ctx, "bob", "bob@exmaple.com", CreateUserOptions{}) require.NoError(t, err) - // TODO: Use Orgs.Create to replace SQL hack when the method is available. - org1, err := db.Create(ctx, "org1", "org1@example.com", CreateUserOptions{}) - require.NoError(t, err) - err = db.Exec( - dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?)", "user"), - UserTypeOrganization, org1.ID, - ).Error - require.NoError(t, err) - // TODO: Use Orgs.Join to replace SQL hack when the method is available. - err = db.Exec(`INSERT INTO org_user (uid, org_id) VALUES (?, ?)`, bob.ID, org1.ID).Error + + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersDeleteInactivated-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + _, err = NewOrganizationsStore(db.DB).Create(ctx, "org1", bob.ID, CreateOrganizationOptions{}) require.NoError(t, err) // User activated state should be skipped @@ -734,11 +730,10 @@ func usersGetByEmail(t *testing.T, ctx context.Context, db *users) { }) t.Run("ignore organization", func(t *testing.T) { - // TODO: Use Orgs.Create to replace SQL hack when the method is available. - org, err := db.Create(ctx, "gogs", "gogs@exmaple.com", CreateUserOptions{}) - require.NoError(t, err) + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersGetByEmail-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) - err = db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserTypeOrganization).Error + org, err := NewOrganizationsStore(db.DB).Create(ctx, "gogs", 1, CreateOrganizationOptions{Email: "gogs@example.com"}) require.NoError(t, err) _, err = db.GetByEmail(ctx, org.Email) @@ -801,16 +796,30 @@ func usersGetByID(t *testing.T, ctx context.Context, db *users) { } func usersGetByUsername(t *testing.T, ctx context.Context, db *users) { - alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) - require.NoError(t, err) + t.Run("correct user type", func(t *testing.T) { + alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{}) + require.NoError(t, err) - user, err := db.GetByUsername(ctx, alice.Name) - require.NoError(t, err) - assert.Equal(t, alice.Name, user.Name) + got, err := db.GetByUsername(ctx, alice.Name) + require.NoError(t, err) + assert.Equal(t, alice.Name, got.Name) - _, err = db.GetByUsername(ctx, "bad_username") - wantErr := ErrUserNotExist{args: errutil.Args{"name": "bad_username"}} - assert.Equal(t, wantErr, err) + _, err = db.GetByUsername(ctx, "bad_username") + wantErr := ErrUserNotExist{args: errutil.Args{"name": "bad_username"}} + assert.Equal(t, wantErr, err) + }) + + t.Run("wrong user type", func(t *testing.T) { + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersGetByUsername-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + org1, err := NewOrganizationsStore(db.DB).Create(ctx, "org1", 1, CreateOrganizationOptions{}) + require.NoError(t, err) + + _, err = db.GetByUsername(ctx, org1.Name) + wantErr := ErrUserNotExist{args: errutil.Args{"name": org1.Name}} + assert.Equal(t, wantErr, err) + }) } func usersGetByKeyID(t *testing.T, ctx context.Context, db *users) { @@ -909,13 +918,10 @@ func usersList(t *testing.T, ctx context.Context, db *users) { require.NoError(t, err) // Create an organization shouldn't count - // TODO: Use Orgs.Create to replace SQL hack when the method is available. - org1, err := db.Create(ctx, "org1", "org1@example.com", CreateUserOptions{}) - require.NoError(t, err) - err = db.Exec( - dbutil.Quote("UPDATE %s SET type = ? WHERE id = ?", "user"), - UserTypeOrganization, org1.ID, - ).Error + tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersList-tempPictureAvatarUploadPath") + conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath}) + + _, err = NewOrganizationsStore(db.DB).Create(ctx, "org1", bob.ID, CreateOrganizationOptions{}) require.NoError(t, err) got, err := db.List(ctx, 1, 1) diff --git a/internal/route/admin/orgs.go b/internal/route/admin/orgs.go index 922d7f8633b..b36276c91e4 100644 --- a/internal/route/admin/orgs.go +++ b/internal/route/admin/orgs.go @@ -23,12 +23,16 @@ func Organizations(c *context.Context) { c.Data["PageIsAdminOrganizations"] = true route.RenderUserSearch(c, &route.UserSearchOptions{ - Type: db.UserTypeOrganization, - Counter: func(gocontext.Context) int64 { - return db.CountOrganizations() - }, - Ranger: func(_ gocontext.Context, page, pageSize int) ([]*db.User, error) { - return db.Organizations(page, pageSize) + Type: db.UserTypeOrganization, + Counter: db.Organizations.Count, + Ranger: func(ctx gocontext.Context, page, pageSize int) ([]*db.User, error) { + return db.Organizations.List( + ctx, + db.ListOrganizationsOptions{ + Page: page, + PageSize: pageSize, + }, + ) }, PageSize: conf.UI.Admin.OrgPagingNum, OrderBy: "id ASC", diff --git a/internal/route/admin/repos.go b/internal/route/admin/repos.go index 5ae8f74965f..cbeb27d8b34 100644 --- a/internal/route/admin/repos.go +++ b/internal/route/admin/repos.go @@ -35,7 +35,7 @@ func Repos(c *context.Context) { keyword := c.Query("q") if keyword == "" { - repos, err = db.Repositories(page, conf.UI.Admin.RepoPagingNum) + repos, err = db.ListRepositories(page, conf.UI.Admin.RepoPagingNum) if err != nil { c.Error(err, "list repositories") return diff --git a/internal/route/api/v1/api.go b/internal/route/api/v1/api.go index 79d346fe69a..8cb877a6d9c 100644 --- a/internal/route/api/v1/api.go +++ b/internal/route/api/v1/api.go @@ -45,7 +45,7 @@ func repoAssignment() macaron.Handler { } c.Repo.Owner = owner - repo, err := db.Repos.GetByName(c.Req.Context(), owner.ID, reponame) + repo, err := db.Repositories.GetByName(c.Req.Context(), owner.ID, reponame) if err != nil { c.NotFoundOrError(err, "get repository by name") return diff --git a/internal/route/api/v1/org/org.go b/internal/route/api/v1/org/org.go index 9b99bead7b4..440ad555996 100644 --- a/internal/route/api/v1/org/org.go +++ b/internal/route/api/v1/org/org.go @@ -20,17 +20,19 @@ func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user * return } - org := &db.User{ - Name: apiForm.UserName, - FullName: apiForm.FullName, - Description: apiForm.Description, - Website: apiForm.Website, - Location: apiForm.Location, - IsActive: true, - Type: db.UserTypeOrganization, - } - if err := db.CreateOrganization(org, user); err != nil { - if db.IsErrUserAlreadyExist(err) || + org, err := db.Organizations.Create( + c.Req.Context(), + apiForm.UserName, + user.ID, + db.CreateOrganizationOptions{ + FullName: apiForm.FullName, + Location: apiForm.Location, + Website: apiForm.Website, + Description: apiForm.Description, + }, + ) + if err != nil { + if db.IsErrOrganizationAlreadyExist(err) || db.IsErrNameNotAllowed(err) { c.ErrorStatus(http.StatusUnprocessableEntity, err) } else { @@ -39,13 +41,13 @@ func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user * return } - c.JSON(201, convert.ToOrganization(org)) + c.JSON(http.StatusCreated, convert.ToOrganization(org)) } func listUserOrgs(c *context.APIContext, u *db.User, all bool) { - orgs, err := db.Orgs.List( + orgs, err := db.Organizations.List( c.Req.Context(), - db.ListOrgsOptions{ + db.ListOrganizationsOptions{ MemberID: u.ID, IncludePrivateMembers: all, }, @@ -104,7 +106,7 @@ func Edit(c *context.APIContext, form api.EditOrgOption) { return } - org, err = db.GetOrgByName(org.Name) + org, err = db.Organizations.GetByName(c.Req.Context(), org.Name) if err != nil { c.Error(err, "get organization") return diff --git a/internal/route/api/v1/repo/repo.go b/internal/route/api/v1/repo/repo.go index a4cc36b3021..bb14dfe8018 100644 --- a/internal/route/api/v1/repo/repo.go +++ b/internal/route/api/v1/repo/repo.go @@ -87,7 +87,14 @@ func listUserRepositories(c *context.APIContext, username string) { // or an organization isn't a member of. var ownRepos []*db.Repository if user.IsOrganization() { - ownRepos, _, err = user.GetUserRepositories(c.User.ID, 1, user.NumRepos) + ownRepos, _, err = db.Organizations.AccessibleRepositoriesByUser( + c.Req.Context(), + user.ID, + c.User.ID, + 1, + user.NumRepos, + db.AccessibleRepositoriesByUserOptions{}, + ) } else { ownRepos, err = db.GetUserRepositories(&db.UserRepoOptions{ UserID: user.ID, @@ -116,7 +123,7 @@ func listUserRepositories(c *context.APIContext, username string) { return } - accessibleRepos, err := db.Repos.GetByCollaboratorIDWithAccessMode(c.Req.Context(), user.ID) + accessibleRepos, err := db.Repositories.GetByCollaboratorIDWithAccessMode(c.Req.Context(), user.ID) if err != nil { c.Error(err, "get repositories accesses by collaborator") return @@ -191,7 +198,7 @@ func Create(c *context.APIContext, opt api.CreateRepoOption) { } func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) { - org, err := db.GetOrgByName(c.Params(":org")) + org, err := db.Organizations.GetByName(c.Req.Context(), c.Params(":org")) if err != nil { c.NotFoundOrError(err, "get organization by name") return diff --git a/internal/route/home.go b/internal/route/home.go index a389c080136..b1a5a7a9f85 100644 --- a/internal/route/home.go +++ b/internal/route/home.go @@ -115,7 +115,7 @@ func RenderUserSearch(c *context.Context, opts *UserSearchOptions) { } else { search := db.Users.SearchByName if opts.Type == db.UserTypeOrganization { - search = db.Orgs.SearchByName + search = db.Organizations.SearchByName } users, count, err = search(c.Req.Context(), keyword, page, opts.PageSize, opts.OrderBy) if err != nil { @@ -152,12 +152,16 @@ func ExploreOrganizations(c *context.Context) { c.Data["PageIsExploreOrganizations"] = true RenderUserSearch(c, &UserSearchOptions{ - Type: db.UserTypeOrganization, - Counter: func(gocontext.Context) int64 { - return db.CountOrganizations() - }, - Ranger: func(_ gocontext.Context, page, pageSize int) ([]*db.User, error) { - return db.Organizations(page, pageSize) + Type: db.UserTypeOrganization, + Counter: db.Organizations.Count, + Ranger: func(ctx gocontext.Context, page, pageSize int) ([]*db.User, error) { + return db.Organizations.List( + ctx, + db.ListOrganizationsOptions{ + Page: page, + PageSize: pageSize, + }, + ) }, PageSize: conf.UI.ExplorePagingNum, OrderBy: "updated_unix DESC", diff --git a/internal/route/lfs/mocks_test.go b/internal/route/lfs/mocks_test.go index b313ce55319..5bf99255450 100644 --- a/internal/route/lfs/mocks_test.go +++ b/internal/route/lfs/mocks_test.go @@ -1489,92 +1489,94 @@ func (c PermsStoreSetRepoPermsFuncCall) Results() []interface{} { return []interface{}{c.Result0} } -// MockReposStore is a mock implementation of the ReposStore interface (from -// the package gogs.io/gogs/internal/db) used for unit testing. -type MockReposStore struct { +// MockRepositoriesStore is a mock implementation of the RepositoriesStore +// interface (from the package gogs.io/gogs/internal/db) used for unit +// testing. +type MockRepositoriesStore struct { // CreateFunc is an instance of a mock function object controlling the // behavior of the method Create. - CreateFunc *ReposStoreCreateFunc + CreateFunc *RepositoriesStoreCreateFunc // GetByCollaboratorIDFunc is an instance of a mock function object // controlling the behavior of the method GetByCollaboratorID. - GetByCollaboratorIDFunc *ReposStoreGetByCollaboratorIDFunc + GetByCollaboratorIDFunc *RepositoriesStoreGetByCollaboratorIDFunc // GetByCollaboratorIDWithAccessModeFunc is an instance of a mock // function object controlling the behavior of the method // GetByCollaboratorIDWithAccessMode. - GetByCollaboratorIDWithAccessModeFunc *ReposStoreGetByCollaboratorIDWithAccessModeFunc + GetByCollaboratorIDWithAccessModeFunc *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc // GetByIDFunc is an instance of a mock function object controlling the // behavior of the method GetByID. - GetByIDFunc *ReposStoreGetByIDFunc + GetByIDFunc *RepositoriesStoreGetByIDFunc // GetByNameFunc is an instance of a mock function object controlling // the behavior of the method GetByName. - GetByNameFunc *ReposStoreGetByNameFunc + GetByNameFunc *RepositoriesStoreGetByNameFunc // HasForkedByFunc is an instance of a mock function object controlling // the behavior of the method HasForkedBy. - HasForkedByFunc *ReposStoreHasForkedByFunc + HasForkedByFunc *RepositoriesStoreHasForkedByFunc // ListWatchesFunc is an instance of a mock function object controlling // the behavior of the method ListWatches. - ListWatchesFunc *ReposStoreListWatchesFunc + ListWatchesFunc *RepositoriesStoreListWatchesFunc // StarFunc is an instance of a mock function object controlling the // behavior of the method Star. - StarFunc *ReposStoreStarFunc + StarFunc *RepositoriesStoreStarFunc // TouchFunc is an instance of a mock function object controlling the // behavior of the method Touch. - TouchFunc *ReposStoreTouchFunc + TouchFunc *RepositoriesStoreTouchFunc // WatchFunc is an instance of a mock function object controlling the // behavior of the method Watch. - WatchFunc *ReposStoreWatchFunc + WatchFunc *RepositoriesStoreWatchFunc } -// NewMockReposStore creates a new mock of the ReposStore interface. All -// methods return zero values for all results, unless overwritten. -func NewMockReposStore() *MockReposStore { - return &MockReposStore{ - CreateFunc: &ReposStoreCreateFunc{ +// NewMockRepositoriesStore creates a new mock of the RepositoriesStore +// interface. All methods return zero values for all results, unless +// overwritten. +func NewMockRepositoriesStore() *MockRepositoriesStore { + return &MockRepositoriesStore{ + CreateFunc: &RepositoriesStoreCreateFunc{ defaultHook: func(context.Context, int64, db.CreateRepoOptions) (r0 *db.Repository, r1 error) { return }, }, - GetByCollaboratorIDFunc: &ReposStoreGetByCollaboratorIDFunc{ + GetByCollaboratorIDFunc: &RepositoriesStoreGetByCollaboratorIDFunc{ defaultHook: func(context.Context, int64, int, string) (r0 []*db.Repository, r1 error) { return }, }, - GetByCollaboratorIDWithAccessModeFunc: &ReposStoreGetByCollaboratorIDWithAccessModeFunc{ + GetByCollaboratorIDWithAccessModeFunc: &RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc{ defaultHook: func(context.Context, int64) (r0 map[*db.Repository]db.AccessMode, r1 error) { return }, }, - GetByIDFunc: &ReposStoreGetByIDFunc{ + GetByIDFunc: &RepositoriesStoreGetByIDFunc{ defaultHook: func(context.Context, int64) (r0 *db.Repository, r1 error) { return }, }, - GetByNameFunc: &ReposStoreGetByNameFunc{ + GetByNameFunc: &RepositoriesStoreGetByNameFunc{ defaultHook: func(context.Context, int64, string) (r0 *db.Repository, r1 error) { return }, }, - HasForkedByFunc: &ReposStoreHasForkedByFunc{ + HasForkedByFunc: &RepositoriesStoreHasForkedByFunc{ defaultHook: func(context.Context, int64, int64) (r0 bool) { return }, }, - ListWatchesFunc: &ReposStoreListWatchesFunc{ + ListWatchesFunc: &RepositoriesStoreListWatchesFunc{ defaultHook: func(context.Context, int64) (r0 []*db.Watch, r1 error) { return }, }, - StarFunc: &ReposStoreStarFunc{ + StarFunc: &RepositoriesStoreStarFunc{ defaultHook: func(context.Context, int64, int64) (r0 error) { return }, }, - TouchFunc: &ReposStoreTouchFunc{ + TouchFunc: &RepositoriesStoreTouchFunc{ defaultHook: func(context.Context, int64) (r0 error) { return }, }, - WatchFunc: &ReposStoreWatchFunc{ + WatchFunc: &RepositoriesStoreWatchFunc{ defaultHook: func(context.Context, int64, int64) (r0 error) { return }, @@ -1582,128 +1584,131 @@ func NewMockReposStore() *MockReposStore { } } -// NewStrictMockReposStore creates a new mock of the ReposStore interface. -// All methods panic on invocation, unless overwritten. -func NewStrictMockReposStore() *MockReposStore { - return &MockReposStore{ - CreateFunc: &ReposStoreCreateFunc{ +// NewStrictMockRepositoriesStore creates a new mock of the +// RepositoriesStore interface. All methods panic on invocation, unless +// overwritten. +func NewStrictMockRepositoriesStore() *MockRepositoriesStore { + return &MockRepositoriesStore{ + CreateFunc: &RepositoriesStoreCreateFunc{ defaultHook: func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) { - panic("unexpected invocation of MockReposStore.Create") + panic("unexpected invocation of MockRepositoriesStore.Create") }, }, - GetByCollaboratorIDFunc: &ReposStoreGetByCollaboratorIDFunc{ + GetByCollaboratorIDFunc: &RepositoriesStoreGetByCollaboratorIDFunc{ defaultHook: func(context.Context, int64, int, string) ([]*db.Repository, error) { - panic("unexpected invocation of MockReposStore.GetByCollaboratorID") + panic("unexpected invocation of MockRepositoriesStore.GetByCollaboratorID") }, }, - GetByCollaboratorIDWithAccessModeFunc: &ReposStoreGetByCollaboratorIDWithAccessModeFunc{ + GetByCollaboratorIDWithAccessModeFunc: &RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc{ defaultHook: func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) { - panic("unexpected invocation of MockReposStore.GetByCollaboratorIDWithAccessMode") + panic("unexpected invocation of MockRepositoriesStore.GetByCollaboratorIDWithAccessMode") }, }, - GetByIDFunc: &ReposStoreGetByIDFunc{ + GetByIDFunc: &RepositoriesStoreGetByIDFunc{ defaultHook: func(context.Context, int64) (*db.Repository, error) { - panic("unexpected invocation of MockReposStore.GetByID") + panic("unexpected invocation of MockRepositoriesStore.GetByID") }, }, - GetByNameFunc: &ReposStoreGetByNameFunc{ + GetByNameFunc: &RepositoriesStoreGetByNameFunc{ defaultHook: func(context.Context, int64, string) (*db.Repository, error) { - panic("unexpected invocation of MockReposStore.GetByName") + panic("unexpected invocation of MockRepositoriesStore.GetByName") }, }, - HasForkedByFunc: &ReposStoreHasForkedByFunc{ + HasForkedByFunc: &RepositoriesStoreHasForkedByFunc{ defaultHook: func(context.Context, int64, int64) bool { - panic("unexpected invocation of MockReposStore.HasForkedBy") + panic("unexpected invocation of MockRepositoriesStore.HasForkedBy") }, }, - ListWatchesFunc: &ReposStoreListWatchesFunc{ + ListWatchesFunc: &RepositoriesStoreListWatchesFunc{ defaultHook: func(context.Context, int64) ([]*db.Watch, error) { - panic("unexpected invocation of MockReposStore.ListWatches") + panic("unexpected invocation of MockRepositoriesStore.ListWatches") }, }, - StarFunc: &ReposStoreStarFunc{ + StarFunc: &RepositoriesStoreStarFunc{ defaultHook: func(context.Context, int64, int64) error { - panic("unexpected invocation of MockReposStore.Star") + panic("unexpected invocation of MockRepositoriesStore.Star") }, }, - TouchFunc: &ReposStoreTouchFunc{ + TouchFunc: &RepositoriesStoreTouchFunc{ defaultHook: func(context.Context, int64) error { - panic("unexpected invocation of MockReposStore.Touch") + panic("unexpected invocation of MockRepositoriesStore.Touch") }, }, - WatchFunc: &ReposStoreWatchFunc{ + WatchFunc: &RepositoriesStoreWatchFunc{ defaultHook: func(context.Context, int64, int64) error { - panic("unexpected invocation of MockReposStore.Watch") + panic("unexpected invocation of MockRepositoriesStore.Watch") }, }, } } -// NewMockReposStoreFrom creates a new mock of the MockReposStore interface. -// All methods delegate to the given implementation, unless overwritten. -func NewMockReposStoreFrom(i db.ReposStore) *MockReposStore { - return &MockReposStore{ - CreateFunc: &ReposStoreCreateFunc{ +// NewMockRepositoriesStoreFrom creates a new mock of the +// MockRepositoriesStore interface. All methods delegate to the given +// implementation, unless overwritten. +func NewMockRepositoriesStoreFrom(i db.RepositoriesStore) *MockRepositoriesStore { + return &MockRepositoriesStore{ + CreateFunc: &RepositoriesStoreCreateFunc{ defaultHook: i.Create, }, - GetByCollaboratorIDFunc: &ReposStoreGetByCollaboratorIDFunc{ + GetByCollaboratorIDFunc: &RepositoriesStoreGetByCollaboratorIDFunc{ defaultHook: i.GetByCollaboratorID, }, - GetByCollaboratorIDWithAccessModeFunc: &ReposStoreGetByCollaboratorIDWithAccessModeFunc{ + GetByCollaboratorIDWithAccessModeFunc: &RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc{ defaultHook: i.GetByCollaboratorIDWithAccessMode, }, - GetByIDFunc: &ReposStoreGetByIDFunc{ + GetByIDFunc: &RepositoriesStoreGetByIDFunc{ defaultHook: i.GetByID, }, - GetByNameFunc: &ReposStoreGetByNameFunc{ + GetByNameFunc: &RepositoriesStoreGetByNameFunc{ defaultHook: i.GetByName, }, - HasForkedByFunc: &ReposStoreHasForkedByFunc{ + HasForkedByFunc: &RepositoriesStoreHasForkedByFunc{ defaultHook: i.HasForkedBy, }, - ListWatchesFunc: &ReposStoreListWatchesFunc{ + ListWatchesFunc: &RepositoriesStoreListWatchesFunc{ defaultHook: i.ListWatches, }, - StarFunc: &ReposStoreStarFunc{ + StarFunc: &RepositoriesStoreStarFunc{ defaultHook: i.Star, }, - TouchFunc: &ReposStoreTouchFunc{ + TouchFunc: &RepositoriesStoreTouchFunc{ defaultHook: i.Touch, }, - WatchFunc: &ReposStoreWatchFunc{ + WatchFunc: &RepositoriesStoreWatchFunc{ defaultHook: i.Watch, }, } } -// ReposStoreCreateFunc describes the behavior when the Create method of the -// parent MockReposStore instance is invoked. -type ReposStoreCreateFunc struct { +// RepositoriesStoreCreateFunc describes the behavior when the Create method +// of the parent MockRepositoriesStore instance is invoked. +type RepositoriesStoreCreateFunc struct { defaultHook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) hooks []func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) - history []ReposStoreCreateFuncCall + history []RepositoriesStoreCreateFuncCall mutex sync.Mutex } // Create delegates to the next hook function in the queue and stores the // parameter and result values of this invocation. -func (m *MockReposStore) Create(v0 context.Context, v1 int64, v2 db.CreateRepoOptions) (*db.Repository, error) { +func (m *MockRepositoriesStore) Create(v0 context.Context, v1 int64, v2 db.CreateRepoOptions) (*db.Repository, error) { r0, r1 := m.CreateFunc.nextHook()(v0, v1, v2) - m.CreateFunc.appendCall(ReposStoreCreateFuncCall{v0, v1, v2, r0, r1}) + m.CreateFunc.appendCall(RepositoriesStoreCreateFuncCall{v0, v1, v2, r0, r1}) return r0, r1 } // SetDefaultHook sets function that is called when the Create method of the -// parent MockReposStore instance is invoked and the hook queue is empty. -func (f *ReposStoreCreateFunc) SetDefaultHook(hook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error)) { +// parent MockRepositoriesStore instance is invoked and the hook queue is +// empty. +func (f *RepositoriesStoreCreateFunc) SetDefaultHook(hook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error)) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// Create method of the parent MockReposStore instance invokes the hook at -// the front of the queue and discards it. After the queue is empty, the -// default hook function is invoked for any future action. -func (f *ReposStoreCreateFunc) PushHook(hook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error)) { +// Create method of the parent MockRepositoriesStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *RepositoriesStoreCreateFunc) PushHook(hook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error)) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -1711,20 +1716,20 @@ func (f *ReposStoreCreateFunc) PushHook(hook func(context.Context, int64, db.Cre // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreCreateFunc) SetDefaultReturn(r0 *db.Repository, r1 error) { +func (f *RepositoriesStoreCreateFunc) SetDefaultReturn(r0 *db.Repository, r1 error) { f.SetDefaultHook(func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) { return r0, r1 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreCreateFunc) PushReturn(r0 *db.Repository, r1 error) { +func (f *RepositoriesStoreCreateFunc) PushReturn(r0 *db.Repository, r1 error) { f.PushHook(func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) { return r0, r1 }) } -func (f *ReposStoreCreateFunc) nextHook() func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) { +func (f *RepositoriesStoreCreateFunc) nextHook() func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) { f.mutex.Lock() defer f.mutex.Unlock() @@ -1737,26 +1742,26 @@ func (f *ReposStoreCreateFunc) nextHook() func(context.Context, int64, db.Create return hook } -func (f *ReposStoreCreateFunc) appendCall(r0 ReposStoreCreateFuncCall) { +func (f *RepositoriesStoreCreateFunc) appendCall(r0 RepositoriesStoreCreateFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreCreateFuncCall objects describing -// the invocations of this function. -func (f *ReposStoreCreateFunc) History() []ReposStoreCreateFuncCall { +// History returns a sequence of RepositoriesStoreCreateFuncCall objects +// describing the invocations of this function. +func (f *RepositoriesStoreCreateFunc) History() []RepositoriesStoreCreateFuncCall { f.mutex.Lock() - history := make([]ReposStoreCreateFuncCall, len(f.history)) + history := make([]RepositoriesStoreCreateFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreCreateFuncCall is an object that describes an invocation of -// method Create on an instance of MockReposStore. -type ReposStoreCreateFuncCall struct { +// RepositoriesStoreCreateFuncCall is an object that describes an invocation +// of method Create on an instance of MockRepositoriesStore. +type RepositoriesStoreCreateFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -1776,46 +1781,47 @@ type ReposStoreCreateFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreCreateFuncCall) Args() []interface{} { +func (c RepositoriesStoreCreateFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1, c.Arg2} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreCreateFuncCall) Results() []interface{} { +func (c RepositoriesStoreCreateFuncCall) Results() []interface{} { return []interface{}{c.Result0, c.Result1} } -// ReposStoreGetByCollaboratorIDFunc describes the behavior when the -// GetByCollaboratorID method of the parent MockReposStore instance is -// invoked. -type ReposStoreGetByCollaboratorIDFunc struct { +// RepositoriesStoreGetByCollaboratorIDFunc describes the behavior when the +// GetByCollaboratorID method of the parent MockRepositoriesStore instance +// is invoked. +type RepositoriesStoreGetByCollaboratorIDFunc struct { defaultHook func(context.Context, int64, int, string) ([]*db.Repository, error) hooks []func(context.Context, int64, int, string) ([]*db.Repository, error) - history []ReposStoreGetByCollaboratorIDFuncCall + history []RepositoriesStoreGetByCollaboratorIDFuncCall mutex sync.Mutex } // GetByCollaboratorID delegates to the next hook function in the queue and // stores the parameter and result values of this invocation. -func (m *MockReposStore) GetByCollaboratorID(v0 context.Context, v1 int64, v2 int, v3 string) ([]*db.Repository, error) { +func (m *MockRepositoriesStore) GetByCollaboratorID(v0 context.Context, v1 int64, v2 int, v3 string) ([]*db.Repository, error) { r0, r1 := m.GetByCollaboratorIDFunc.nextHook()(v0, v1, v2, v3) - m.GetByCollaboratorIDFunc.appendCall(ReposStoreGetByCollaboratorIDFuncCall{v0, v1, v2, v3, r0, r1}) + m.GetByCollaboratorIDFunc.appendCall(RepositoriesStoreGetByCollaboratorIDFuncCall{v0, v1, v2, v3, r0, r1}) return r0, r1 } // SetDefaultHook sets function that is called when the GetByCollaboratorID -// method of the parent MockReposStore instance is invoked and the hook -// queue is empty. -func (f *ReposStoreGetByCollaboratorIDFunc) SetDefaultHook(hook func(context.Context, int64, int, string) ([]*db.Repository, error)) { +// method of the parent MockRepositoriesStore instance is invoked and the +// hook queue is empty. +func (f *RepositoriesStoreGetByCollaboratorIDFunc) SetDefaultHook(hook func(context.Context, int64, int, string) ([]*db.Repository, error)) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// GetByCollaboratorID method of the parent MockReposStore instance invokes -// the hook at the front of the queue and discards it. After the queue is -// empty, the default hook function is invoked for any future action. -func (f *ReposStoreGetByCollaboratorIDFunc) PushHook(hook func(context.Context, int64, int, string) ([]*db.Repository, error)) { +// GetByCollaboratorID method of the parent MockRepositoriesStore instance +// invokes the hook at the front of the queue and discards it. After the +// queue is empty, the default hook function is invoked for any future +// action. +func (f *RepositoriesStoreGetByCollaboratorIDFunc) PushHook(hook func(context.Context, int64, int, string) ([]*db.Repository, error)) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -1823,20 +1829,20 @@ func (f *ReposStoreGetByCollaboratorIDFunc) PushHook(hook func(context.Context, // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreGetByCollaboratorIDFunc) SetDefaultReturn(r0 []*db.Repository, r1 error) { +func (f *RepositoriesStoreGetByCollaboratorIDFunc) SetDefaultReturn(r0 []*db.Repository, r1 error) { f.SetDefaultHook(func(context.Context, int64, int, string) ([]*db.Repository, error) { return r0, r1 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreGetByCollaboratorIDFunc) PushReturn(r0 []*db.Repository, r1 error) { +func (f *RepositoriesStoreGetByCollaboratorIDFunc) PushReturn(r0 []*db.Repository, r1 error) { f.PushHook(func(context.Context, int64, int, string) ([]*db.Repository, error) { return r0, r1 }) } -func (f *ReposStoreGetByCollaboratorIDFunc) nextHook() func(context.Context, int64, int, string) ([]*db.Repository, error) { +func (f *RepositoriesStoreGetByCollaboratorIDFunc) nextHook() func(context.Context, int64, int, string) ([]*db.Repository, error) { f.mutex.Lock() defer f.mutex.Unlock() @@ -1849,27 +1855,28 @@ func (f *ReposStoreGetByCollaboratorIDFunc) nextHook() func(context.Context, int return hook } -func (f *ReposStoreGetByCollaboratorIDFunc) appendCall(r0 ReposStoreGetByCollaboratorIDFuncCall) { +func (f *RepositoriesStoreGetByCollaboratorIDFunc) appendCall(r0 RepositoriesStoreGetByCollaboratorIDFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreGetByCollaboratorIDFuncCall -// objects describing the invocations of this function. -func (f *ReposStoreGetByCollaboratorIDFunc) History() []ReposStoreGetByCollaboratorIDFuncCall { +// History returns a sequence of +// RepositoriesStoreGetByCollaboratorIDFuncCall objects describing the +// invocations of this function. +func (f *RepositoriesStoreGetByCollaboratorIDFunc) History() []RepositoriesStoreGetByCollaboratorIDFuncCall { f.mutex.Lock() - history := make([]ReposStoreGetByCollaboratorIDFuncCall, len(f.history)) + history := make([]RepositoriesStoreGetByCollaboratorIDFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreGetByCollaboratorIDFuncCall is an object that describes an -// invocation of method GetByCollaboratorID on an instance of -// MockReposStore. -type ReposStoreGetByCollaboratorIDFuncCall struct { +// RepositoriesStoreGetByCollaboratorIDFuncCall is an object that describes +// an invocation of method GetByCollaboratorID on an instance of +// MockRepositoriesStore. +type RepositoriesStoreGetByCollaboratorIDFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -1892,47 +1899,47 @@ type ReposStoreGetByCollaboratorIDFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreGetByCollaboratorIDFuncCall) Args() []interface{} { +func (c RepositoriesStoreGetByCollaboratorIDFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1, c.Arg2, c.Arg3} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreGetByCollaboratorIDFuncCall) Results() []interface{} { +func (c RepositoriesStoreGetByCollaboratorIDFuncCall) Results() []interface{} { return []interface{}{c.Result0, c.Result1} } -// ReposStoreGetByCollaboratorIDWithAccessModeFunc describes the behavior -// when the GetByCollaboratorIDWithAccessMode method of the parent -// MockReposStore instance is invoked. -type ReposStoreGetByCollaboratorIDWithAccessModeFunc struct { +// RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc describes the +// behavior when the GetByCollaboratorIDWithAccessMode method of the parent +// MockRepositoriesStore instance is invoked. +type RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc struct { defaultHook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) hooks []func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) - history []ReposStoreGetByCollaboratorIDWithAccessModeFuncCall + history []RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall mutex sync.Mutex } // GetByCollaboratorIDWithAccessMode delegates to the next hook function in // the queue and stores the parameter and result values of this invocation. -func (m *MockReposStore) GetByCollaboratorIDWithAccessMode(v0 context.Context, v1 int64) (map[*db.Repository]db.AccessMode, error) { +func (m *MockRepositoriesStore) GetByCollaboratorIDWithAccessMode(v0 context.Context, v1 int64) (map[*db.Repository]db.AccessMode, error) { r0, r1 := m.GetByCollaboratorIDWithAccessModeFunc.nextHook()(v0, v1) - m.GetByCollaboratorIDWithAccessModeFunc.appendCall(ReposStoreGetByCollaboratorIDWithAccessModeFuncCall{v0, v1, r0, r1}) + m.GetByCollaboratorIDWithAccessModeFunc.appendCall(RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall{v0, v1, r0, r1}) return r0, r1 } // SetDefaultHook sets function that is called when the -// GetByCollaboratorIDWithAccessMode method of the parent MockReposStore -// instance is invoked and the hook queue is empty. -func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) SetDefaultHook(hook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)) { +// GetByCollaboratorIDWithAccessMode method of the parent +// MockRepositoriesStore instance is invoked and the hook queue is empty. +func (f *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc) SetDefaultHook(hook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// GetByCollaboratorIDWithAccessMode method of the parent MockReposStore -// instance invokes the hook at the front of the queue and discards it. -// After the queue is empty, the default hook function is invoked for any -// future action. -func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) PushHook(hook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)) { +// GetByCollaboratorIDWithAccessMode method of the parent +// MockRepositoriesStore instance invokes the hook at the front of the queue +// and discards it. After the queue is empty, the default hook function is +// invoked for any future action. +func (f *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc) PushHook(hook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -1940,20 +1947,20 @@ func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) PushHook(hook func(con // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) SetDefaultReturn(r0 map[*db.Repository]db.AccessMode, r1 error) { +func (f *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc) SetDefaultReturn(r0 map[*db.Repository]db.AccessMode, r1 error) { f.SetDefaultHook(func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) { return r0, r1 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) PushReturn(r0 map[*db.Repository]db.AccessMode, r1 error) { +func (f *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc) PushReturn(r0 map[*db.Repository]db.AccessMode, r1 error) { f.PushHook(func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) { return r0, r1 }) } -func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) nextHook() func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) { +func (f *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc) nextHook() func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) { f.mutex.Lock() defer f.mutex.Unlock() @@ -1966,28 +1973,28 @@ func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) nextHook() func(contex return hook } -func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) appendCall(r0 ReposStoreGetByCollaboratorIDWithAccessModeFuncCall) { +func (f *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc) appendCall(r0 RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } // History returns a sequence of -// ReposStoreGetByCollaboratorIDWithAccessModeFuncCall objects describing -// the invocations of this function. -func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) History() []ReposStoreGetByCollaboratorIDWithAccessModeFuncCall { +// RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall objects +// describing the invocations of this function. +func (f *RepositoriesStoreGetByCollaboratorIDWithAccessModeFunc) History() []RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall { f.mutex.Lock() - history := make([]ReposStoreGetByCollaboratorIDWithAccessModeFuncCall, len(f.history)) + history := make([]RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreGetByCollaboratorIDWithAccessModeFuncCall is an object that -// describes an invocation of method GetByCollaboratorIDWithAccessMode on an -// instance of MockReposStore. -type ReposStoreGetByCollaboratorIDWithAccessModeFuncCall struct { +// RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall is an object +// that describes an invocation of method GetByCollaboratorIDWithAccessMode +// on an instance of MockRepositoriesStore. +type RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2004,45 +2011,45 @@ type ReposStoreGetByCollaboratorIDWithAccessModeFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreGetByCollaboratorIDWithAccessModeFuncCall) Args() []interface{} { +func (c RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreGetByCollaboratorIDWithAccessModeFuncCall) Results() []interface{} { +func (c RepositoriesStoreGetByCollaboratorIDWithAccessModeFuncCall) Results() []interface{} { return []interface{}{c.Result0, c.Result1} } -// ReposStoreGetByIDFunc describes the behavior when the GetByID method of -// the parent MockReposStore instance is invoked. -type ReposStoreGetByIDFunc struct { +// RepositoriesStoreGetByIDFunc describes the behavior when the GetByID +// method of the parent MockRepositoriesStore instance is invoked. +type RepositoriesStoreGetByIDFunc struct { defaultHook func(context.Context, int64) (*db.Repository, error) hooks []func(context.Context, int64) (*db.Repository, error) - history []ReposStoreGetByIDFuncCall + history []RepositoriesStoreGetByIDFuncCall mutex sync.Mutex } // GetByID delegates to the next hook function in the queue and stores the // parameter and result values of this invocation. -func (m *MockReposStore) GetByID(v0 context.Context, v1 int64) (*db.Repository, error) { +func (m *MockRepositoriesStore) GetByID(v0 context.Context, v1 int64) (*db.Repository, error) { r0, r1 := m.GetByIDFunc.nextHook()(v0, v1) - m.GetByIDFunc.appendCall(ReposStoreGetByIDFuncCall{v0, v1, r0, r1}) + m.GetByIDFunc.appendCall(RepositoriesStoreGetByIDFuncCall{v0, v1, r0, r1}) return r0, r1 } // SetDefaultHook sets function that is called when the GetByID method of -// the parent MockReposStore instance is invoked and the hook queue is -// empty. -func (f *ReposStoreGetByIDFunc) SetDefaultHook(hook func(context.Context, int64) (*db.Repository, error)) { +// the parent MockRepositoriesStore instance is invoked and the hook queue +// is empty. +func (f *RepositoriesStoreGetByIDFunc) SetDefaultHook(hook func(context.Context, int64) (*db.Repository, error)) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// GetByID method of the parent MockReposStore instance invokes the hook at -// the front of the queue and discards it. After the queue is empty, the -// default hook function is invoked for any future action. -func (f *ReposStoreGetByIDFunc) PushHook(hook func(context.Context, int64) (*db.Repository, error)) { +// GetByID method of the parent MockRepositoriesStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *RepositoriesStoreGetByIDFunc) PushHook(hook func(context.Context, int64) (*db.Repository, error)) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -2050,20 +2057,20 @@ func (f *ReposStoreGetByIDFunc) PushHook(hook func(context.Context, int64) (*db. // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreGetByIDFunc) SetDefaultReturn(r0 *db.Repository, r1 error) { +func (f *RepositoriesStoreGetByIDFunc) SetDefaultReturn(r0 *db.Repository, r1 error) { f.SetDefaultHook(func(context.Context, int64) (*db.Repository, error) { return r0, r1 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreGetByIDFunc) PushReturn(r0 *db.Repository, r1 error) { +func (f *RepositoriesStoreGetByIDFunc) PushReturn(r0 *db.Repository, r1 error) { f.PushHook(func(context.Context, int64) (*db.Repository, error) { return r0, r1 }) } -func (f *ReposStoreGetByIDFunc) nextHook() func(context.Context, int64) (*db.Repository, error) { +func (f *RepositoriesStoreGetByIDFunc) nextHook() func(context.Context, int64) (*db.Repository, error) { f.mutex.Lock() defer f.mutex.Unlock() @@ -2076,26 +2083,26 @@ func (f *ReposStoreGetByIDFunc) nextHook() func(context.Context, int64) (*db.Rep return hook } -func (f *ReposStoreGetByIDFunc) appendCall(r0 ReposStoreGetByIDFuncCall) { +func (f *RepositoriesStoreGetByIDFunc) appendCall(r0 RepositoriesStoreGetByIDFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreGetByIDFuncCall objects +// History returns a sequence of RepositoriesStoreGetByIDFuncCall objects // describing the invocations of this function. -func (f *ReposStoreGetByIDFunc) History() []ReposStoreGetByIDFuncCall { +func (f *RepositoriesStoreGetByIDFunc) History() []RepositoriesStoreGetByIDFuncCall { f.mutex.Lock() - history := make([]ReposStoreGetByIDFuncCall, len(f.history)) + history := make([]RepositoriesStoreGetByIDFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreGetByIDFuncCall is an object that describes an invocation of -// method GetByID on an instance of MockReposStore. -type ReposStoreGetByIDFuncCall struct { +// RepositoriesStoreGetByIDFuncCall is an object that describes an +// invocation of method GetByID on an instance of MockRepositoriesStore. +type RepositoriesStoreGetByIDFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2112,45 +2119,45 @@ type ReposStoreGetByIDFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreGetByIDFuncCall) Args() []interface{} { +func (c RepositoriesStoreGetByIDFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreGetByIDFuncCall) Results() []interface{} { +func (c RepositoriesStoreGetByIDFuncCall) Results() []interface{} { return []interface{}{c.Result0, c.Result1} } -// ReposStoreGetByNameFunc describes the behavior when the GetByName method -// of the parent MockReposStore instance is invoked. -type ReposStoreGetByNameFunc struct { +// RepositoriesStoreGetByNameFunc describes the behavior when the GetByName +// method of the parent MockRepositoriesStore instance is invoked. +type RepositoriesStoreGetByNameFunc struct { defaultHook func(context.Context, int64, string) (*db.Repository, error) hooks []func(context.Context, int64, string) (*db.Repository, error) - history []ReposStoreGetByNameFuncCall + history []RepositoriesStoreGetByNameFuncCall mutex sync.Mutex } // GetByName delegates to the next hook function in the queue and stores the // parameter and result values of this invocation. -func (m *MockReposStore) GetByName(v0 context.Context, v1 int64, v2 string) (*db.Repository, error) { +func (m *MockRepositoriesStore) GetByName(v0 context.Context, v1 int64, v2 string) (*db.Repository, error) { r0, r1 := m.GetByNameFunc.nextHook()(v0, v1, v2) - m.GetByNameFunc.appendCall(ReposStoreGetByNameFuncCall{v0, v1, v2, r0, r1}) + m.GetByNameFunc.appendCall(RepositoriesStoreGetByNameFuncCall{v0, v1, v2, r0, r1}) return r0, r1 } // SetDefaultHook sets function that is called when the GetByName method of -// the parent MockReposStore instance is invoked and the hook queue is -// empty. -func (f *ReposStoreGetByNameFunc) SetDefaultHook(hook func(context.Context, int64, string) (*db.Repository, error)) { +// the parent MockRepositoriesStore instance is invoked and the hook queue +// is empty. +func (f *RepositoriesStoreGetByNameFunc) SetDefaultHook(hook func(context.Context, int64, string) (*db.Repository, error)) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// GetByName method of the parent MockReposStore instance invokes the hook -// at the front of the queue and discards it. After the queue is empty, the -// default hook function is invoked for any future action. -func (f *ReposStoreGetByNameFunc) PushHook(hook func(context.Context, int64, string) (*db.Repository, error)) { +// GetByName method of the parent MockRepositoriesStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *RepositoriesStoreGetByNameFunc) PushHook(hook func(context.Context, int64, string) (*db.Repository, error)) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -2158,20 +2165,20 @@ func (f *ReposStoreGetByNameFunc) PushHook(hook func(context.Context, int64, str // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreGetByNameFunc) SetDefaultReturn(r0 *db.Repository, r1 error) { +func (f *RepositoriesStoreGetByNameFunc) SetDefaultReturn(r0 *db.Repository, r1 error) { f.SetDefaultHook(func(context.Context, int64, string) (*db.Repository, error) { return r0, r1 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreGetByNameFunc) PushReturn(r0 *db.Repository, r1 error) { +func (f *RepositoriesStoreGetByNameFunc) PushReturn(r0 *db.Repository, r1 error) { f.PushHook(func(context.Context, int64, string) (*db.Repository, error) { return r0, r1 }) } -func (f *ReposStoreGetByNameFunc) nextHook() func(context.Context, int64, string) (*db.Repository, error) { +func (f *RepositoriesStoreGetByNameFunc) nextHook() func(context.Context, int64, string) (*db.Repository, error) { f.mutex.Lock() defer f.mutex.Unlock() @@ -2184,26 +2191,26 @@ func (f *ReposStoreGetByNameFunc) nextHook() func(context.Context, int64, string return hook } -func (f *ReposStoreGetByNameFunc) appendCall(r0 ReposStoreGetByNameFuncCall) { +func (f *RepositoriesStoreGetByNameFunc) appendCall(r0 RepositoriesStoreGetByNameFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreGetByNameFuncCall objects +// History returns a sequence of RepositoriesStoreGetByNameFuncCall objects // describing the invocations of this function. -func (f *ReposStoreGetByNameFunc) History() []ReposStoreGetByNameFuncCall { +func (f *RepositoriesStoreGetByNameFunc) History() []RepositoriesStoreGetByNameFuncCall { f.mutex.Lock() - history := make([]ReposStoreGetByNameFuncCall, len(f.history)) + history := make([]RepositoriesStoreGetByNameFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreGetByNameFuncCall is an object that describes an invocation of -// method GetByName on an instance of MockReposStore. -type ReposStoreGetByNameFuncCall struct { +// RepositoriesStoreGetByNameFuncCall is an object that describes an +// invocation of method GetByName on an instance of MockRepositoriesStore. +type RepositoriesStoreGetByNameFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2223,45 +2230,46 @@ type ReposStoreGetByNameFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreGetByNameFuncCall) Args() []interface{} { +func (c RepositoriesStoreGetByNameFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1, c.Arg2} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreGetByNameFuncCall) Results() []interface{} { +func (c RepositoriesStoreGetByNameFuncCall) Results() []interface{} { return []interface{}{c.Result0, c.Result1} } -// ReposStoreHasForkedByFunc describes the behavior when the HasForkedBy -// method of the parent MockReposStore instance is invoked. -type ReposStoreHasForkedByFunc struct { +// RepositoriesStoreHasForkedByFunc describes the behavior when the +// HasForkedBy method of the parent MockRepositoriesStore instance is +// invoked. +type RepositoriesStoreHasForkedByFunc struct { defaultHook func(context.Context, int64, int64) bool hooks []func(context.Context, int64, int64) bool - history []ReposStoreHasForkedByFuncCall + history []RepositoriesStoreHasForkedByFuncCall mutex sync.Mutex } // HasForkedBy delegates to the next hook function in the queue and stores // the parameter and result values of this invocation. -func (m *MockReposStore) HasForkedBy(v0 context.Context, v1 int64, v2 int64) bool { +func (m *MockRepositoriesStore) HasForkedBy(v0 context.Context, v1 int64, v2 int64) bool { r0 := m.HasForkedByFunc.nextHook()(v0, v1, v2) - m.HasForkedByFunc.appendCall(ReposStoreHasForkedByFuncCall{v0, v1, v2, r0}) + m.HasForkedByFunc.appendCall(RepositoriesStoreHasForkedByFuncCall{v0, v1, v2, r0}) return r0 } // SetDefaultHook sets function that is called when the HasForkedBy method -// of the parent MockReposStore instance is invoked and the hook queue is -// empty. -func (f *ReposStoreHasForkedByFunc) SetDefaultHook(hook func(context.Context, int64, int64) bool) { +// of the parent MockRepositoriesStore instance is invoked and the hook +// queue is empty. +func (f *RepositoriesStoreHasForkedByFunc) SetDefaultHook(hook func(context.Context, int64, int64) bool) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// HasForkedBy method of the parent MockReposStore instance invokes the hook -// at the front of the queue and discards it. After the queue is empty, the -// default hook function is invoked for any future action. -func (f *ReposStoreHasForkedByFunc) PushHook(hook func(context.Context, int64, int64) bool) { +// HasForkedBy method of the parent MockRepositoriesStore instance invokes +// the hook at the front of the queue and discards it. After the queue is +// empty, the default hook function is invoked for any future action. +func (f *RepositoriesStoreHasForkedByFunc) PushHook(hook func(context.Context, int64, int64) bool) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -2269,20 +2277,20 @@ func (f *ReposStoreHasForkedByFunc) PushHook(hook func(context.Context, int64, i // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreHasForkedByFunc) SetDefaultReturn(r0 bool) { +func (f *RepositoriesStoreHasForkedByFunc) SetDefaultReturn(r0 bool) { f.SetDefaultHook(func(context.Context, int64, int64) bool { return r0 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreHasForkedByFunc) PushReturn(r0 bool) { +func (f *RepositoriesStoreHasForkedByFunc) PushReturn(r0 bool) { f.PushHook(func(context.Context, int64, int64) bool { return r0 }) } -func (f *ReposStoreHasForkedByFunc) nextHook() func(context.Context, int64, int64) bool { +func (f *RepositoriesStoreHasForkedByFunc) nextHook() func(context.Context, int64, int64) bool { f.mutex.Lock() defer f.mutex.Unlock() @@ -2295,26 +2303,26 @@ func (f *ReposStoreHasForkedByFunc) nextHook() func(context.Context, int64, int6 return hook } -func (f *ReposStoreHasForkedByFunc) appendCall(r0 ReposStoreHasForkedByFuncCall) { +func (f *RepositoriesStoreHasForkedByFunc) appendCall(r0 RepositoriesStoreHasForkedByFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreHasForkedByFuncCall objects -// describing the invocations of this function. -func (f *ReposStoreHasForkedByFunc) History() []ReposStoreHasForkedByFuncCall { +// History returns a sequence of RepositoriesStoreHasForkedByFuncCall +// objects describing the invocations of this function. +func (f *RepositoriesStoreHasForkedByFunc) History() []RepositoriesStoreHasForkedByFuncCall { f.mutex.Lock() - history := make([]ReposStoreHasForkedByFuncCall, len(f.history)) + history := make([]RepositoriesStoreHasForkedByFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreHasForkedByFuncCall is an object that describes an invocation -// of method HasForkedBy on an instance of MockReposStore. -type ReposStoreHasForkedByFuncCall struct { +// RepositoriesStoreHasForkedByFuncCall is an object that describes an +// invocation of method HasForkedBy on an instance of MockRepositoriesStore. +type RepositoriesStoreHasForkedByFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2331,45 +2339,46 @@ type ReposStoreHasForkedByFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreHasForkedByFuncCall) Args() []interface{} { +func (c RepositoriesStoreHasForkedByFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1, c.Arg2} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreHasForkedByFuncCall) Results() []interface{} { +func (c RepositoriesStoreHasForkedByFuncCall) Results() []interface{} { return []interface{}{c.Result0} } -// ReposStoreListWatchesFunc describes the behavior when the ListWatches -// method of the parent MockReposStore instance is invoked. -type ReposStoreListWatchesFunc struct { +// RepositoriesStoreListWatchesFunc describes the behavior when the +// ListWatches method of the parent MockRepositoriesStore instance is +// invoked. +type RepositoriesStoreListWatchesFunc struct { defaultHook func(context.Context, int64) ([]*db.Watch, error) hooks []func(context.Context, int64) ([]*db.Watch, error) - history []ReposStoreListWatchesFuncCall + history []RepositoriesStoreListWatchesFuncCall mutex sync.Mutex } // ListWatches delegates to the next hook function in the queue and stores // the parameter and result values of this invocation. -func (m *MockReposStore) ListWatches(v0 context.Context, v1 int64) ([]*db.Watch, error) { +func (m *MockRepositoriesStore) ListWatches(v0 context.Context, v1 int64) ([]*db.Watch, error) { r0, r1 := m.ListWatchesFunc.nextHook()(v0, v1) - m.ListWatchesFunc.appendCall(ReposStoreListWatchesFuncCall{v0, v1, r0, r1}) + m.ListWatchesFunc.appendCall(RepositoriesStoreListWatchesFuncCall{v0, v1, r0, r1}) return r0, r1 } // SetDefaultHook sets function that is called when the ListWatches method -// of the parent MockReposStore instance is invoked and the hook queue is -// empty. -func (f *ReposStoreListWatchesFunc) SetDefaultHook(hook func(context.Context, int64) ([]*db.Watch, error)) { +// of the parent MockRepositoriesStore instance is invoked and the hook +// queue is empty. +func (f *RepositoriesStoreListWatchesFunc) SetDefaultHook(hook func(context.Context, int64) ([]*db.Watch, error)) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// ListWatches method of the parent MockReposStore instance invokes the hook -// at the front of the queue and discards it. After the queue is empty, the -// default hook function is invoked for any future action. -func (f *ReposStoreListWatchesFunc) PushHook(hook func(context.Context, int64) ([]*db.Watch, error)) { +// ListWatches method of the parent MockRepositoriesStore instance invokes +// the hook at the front of the queue and discards it. After the queue is +// empty, the default hook function is invoked for any future action. +func (f *RepositoriesStoreListWatchesFunc) PushHook(hook func(context.Context, int64) ([]*db.Watch, error)) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -2377,20 +2386,20 @@ func (f *ReposStoreListWatchesFunc) PushHook(hook func(context.Context, int64) ( // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreListWatchesFunc) SetDefaultReturn(r0 []*db.Watch, r1 error) { +func (f *RepositoriesStoreListWatchesFunc) SetDefaultReturn(r0 []*db.Watch, r1 error) { f.SetDefaultHook(func(context.Context, int64) ([]*db.Watch, error) { return r0, r1 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreListWatchesFunc) PushReturn(r0 []*db.Watch, r1 error) { +func (f *RepositoriesStoreListWatchesFunc) PushReturn(r0 []*db.Watch, r1 error) { f.PushHook(func(context.Context, int64) ([]*db.Watch, error) { return r0, r1 }) } -func (f *ReposStoreListWatchesFunc) nextHook() func(context.Context, int64) ([]*db.Watch, error) { +func (f *RepositoriesStoreListWatchesFunc) nextHook() func(context.Context, int64) ([]*db.Watch, error) { f.mutex.Lock() defer f.mutex.Unlock() @@ -2403,26 +2412,26 @@ func (f *ReposStoreListWatchesFunc) nextHook() func(context.Context, int64) ([]* return hook } -func (f *ReposStoreListWatchesFunc) appendCall(r0 ReposStoreListWatchesFuncCall) { +func (f *RepositoriesStoreListWatchesFunc) appendCall(r0 RepositoriesStoreListWatchesFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreListWatchesFuncCall objects -// describing the invocations of this function. -func (f *ReposStoreListWatchesFunc) History() []ReposStoreListWatchesFuncCall { +// History returns a sequence of RepositoriesStoreListWatchesFuncCall +// objects describing the invocations of this function. +func (f *RepositoriesStoreListWatchesFunc) History() []RepositoriesStoreListWatchesFuncCall { f.mutex.Lock() - history := make([]ReposStoreListWatchesFuncCall, len(f.history)) + history := make([]RepositoriesStoreListWatchesFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreListWatchesFuncCall is an object that describes an invocation -// of method ListWatches on an instance of MockReposStore. -type ReposStoreListWatchesFuncCall struct { +// RepositoriesStoreListWatchesFuncCall is an object that describes an +// invocation of method ListWatches on an instance of MockRepositoriesStore. +type RepositoriesStoreListWatchesFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2439,44 +2448,45 @@ type ReposStoreListWatchesFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreListWatchesFuncCall) Args() []interface{} { +func (c RepositoriesStoreListWatchesFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreListWatchesFuncCall) Results() []interface{} { +func (c RepositoriesStoreListWatchesFuncCall) Results() []interface{} { return []interface{}{c.Result0, c.Result1} } -// ReposStoreStarFunc describes the behavior when the Star method of the -// parent MockReposStore instance is invoked. -type ReposStoreStarFunc struct { +// RepositoriesStoreStarFunc describes the behavior when the Star method of +// the parent MockRepositoriesStore instance is invoked. +type RepositoriesStoreStarFunc struct { defaultHook func(context.Context, int64, int64) error hooks []func(context.Context, int64, int64) error - history []ReposStoreStarFuncCall + history []RepositoriesStoreStarFuncCall mutex sync.Mutex } // Star delegates to the next hook function in the queue and stores the // parameter and result values of this invocation. -func (m *MockReposStore) Star(v0 context.Context, v1 int64, v2 int64) error { +func (m *MockRepositoriesStore) Star(v0 context.Context, v1 int64, v2 int64) error { r0 := m.StarFunc.nextHook()(v0, v1, v2) - m.StarFunc.appendCall(ReposStoreStarFuncCall{v0, v1, v2, r0}) + m.StarFunc.appendCall(RepositoriesStoreStarFuncCall{v0, v1, v2, r0}) return r0 } // SetDefaultHook sets function that is called when the Star method of the -// parent MockReposStore instance is invoked and the hook queue is empty. -func (f *ReposStoreStarFunc) SetDefaultHook(hook func(context.Context, int64, int64) error) { +// parent MockRepositoriesStore instance is invoked and the hook queue is +// empty. +func (f *RepositoriesStoreStarFunc) SetDefaultHook(hook func(context.Context, int64, int64) error) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// Star method of the parent MockReposStore instance invokes the hook at the -// front of the queue and discards it. After the queue is empty, the default -// hook function is invoked for any future action. -func (f *ReposStoreStarFunc) PushHook(hook func(context.Context, int64, int64) error) { +// Star method of the parent MockRepositoriesStore instance invokes the hook +// at the front of the queue and discards it. After the queue is empty, the +// default hook function is invoked for any future action. +func (f *RepositoriesStoreStarFunc) PushHook(hook func(context.Context, int64, int64) error) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -2484,20 +2494,20 @@ func (f *ReposStoreStarFunc) PushHook(hook func(context.Context, int64, int64) e // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreStarFunc) SetDefaultReturn(r0 error) { +func (f *RepositoriesStoreStarFunc) SetDefaultReturn(r0 error) { f.SetDefaultHook(func(context.Context, int64, int64) error { return r0 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreStarFunc) PushReturn(r0 error) { +func (f *RepositoriesStoreStarFunc) PushReturn(r0 error) { f.PushHook(func(context.Context, int64, int64) error { return r0 }) } -func (f *ReposStoreStarFunc) nextHook() func(context.Context, int64, int64) error { +func (f *RepositoriesStoreStarFunc) nextHook() func(context.Context, int64, int64) error { f.mutex.Lock() defer f.mutex.Unlock() @@ -2510,26 +2520,26 @@ func (f *ReposStoreStarFunc) nextHook() func(context.Context, int64, int64) erro return hook } -func (f *ReposStoreStarFunc) appendCall(r0 ReposStoreStarFuncCall) { +func (f *RepositoriesStoreStarFunc) appendCall(r0 RepositoriesStoreStarFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreStarFuncCall objects describing -// the invocations of this function. -func (f *ReposStoreStarFunc) History() []ReposStoreStarFuncCall { +// History returns a sequence of RepositoriesStoreStarFuncCall objects +// describing the invocations of this function. +func (f *RepositoriesStoreStarFunc) History() []RepositoriesStoreStarFuncCall { f.mutex.Lock() - history := make([]ReposStoreStarFuncCall, len(f.history)) + history := make([]RepositoriesStoreStarFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreStarFuncCall is an object that describes an invocation of -// method Star on an instance of MockReposStore. -type ReposStoreStarFuncCall struct { +// RepositoriesStoreStarFuncCall is an object that describes an invocation +// of method Star on an instance of MockRepositoriesStore. +type RepositoriesStoreStarFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2546,44 +2556,45 @@ type ReposStoreStarFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreStarFuncCall) Args() []interface{} { +func (c RepositoriesStoreStarFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1, c.Arg2} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreStarFuncCall) Results() []interface{} { +func (c RepositoriesStoreStarFuncCall) Results() []interface{} { return []interface{}{c.Result0} } -// ReposStoreTouchFunc describes the behavior when the Touch method of the -// parent MockReposStore instance is invoked. -type ReposStoreTouchFunc struct { +// RepositoriesStoreTouchFunc describes the behavior when the Touch method +// of the parent MockRepositoriesStore instance is invoked. +type RepositoriesStoreTouchFunc struct { defaultHook func(context.Context, int64) error hooks []func(context.Context, int64) error - history []ReposStoreTouchFuncCall + history []RepositoriesStoreTouchFuncCall mutex sync.Mutex } // Touch delegates to the next hook function in the queue and stores the // parameter and result values of this invocation. -func (m *MockReposStore) Touch(v0 context.Context, v1 int64) error { +func (m *MockRepositoriesStore) Touch(v0 context.Context, v1 int64) error { r0 := m.TouchFunc.nextHook()(v0, v1) - m.TouchFunc.appendCall(ReposStoreTouchFuncCall{v0, v1, r0}) + m.TouchFunc.appendCall(RepositoriesStoreTouchFuncCall{v0, v1, r0}) return r0 } // SetDefaultHook sets function that is called when the Touch method of the -// parent MockReposStore instance is invoked and the hook queue is empty. -func (f *ReposStoreTouchFunc) SetDefaultHook(hook func(context.Context, int64) error) { +// parent MockRepositoriesStore instance is invoked and the hook queue is +// empty. +func (f *RepositoriesStoreTouchFunc) SetDefaultHook(hook func(context.Context, int64) error) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// Touch method of the parent MockReposStore instance invokes the hook at -// the front of the queue and discards it. After the queue is empty, the -// default hook function is invoked for any future action. -func (f *ReposStoreTouchFunc) PushHook(hook func(context.Context, int64) error) { +// Touch method of the parent MockRepositoriesStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *RepositoriesStoreTouchFunc) PushHook(hook func(context.Context, int64) error) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -2591,20 +2602,20 @@ func (f *ReposStoreTouchFunc) PushHook(hook func(context.Context, int64) error) // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreTouchFunc) SetDefaultReturn(r0 error) { +func (f *RepositoriesStoreTouchFunc) SetDefaultReturn(r0 error) { f.SetDefaultHook(func(context.Context, int64) error { return r0 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreTouchFunc) PushReturn(r0 error) { +func (f *RepositoriesStoreTouchFunc) PushReturn(r0 error) { f.PushHook(func(context.Context, int64) error { return r0 }) } -func (f *ReposStoreTouchFunc) nextHook() func(context.Context, int64) error { +func (f *RepositoriesStoreTouchFunc) nextHook() func(context.Context, int64) error { f.mutex.Lock() defer f.mutex.Unlock() @@ -2617,26 +2628,26 @@ func (f *ReposStoreTouchFunc) nextHook() func(context.Context, int64) error { return hook } -func (f *ReposStoreTouchFunc) appendCall(r0 ReposStoreTouchFuncCall) { +func (f *RepositoriesStoreTouchFunc) appendCall(r0 RepositoriesStoreTouchFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreTouchFuncCall objects describing -// the invocations of this function. -func (f *ReposStoreTouchFunc) History() []ReposStoreTouchFuncCall { +// History returns a sequence of RepositoriesStoreTouchFuncCall objects +// describing the invocations of this function. +func (f *RepositoriesStoreTouchFunc) History() []RepositoriesStoreTouchFuncCall { f.mutex.Lock() - history := make([]ReposStoreTouchFuncCall, len(f.history)) + history := make([]RepositoriesStoreTouchFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreTouchFuncCall is an object that describes an invocation of -// method Touch on an instance of MockReposStore. -type ReposStoreTouchFuncCall struct { +// RepositoriesStoreTouchFuncCall is an object that describes an invocation +// of method Touch on an instance of MockRepositoriesStore. +type RepositoriesStoreTouchFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2650,44 +2661,45 @@ type ReposStoreTouchFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreTouchFuncCall) Args() []interface{} { +func (c RepositoriesStoreTouchFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreTouchFuncCall) Results() []interface{} { +func (c RepositoriesStoreTouchFuncCall) Results() []interface{} { return []interface{}{c.Result0} } -// ReposStoreWatchFunc describes the behavior when the Watch method of the -// parent MockReposStore instance is invoked. -type ReposStoreWatchFunc struct { +// RepositoriesStoreWatchFunc describes the behavior when the Watch method +// of the parent MockRepositoriesStore instance is invoked. +type RepositoriesStoreWatchFunc struct { defaultHook func(context.Context, int64, int64) error hooks []func(context.Context, int64, int64) error - history []ReposStoreWatchFuncCall + history []RepositoriesStoreWatchFuncCall mutex sync.Mutex } // Watch delegates to the next hook function in the queue and stores the // parameter and result values of this invocation. -func (m *MockReposStore) Watch(v0 context.Context, v1 int64, v2 int64) error { +func (m *MockRepositoriesStore) Watch(v0 context.Context, v1 int64, v2 int64) error { r0 := m.WatchFunc.nextHook()(v0, v1, v2) - m.WatchFunc.appendCall(ReposStoreWatchFuncCall{v0, v1, v2, r0}) + m.WatchFunc.appendCall(RepositoriesStoreWatchFuncCall{v0, v1, v2, r0}) return r0 } // SetDefaultHook sets function that is called when the Watch method of the -// parent MockReposStore instance is invoked and the hook queue is empty. -func (f *ReposStoreWatchFunc) SetDefaultHook(hook func(context.Context, int64, int64) error) { +// parent MockRepositoriesStore instance is invoked and the hook queue is +// empty. +func (f *RepositoriesStoreWatchFunc) SetDefaultHook(hook func(context.Context, int64, int64) error) { f.defaultHook = hook } // PushHook adds a function to the end of hook queue. Each invocation of the -// Watch method of the parent MockReposStore instance invokes the hook at -// the front of the queue and discards it. After the queue is empty, the -// default hook function is invoked for any future action. -func (f *ReposStoreWatchFunc) PushHook(hook func(context.Context, int64, int64) error) { +// Watch method of the parent MockRepositoriesStore instance invokes the +// hook at the front of the queue and discards it. After the queue is empty, +// the default hook function is invoked for any future action. +func (f *RepositoriesStoreWatchFunc) PushHook(hook func(context.Context, int64, int64) error) { f.mutex.Lock() f.hooks = append(f.hooks, hook) f.mutex.Unlock() @@ -2695,20 +2707,20 @@ func (f *ReposStoreWatchFunc) PushHook(hook func(context.Context, int64, int64) // SetDefaultReturn calls SetDefaultHook with a function that returns the // given values. -func (f *ReposStoreWatchFunc) SetDefaultReturn(r0 error) { +func (f *RepositoriesStoreWatchFunc) SetDefaultReturn(r0 error) { f.SetDefaultHook(func(context.Context, int64, int64) error { return r0 }) } // PushReturn calls PushHook with a function that returns the given values. -func (f *ReposStoreWatchFunc) PushReturn(r0 error) { +func (f *RepositoriesStoreWatchFunc) PushReturn(r0 error) { f.PushHook(func(context.Context, int64, int64) error { return r0 }) } -func (f *ReposStoreWatchFunc) nextHook() func(context.Context, int64, int64) error { +func (f *RepositoriesStoreWatchFunc) nextHook() func(context.Context, int64, int64) error { f.mutex.Lock() defer f.mutex.Unlock() @@ -2721,26 +2733,26 @@ func (f *ReposStoreWatchFunc) nextHook() func(context.Context, int64, int64) err return hook } -func (f *ReposStoreWatchFunc) appendCall(r0 ReposStoreWatchFuncCall) { +func (f *RepositoriesStoreWatchFunc) appendCall(r0 RepositoriesStoreWatchFuncCall) { f.mutex.Lock() f.history = append(f.history, r0) f.mutex.Unlock() } -// History returns a sequence of ReposStoreWatchFuncCall objects describing -// the invocations of this function. -func (f *ReposStoreWatchFunc) History() []ReposStoreWatchFuncCall { +// History returns a sequence of RepositoriesStoreWatchFuncCall objects +// describing the invocations of this function. +func (f *RepositoriesStoreWatchFunc) History() []RepositoriesStoreWatchFuncCall { f.mutex.Lock() - history := make([]ReposStoreWatchFuncCall, len(f.history)) + history := make([]RepositoriesStoreWatchFuncCall, len(f.history)) copy(history, f.history) f.mutex.Unlock() return history } -// ReposStoreWatchFuncCall is an object that describes an invocation of -// method Watch on an instance of MockReposStore. -type ReposStoreWatchFuncCall struct { +// RepositoriesStoreWatchFuncCall is an object that describes an invocation +// of method Watch on an instance of MockRepositoriesStore. +type RepositoriesStoreWatchFuncCall struct { // Arg0 is the value of the 1st argument passed to this method // invocation. Arg0 context.Context @@ -2757,13 +2769,13 @@ type ReposStoreWatchFuncCall struct { // Args returns an interface slice containing the arguments of this // invocation. -func (c ReposStoreWatchFuncCall) Args() []interface{} { +func (c RepositoriesStoreWatchFuncCall) Args() []interface{} { return []interface{}{c.Arg0, c.Arg1, c.Arg2} } // Results returns an interface slice containing the results of this // invocation. -func (c ReposStoreWatchFuncCall) Results() []interface{} { +func (c RepositoriesStoreWatchFuncCall) Results() []interface{} { return []interface{}{c.Result0} } diff --git a/internal/route/lfs/route.go b/internal/route/lfs/route.go index 3440c89b0ce..edd16b885f8 100644 --- a/internal/route/lfs/route.go +++ b/internal/route/lfs/route.go @@ -117,7 +117,7 @@ func authorize(mode db.AccessMode) macaron.Handler { return } - repo, err := db.Repos.GetByName(c.Req.Context(), owner.ID, reponame) + repo, err := db.Repositories.GetByName(c.Req.Context(), owner.ID, reponame) if err != nil { if db.IsErrRepoNotExist(err) { c.Status(http.StatusNotFound) diff --git a/internal/route/lfs/route_test.go b/internal/route/lfs/route_test.go index 9ce078465e7..5c640fbf8f2 100644 --- a/internal/route/lfs/route_test.go +++ b/internal/route/lfs/route_test.go @@ -192,7 +192,7 @@ func Test_authorize(t *testing.T) { name string authroize macaron.Handler mockUsersStore func() db.UsersStore - mockReposStore func() db.ReposStore + mockReposStore func() db.RepositoriesStore mockPermsStore func() db.PermsStore expStatusCode int expBody string @@ -217,8 +217,8 @@ func Test_authorize(t *testing.T) { }) return mock }, - mockReposStore: func() db.ReposStore { - mock := NewMockReposStore() + mockReposStore: func() db.RepositoriesStore { + mock := NewMockRepositoriesStore() mock.GetByNameFunc.SetDefaultReturn(nil, db.ErrRepoNotExist{}) return mock }, @@ -234,8 +234,8 @@ func Test_authorize(t *testing.T) { }) return mock }, - mockReposStore: func() db.ReposStore { - mock := NewMockReposStore() + mockReposStore: func() db.RepositoriesStore { + mock := NewMockRepositoriesStore() mock.GetByNameFunc.SetDefaultHook(func(ctx context.Context, ownerID int64, name string) (*db.Repository, error) { return &db.Repository{Name: name}, nil }) @@ -261,8 +261,8 @@ func Test_authorize(t *testing.T) { }) return mock }, - mockReposStore: func() db.ReposStore { - mock := NewMockReposStore() + mockReposStore: func() db.RepositoriesStore { + mock := NewMockRepositoriesStore() mock.GetByNameFunc.SetDefaultHook(func(ctx context.Context, ownerID int64, name string) (*db.Repository, error) { return &db.Repository{Name: name}, nil }) diff --git a/internal/route/org/members.go b/internal/route/org/members.go index b53034e436f..85c6832af41 100644 --- a/internal/route/org/members.go +++ b/internal/route/org/members.go @@ -23,11 +23,12 @@ func Members(c *context.Context) { c.Data["Title"] = org.FullName c.Data["PageIsOrgMembers"] = true - if err := org.GetMembers(0); err != nil { - c.Error(err, "get members") + members, err := db.Organizations.ListMembers(c.Req.Context(), org.ID, db.ListOrgMembersOptions{}) + if err != nil { + c.Error(err, "list members") return } - c.Data["Members"] = org.Members + c.Data["Members"] = members c.Success(MEMBERS) } @@ -47,26 +48,26 @@ func MembersAction(c *context.Context) { c.NotFound() return } - err = db.ChangeOrgUserStatus(org.ID, uid, false) + err = db.Organizations.SetMemberVisibility(c.Req.Context(), org.ID, uid, false) case "public": if c.User.ID != uid && !c.Org.IsOwner { c.NotFound() return } - err = db.ChangeOrgUserStatus(org.ID, uid, true) + err = db.Organizations.SetMemberVisibility(c.Req.Context(), org.ID, uid, true) case "remove": if !c.Org.IsOwner { c.NotFound() return } - err = org.RemoveMember(uid) + err = db.Organizations.RemoveMember(c.Req.Context(), org.ID, uid) if db.IsErrLastOrgOwner(err) { c.Flash.Error(c.Tr("form.last_org_owner")) c.Redirect(c.Org.OrgLink + "/members") return } case "leave": - err = org.RemoveMember(c.User.ID) + err = db.Organizations.RemoveMember(c.Req.Context(), org.ID, c.User.ID) if db.IsErrLastOrgOwner(err) { c.Flash.Error(c.Tr("form.last_org_owner")) c.Redirect(c.Org.OrgLink + "/members") @@ -108,7 +109,7 @@ func Invitation(c *context.Context) { return } - if err = org.AddMember(u.ID); err != nil { + if err = db.Organizations.AddMember(c.Req.Context(), org.ID, u.ID); err != nil { c.Error(err, "add member") return } diff --git a/internal/route/org/org.go b/internal/route/org/org.go index badd0d81d67..b413fa36e02 100644 --- a/internal/route/org/org.go +++ b/internal/route/org/org.go @@ -29,16 +29,16 @@ func CreatePost(c *context.Context, f form.CreateOrg) { return } - org := &db.User{ - Name: f.OrgName, - IsActive: true, - Type: db.UserTypeOrganization, - } - - if err := db.CreateOrganization(org, c.User); err != nil { + org, err := db.Organizations.Create( + c.Req.Context(), + f.OrgName, + c.User.ID, + db.CreateOrganizationOptions{}, + ) + if err != nil { c.Data["Err_OrgName"] = true switch { - case db.IsErrUserAlreadyExist(err): + case db.IsErrOrganizationAlreadyExist(err): c.RenderWithErr(c.Tr("form.org_name_been_taken"), CREATE, &f) case db.IsErrNameNotAllowed(err): c.RenderWithErr(c.Tr("org.form.name_not_allowed", err.(db.ErrNameNotAllowed).Value()), CREATE, &f) diff --git a/internal/route/org/setting.go b/internal/route/org/setting.go index e632e8a7a25..817022d84d4 100644 --- a/internal/route/org/setting.go +++ b/internal/route/org/setting.go @@ -115,8 +115,9 @@ func SettingsDelete(c *context.Context) { return } - if err := db.DeleteOrganization(org); err != nil { - if db.IsErrUserOwnRepos(err) { + err := db.Organizations.DeleteByID(c.Req.Context(), org.ID) + if err != nil { + if db.IsErrOrganizationOwnRepos(err) { c.Flash.Error(c.Tr("form.org_still_own_repo")) c.Redirect(c.Org.OrgLink + "/settings/delete") } else { diff --git a/internal/route/repo/http.go b/internal/route/repo/http.go index 96ac22e0741..44a4e20f124 100644 --- a/internal/route/repo/http.go +++ b/internal/route/repo/http.go @@ -77,7 +77,7 @@ func HTTPContexter() macaron.Handler { return } - repo, err := db.Repos.GetByName(c.Req.Context(), owner.ID, repoName) + repo, err := db.Repositories.GetByName(c.Req.Context(), owner.ID, repoName) if err != nil { if db.IsErrRepoNotExist(err) { c.Status(http.StatusNotFound) diff --git a/internal/route/repo/issue.go b/internal/route/repo/issue.go index 7f839045557..95c74546e59 100644 --- a/internal/route/repo/issue.go +++ b/internal/route/repo/issue.go @@ -68,7 +68,7 @@ func MustAllowPulls(c *context.Context) { } // User can send pull request if owns a forked repository. - if c.IsLogged && db.Repos.HasForkedBy(c.Req.Context(), c.Repo.Repository.ID, c.User.ID) { + if c.IsLogged && db.Repositories.HasForkedBy(c.Req.Context(), c.Repo.Repository.ID, c.User.ID) { c.Repo.PullRequest.Allowed = true c.Repo.PullRequest.HeadInfo = c.User.Name + ":" + c.Repo.BranchName } diff --git a/internal/route/repo/pull.go b/internal/route/repo/pull.go index 7eedcb3a9b7..721ee9607ad 100644 --- a/internal/route/repo/pull.go +++ b/internal/route/repo/pull.go @@ -69,9 +69,9 @@ func parseBaseRepository(c *context.Context) *db.Repository { } c.Data["ForkFrom"] = baseRepo.Owner.Name + "/" + baseRepo.Name - orgs, err := db.Orgs.List( + orgs, err := db.Organizations.List( c.Req.Context(), - db.ListOrgsOptions{ + db.ListOrganizationsOptions{ MemberID: c.User.ID, IncludePrivateMembers: true, }, diff --git a/internal/route/repo/repo.go b/internal/route/repo/repo.go index f19bd3dfbb1..5da0dfcbed5 100644 --- a/internal/route/repo/repo.go +++ b/internal/route/repo/repo.go @@ -35,7 +35,13 @@ func MustBeNotBare(c *context.Context) { } func checkContextUser(c *context.Context, uid int64) *db.User { - orgs, err := db.GetOwnedOrgsByUserIDDesc(c.User.ID, "updated_unix") + orgs, err := db.Organizations.List( + c.Req.Context(), + db.ListOrganizationsOptions{ + OwnerID: c.User.ID, + OrderBy: db.OrderByUpdatedDesc, + }, + ) if err != nil { c.Error(err, "get owned organization by user ID") return nil diff --git a/internal/route/repo/tasks.go b/internal/route/repo/tasks.go index d9215885620..767d0bdebfe 100644 --- a/internal/route/repo/tasks.go +++ b/internal/route/repo/tasks.go @@ -44,7 +44,7 @@ func TriggerTask(c *macaron.Context) { return } - repo, err := db.Repos.GetByName(c.Req.Context(), owner.ID, reponame) + repo, err := db.Repositories.GetByName(c.Req.Context(), owner.ID, reponame) if err != nil { if db.IsErrRepoNotExist(err) { c.Error(http.StatusBadRequest, "Repository does not exist") diff --git a/internal/route/user/home.go b/internal/route/user/home.go index 4521f511bf8..62e7e1576d5 100644 --- a/internal/route/user/home.go +++ b/internal/route/user/home.go @@ -40,9 +40,9 @@ func getDashboardContextUser(c *context.Context) *db.User { } c.Data["ContextUser"] = ctxUser - orgs, err := db.Orgs.List( + orgs, err := db.Organizations.List( c.Req.Context(), - db.ListOrgsOptions{ + db.ListOrganizationsOptions{ MemberID: c.User.ID, IncludePrivateMembers: true, }, @@ -125,7 +125,7 @@ func Dashboard(c *context.Context) { // Only user can have collaborative repositories. if !ctxUser.IsOrganization() { - collaborateRepos, err := db.Repos.GetByCollaboratorID(c.Req.Context(), c.User.ID, conf.UI.User.RepoPagingNum, "updated_unix DESC") + collaborateRepos, err := db.Repositories.GetByCollaboratorID(c.Req.Context(), c.User.ID, conf.UI.User.RepoPagingNum, "updated_unix DESC") if err != nil { c.Error(err, "get accessible repositories by collaborator") return @@ -140,13 +140,20 @@ func Dashboard(c *context.Context) { var repos, mirrors []*db.Repository var repoCount int64 if ctxUser.IsOrganization() { - repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, conf.UI.User.RepoPagingNum) + repos, repoCount, err = db.Organizations.AccessibleRepositoriesByUser( + c.Req.Context(), + ctxUser.ID, + c.User.ID, + 1, + conf.UI.User.RepoPagingNum, + db.AccessibleRepositoriesByUserOptions{}, + ) if err != nil { c.Error(err, "get user repositories") return } - mirrors, err = ctxUser.GetUserMirrorRepositories(c.User.ID) + mirrors, err = db.Organizations.MirrorRepositoriesByUser(c.Req.Context(), ctxUser.ID, c.User.ID) if err != nil { c.Error(err, "get user mirror repositories") return @@ -236,7 +243,14 @@ func Issues(c *context.Context) { showRepos = make([]*db.Repository, 0, 10) ) if ctxUser.IsOrganization() { - repos, _, err = ctxUser.GetUserRepositories(c.User.ID, 1, ctxUser.NumRepos) + repos, _, err = db.Organizations.AccessibleRepositoriesByUser( + c.Req.Context(), + ctxUser.ID, + c.User.ID, + 1, + ctxUser.NumRepos, + db.AccessibleRepositoriesByUserOptions{SkipCount: true}, + ) if err != nil { c.Error(err, "get repositories") return @@ -409,7 +423,14 @@ func showOrgProfile(c *context.Context) { err error ) if c.IsLogged && !c.User.IsAdmin { - repos, count, err = org.GetUserRepositories(c.User.ID, page, conf.UI.User.RepoPagingNum) + repos, count, err = db.Organizations.AccessibleRepositoriesByUser( + c.Req.Context(), + org.ID, + c.User.ID, + page, + conf.UI.User.RepoPagingNum, + db.AccessibleRepositoriesByUserOptions{}, + ) if err != nil { c.Error(err, "get user repositories") return @@ -432,11 +453,12 @@ func showOrgProfile(c *context.Context) { } c.Data["Page"] = paginater.New(int(count), conf.UI.User.RepoPagingNum, page, 5) - if err := org.GetMembers(12); err != nil { - c.Error(err, "get members") + members, err := db.Organizations.ListMembers(c.Req.Context(), org.ID, db.ListOrgMembersOptions{Limit: 12}) + if err != nil { + c.Error(err, "list members") return } - c.Data["Members"] = org.Members + c.Data["Members"] = members c.Data["Teams"] = org.Teams diff --git a/internal/route/user/profile.go b/internal/route/user/profile.go index 799868ca2e5..546ed008924 100644 --- a/internal/route/user/profile.go +++ b/internal/route/user/profile.go @@ -42,9 +42,15 @@ func Profile(c *context.Context, puser *context.ParamsUser) { c.PageIs("UserProfile") c.Data["Owner"] = puser - orgs, err := db.GetOrgsByUserID(puser.ID, c.IsLogged && (c.User.IsAdmin || c.User.ID == puser.ID)) + orgs, err := db.Organizations.List( + c.Req.Context(), + db.ListOrganizationsOptions{ + MemberID: puser.ID, + IncludePrivateMembers: c.IsLogged && (c.User.IsAdmin || c.User.ID == puser.ID), + }, + ) if err != nil { - c.Error(err, "get organizations by user ID") + c.Error(err, "list organizations by user ID") return } diff --git a/internal/route/user/setting.go b/internal/route/user/setting.go index 6ee5fa4d386..4f4c0f1815e 100644 --- a/internal/route/user/setting.go +++ b/internal/route/user/setting.go @@ -555,9 +555,15 @@ func SettingsOrganizations(c *context.Context) { c.Title("settings.orgs") c.PageIs("SettingsOrganizations") - orgs, err := db.GetOrgsByUserID(c.User.ID, true) + orgs, err := db.Organizations.List( + c.Req.Context(), + db.ListOrganizationsOptions{ + MemberID: c.User.ID, + IncludePrivateMembers: true, + }, + ) if err != nil { - c.Errorf(err, "get organizations by user ID") + c.Errorf(err, "list organizations by user ID") return } c.Data["Orgs"] = orgs @@ -566,7 +572,7 @@ func SettingsOrganizations(c *context.Context) { } func SettingsLeaveOrganization(c *context.Context) { - if err := db.RemoveOrgUser(c.QueryInt64("id"), c.User.ID); err != nil { + if err := db.Organizations.RemoveMember(c.Req.Context(), c.QueryInt64("id"), c.User.ID); err != nil { if db.IsErrLastOrgOwner(err) { c.Flash.Error(c.Tr("form.last_org_owner")) } else { diff --git a/mockgen.yaml b/mockgen.yaml index 6a9a04ed965..703d89e89aa 100644 --- a/mockgen.yaml +++ b/mockgen.yaml @@ -40,5 +40,5 @@ mocks: - UsersStore - TwoFactorsStore - AccessTokensStore - - ReposStore + - RepositoriesStore - PermsStore pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy