mirror of https://github.com/gogs/gogs.git
refactor(db): merge relation stores into entity stores (#7341)
parent
133b9d9044
commit
8350daf505
|
@ -403,7 +403,7 @@ func RepoRef() macaron.Handler {
|
||||||
c.Data["IsViewCommit"] = c.Repo.IsViewCommit
|
c.Data["IsViewCommit"] = c.Repo.IsViewCommit
|
||||||
|
|
||||||
// People who have push access or have forked repository can propose a new pull request.
|
// People who have push access or have forked repository can propose a new pull request.
|
||||||
if c.Repo.IsWriter() || (c.IsLogged && db.Users.HasForkedRepository(c.Req.Context(), c.User.ID, c.Repo.Repository.ID)) {
|
if c.Repo.IsWriter() || (c.IsLogged && db.Repos.HasForkedBy(c.Req.Context(), c.Repo.Repository.ID, c.User.ID)) {
|
||||||
// Pull request is allowed if this is a fork repository
|
// Pull request is allowed if this is a fork repository
|
||||||
// and base repository accepts pull requests.
|
// and base repository accepts pull requests.
|
||||||
if c.Repo.Repository.BaseRepo != nil {
|
if c.Repo.Repository.BaseRepo != nil {
|
||||||
|
|
|
@ -18,8 +18,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccessTokensStore is the persistent interface for access tokens.
|
// AccessTokensStore is the persistent interface for access tokens.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type AccessTokensStore interface {
|
type AccessTokensStore interface {
|
||||||
// Create creates a new access token and persist to database. It returns
|
// Create creates a new access token and persist to database. It returns
|
||||||
// ErrAccessTokenAlreadyExist when an access token with same name already exists
|
// ErrAccessTokenAlreadyExist when an access token with same name already exists
|
||||||
|
|
|
@ -29,8 +29,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ActionsStore is the persistent interface for actions.
|
// ActionsStore is the persistent interface for actions.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type ActionsStore interface {
|
type ActionsStore interface {
|
||||||
// CommitRepo creates actions for pushing commits to the repository. An action
|
// CommitRepo creates actions for pushing commits to the repository. An action
|
||||||
// with the type ActionDeleteBranch is created if the push deletes a branch; an
|
// with the type ActionDeleteBranch is created if the push deletes a branch; an
|
||||||
|
@ -166,7 +164,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.
|
// 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 {
|
func (db *actions) notifyWatchers(ctx context.Context, act *Action) error {
|
||||||
watches, err := NewWatchesStore(db.DB).ListByRepo(ctx, act.RepoID)
|
watches, err := NewReposStore(db.DB).ListWatches(ctx, act.RepoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "list watches")
|
return errors.Wrap(err, "list watches")
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,16 +122,13 @@ func Init(w logger.Writer) (*gorm.DB, error) {
|
||||||
AccessTokens = &accessTokens{DB: db}
|
AccessTokens = &accessTokens{DB: db}
|
||||||
Actions = NewActionsStore(db)
|
Actions = NewActionsStore(db)
|
||||||
EmailAddresses = NewEmailAddressesStore(db)
|
EmailAddresses = NewEmailAddressesStore(db)
|
||||||
Follows = NewFollowsStore(db)
|
|
||||||
LoginSources = &loginSources{DB: db, files: sourceFiles}
|
LoginSources = &loginSources{DB: db, files: sourceFiles}
|
||||||
LFS = &lfs{DB: db}
|
LFS = &lfs{DB: db}
|
||||||
Orgs = NewOrgsStore(db)
|
Orgs = NewOrgsStore(db)
|
||||||
OrgUsers = NewOrgUsersStore(db)
|
|
||||||
Perms = NewPermsStore(db)
|
Perms = NewPermsStore(db)
|
||||||
Repos = NewReposStore(db)
|
Repos = NewReposStore(db)
|
||||||
TwoFactors = &twoFactors{DB: db}
|
TwoFactors = &twoFactors{DB: db}
|
||||||
Users = NewUsersStore(db)
|
Users = NewUsersStore(db)
|
||||||
Watches = NewWatchesStore(db)
|
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmailAddressesStore is the persistent interface for email addresses.
|
// EmailAddressesStore is the persistent interface for email addresses.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type EmailAddressesStore interface {
|
type EmailAddressesStore interface {
|
||||||
// GetByEmail returns the email address with given email. If `needsActivated` is
|
// GetByEmail returns the email address with given email. If `needsActivated` is
|
||||||
// true, only activated email will be returned, otherwise, it may return
|
// true, only activated email will be returned, otherwise, it may return
|
||||||
|
|
|
@ -1,127 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FollowsStore is the persistent interface for user follows.
|
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type FollowsStore interface {
|
|
||||||
// Follow marks the user to follow the other user.
|
|
||||||
Follow(ctx context.Context, userID, followID int64) error
|
|
||||||
// IsFollowing returns true if the user is following the other user.
|
|
||||||
IsFollowing(ctx context.Context, userID, followID int64) bool
|
|
||||||
// Unfollow removes the mark the user to follow the other user.
|
|
||||||
Unfollow(ctx context.Context, userID, followID int64) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var Follows FollowsStore
|
|
||||||
|
|
||||||
var _ FollowsStore = (*follows)(nil)
|
|
||||||
|
|
||||||
type follows struct {
|
|
||||||
*gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFollowsStore returns a persistent interface for user follows with given
|
|
||||||
// database connection.
|
|
||||||
func NewFollowsStore(db *gorm.DB) FollowsStore {
|
|
||||||
return &follows{DB: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*follows) updateFollowingCount(tx *gorm.DB, userID, followID int64) error {
|
|
||||||
/*
|
|
||||||
Equivalent SQL for PostgreSQL:
|
|
||||||
|
|
||||||
UPDATE "user"
|
|
||||||
SET num_followers = (
|
|
||||||
SELECT COUNT(*) FROM follow WHERE follow_id = @followID
|
|
||||||
)
|
|
||||||
WHERE id = @followID
|
|
||||||
*/
|
|
||||||
err := tx.Model(&User{}).
|
|
||||||
Where("id = ?", followID).
|
|
||||||
Update(
|
|
||||||
"num_followers",
|
|
||||||
tx.Model(&Follow{}).Select("COUNT(*)").Where("follow_id = ?", followID),
|
|
||||||
).
|
|
||||||
Error
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, `update "user.num_followers"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Equivalent SQL for PostgreSQL:
|
|
||||||
|
|
||||||
UPDATE "user"
|
|
||||||
SET num_following = (
|
|
||||||
SELECT COUNT(*) FROM follow WHERE user_id = @userID
|
|
||||||
)
|
|
||||||
WHERE id = @userID
|
|
||||||
*/
|
|
||||||
err = tx.Model(&User{}).
|
|
||||||
Where("id = ?", userID).
|
|
||||||
Update(
|
|
||||||
"num_following",
|
|
||||||
tx.Model(&Follow{}).Select("COUNT(*)").Where("user_id = ?", userID),
|
|
||||||
).
|
|
||||||
Error
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, `update "user.num_following"`)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *follows) Follow(ctx context.Context, userID, followID int64) error {
|
|
||||||
if userID == followID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
f := &Follow{
|
|
||||||
UserID: userID,
|
|
||||||
FollowID: followID,
|
|
||||||
}
|
|
||||||
result := tx.FirstOrCreate(f, f)
|
|
||||||
if result.Error != nil {
|
|
||||||
return errors.Wrap(result.Error, "upsert")
|
|
||||||
} else if result.RowsAffected <= 0 {
|
|
||||||
return nil // Relation already exists
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.updateFollowingCount(tx, userID, followID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *follows) IsFollowing(ctx context.Context, userID, followID int64) bool {
|
|
||||||
return db.WithContext(ctx).Where("user_id = ? AND follow_id = ?", userID, followID).First(&Follow{}).Error == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *follows) Unfollow(ctx context.Context, userID, followID int64) error {
|
|
||||||
if userID == followID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
err := tx.Where("user_id = ? AND follow_id = ?", userID, followID).Delete(&Follow{}).Error
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "delete")
|
|
||||||
}
|
|
||||||
return db.updateFollowingCount(tx, userID, followID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow represents relations of users and their followers.
|
|
||||||
type Follow struct {
|
|
||||||
ID int64 `gorm:"primaryKey"`
|
|
||||||
UserID int64 `xorm:"UNIQUE(follow)" gorm:"uniqueIndex:follow_user_follow_unique;not null"`
|
|
||||||
FollowID int64 `xorm:"UNIQUE(follow)" gorm:"uniqueIndex:follow_user_follow_unique;not null"`
|
|
||||||
}
|
|
|
@ -1,122 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFollows(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tables := []any{new(User), new(EmailAddress), new(Follow)}
|
|
||||||
db := &follows{
|
|
||||||
DB: dbtest.NewDB(t, "follows", tables...),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
test func(t *testing.T, db *follows)
|
|
||||||
}{
|
|
||||||
{"Follow", followsFollow},
|
|
||||||
{"IsFollowing", followsIsFollowing},
|
|
||||||
{"Unfollow", followsUnfollow},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err := clearTables(t, db.DB, tables...)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
tc.test(t, db)
|
|
||||||
})
|
|
||||||
if t.Failed() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func followsFollow(t *testing.T, db *follows) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
err = db.Follow(ctx, alice.ID, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// It is OK to follow multiple times and just be noop.
|
|
||||||
err = db.Follow(ctx, alice.ID, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
alice, err = usersStore.GetByID(ctx, alice.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 1, alice.NumFollowing)
|
|
||||||
|
|
||||||
bob, err = usersStore.GetByID(ctx, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 1, bob.NumFollowers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func followsIsFollowing(t *testing.T, db *follows) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
got := db.IsFollowing(ctx, alice.ID, bob.ID)
|
|
||||||
assert.False(t, got)
|
|
||||||
|
|
||||||
err = db.Follow(ctx, alice.ID, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
got = db.IsFollowing(ctx, alice.ID, bob.ID)
|
|
||||||
assert.True(t, got)
|
|
||||||
|
|
||||||
err = db.Unfollow(ctx, alice.ID, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
got = db.IsFollowing(ctx, alice.ID, bob.ID)
|
|
||||||
assert.False(t, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
func followsUnfollow(t *testing.T, db *follows) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
err = db.Follow(ctx, alice.ID, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// It is OK to unfollow multiple times and just be noop.
|
|
||||||
err = db.Unfollow(ctx, alice.ID, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = db.Unfollow(ctx, alice.ID, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
alice, err = usersStore.GetByID(ctx, alice.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 0, alice.NumFollowing)
|
|
||||||
|
|
||||||
bob, err = usersStore.GetByID(ctx, bob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, 0, bob.NumFollowers)
|
|
||||||
}
|
|
|
@ -16,8 +16,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// LFSStore is the persistent interface for LFS objects.
|
// LFSStore is the persistent interface for LFS objects.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type LFSStore interface {
|
type LFSStore interface {
|
||||||
// CreateObject creates a LFS object record in database.
|
// CreateObject creates a LFS object record in database.
|
||||||
CreateObject(ctx context.Context, repoID int64, oid lfsutil.OID, size int64, storage lfsutil.Storage) error
|
CreateObject(ctx context.Context, repoID int64, oid lfsutil.OID, size int64, storage lfsutil.Storage) error
|
||||||
|
|
|
@ -25,8 +25,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// loginSourceFilesStore is the in-memory interface for login source files stored on file system.
|
// loginSourceFilesStore is the in-memory interface for login source files stored on file system.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type loginSourceFilesStore interface {
|
type loginSourceFilesStore interface {
|
||||||
// GetByID returns a clone of login source by given ID.
|
// GetByID returns a clone of login source by given ID.
|
||||||
GetByID(id int64) (*LoginSource, error)
|
GetByID(id int64) (*LoginSource, error)
|
||||||
|
|
|
@ -23,8 +23,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoginSourcesStore is the persistent interface for login sources.
|
// LoginSourcesStore is the persistent interface for login sources.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type LoginSourcesStore interface {
|
type LoginSourcesStore interface {
|
||||||
// Create creates a new login source and persist to database. It returns
|
// Create creates a new login source and persist to database. It returns
|
||||||
// ErrLoginSourceAlreadyExist when a login source with same name already exists.
|
// ErrLoginSourceAlreadyExist when a login source with same name already exists.
|
||||||
|
|
|
@ -1,38 +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"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OrgUsersStore is the persistent interface for organization-user relations.
|
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type OrgUsersStore interface {
|
|
||||||
// CountByUser returns the number of organizations the user is a member of.
|
|
||||||
CountByUser(ctx context.Context, userID int64) (int64, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var OrgUsers OrgUsersStore
|
|
||||||
|
|
||||||
var _ OrgUsersStore = (*orgUsers)(nil)
|
|
||||||
|
|
||||||
type orgUsers struct {
|
|
||||||
*gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOrgUsersStore returns a persistent interface for organization-user
|
|
||||||
// relations with given database connection.
|
|
||||||
func NewOrgUsersStore(db *gorm.DB) OrgUsersStore {
|
|
||||||
return &orgUsers{DB: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *orgUsers) CountByUser(ctx context.Context, userID int64) (int64, error) {
|
|
||||||
var count int64
|
|
||||||
return count, db.WithContext(ctx).Model(&OrgUser{}).Where("uid = ?", userID).Count(&count).Error
|
|
||||||
}
|
|
|
@ -1,63 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOrgUsers(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tables := []any{new(OrgUser)}
|
|
||||||
db := &orgUsers{
|
|
||||||
DB: dbtest.NewDB(t, "orgUsers", tables...),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
test func(t *testing.T, db *orgUsers)
|
|
||||||
}{
|
|
||||||
{"CountByUser", orgUsersCountByUser},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err := clearTables(t, db.DB, tables...)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
tc.test(t, db)
|
|
||||||
})
|
|
||||||
if t.Failed() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func orgUsersCountByUser(t *testing.T, db *orgUsers) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
|
@ -14,8 +14,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrgsStore is the persistent interface for organizations.
|
// OrgsStore is the persistent interface for organizations.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type OrgsStore interface {
|
type OrgsStore interface {
|
||||||
// List returns a list of organizations filtered by options.
|
// List returns a list of organizations filtered by options.
|
||||||
List(ctx context.Context, opts ListOrgsOptions) ([]*Organization, error)
|
List(ctx context.Context, opts ListOrgsOptions) ([]*Organization, error)
|
||||||
|
@ -25,6 +23,9 @@ type OrgsStore interface {
|
||||||
// count of all results is also returned. If the order is not given, it's up to
|
// count of all results is also returned. If the order is not given, it's up to
|
||||||
// the database to decide.
|
// the database to decide.
|
||||||
SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error)
|
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 Orgs OrgsStore
|
||||||
|
@ -79,6 +80,11 @@ func (db *orgs) SearchByName(ctx context.Context, keyword string, page, pageSize
|
||||||
return searchUserByName(ctx, db.DB, UserTypeOrganization, keyword, page, pageSize, orderBy)
|
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
|
type Organization = User
|
||||||
|
|
||||||
func (o *Organization) TableName() string {
|
func (o *Organization) TableName() string {
|
||||||
|
|
|
@ -32,6 +32,7 @@ func TestOrgs(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"List", orgsList},
|
{"List", orgsList},
|
||||||
{"SearchByName", orgsSearchByName},
|
{"SearchByName", orgsSearchByName},
|
||||||
|
{"CountByUser", orgsCountByUser},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
@ -164,3 +165,21 @@ func orgsSearchByName(t *testing.T, db *orgs) {
|
||||||
assert.Equal(t, org2.ID, orgs[0].ID)
|
assert.Equal(t, org2.ID, orgs[0].ID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func orgsCountByUser(t *testing.T, db *orgs) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// PermsStore is the persistent interface for permissions.
|
// PermsStore is the persistent interface for permissions.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type PermsStore interface {
|
type PermsStore interface {
|
||||||
// AccessMode returns the access mode of given user has to the repository.
|
// AccessMode returns the access mode of given user has to the repository.
|
||||||
AccessMode(ctx context.Context, userID, repoID int64, opts AccessModeOptions) AccessMode
|
AccessMode(ctx context.Context, userID, repoID int64, opts AccessModeOptions) AccessMode
|
||||||
|
|
|
@ -16,8 +16,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicKeysStore is the persistent interface for public keys.
|
// PublicKeysStore is the persistent interface for public keys.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type PublicKeysStore interface {
|
type PublicKeysStore interface {
|
||||||
// RewriteAuthorizedKeys rewrites the "authorized_keys" file under the SSH root
|
// RewriteAuthorizedKeys rewrites the "authorized_keys" file under the SSH root
|
||||||
// path with all public keys stored in the database.
|
// path with all public keys stored in the database.
|
||||||
|
|
|
@ -19,8 +19,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReposStore is the persistent interface for repositories.
|
// ReposStore is the persistent interface for repositories.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type ReposStore interface {
|
type ReposStore interface {
|
||||||
// Create creates a new repository record in the database. It returns
|
// Create creates a new repository record in the database. It returns
|
||||||
// ErrNameNotAllowed when the repository name is not allowed, or
|
// ErrNameNotAllowed when the repository name is not allowed, or
|
||||||
|
@ -48,6 +46,14 @@ type ReposStore interface {
|
||||||
// Touch updates the updated time to the current time and removes the bare state
|
// Touch updates the updated time to the current time and removes the bare state
|
||||||
// of the given repository.
|
// of the given repository.
|
||||||
Touch(ctx context.Context, id int64) error
|
Touch(ctx context.Context, id int64) error
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 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 Repos ReposStore
|
||||||
|
@ -189,7 +195,7 @@ func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptio
|
||||||
return errors.Wrap(err, "create")
|
return errors.Wrap(err, "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = NewWatchesStore(tx).Watch(ctx, ownerID, repo.ID)
|
err = NewReposStore(tx).Watch(ctx, ownerID, repo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "watch")
|
return errors.Wrap(err, "watch")
|
||||||
}
|
}
|
||||||
|
@ -371,3 +377,50 @@ func (db *repos) Touch(ctx context.Context, id int64) error {
|
||||||
}).
|
}).
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *repos) 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 {
|
||||||
|
/*
|
||||||
|
Equivalent SQL for PostgreSQL:
|
||||||
|
|
||||||
|
UPDATE repository
|
||||||
|
SET num_watches = (
|
||||||
|
SELECT COUNT(*) FROM watch WHERE repo_id = @repoID
|
||||||
|
)
|
||||||
|
WHERE id = @repoID
|
||||||
|
*/
|
||||||
|
return tx.Model(&Repository{}).
|
||||||
|
Where("id = ?", repoID).
|
||||||
|
Update(
|
||||||
|
"num_watches",
|
||||||
|
tx.Model(&Watch{}).Select("COUNT(*)").Where("repo_id = ?", repoID),
|
||||||
|
).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *repos) Watch(ctx context.Context, userID, repoID int64) error {
|
||||||
|
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
w := &Watch{
|
||||||
|
UserID: userID,
|
||||||
|
RepoID: repoID,
|
||||||
|
}
|
||||||
|
result := tx.FirstOrCreate(w, w)
|
||||||
|
if result.Error != nil {
|
||||||
|
return errors.Wrap(result.Error, "upsert")
|
||||||
|
} else if result.RowsAffected <= 0 {
|
||||||
|
return nil // Relation already exists
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.recountWatches(tx, repoID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *repos) 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
|
||||||
|
}
|
||||||
|
|
|
@ -101,6 +101,9 @@ func TestRepos(t *testing.T) {
|
||||||
{"GetByName", reposGetByName},
|
{"GetByName", reposGetByName},
|
||||||
{"Star", reposStar},
|
{"Star", reposStar},
|
||||||
{"Touch", reposTouch},
|
{"Touch", reposTouch},
|
||||||
|
{"ListByRepo", reposListWatches},
|
||||||
|
{"Watch", reposWatch},
|
||||||
|
{"HasForkedBy", reposHasForkedBy},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
@ -298,3 +301,65 @@ func reposTouch(t *testing.T, db *repos) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, got.IsBare)
|
assert.False(t, got.IsBare)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reposListWatches(t *testing.T, db *repos) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err := db.Watch(ctx, 1, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = db.Watch(ctx, 2, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = db.Watch(ctx, 2, 2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := db.ListWatches(ctx, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, w := range got {
|
||||||
|
w.ID = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []*Watch{
|
||||||
|
{UserID: 1, RepoID: 1},
|
||||||
|
{UserID: 2, RepoID: 1},
|
||||||
|
}
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reposWatch(t *testing.T, db *repos) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
reposStore := NewReposStore(db.DB)
|
||||||
|
repo1, err := reposStore.Create(ctx, 1, CreateRepoOptions{Name: "repo1"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = db.Watch(ctx, 2, repo1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// It is OK to watch multiple times and just be noop.
|
||||||
|
err = db.Watch(ctx, 2, repo1.ID)
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
|
||||||
|
func reposHasForkedBy(t *testing.T, db *repos) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
has := db.HasForkedBy(ctx, 1, 2)
|
||||||
|
assert.False(t, has)
|
||||||
|
|
||||||
|
_, err := NewReposStore(db.DB).Create(
|
||||||
|
ctx,
|
||||||
|
2,
|
||||||
|
CreateRepoOptions{
|
||||||
|
Name: "repo1",
|
||||||
|
ForkID: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
has = db.HasForkedBy(ctx, 1, 2)
|
||||||
|
assert.True(t, has)
|
||||||
|
}
|
||||||
|
|
|
@ -21,8 +21,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// TwoFactorsStore is the persistent interface for 2FA.
|
// TwoFactorsStore is the persistent interface for 2FA.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type TwoFactorsStore interface {
|
type TwoFactorsStore interface {
|
||||||
// Create creates a new 2FA token and recovery codes for given user. The "key"
|
// Create creates a new 2FA token and recovery codes for given user. The "key"
|
||||||
// is used to encrypt and later decrypt given "secret", which should be
|
// is used to encrypt and later decrypt given "secret", which should be
|
||||||
|
|
|
@ -32,8 +32,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersStore is the persistent interface for users.
|
// UsersStore is the persistent interface for users.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type UsersStore interface {
|
type UsersStore interface {
|
||||||
// Authenticate validates username and password via given login source ID. It
|
// Authenticate validates username and password via given login source ID. It
|
||||||
// returns ErrUserNotExist when the user was not found.
|
// returns ErrUserNotExist when the user was not found.
|
||||||
|
@ -47,29 +45,12 @@ type UsersStore interface {
|
||||||
// When the "loginSourceID" is positive, it tries to authenticate via given
|
// When the "loginSourceID" is positive, it tries to authenticate via given
|
||||||
// login source and creates a new user when not yet exists in the database.
|
// login source and creates a new user when not yet exists in the database.
|
||||||
Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error)
|
Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error)
|
||||||
// ChangeUsername changes the username of the given user and updates all
|
|
||||||
// references to the old username. It returns ErrNameNotAllowed if the given
|
|
||||||
// name or pattern of the name is not allowed as a username, or
|
|
||||||
// ErrUserAlreadyExist when another user with same name already exists.
|
|
||||||
ChangeUsername(ctx context.Context, userID int64, newUsername string) error
|
|
||||||
// Count returns the total number of users.
|
|
||||||
Count(ctx context.Context) int64
|
|
||||||
// Create creates a new user and persists to database. It returns
|
// 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
|
// 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,
|
// a username, or ErrUserAlreadyExist when a user with same name already exists,
|
||||||
// or ErrEmailAlreadyUsed if the email has been used by another user.
|
// or ErrEmailAlreadyUsed if the email has been used by another user.
|
||||||
Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error)
|
Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error)
|
||||||
// DeleteCustomAvatar deletes the current user custom avatar and falls back to
|
|
||||||
// use look up avatar by email.
|
|
||||||
DeleteCustomAvatar(ctx context.Context, userID int64) error
|
|
||||||
// DeleteByID deletes the given user and all their resources. It returns
|
|
||||||
// ErrUserOwnRepos when the user still has repository ownership, or returns
|
|
||||||
// ErrUserHasOrgs when the user still has organization membership. It is more
|
|
||||||
// performant to skip rewriting the "authorized_keys" file for individual
|
|
||||||
// deletion in a batch operation.
|
|
||||||
DeleteByID(ctx context.Context, userID int64, skipRewriteAuthorizedKeys bool) error
|
|
||||||
// DeleteInactivated deletes all inactivated users.
|
|
||||||
DeleteInactivated() error
|
|
||||||
// GetByEmail returns the user (not organization) with given email. It ignores
|
// GetByEmail returns the user (not organization) with given email. It ignores
|
||||||
// records with unverified emails and returns ErrUserNotExist when not found.
|
// records with unverified emails and returns ErrUserNotExist when not found.
|
||||||
GetByEmail(ctx context.Context, email string) (*User, error)
|
GetByEmail(ctx context.Context, email string) (*User, error)
|
||||||
|
@ -86,15 +67,45 @@ type UsersStore interface {
|
||||||
// addresses (where email notifications are sent to) of users with given list of
|
// addresses (where email notifications are sent to) of users with given list of
|
||||||
// usernames. Non-existing usernames are ignored.
|
// usernames. Non-existing usernames are ignored.
|
||||||
GetMailableEmailsByUsernames(ctx context.Context, usernames []string) ([]string, error)
|
GetMailableEmailsByUsernames(ctx context.Context, usernames []string) ([]string, error)
|
||||||
// HasForkedRepository returns true if the user has forked given repository.
|
// SearchByName returns a list of users whose username or full name matches the
|
||||||
HasForkedRepository(ctx context.Context, userID, repoID int64) bool
|
// 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) ([]*User, int64, error)
|
||||||
|
|
||||||
// IsUsernameUsed returns true if the given username has been used other than
|
// IsUsernameUsed returns true if the given username has been used other than
|
||||||
// the excluded user (a non-positive ID effectively meaning check against all
|
// the excluded user (a non-positive ID effectively meaning check against all
|
||||||
// users).
|
// users).
|
||||||
IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool
|
IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool
|
||||||
// List returns a list of users. Results are paginated by given page and page
|
// ChangeUsername changes the username of the given user and updates all
|
||||||
// size, and sorted by primary key (id) in ascending order.
|
// references to the old username. It returns ErrNameNotAllowed if the given
|
||||||
List(ctx context.Context, page, pageSize int) ([]*User, error)
|
// name or pattern of the name is not allowed as a username, or
|
||||||
|
// ErrUserAlreadyExist when another user with same name already exists.
|
||||||
|
ChangeUsername(ctx context.Context, userID int64, newUsername string) error
|
||||||
|
// Update updates fields for the given user.
|
||||||
|
Update(ctx context.Context, userID int64, opts UpdateUserOptions) error
|
||||||
|
// UseCustomAvatar uses the given avatar as the user custom avatar.
|
||||||
|
UseCustomAvatar(ctx context.Context, userID int64, avatar []byte) error
|
||||||
|
|
||||||
|
// DeleteCustomAvatar deletes the current user custom avatar and falls back to
|
||||||
|
// use look up avatar by email.
|
||||||
|
DeleteCustomAvatar(ctx context.Context, userID int64) error
|
||||||
|
// DeleteByID deletes the given user and all their resources. It returns
|
||||||
|
// ErrUserOwnRepos when the user still has repository ownership, or returns
|
||||||
|
// ErrUserHasOrgs when the user still has organization membership. It is more
|
||||||
|
// performant to skip rewriting the "authorized_keys" file for individual
|
||||||
|
// deletion in a batch operation.
|
||||||
|
DeleteByID(ctx context.Context, userID int64, skipRewriteAuthorizedKeys bool) error
|
||||||
|
// DeleteInactivated deletes all inactivated users.
|
||||||
|
DeleteInactivated() error
|
||||||
|
|
||||||
|
// Follow marks the user to follow the other user.
|
||||||
|
Follow(ctx context.Context, userID, followID int64) error
|
||||||
|
// Unfollow removes the mark the user to follow the other user.
|
||||||
|
Unfollow(ctx context.Context, userID, followID int64) error
|
||||||
|
// IsFollowing returns true if the user is following the other user.
|
||||||
|
IsFollowing(ctx context.Context, userID, followID int64) bool
|
||||||
// ListFollowers returns a list of users that are following the given user.
|
// ListFollowers returns a list of users that are following the given user.
|
||||||
// Results are paginated by given page and page size, and sorted by the time of
|
// Results are paginated by given page and page size, and sorted by the time of
|
||||||
// follow in descending order.
|
// follow in descending order.
|
||||||
|
@ -103,16 +114,12 @@ type UsersStore interface {
|
||||||
// Results are paginated by given page and page size, and sorted by the time of
|
// Results are paginated by given page and page size, and sorted by the time of
|
||||||
// follow in descending order.
|
// follow in descending order.
|
||||||
ListFollowings(ctx context.Context, userID int64, page, pageSize int) ([]*User, error)
|
ListFollowings(ctx context.Context, userID int64, page, pageSize int) ([]*User, error)
|
||||||
// SearchByName returns a list of users whose username or full name matches the
|
|
||||||
// given keyword case-insensitively. Results are paginated by given page and
|
// List returns a list of users. Results are paginated by given page and page
|
||||||
// page size, and sorted by the given order (e.g. "id DESC"). A total count of
|
// size, and sorted by primary key (id) in ascending order.
|
||||||
// all results is also returned. If the order is not given, it's up to the
|
List(ctx context.Context, page, pageSize int) ([]*User, error)
|
||||||
// database to decide.
|
// Count returns the total number of users.
|
||||||
SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*User, int64, error)
|
Count(ctx context.Context) int64
|
||||||
// Update updates fields for the given user.
|
|
||||||
Update(ctx context.Context, userID int64, opts UpdateUserOptions) error
|
|
||||||
// UseCustomAvatar uses the given avatar as the user custom avatar.
|
|
||||||
UseCustomAvatar(ctx context.Context, userID int64, avatar []byte) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var Users UsersStore
|
var Users UsersStore
|
||||||
|
@ -650,6 +657,88 @@ func (db *users) DeleteInactivated() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*users) recountFollows(tx *gorm.DB, userID, followID int64) error {
|
||||||
|
/*
|
||||||
|
Equivalent SQL for PostgreSQL:
|
||||||
|
|
||||||
|
UPDATE "user"
|
||||||
|
SET num_followers = (
|
||||||
|
SELECT COUNT(*) FROM follow WHERE follow_id = @followID
|
||||||
|
)
|
||||||
|
WHERE id = @followID
|
||||||
|
*/
|
||||||
|
err := tx.Model(&User{}).
|
||||||
|
Where("id = ?", followID).
|
||||||
|
Update(
|
||||||
|
"num_followers",
|
||||||
|
tx.Model(&Follow{}).Select("COUNT(*)").Where("follow_id = ?", followID),
|
||||||
|
).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, `update "user.num_followers"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Equivalent SQL for PostgreSQL:
|
||||||
|
|
||||||
|
UPDATE "user"
|
||||||
|
SET num_following = (
|
||||||
|
SELECT COUNT(*) FROM follow WHERE user_id = @userID
|
||||||
|
)
|
||||||
|
WHERE id = @userID
|
||||||
|
*/
|
||||||
|
err = tx.Model(&User{}).
|
||||||
|
Where("id = ?", userID).
|
||||||
|
Update(
|
||||||
|
"num_following",
|
||||||
|
tx.Model(&Follow{}).Select("COUNT(*)").Where("user_id = ?", userID),
|
||||||
|
).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, `update "user.num_following"`)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *users) Follow(ctx context.Context, userID, followID int64) error {
|
||||||
|
if userID == followID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
f := &Follow{
|
||||||
|
UserID: userID,
|
||||||
|
FollowID: followID,
|
||||||
|
}
|
||||||
|
result := tx.FirstOrCreate(f, f)
|
||||||
|
if result.Error != nil {
|
||||||
|
return errors.Wrap(result.Error, "upsert")
|
||||||
|
} else if result.RowsAffected <= 0 {
|
||||||
|
return nil // Relation already exists
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.recountFollows(tx, userID, followID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *users) Unfollow(ctx context.Context, userID, followID int64) error {
|
||||||
|
if userID == followID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Where("user_id = ? AND follow_id = ?", userID, followID).Delete(&Follow{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "delete")
|
||||||
|
}
|
||||||
|
return db.recountFollows(tx, userID, followID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *users) IsFollowing(ctx context.Context, userID, followID int64) bool {
|
||||||
|
return db.WithContext(ctx).Where("user_id = ? AND follow_id = ?", userID, followID).First(&Follow{}).Error == nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ errutil.NotFound = (*ErrUserNotExist)(nil)
|
var _ errutil.NotFound = (*ErrUserNotExist)(nil)
|
||||||
|
|
||||||
type ErrUserNotExist struct {
|
type ErrUserNotExist struct {
|
||||||
|
@ -757,12 +846,6 @@ func (db *users) GetMailableEmailsByUsernames(ctx context.Context, usernames []s
|
||||||
Find(&emails).Error
|
Find(&emails).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *users) HasForkedRepository(ctx context.Context, userID, repoID int64) bool {
|
|
||||||
var count int64
|
|
||||||
db.WithContext(ctx).Model(new(Repository)).Where("owner_id = ? AND fork_id = ?", userID, repoID).Count(&count)
|
|
||||||
return count > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *users) IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool {
|
func (db *users) IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return false
|
return false
|
||||||
|
@ -1181,7 +1264,7 @@ func (u *User) AvatarURL() string {
|
||||||
// TODO(unknwon): This is also used in templates, which should be fixed by
|
// TODO(unknwon): This is also used in templates, which should be fixed by
|
||||||
// having a dedicated type `template.User`.
|
// having a dedicated type `template.User`.
|
||||||
func (u *User) IsFollowing(followID int64) bool {
|
func (u *User) IsFollowing(followID int64) bool {
|
||||||
return Follows.IsFollowing(context.TODO(), u.ID, followID)
|
return Users.IsFollowing(context.TODO(), u.ID, followID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUserOrgOwner returns true if the user is in the owner team of the given
|
// IsUserOrgOwner returns true if the user is in the owner team of the given
|
||||||
|
@ -1208,7 +1291,7 @@ func (u *User) IsPublicMember(orgId int64) bool {
|
||||||
// TODO(unknwon): This is also used in templates, which should be fixed by
|
// TODO(unknwon): This is also used in templates, which should be fixed by
|
||||||
// having a dedicated type `template.User`.
|
// having a dedicated type `template.User`.
|
||||||
func (u *User) GetOrganizationCount() (int64, error) {
|
func (u *User) GetOrganizationCount() (int64, error) {
|
||||||
return OrgUsers.CountByUser(context.TODO(), u.ID)
|
return Orgs.CountByUser(context.TODO(), u.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShortName truncates and returns the username at most in given length.
|
// ShortName truncates and returns the username at most in given length.
|
||||||
|
@ -1336,3 +1419,10 @@ func isNameAllowed(names map[string]struct{}, patterns []string, name string) er
|
||||||
func isUsernameAllowed(name string) error {
|
func isUsernameAllowed(name string) error {
|
||||||
return isNameAllowed(reservedUsernames, reservedUsernamePatterns, name)
|
return isNameAllowed(reservedUsernames, reservedUsernamePatterns, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Follow represents relations of users and their followers.
|
||||||
|
type Follow struct {
|
||||||
|
ID int64 `gorm:"primaryKey"`
|
||||||
|
UserID int64 `xorm:"UNIQUE(follow)" gorm:"uniqueIndex:follow_user_follow_unique;not null"`
|
||||||
|
FollowID int64 `xorm:"UNIQUE(follow)" gorm:"uniqueIndex:follow_user_follow_unique;not null"`
|
||||||
|
}
|
||||||
|
|
|
@ -107,7 +107,6 @@ func TestUsers(t *testing.T) {
|
||||||
{"GetByUsername", usersGetByUsername},
|
{"GetByUsername", usersGetByUsername},
|
||||||
{"GetByKeyID", usersGetByKeyID},
|
{"GetByKeyID", usersGetByKeyID},
|
||||||
{"GetMailableEmailsByUsernames", usersGetMailableEmailsByUsernames},
|
{"GetMailableEmailsByUsernames", usersGetMailableEmailsByUsernames},
|
||||||
{"HasForkedRepository", usersHasForkedRepository},
|
|
||||||
{"IsUsernameUsed", usersIsUsernameUsed},
|
{"IsUsernameUsed", usersIsUsernameUsed},
|
||||||
{"List", usersList},
|
{"List", usersList},
|
||||||
{"ListFollowers", usersListFollowers},
|
{"ListFollowers", usersListFollowers},
|
||||||
|
@ -115,6 +114,9 @@ func TestUsers(t *testing.T) {
|
||||||
{"SearchByName", usersSearchByName},
|
{"SearchByName", usersSearchByName},
|
||||||
{"Update", usersUpdate},
|
{"Update", usersUpdate},
|
||||||
{"UseCustomAvatar", usersUseCustomAvatar},
|
{"UseCustomAvatar", usersUseCustomAvatar},
|
||||||
|
{"Follow", usersFollow},
|
||||||
|
{"IsFollowing", usersIsFollowing},
|
||||||
|
{"Unfollow", usersUnfollow},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
@ -518,14 +520,13 @@ func usersDeleteByID(t *testing.T, db *users) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Mock watches, stars and follows
|
// Mock watches, stars and follows
|
||||||
err = NewWatchesStore(db.DB).Watch(ctx, testUser.ID, repo2.ID)
|
err = reposStore.Watch(ctx, testUser.ID, repo2.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = reposStore.Star(ctx, testUser.ID, repo2.ID)
|
err = reposStore.Star(ctx, testUser.ID, repo2.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
followsStore := NewFollowsStore(db.DB)
|
err = db.Follow(ctx, testUser.ID, cindy.ID)
|
||||||
err = followsStore.Follow(ctx, testUser.ID, cindy.ID)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = followsStore.Follow(ctx, frank.ID, testUser.ID)
|
err = db.Follow(ctx, frank.ID, testUser.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Mock "authorized_keys" file
|
// Mock "authorized_keys" file
|
||||||
|
@ -865,26 +866,6 @@ func usersGetMailableEmailsByUsernames(t *testing.T, db *users) {
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
func usersHasForkedRepository(t *testing.T, db *users) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
has := db.HasForkedRepository(ctx, 1, 1)
|
|
||||||
assert.False(t, has)
|
|
||||||
|
|
||||||
_, err := NewReposStore(db.DB).Create(
|
|
||||||
ctx,
|
|
||||||
1,
|
|
||||||
CreateRepoOptions{
|
|
||||||
Name: "repo1",
|
|
||||||
ForkID: 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
has = db.HasForkedRepository(ctx, 1, 1)
|
|
||||||
assert.True(t, has)
|
|
||||||
}
|
|
||||||
|
|
||||||
func usersIsUsernameUsed(t *testing.T, db *users) {
|
func usersIsUsernameUsed(t *testing.T, db *users) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -987,10 +968,9 @@ func usersListFollowers(t *testing.T, db *users) {
|
||||||
bob, err := db.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
|
bob, err := db.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
followsStore := NewFollowsStore(db.DB)
|
err = db.Follow(ctx, alice.ID, john.ID)
|
||||||
err = followsStore.Follow(ctx, alice.ID, john.ID)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = followsStore.Follow(ctx, bob.ID, john.ID)
|
err = db.Follow(ctx, bob.ID, john.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// First page only has bob
|
// First page only has bob
|
||||||
|
@ -1021,10 +1001,9 @@ func usersListFollowings(t *testing.T, db *users) {
|
||||||
bob, err := db.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
|
bob, err := db.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
followsStore := NewFollowsStore(db.DB)
|
err = db.Follow(ctx, john.ID, alice.ID)
|
||||||
err = followsStore.Follow(ctx, john.ID, alice.ID)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = followsStore.Follow(ctx, john.ID, bob.ID)
|
err = db.Follow(ctx, john.ID, bob.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// First page only has bob
|
// First page only has bob
|
||||||
|
@ -1222,3 +1201,78 @@ func TestIsUsernameAllowed(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func usersFollow(t *testing.T, db *users) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
err = db.Follow(ctx, alice.ID, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// It is OK to follow multiple times and just be noop.
|
||||||
|
err = db.Follow(ctx, alice.ID, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
alice, err = usersStore.GetByID(ctx, alice.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, alice.NumFollowing)
|
||||||
|
|
||||||
|
bob, err = usersStore.GetByID(ctx, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, bob.NumFollowers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func usersIsFollowing(t *testing.T, db *users) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
got := db.IsFollowing(ctx, alice.ID, bob.ID)
|
||||||
|
assert.False(t, got)
|
||||||
|
|
||||||
|
err = db.Follow(ctx, alice.ID, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
got = db.IsFollowing(ctx, alice.ID, bob.ID)
|
||||||
|
assert.True(t, got)
|
||||||
|
|
||||||
|
err = db.Unfollow(ctx, alice.ID, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
got = db.IsFollowing(ctx, alice.ID, bob.ID)
|
||||||
|
assert.False(t, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func usersUnfollow(t *testing.T, db *users) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
err = db.Follow(ctx, alice.ID, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// It is OK to unfollow multiple times and just be noop.
|
||||||
|
err = db.Unfollow(ctx, alice.ID, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = db.Unfollow(ctx, alice.ID, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
alice, err = usersStore.GetByID(ctx, alice.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, alice.NumFollowing)
|
||||||
|
|
||||||
|
bob, err = usersStore.GetByID(ctx, bob.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, bob.NumFollowers)
|
||||||
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
// Copyright 2020 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WatchesStore is the persistent interface for watches.
|
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type WatchesStore interface {
|
|
||||||
// ListByRepo returns all watches of the given repository.
|
|
||||||
ListByRepo(ctx context.Context, repoID int64) ([]*Watch, error)
|
|
||||||
// Watch marks the user to watch the repository.
|
|
||||||
Watch(ctx context.Context, userID, repoID int64) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var Watches WatchesStore
|
|
||||||
|
|
||||||
var _ WatchesStore = (*watches)(nil)
|
|
||||||
|
|
||||||
type watches struct {
|
|
||||||
*gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatchesStore returns a persistent interface for watches with given
|
|
||||||
// database connection.
|
|
||||||
func NewWatchesStore(db *gorm.DB) WatchesStore {
|
|
||||||
return &watches{DB: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *watches) ListByRepo(ctx context.Context, repoID int64) ([]*Watch, error) {
|
|
||||||
var watches []*Watch
|
|
||||||
return watches, db.WithContext(ctx).Where("repo_id = ?", repoID).Find(&watches).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *watches) updateWatchingCount(tx *gorm.DB, repoID int64) error {
|
|
||||||
/*
|
|
||||||
Equivalent SQL for PostgreSQL:
|
|
||||||
|
|
||||||
UPDATE repository
|
|
||||||
SET num_watches = (
|
|
||||||
SELECT COUNT(*) FROM watch WHERE repo_id = @repoID
|
|
||||||
)
|
|
||||||
WHERE id = @repoID
|
|
||||||
*/
|
|
||||||
return tx.Model(&Repository{}).
|
|
||||||
Where("id = ?", repoID).
|
|
||||||
Update(
|
|
||||||
"num_watches",
|
|
||||||
tx.Model(&Watch{}).Select("COUNT(*)").Where("repo_id = ?", repoID),
|
|
||||||
).
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *watches) Watch(ctx context.Context, userID, repoID int64) error {
|
|
||||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
w := &Watch{
|
|
||||||
UserID: userID,
|
|
||||||
RepoID: repoID,
|
|
||||||
}
|
|
||||||
result := tx.FirstOrCreate(w, w)
|
|
||||||
if result.Error != nil {
|
|
||||||
return errors.Wrap(result.Error, "upsert")
|
|
||||||
} else if result.RowsAffected <= 0 {
|
|
||||||
return nil // Relation already exists
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.updateWatchingCount(tx, repoID)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,88 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWatches(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tables := []any{new(Watch), new(Repository)}
|
|
||||||
db := &watches{
|
|
||||||
DB: dbtest.NewDB(t, "watches", tables...),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
test func(t *testing.T, db *watches)
|
|
||||||
}{
|
|
||||||
{"ListByRepo", watchesListByRepo},
|
|
||||||
{"Watch", watchesWatch},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err := clearTables(t, db.DB, tables...)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
tc.test(t, db)
|
|
||||||
})
|
|
||||||
if t.Failed() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchesListByRepo(t *testing.T, db *watches) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
err := db.Watch(ctx, 1, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = db.Watch(ctx, 2, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = db.Watch(ctx, 2, 2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := db.ListByRepo(ctx, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, w := range got {
|
|
||||||
w.ID = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
want := []*Watch{
|
|
||||||
{UserID: 1, RepoID: 1},
|
|
||||||
{UserID: 2, RepoID: 1},
|
|
||||||
}
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchesWatch(t *testing.T, db *watches) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
reposStore := NewReposStore(db.DB)
|
|
||||||
repo1, err := reposStore.Create(ctx, 1, CreateRepoOptions{Name: "repo1"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Watch(ctx, 2, repo1.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// It is OK to watch multiple times and just be noop.
|
|
||||||
err = db.Watch(ctx, 2, repo1.ID)
|
|
||||||
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.
|
|
||||||
}
|
|
|
@ -9,8 +9,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ModuleStore is the interface for Git operations.
|
// ModuleStore is the interface for Git operations.
|
||||||
//
|
|
||||||
// NOTE: All methods are sorted in alphabetical order.
|
|
||||||
type ModuleStore interface {
|
type ModuleStore interface {
|
||||||
// RemoteAdd adds a new remote to the repository in given path.
|
// RemoteAdd adds a new remote to the repository in given path.
|
||||||
RemoteAdd(repoPath, name, url string, opts ...git.RemoteAddOptions) error
|
RemoteAdd(repoPath, name, url string, opts ...git.RemoteAddOptions) error
|
||||||
|
|
|
@ -62,7 +62,7 @@ func ListFollowing(c *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkUserFollowing(c *context.APIContext, u *db.User, followID int64) {
|
func checkUserFollowing(c *context.APIContext, u *db.User, followID int64) {
|
||||||
if db.Follows.IsFollowing(c.Req.Context(), u.ID, followID) {
|
if db.Users.IsFollowing(c.Req.Context(), u.ID, followID) {
|
||||||
c.NoContent()
|
c.NoContent()
|
||||||
} else {
|
} else {
|
||||||
c.NotFound()
|
c.NotFound()
|
||||||
|
@ -94,7 +94,7 @@ func Follow(c *context.APIContext) {
|
||||||
if c.Written() {
|
if c.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := db.Follows.Follow(c.Req.Context(), c.User.ID, target.ID); err != nil {
|
if err := db.Users.Follow(c.Req.Context(), c.User.ID, target.ID); err != nil {
|
||||||
c.Error(err, "follow user")
|
c.Error(err, "follow user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ func Unfollow(c *context.APIContext) {
|
||||||
if c.Written() {
|
if c.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := db.Follows.Unfollow(c.Req.Context(), c.User.ID, target.ID); err != nil {
|
if err := db.Users.Unfollow(c.Req.Context(), c.User.ID, target.ID); err != nil {
|
||||||
c.Error(err, "unfollow user")
|
c.Error(err, "unfollow user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1508,12 +1508,21 @@ type MockReposStore struct {
|
||||||
// GetByNameFunc is an instance of a mock function object controlling
|
// GetByNameFunc is an instance of a mock function object controlling
|
||||||
// the behavior of the method GetByName.
|
// the behavior of the method GetByName.
|
||||||
GetByNameFunc *ReposStoreGetByNameFunc
|
GetByNameFunc *ReposStoreGetByNameFunc
|
||||||
|
// HasForkedByFunc is an instance of a mock function object controlling
|
||||||
|
// the behavior of the method HasForkedBy.
|
||||||
|
HasForkedByFunc *ReposStoreHasForkedByFunc
|
||||||
|
// ListWatchesFunc is an instance of a mock function object controlling
|
||||||
|
// the behavior of the method ListWatches.
|
||||||
|
ListWatchesFunc *ReposStoreListWatchesFunc
|
||||||
// StarFunc is an instance of a mock function object controlling the
|
// StarFunc is an instance of a mock function object controlling the
|
||||||
// behavior of the method Star.
|
// behavior of the method Star.
|
||||||
StarFunc *ReposStoreStarFunc
|
StarFunc *ReposStoreStarFunc
|
||||||
// TouchFunc is an instance of a mock function object controlling the
|
// TouchFunc is an instance of a mock function object controlling the
|
||||||
// behavior of the method Touch.
|
// behavior of the method Touch.
|
||||||
TouchFunc *ReposStoreTouchFunc
|
TouchFunc *ReposStoreTouchFunc
|
||||||
|
// WatchFunc is an instance of a mock function object controlling the
|
||||||
|
// behavior of the method Watch.
|
||||||
|
WatchFunc *ReposStoreWatchFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockReposStore creates a new mock of the ReposStore interface. All
|
// NewMockReposStore creates a new mock of the ReposStore interface. All
|
||||||
|
@ -1545,6 +1554,16 @@ func NewMockReposStore() *MockReposStore {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
HasForkedByFunc: &ReposStoreHasForkedByFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) (r0 bool) {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ListWatchesFunc: &ReposStoreListWatchesFunc{
|
||||||
|
defaultHook: func(context.Context, int64) (r0 []*db.Watch, r1 error) {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
StarFunc: &ReposStoreStarFunc{
|
StarFunc: &ReposStoreStarFunc{
|
||||||
defaultHook: func(context.Context, int64, int64) (r0 error) {
|
defaultHook: func(context.Context, int64, int64) (r0 error) {
|
||||||
return
|
return
|
||||||
|
@ -1555,6 +1574,11 @@ func NewMockReposStore() *MockReposStore {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
WatchFunc: &ReposStoreWatchFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) (r0 error) {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1587,6 +1611,16 @@ func NewStrictMockReposStore() *MockReposStore {
|
||||||
panic("unexpected invocation of MockReposStore.GetByName")
|
panic("unexpected invocation of MockReposStore.GetByName")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
HasForkedByFunc: &ReposStoreHasForkedByFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) bool {
|
||||||
|
panic("unexpected invocation of MockReposStore.HasForkedBy")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ListWatchesFunc: &ReposStoreListWatchesFunc{
|
||||||
|
defaultHook: func(context.Context, int64) ([]*db.Watch, error) {
|
||||||
|
panic("unexpected invocation of MockReposStore.ListWatches")
|
||||||
|
},
|
||||||
|
},
|
||||||
StarFunc: &ReposStoreStarFunc{
|
StarFunc: &ReposStoreStarFunc{
|
||||||
defaultHook: func(context.Context, int64, int64) error {
|
defaultHook: func(context.Context, int64, int64) error {
|
||||||
panic("unexpected invocation of MockReposStore.Star")
|
panic("unexpected invocation of MockReposStore.Star")
|
||||||
|
@ -1597,6 +1631,11 @@ func NewStrictMockReposStore() *MockReposStore {
|
||||||
panic("unexpected invocation of MockReposStore.Touch")
|
panic("unexpected invocation of MockReposStore.Touch")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
WatchFunc: &ReposStoreWatchFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) error {
|
||||||
|
panic("unexpected invocation of MockReposStore.Watch")
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1619,12 +1658,21 @@ func NewMockReposStoreFrom(i db.ReposStore) *MockReposStore {
|
||||||
GetByNameFunc: &ReposStoreGetByNameFunc{
|
GetByNameFunc: &ReposStoreGetByNameFunc{
|
||||||
defaultHook: i.GetByName,
|
defaultHook: i.GetByName,
|
||||||
},
|
},
|
||||||
|
HasForkedByFunc: &ReposStoreHasForkedByFunc{
|
||||||
|
defaultHook: i.HasForkedBy,
|
||||||
|
},
|
||||||
|
ListWatchesFunc: &ReposStoreListWatchesFunc{
|
||||||
|
defaultHook: i.ListWatches,
|
||||||
|
},
|
||||||
StarFunc: &ReposStoreStarFunc{
|
StarFunc: &ReposStoreStarFunc{
|
||||||
defaultHook: i.Star,
|
defaultHook: i.Star,
|
||||||
},
|
},
|
||||||
TouchFunc: &ReposStoreTouchFunc{
|
TouchFunc: &ReposStoreTouchFunc{
|
||||||
defaultHook: i.Touch,
|
defaultHook: i.Touch,
|
||||||
},
|
},
|
||||||
|
WatchFunc: &ReposStoreWatchFunc{
|
||||||
|
defaultHook: i.Watch,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2185,6 +2233,222 @@ func (c ReposStoreGetByNameFuncCall) Results() []interface{} {
|
||||||
return []interface{}{c.Result0, c.Result1}
|
return []interface{}{c.Result0, c.Result1}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReposStoreHasForkedByFunc describes the behavior when the HasForkedBy
|
||||||
|
// method of the parent MockReposStore instance is invoked.
|
||||||
|
type ReposStoreHasForkedByFunc struct {
|
||||||
|
defaultHook func(context.Context, int64, int64) bool
|
||||||
|
hooks []func(context.Context, int64, int64) bool
|
||||||
|
history []ReposStoreHasForkedByFuncCall
|
||||||
|
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 {
|
||||||
|
r0 := m.HasForkedByFunc.nextHook()(v0, v1, v2)
|
||||||
|
m.HasForkedByFunc.appendCall(ReposStoreHasForkedByFuncCall{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) {
|
||||||
|
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) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.hooks = append(f.hooks, hook)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||||
|
// given values.
|
||||||
|
func (f *ReposStoreHasForkedByFunc) 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) {
|
||||||
|
f.PushHook(func(context.Context, int64, int64) bool {
|
||||||
|
return r0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReposStoreHasForkedByFunc) nextHook() func(context.Context, int64, int64) bool {
|
||||||
|
f.mutex.Lock()
|
||||||
|
defer f.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(f.hooks) == 0 {
|
||||||
|
return f.defaultHook
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := f.hooks[0]
|
||||||
|
f.hooks = f.hooks[1:]
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReposStoreHasForkedByFunc) appendCall(r0 ReposStoreHasForkedByFuncCall) {
|
||||||
|
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 {
|
||||||
|
f.mutex.Lock()
|
||||||
|
history := make([]ReposStoreHasForkedByFuncCall, 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 {
|
||||||
|
// Arg0 is the value of the 1st argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg0 context.Context
|
||||||
|
// Arg1 is the value of the 2nd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg1 int64
|
||||||
|
// Arg2 is the value of the 3rd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg2 int64
|
||||||
|
// Result0 is the value of the 1st result returned from this method
|
||||||
|
// invocation.
|
||||||
|
Result0 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns an interface slice containing the arguments of this
|
||||||
|
// invocation.
|
||||||
|
func (c ReposStoreHasForkedByFuncCall) 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{} {
|
||||||
|
return []interface{}{c.Result0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReposStoreListWatchesFunc describes the behavior when the ListWatches
|
||||||
|
// method of the parent MockReposStore instance is invoked.
|
||||||
|
type ReposStoreListWatchesFunc struct {
|
||||||
|
defaultHook func(context.Context, int64) ([]*db.Watch, error)
|
||||||
|
hooks []func(context.Context, int64) ([]*db.Watch, error)
|
||||||
|
history []ReposStoreListWatchesFuncCall
|
||||||
|
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) {
|
||||||
|
r0, r1 := m.ListWatchesFunc.nextHook()(v0, v1)
|
||||||
|
m.ListWatchesFunc.appendCall(ReposStoreListWatchesFuncCall{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)) {
|
||||||
|
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)) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.hooks = append(f.hooks, hook)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||||
|
// given values.
|
||||||
|
func (f *ReposStoreListWatchesFunc) 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) {
|
||||||
|
f.PushHook(func(context.Context, int64) ([]*db.Watch, error) {
|
||||||
|
return r0, r1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReposStoreListWatchesFunc) nextHook() func(context.Context, int64) ([]*db.Watch, error) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
defer f.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(f.hooks) == 0 {
|
||||||
|
return f.defaultHook
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := f.hooks[0]
|
||||||
|
f.hooks = f.hooks[1:]
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReposStoreListWatchesFunc) appendCall(r0 ReposStoreListWatchesFuncCall) {
|
||||||
|
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 {
|
||||||
|
f.mutex.Lock()
|
||||||
|
history := make([]ReposStoreListWatchesFuncCall, 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 {
|
||||||
|
// Arg0 is the value of the 1st argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg0 context.Context
|
||||||
|
// Arg1 is the value of the 2nd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg1 int64
|
||||||
|
// Result0 is the value of the 1st result returned from this method
|
||||||
|
// invocation.
|
||||||
|
Result0 []*db.Watch
|
||||||
|
// Result1 is the value of the 2nd result returned from this method
|
||||||
|
// invocation.
|
||||||
|
Result1 error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns an interface slice containing the arguments of this
|
||||||
|
// invocation.
|
||||||
|
func (c ReposStoreListWatchesFuncCall) Args() []interface{} {
|
||||||
|
return []interface{}{c.Arg0, c.Arg1}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results returns an interface slice containing the results of this
|
||||||
|
// invocation.
|
||||||
|
func (c ReposStoreListWatchesFuncCall) Results() []interface{} {
|
||||||
|
return []interface{}{c.Result0, c.Result1}
|
||||||
|
}
|
||||||
|
|
||||||
// ReposStoreStarFunc describes the behavior when the Star method of the
|
// ReposStoreStarFunc describes the behavior when the Star method of the
|
||||||
// parent MockReposStore instance is invoked.
|
// parent MockReposStore instance is invoked.
|
||||||
type ReposStoreStarFunc struct {
|
type ReposStoreStarFunc struct {
|
||||||
|
@ -2396,6 +2660,113 @@ func (c ReposStoreTouchFuncCall) Results() []interface{} {
|
||||||
return []interface{}{c.Result0}
|
return []interface{}{c.Result0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReposStoreWatchFunc describes the behavior when the Watch method of the
|
||||||
|
// parent MockReposStore instance is invoked.
|
||||||
|
type ReposStoreWatchFunc struct {
|
||||||
|
defaultHook func(context.Context, int64, int64) error
|
||||||
|
hooks []func(context.Context, int64, int64) error
|
||||||
|
history []ReposStoreWatchFuncCall
|
||||||
|
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 {
|
||||||
|
r0 := m.WatchFunc.nextHook()(v0, v1, v2)
|
||||||
|
m.WatchFunc.appendCall(ReposStoreWatchFuncCall{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) {
|
||||||
|
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) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.hooks = append(f.hooks, hook)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||||
|
// given values.
|
||||||
|
func (f *ReposStoreWatchFunc) 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) {
|
||||||
|
f.PushHook(func(context.Context, int64, int64) error {
|
||||||
|
return r0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReposStoreWatchFunc) nextHook() func(context.Context, int64, int64) error {
|
||||||
|
f.mutex.Lock()
|
||||||
|
defer f.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(f.hooks) == 0 {
|
||||||
|
return f.defaultHook
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := f.hooks[0]
|
||||||
|
f.hooks = f.hooks[1:]
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ReposStoreWatchFunc) appendCall(r0 ReposStoreWatchFuncCall) {
|
||||||
|
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 {
|
||||||
|
f.mutex.Lock()
|
||||||
|
history := make([]ReposStoreWatchFuncCall, 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 {
|
||||||
|
// Arg0 is the value of the 1st argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg0 context.Context
|
||||||
|
// Arg1 is the value of the 2nd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg1 int64
|
||||||
|
// Arg2 is the value of the 3rd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg2 int64
|
||||||
|
// Result0 is the value of the 1st result returned from this method
|
||||||
|
// invocation.
|
||||||
|
Result0 error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns an interface slice containing the arguments of this
|
||||||
|
// invocation.
|
||||||
|
func (c ReposStoreWatchFuncCall) 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{} {
|
||||||
|
return []interface{}{c.Result0}
|
||||||
|
}
|
||||||
|
|
||||||
// MockTwoFactorsStore is a mock implementation of the TwoFactorsStore
|
// MockTwoFactorsStore is a mock implementation of the TwoFactorsStore
|
||||||
// interface (from the package gogs.io/gogs/internal/db) used for unit
|
// interface (from the package gogs.io/gogs/internal/db) used for unit
|
||||||
// testing.
|
// testing.
|
||||||
|
@ -2821,6 +3192,9 @@ type MockUsersStore struct {
|
||||||
// DeleteInactivatedFunc is an instance of a mock function object
|
// DeleteInactivatedFunc is an instance of a mock function object
|
||||||
// controlling the behavior of the method DeleteInactivated.
|
// controlling the behavior of the method DeleteInactivated.
|
||||||
DeleteInactivatedFunc *UsersStoreDeleteInactivatedFunc
|
DeleteInactivatedFunc *UsersStoreDeleteInactivatedFunc
|
||||||
|
// FollowFunc is an instance of a mock function object controlling the
|
||||||
|
// behavior of the method Follow.
|
||||||
|
FollowFunc *UsersStoreFollowFunc
|
||||||
// GetByEmailFunc is an instance of a mock function object controlling
|
// GetByEmailFunc is an instance of a mock function object controlling
|
||||||
// the behavior of the method GetByEmail.
|
// the behavior of the method GetByEmail.
|
||||||
GetByEmailFunc *UsersStoreGetByEmailFunc
|
GetByEmailFunc *UsersStoreGetByEmailFunc
|
||||||
|
@ -2837,9 +3211,9 @@ type MockUsersStore struct {
|
||||||
// object controlling the behavior of the method
|
// object controlling the behavior of the method
|
||||||
// GetMailableEmailsByUsernames.
|
// GetMailableEmailsByUsernames.
|
||||||
GetMailableEmailsByUsernamesFunc *UsersStoreGetMailableEmailsByUsernamesFunc
|
GetMailableEmailsByUsernamesFunc *UsersStoreGetMailableEmailsByUsernamesFunc
|
||||||
// HasForkedRepositoryFunc is an instance of a mock function object
|
// IsFollowingFunc is an instance of a mock function object controlling
|
||||||
// controlling the behavior of the method HasForkedRepository.
|
// the behavior of the method IsFollowing.
|
||||||
HasForkedRepositoryFunc *UsersStoreHasForkedRepositoryFunc
|
IsFollowingFunc *UsersStoreIsFollowingFunc
|
||||||
// IsUsernameUsedFunc is an instance of a mock function object
|
// IsUsernameUsedFunc is an instance of a mock function object
|
||||||
// controlling the behavior of the method IsUsernameUsed.
|
// controlling the behavior of the method IsUsernameUsed.
|
||||||
IsUsernameUsedFunc *UsersStoreIsUsernameUsedFunc
|
IsUsernameUsedFunc *UsersStoreIsUsernameUsedFunc
|
||||||
|
@ -2855,6 +3229,9 @@ type MockUsersStore struct {
|
||||||
// SearchByNameFunc is an instance of a mock function object controlling
|
// SearchByNameFunc is an instance of a mock function object controlling
|
||||||
// the behavior of the method SearchByName.
|
// the behavior of the method SearchByName.
|
||||||
SearchByNameFunc *UsersStoreSearchByNameFunc
|
SearchByNameFunc *UsersStoreSearchByNameFunc
|
||||||
|
// UnfollowFunc is an instance of a mock function object controlling the
|
||||||
|
// behavior of the method Unfollow.
|
||||||
|
UnfollowFunc *UsersStoreUnfollowFunc
|
||||||
// UpdateFunc is an instance of a mock function object controlling the
|
// UpdateFunc is an instance of a mock function object controlling the
|
||||||
// behavior of the method Update.
|
// behavior of the method Update.
|
||||||
UpdateFunc *UsersStoreUpdateFunc
|
UpdateFunc *UsersStoreUpdateFunc
|
||||||
|
@ -2902,6 +3279,11 @@ func NewMockUsersStore() *MockUsersStore {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
FollowFunc: &UsersStoreFollowFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) (r0 error) {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
GetByEmailFunc: &UsersStoreGetByEmailFunc{
|
GetByEmailFunc: &UsersStoreGetByEmailFunc{
|
||||||
defaultHook: func(context.Context, string) (r0 *db.User, r1 error) {
|
defaultHook: func(context.Context, string) (r0 *db.User, r1 error) {
|
||||||
return
|
return
|
||||||
|
@ -2927,7 +3309,7 @@ func NewMockUsersStore() *MockUsersStore {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HasForkedRepositoryFunc: &UsersStoreHasForkedRepositoryFunc{
|
IsFollowingFunc: &UsersStoreIsFollowingFunc{
|
||||||
defaultHook: func(context.Context, int64, int64) (r0 bool) {
|
defaultHook: func(context.Context, int64, int64) (r0 bool) {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
|
@ -2957,6 +3339,11 @@ func NewMockUsersStore() *MockUsersStore {
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
UnfollowFunc: &UsersStoreUnfollowFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) (r0 error) {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
UpdateFunc: &UsersStoreUpdateFunc{
|
UpdateFunc: &UsersStoreUpdateFunc{
|
||||||
defaultHook: func(context.Context, int64, db.UpdateUserOptions) (r0 error) {
|
defaultHook: func(context.Context, int64, db.UpdateUserOptions) (r0 error) {
|
||||||
return
|
return
|
||||||
|
@ -3009,6 +3396,11 @@ func NewStrictMockUsersStore() *MockUsersStore {
|
||||||
panic("unexpected invocation of MockUsersStore.DeleteInactivated")
|
panic("unexpected invocation of MockUsersStore.DeleteInactivated")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
FollowFunc: &UsersStoreFollowFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) error {
|
||||||
|
panic("unexpected invocation of MockUsersStore.Follow")
|
||||||
|
},
|
||||||
|
},
|
||||||
GetByEmailFunc: &UsersStoreGetByEmailFunc{
|
GetByEmailFunc: &UsersStoreGetByEmailFunc{
|
||||||
defaultHook: func(context.Context, string) (*db.User, error) {
|
defaultHook: func(context.Context, string) (*db.User, error) {
|
||||||
panic("unexpected invocation of MockUsersStore.GetByEmail")
|
panic("unexpected invocation of MockUsersStore.GetByEmail")
|
||||||
|
@ -3034,9 +3426,9 @@ func NewStrictMockUsersStore() *MockUsersStore {
|
||||||
panic("unexpected invocation of MockUsersStore.GetMailableEmailsByUsernames")
|
panic("unexpected invocation of MockUsersStore.GetMailableEmailsByUsernames")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HasForkedRepositoryFunc: &UsersStoreHasForkedRepositoryFunc{
|
IsFollowingFunc: &UsersStoreIsFollowingFunc{
|
||||||
defaultHook: func(context.Context, int64, int64) bool {
|
defaultHook: func(context.Context, int64, int64) bool {
|
||||||
panic("unexpected invocation of MockUsersStore.HasForkedRepository")
|
panic("unexpected invocation of MockUsersStore.IsFollowing")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IsUsernameUsedFunc: &UsersStoreIsUsernameUsedFunc{
|
IsUsernameUsedFunc: &UsersStoreIsUsernameUsedFunc{
|
||||||
|
@ -3064,6 +3456,11 @@ func NewStrictMockUsersStore() *MockUsersStore {
|
||||||
panic("unexpected invocation of MockUsersStore.SearchByName")
|
panic("unexpected invocation of MockUsersStore.SearchByName")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
UnfollowFunc: &UsersStoreUnfollowFunc{
|
||||||
|
defaultHook: func(context.Context, int64, int64) error {
|
||||||
|
panic("unexpected invocation of MockUsersStore.Unfollow")
|
||||||
|
},
|
||||||
|
},
|
||||||
UpdateFunc: &UsersStoreUpdateFunc{
|
UpdateFunc: &UsersStoreUpdateFunc{
|
||||||
defaultHook: func(context.Context, int64, db.UpdateUserOptions) error {
|
defaultHook: func(context.Context, int64, db.UpdateUserOptions) error {
|
||||||
panic("unexpected invocation of MockUsersStore.Update")
|
panic("unexpected invocation of MockUsersStore.Update")
|
||||||
|
@ -3102,6 +3499,9 @@ func NewMockUsersStoreFrom(i db.UsersStore) *MockUsersStore {
|
||||||
DeleteInactivatedFunc: &UsersStoreDeleteInactivatedFunc{
|
DeleteInactivatedFunc: &UsersStoreDeleteInactivatedFunc{
|
||||||
defaultHook: i.DeleteInactivated,
|
defaultHook: i.DeleteInactivated,
|
||||||
},
|
},
|
||||||
|
FollowFunc: &UsersStoreFollowFunc{
|
||||||
|
defaultHook: i.Follow,
|
||||||
|
},
|
||||||
GetByEmailFunc: &UsersStoreGetByEmailFunc{
|
GetByEmailFunc: &UsersStoreGetByEmailFunc{
|
||||||
defaultHook: i.GetByEmail,
|
defaultHook: i.GetByEmail,
|
||||||
},
|
},
|
||||||
|
@ -3117,8 +3517,8 @@ func NewMockUsersStoreFrom(i db.UsersStore) *MockUsersStore {
|
||||||
GetMailableEmailsByUsernamesFunc: &UsersStoreGetMailableEmailsByUsernamesFunc{
|
GetMailableEmailsByUsernamesFunc: &UsersStoreGetMailableEmailsByUsernamesFunc{
|
||||||
defaultHook: i.GetMailableEmailsByUsernames,
|
defaultHook: i.GetMailableEmailsByUsernames,
|
||||||
},
|
},
|
||||||
HasForkedRepositoryFunc: &UsersStoreHasForkedRepositoryFunc{
|
IsFollowingFunc: &UsersStoreIsFollowingFunc{
|
||||||
defaultHook: i.HasForkedRepository,
|
defaultHook: i.IsFollowing,
|
||||||
},
|
},
|
||||||
IsUsernameUsedFunc: &UsersStoreIsUsernameUsedFunc{
|
IsUsernameUsedFunc: &UsersStoreIsUsernameUsedFunc{
|
||||||
defaultHook: i.IsUsernameUsed,
|
defaultHook: i.IsUsernameUsed,
|
||||||
|
@ -3135,6 +3535,9 @@ func NewMockUsersStoreFrom(i db.UsersStore) *MockUsersStore {
|
||||||
SearchByNameFunc: &UsersStoreSearchByNameFunc{
|
SearchByNameFunc: &UsersStoreSearchByNameFunc{
|
||||||
defaultHook: i.SearchByName,
|
defaultHook: i.SearchByName,
|
||||||
},
|
},
|
||||||
|
UnfollowFunc: &UsersStoreUnfollowFunc{
|
||||||
|
defaultHook: i.Unfollow,
|
||||||
|
},
|
||||||
UpdateFunc: &UsersStoreUpdateFunc{
|
UpdateFunc: &UsersStoreUpdateFunc{
|
||||||
defaultHook: i.Update,
|
defaultHook: i.Update,
|
||||||
},
|
},
|
||||||
|
@ -3894,6 +4297,113 @@ func (c UsersStoreDeleteInactivatedFuncCall) Results() []interface{} {
|
||||||
return []interface{}{c.Result0}
|
return []interface{}{c.Result0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsersStoreFollowFunc describes the behavior when the Follow method of the
|
||||||
|
// parent MockUsersStore instance is invoked.
|
||||||
|
type UsersStoreFollowFunc struct {
|
||||||
|
defaultHook func(context.Context, int64, int64) error
|
||||||
|
hooks []func(context.Context, int64, int64) error
|
||||||
|
history []UsersStoreFollowFuncCall
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow delegates to the next hook function in the queue and stores the
|
||||||
|
// parameter and result values of this invocation.
|
||||||
|
func (m *MockUsersStore) Follow(v0 context.Context, v1 int64, v2 int64) error {
|
||||||
|
r0 := m.FollowFunc.nextHook()(v0, v1, v2)
|
||||||
|
m.FollowFunc.appendCall(UsersStoreFollowFuncCall{v0, v1, v2, r0})
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultHook sets function that is called when the Follow method of the
|
||||||
|
// parent MockUsersStore instance is invoked and the hook queue is empty.
|
||||||
|
func (f *UsersStoreFollowFunc) 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
|
||||||
|
// Follow method of the parent MockUsersStore 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 *UsersStoreFollowFunc) PushHook(hook func(context.Context, int64, int64) error) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.hooks = append(f.hooks, hook)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||||
|
// given values.
|
||||||
|
func (f *UsersStoreFollowFunc) 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 *UsersStoreFollowFunc) PushReturn(r0 error) {
|
||||||
|
f.PushHook(func(context.Context, int64, int64) error {
|
||||||
|
return r0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UsersStoreFollowFunc) nextHook() func(context.Context, int64, int64) error {
|
||||||
|
f.mutex.Lock()
|
||||||
|
defer f.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(f.hooks) == 0 {
|
||||||
|
return f.defaultHook
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := f.hooks[0]
|
||||||
|
f.hooks = f.hooks[1:]
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UsersStoreFollowFunc) appendCall(r0 UsersStoreFollowFuncCall) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.history = append(f.history, r0)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// History returns a sequence of UsersStoreFollowFuncCall objects describing
|
||||||
|
// the invocations of this function.
|
||||||
|
func (f *UsersStoreFollowFunc) History() []UsersStoreFollowFuncCall {
|
||||||
|
f.mutex.Lock()
|
||||||
|
history := make([]UsersStoreFollowFuncCall, len(f.history))
|
||||||
|
copy(history, f.history)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
|
||||||
|
return history
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsersStoreFollowFuncCall is an object that describes an invocation of
|
||||||
|
// method Follow on an instance of MockUsersStore.
|
||||||
|
type UsersStoreFollowFuncCall struct {
|
||||||
|
// Arg0 is the value of the 1st argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg0 context.Context
|
||||||
|
// Arg1 is the value of the 2nd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg1 int64
|
||||||
|
// Arg2 is the value of the 3rd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg2 int64
|
||||||
|
// Result0 is the value of the 1st result returned from this method
|
||||||
|
// invocation.
|
||||||
|
Result0 error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns an interface slice containing the arguments of this
|
||||||
|
// invocation.
|
||||||
|
func (c UsersStoreFollowFuncCall) Args() []interface{} {
|
||||||
|
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results returns an interface slice containing the results of this
|
||||||
|
// invocation.
|
||||||
|
func (c UsersStoreFollowFuncCall) Results() []interface{} {
|
||||||
|
return []interface{}{c.Result0}
|
||||||
|
}
|
||||||
|
|
||||||
// UsersStoreGetByEmailFunc describes the behavior when the GetByEmail
|
// UsersStoreGetByEmailFunc describes the behavior when the GetByEmail
|
||||||
// method of the parent MockUsersStore instance is invoked.
|
// method of the parent MockUsersStore instance is invoked.
|
||||||
type UsersStoreGetByEmailFunc struct {
|
type UsersStoreGetByEmailFunc struct {
|
||||||
|
@ -4438,36 +4948,35 @@ func (c UsersStoreGetMailableEmailsByUsernamesFuncCall) Results() []interface{}
|
||||||
return []interface{}{c.Result0, c.Result1}
|
return []interface{}{c.Result0, c.Result1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsersStoreHasForkedRepositoryFunc describes the behavior when the
|
// UsersStoreIsFollowingFunc describes the behavior when the IsFollowing
|
||||||
// HasForkedRepository method of the parent MockUsersStore instance is
|
// method of the parent MockUsersStore instance is invoked.
|
||||||
// invoked.
|
type UsersStoreIsFollowingFunc struct {
|
||||||
type UsersStoreHasForkedRepositoryFunc struct {
|
|
||||||
defaultHook func(context.Context, int64, int64) bool
|
defaultHook func(context.Context, int64, int64) bool
|
||||||
hooks []func(context.Context, int64, int64) bool
|
hooks []func(context.Context, int64, int64) bool
|
||||||
history []UsersStoreHasForkedRepositoryFuncCall
|
history []UsersStoreIsFollowingFuncCall
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasForkedRepository delegates to the next hook function in the queue and
|
// IsFollowing delegates to the next hook function in the queue and stores
|
||||||
// stores the parameter and result values of this invocation.
|
// the parameter and result values of this invocation.
|
||||||
func (m *MockUsersStore) HasForkedRepository(v0 context.Context, v1 int64, v2 int64) bool {
|
func (m *MockUsersStore) IsFollowing(v0 context.Context, v1 int64, v2 int64) bool {
|
||||||
r0 := m.HasForkedRepositoryFunc.nextHook()(v0, v1, v2)
|
r0 := m.IsFollowingFunc.nextHook()(v0, v1, v2)
|
||||||
m.HasForkedRepositoryFunc.appendCall(UsersStoreHasForkedRepositoryFuncCall{v0, v1, v2, r0})
|
m.IsFollowingFunc.appendCall(UsersStoreIsFollowingFuncCall{v0, v1, v2, r0})
|
||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultHook sets function that is called when the HasForkedRepository
|
// SetDefaultHook sets function that is called when the IsFollowing method
|
||||||
// method of the parent MockUsersStore instance is invoked and the hook
|
// of the parent MockUsersStore instance is invoked and the hook queue is
|
||||||
// queue is empty.
|
// empty.
|
||||||
func (f *UsersStoreHasForkedRepositoryFunc) SetDefaultHook(hook func(context.Context, int64, int64) bool) {
|
func (f *UsersStoreIsFollowingFunc) SetDefaultHook(hook func(context.Context, int64, int64) bool) {
|
||||||
f.defaultHook = hook
|
f.defaultHook = hook
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||||
// HasForkedRepository method of the parent MockUsersStore instance invokes
|
// IsFollowing method of the parent MockUsersStore instance invokes the hook
|
||||||
// the hook at the front of the queue and discards it. After the queue is
|
// at the front of the queue and discards it. After the queue is empty, the
|
||||||
// empty, the default hook function is invoked for any future action.
|
// default hook function is invoked for any future action.
|
||||||
func (f *UsersStoreHasForkedRepositoryFunc) PushHook(hook func(context.Context, int64, int64) bool) {
|
func (f *UsersStoreIsFollowingFunc) PushHook(hook func(context.Context, int64, int64) bool) {
|
||||||
f.mutex.Lock()
|
f.mutex.Lock()
|
||||||
f.hooks = append(f.hooks, hook)
|
f.hooks = append(f.hooks, hook)
|
||||||
f.mutex.Unlock()
|
f.mutex.Unlock()
|
||||||
|
@ -4475,20 +4984,20 @@ func (f *UsersStoreHasForkedRepositoryFunc) PushHook(hook func(context.Context,
|
||||||
|
|
||||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||||
// given values.
|
// given values.
|
||||||
func (f *UsersStoreHasForkedRepositoryFunc) SetDefaultReturn(r0 bool) {
|
func (f *UsersStoreIsFollowingFunc) SetDefaultReturn(r0 bool) {
|
||||||
f.SetDefaultHook(func(context.Context, int64, int64) bool {
|
f.SetDefaultHook(func(context.Context, int64, int64) bool {
|
||||||
return r0
|
return r0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushReturn calls PushHook with a function that returns the given values.
|
// PushReturn calls PushHook with a function that returns the given values.
|
||||||
func (f *UsersStoreHasForkedRepositoryFunc) PushReturn(r0 bool) {
|
func (f *UsersStoreIsFollowingFunc) PushReturn(r0 bool) {
|
||||||
f.PushHook(func(context.Context, int64, int64) bool {
|
f.PushHook(func(context.Context, int64, int64) bool {
|
||||||
return r0
|
return r0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *UsersStoreHasForkedRepositoryFunc) nextHook() func(context.Context, int64, int64) bool {
|
func (f *UsersStoreIsFollowingFunc) nextHook() func(context.Context, int64, int64) bool {
|
||||||
f.mutex.Lock()
|
f.mutex.Lock()
|
||||||
defer f.mutex.Unlock()
|
defer f.mutex.Unlock()
|
||||||
|
|
||||||
|
@ -4501,27 +5010,26 @@ func (f *UsersStoreHasForkedRepositoryFunc) nextHook() func(context.Context, int
|
||||||
return hook
|
return hook
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *UsersStoreHasForkedRepositoryFunc) appendCall(r0 UsersStoreHasForkedRepositoryFuncCall) {
|
func (f *UsersStoreIsFollowingFunc) appendCall(r0 UsersStoreIsFollowingFuncCall) {
|
||||||
f.mutex.Lock()
|
f.mutex.Lock()
|
||||||
f.history = append(f.history, r0)
|
f.history = append(f.history, r0)
|
||||||
f.mutex.Unlock()
|
f.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// History returns a sequence of UsersStoreHasForkedRepositoryFuncCall
|
// History returns a sequence of UsersStoreIsFollowingFuncCall objects
|
||||||
// objects describing the invocations of this function.
|
// describing the invocations of this function.
|
||||||
func (f *UsersStoreHasForkedRepositoryFunc) History() []UsersStoreHasForkedRepositoryFuncCall {
|
func (f *UsersStoreIsFollowingFunc) History() []UsersStoreIsFollowingFuncCall {
|
||||||
f.mutex.Lock()
|
f.mutex.Lock()
|
||||||
history := make([]UsersStoreHasForkedRepositoryFuncCall, len(f.history))
|
history := make([]UsersStoreIsFollowingFuncCall, len(f.history))
|
||||||
copy(history, f.history)
|
copy(history, f.history)
|
||||||
f.mutex.Unlock()
|
f.mutex.Unlock()
|
||||||
|
|
||||||
return history
|
return history
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsersStoreHasForkedRepositoryFuncCall is an object that describes an
|
// UsersStoreIsFollowingFuncCall is an object that describes an invocation
|
||||||
// invocation of method HasForkedRepository on an instance of
|
// of method IsFollowing on an instance of MockUsersStore.
|
||||||
// MockUsersStore.
|
type UsersStoreIsFollowingFuncCall struct {
|
||||||
type UsersStoreHasForkedRepositoryFuncCall struct {
|
|
||||||
// Arg0 is the value of the 1st argument passed to this method
|
// Arg0 is the value of the 1st argument passed to this method
|
||||||
// invocation.
|
// invocation.
|
||||||
Arg0 context.Context
|
Arg0 context.Context
|
||||||
|
@ -4538,13 +5046,13 @@ type UsersStoreHasForkedRepositoryFuncCall struct {
|
||||||
|
|
||||||
// Args returns an interface slice containing the arguments of this
|
// Args returns an interface slice containing the arguments of this
|
||||||
// invocation.
|
// invocation.
|
||||||
func (c UsersStoreHasForkedRepositoryFuncCall) Args() []interface{} {
|
func (c UsersStoreIsFollowingFuncCall) Args() []interface{} {
|
||||||
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
|
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Results returns an interface slice containing the results of this
|
// Results returns an interface slice containing the results of this
|
||||||
// invocation.
|
// invocation.
|
||||||
func (c UsersStoreHasForkedRepositoryFuncCall) Results() []interface{} {
|
func (c UsersStoreIsFollowingFuncCall) Results() []interface{} {
|
||||||
return []interface{}{c.Result0}
|
return []interface{}{c.Result0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5114,6 +5622,114 @@ func (c UsersStoreSearchByNameFuncCall) Results() []interface{} {
|
||||||
return []interface{}{c.Result0, c.Result1, c.Result2}
|
return []interface{}{c.Result0, c.Result1, c.Result2}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsersStoreUnfollowFunc describes the behavior when the Unfollow method of
|
||||||
|
// the parent MockUsersStore instance is invoked.
|
||||||
|
type UsersStoreUnfollowFunc struct {
|
||||||
|
defaultHook func(context.Context, int64, int64) error
|
||||||
|
hooks []func(context.Context, int64, int64) error
|
||||||
|
history []UsersStoreUnfollowFuncCall
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unfollow delegates to the next hook function in the queue and stores the
|
||||||
|
// parameter and result values of this invocation.
|
||||||
|
func (m *MockUsersStore) Unfollow(v0 context.Context, v1 int64, v2 int64) error {
|
||||||
|
r0 := m.UnfollowFunc.nextHook()(v0, v1, v2)
|
||||||
|
m.UnfollowFunc.appendCall(UsersStoreUnfollowFuncCall{v0, v1, v2, r0})
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultHook sets function that is called when the Unfollow method of
|
||||||
|
// the parent MockUsersStore instance is invoked and the hook queue is
|
||||||
|
// empty.
|
||||||
|
func (f *UsersStoreUnfollowFunc) 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
|
||||||
|
// Unfollow method of the parent MockUsersStore 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 *UsersStoreUnfollowFunc) PushHook(hook func(context.Context, int64, int64) error) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.hooks = append(f.hooks, hook)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||||
|
// given values.
|
||||||
|
func (f *UsersStoreUnfollowFunc) 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 *UsersStoreUnfollowFunc) PushReturn(r0 error) {
|
||||||
|
f.PushHook(func(context.Context, int64, int64) error {
|
||||||
|
return r0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UsersStoreUnfollowFunc) nextHook() func(context.Context, int64, int64) error {
|
||||||
|
f.mutex.Lock()
|
||||||
|
defer f.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(f.hooks) == 0 {
|
||||||
|
return f.defaultHook
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := f.hooks[0]
|
||||||
|
f.hooks = f.hooks[1:]
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UsersStoreUnfollowFunc) appendCall(r0 UsersStoreUnfollowFuncCall) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.history = append(f.history, r0)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// History returns a sequence of UsersStoreUnfollowFuncCall objects
|
||||||
|
// describing the invocations of this function.
|
||||||
|
func (f *UsersStoreUnfollowFunc) History() []UsersStoreUnfollowFuncCall {
|
||||||
|
f.mutex.Lock()
|
||||||
|
history := make([]UsersStoreUnfollowFuncCall, len(f.history))
|
||||||
|
copy(history, f.history)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
|
||||||
|
return history
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsersStoreUnfollowFuncCall is an object that describes an invocation of
|
||||||
|
// method Unfollow on an instance of MockUsersStore.
|
||||||
|
type UsersStoreUnfollowFuncCall struct {
|
||||||
|
// Arg0 is the value of the 1st argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg0 context.Context
|
||||||
|
// Arg1 is the value of the 2nd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg1 int64
|
||||||
|
// Arg2 is the value of the 3rd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg2 int64
|
||||||
|
// Result0 is the value of the 1st result returned from this method
|
||||||
|
// invocation.
|
||||||
|
Result0 error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns an interface slice containing the arguments of this
|
||||||
|
// invocation.
|
||||||
|
func (c UsersStoreUnfollowFuncCall) Args() []interface{} {
|
||||||
|
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results returns an interface slice containing the results of this
|
||||||
|
// invocation.
|
||||||
|
func (c UsersStoreUnfollowFuncCall) Results() []interface{} {
|
||||||
|
return []interface{}{c.Result0}
|
||||||
|
}
|
||||||
|
|
||||||
// UsersStoreUpdateFunc describes the behavior when the Update method of the
|
// UsersStoreUpdateFunc describes the behavior when the Update method of the
|
||||||
// parent MockUsersStore instance is invoked.
|
// parent MockUsersStore instance is invoked.
|
||||||
type UsersStoreUpdateFunc struct {
|
type UsersStoreUpdateFunc struct {
|
||||||
|
|
|
@ -68,7 +68,7 @@ func MustAllowPulls(c *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// User can send pull request if owns a forked repository.
|
// User can send pull request if owns a forked repository.
|
||||||
if c.IsLogged && db.Users.HasForkedRepository(c.Req.Context(), c.User.ID, c.Repo.Repository.ID) {
|
if c.IsLogged && db.Repos.HasForkedBy(c.Req.Context(), c.Repo.Repository.ID, c.User.ID) {
|
||||||
c.Repo.PullRequest.Allowed = true
|
c.Repo.PullRequest.Allowed = true
|
||||||
c.Repo.PullRequest.HeadInfo = c.User.Name + ":" + c.Repo.BranchName
|
c.Repo.PullRequest.HeadInfo = c.User.Name + ":" + c.Repo.BranchName
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,9 +120,9 @@ func Action(c *context.Context, puser *context.ParamsUser) {
|
||||||
var err error
|
var err error
|
||||||
switch c.Params(":action") {
|
switch c.Params(":action") {
|
||||||
case "follow":
|
case "follow":
|
||||||
err = db.Follows.Follow(c.Req.Context(), c.UserID(), puser.ID)
|
err = db.Users.Follow(c.Req.Context(), c.UserID(), puser.ID)
|
||||||
case "unfollow":
|
case "unfollow":
|
||||||
err = db.Follows.Unfollow(c.Req.Context(), c.UserID(), puser.ID)
|
err = db.Users.Unfollow(c.Req.Context(), c.UserID(), puser.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue