ソースを参照

refactor(db): migrate methods off `user.go` (#7336)

Joe Chen 2 年 前
コミット
7ff09cf359

+ 4 - 0
internal/db/actions_test.go

@@ -720,6 +720,10 @@ func actionsNewRepo(t *testing.T, db *actions) {
 func actionsPushTag(t *testing.T, db *actions) {
 	ctx := context.Background()
 
+	// NOTE: We set a noop mock here to avoid data race with other tests that writes
+	// to the mock server because this function holds a lock.
+	conf.SetMockServer(t, conf.ServerOpts{})
+
 	alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "[email protected]", CreateUserOptions{})
 	require.NoError(t, err)
 	repo, err := NewReposStore(db.DB).Create(ctx,

+ 1 - 1
internal/db/db.go

@@ -127,7 +127,7 @@ func Init(w logger.Writer) (*gorm.DB, error) {
 	LFS = &lfs{DB: db}
 	Orgs = NewOrgsStore(db)
 	OrgUsers = NewOrgUsersStore(db)
-	Perms = &perms{DB: db}
+	Perms = NewPermsStore(db)
 	Repos = NewReposStore(db)
 	TwoFactors = &twoFactors{DB: db}
 	Users = NewUsersStore(db)

+ 6 - 0
internal/db/perms.go

@@ -82,6 +82,12 @@ type perms struct {
 	*gorm.DB
 }
 
+// NewPermsStore returns a persistent interface for permissions with given
+// database connection.
+func NewPermsStore(db *gorm.DB) PermsStore {
+	return &perms{DB: db}
+}
+
 type AccessModeOptions struct {
 	OwnerID int64 // The ID of the repository owner.
 	Private bool  // Whether the repository is private.

+ 63 - 0
internal/db/repos.go

@@ -26,6 +26,16 @@ type ReposStore interface {
 	// ErrRepoAlreadyExist when a repository with same name already exists for the
 	// owner.
 	Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error)
+	// GetByCollaboratorID returns a list of repositories that the given
+	// collaborator has access to. Results are limited to the given limit and sorted
+	// by the given order (e.g. "updated_unix DESC"). Repositories that are owned
+	// directly by the given collaborator are not included.
+	GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error)
+	// GetByCollaboratorIDWithAccessMode returns a list of repositories and
+	// corresponding access mode that the given collaborator has access to.
+	// Repositories that are owned directly by the given collaborator are not
+	// included.
+	GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error)
 	// GetByName returns the repository with given owner and name. It returns
 	// ErrRepoNotExist when not found.
 	GetByName(ctx context.Context, ownerID int64, name string) (*Repository, error)
@@ -170,6 +180,59 @@ func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptio
 	return repo, db.WithContext(ctx).Create(repo).Error
 }
 
+func (db *repos) GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error) {
+	/*
+		Equivalent SQL for PostgreSQL:
+
+		SELECT * FROM repository
+		JOIN access ON access.repo_id = repository.id AND access.user_id = @collaboratorID
+		WHERE access.mode >= @accessModeRead
+		ORDER BY @orderBy
+		LIMIT @limit
+	*/
+	var repos []*Repository
+	return repos, db.WithContext(ctx).
+		Joins("JOIN access ON access.repo_id = repository.id AND access.user_id = ?", collaboratorID).
+		Where("access.mode >= ?", AccessModeRead).
+		Order(orderBy).
+		Limit(limit).
+		Find(&repos).
+		Error
+}
+
+func (db *repos) GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error) {
+	/*
+		Equivalent SQL for PostgreSQL:
+
+		SELECT
+			repository.*,
+			access.mode
+		FROM repository
+		JOIN access ON access.repo_id = repository.id AND access.user_id = @collaboratorID
+		WHERE access.mode >= @accessModeRead
+	*/
+	var reposWithAccessMode []*struct {
+		*Repository
+		Mode AccessMode
+	}
+	err := db.WithContext(ctx).
+		Select("repository.*", "access.mode").
+		Table("repository").
+		Joins("JOIN access ON access.repo_id = repository.id AND access.user_id = ?", collaboratorID).
+		Where("access.mode >= ?", AccessModeRead).
+		Find(&reposWithAccessMode).
+		Error
+	if err != nil {
+		return nil, err
+	}
+
+	repos := make(map[*Repository]AccessMode, len(reposWithAccessMode))
+	for _, repoWithAccessMode := range reposWithAccessMode {
+		repos[repoWithAccessMode.Repository] = repoWithAccessMode.Mode
+	}
+	return repos, nil
+}
+
 var _ errutil.NotFound = (*ErrRepoNotExist)(nil)
 
 type ErrRepoNotExist struct {

+ 61 - 1
internal/db/repos_test.go

@@ -85,7 +85,7 @@ func TestRepos(t *testing.T) {
 	}
 	t.Parallel()
 
-	tables := []any{new(Repository)}
+	tables := []any{new(Repository), new(Access)}
 	db := &repos{
 		DB: dbtest.NewDB(t, "repos", tables...),
 	}
@@ -95,6 +95,8 @@ func TestRepos(t *testing.T) {
 		test func(t *testing.T, db *repos)
 	}{
 		{"Create", reposCreate},
+		{"GetByCollaboratorID", reposGetByCollaboratorID},
+		{"GetByCollaboratorIDWithAccessMode", reposGetByCollaboratorIDWithAccessMode},
 		{"GetByName", reposGetByName},
 		{"Touch", reposTouch},
 	} {
@@ -154,6 +156,64 @@ func reposCreate(t *testing.T, db *repos) {
 	assert.Equal(t, db.NowFunc().Format(time.RFC3339), repo.Created.UTC().Format(time.RFC3339))
 }
 
+func reposGetByCollaboratorID(t *testing.T, db *repos) {
+	ctx := context.Background()
+
+	repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"})
+	require.NoError(t, err)
+	repo2, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo2"})
+	require.NoError(t, err)
+
+	permsStore := NewPermsStore(db.DB)
+	err = permsStore.SetRepoPerms(ctx, repo1.ID, map[int64]AccessMode{3: AccessModeRead})
+	require.NoError(t, err)
+	err = permsStore.SetRepoPerms(ctx, repo2.ID, map[int64]AccessMode{4: AccessModeAdmin})
+	require.NoError(t, err)
+
+	t.Run("user 3 is a collaborator of repo1", func(t *testing.T) {
+		got, err := db.GetByCollaboratorID(ctx, 3, 10, "")
+		require.NoError(t, err)
+		require.Len(t, got, 1)
+		assert.Equal(t, repo1.ID, got[0].ID)
+	})
+
+	t.Run("do not return directly owned repository", func(t *testing.T) {
+		got, err := db.GetByCollaboratorID(ctx, 1, 10, "")
+		require.NoError(t, err)
+		require.Len(t, got, 0)
+	})
+}
+
+func reposGetByCollaboratorIDWithAccessMode(t *testing.T, db *repos) {
+	ctx := context.Background()
+
+	repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"})
+	require.NoError(t, err)
+	repo2, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo2"})
+	require.NoError(t, err)
+	repo3, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo3"})
+	require.NoError(t, err)
+
+	permsStore := NewPermsStore(db.DB)
+	err = permsStore.SetRepoPerms(ctx, repo1.ID, map[int64]AccessMode{3: AccessModeRead})
+	require.NoError(t, err)
+	err = permsStore.SetRepoPerms(ctx, repo2.ID, map[int64]AccessMode{3: AccessModeAdmin, 4: AccessModeWrite})
+	require.NoError(t, err)
+	err = permsStore.SetRepoPerms(ctx, repo3.ID, map[int64]AccessMode{4: AccessModeWrite})
+	require.NoError(t, err)
+
+	got, err := db.GetByCollaboratorIDWithAccessMode(ctx, 3)
+	require.NoError(t, err)
+	require.Len(t, got, 2)
+
+	accessModes := make(map[int64]AccessMode)
+	for repo, mode := range got {
+		accessModes[repo.ID] = mode
+	}
+	assert.Equal(t, AccessModeRead, accessModes[repo1.ID])
+	assert.Equal(t, AccessModeAdmin, accessModes[repo2.ID])
+}
+
 func reposGetByName(t *testing.T, db *repos) {
 	ctx := context.Background()
 

+ 0 - 39
internal/db/user.go

@@ -10,7 +10,6 @@ import (
 	"os"
 	"time"
 
-	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
 	"gogs.io/gogs/internal/repoutil"
@@ -196,41 +195,3 @@ func DeleteInactivateUsers() (err error) {
 	_, err = x.Where("is_activated = ?", false).Delete(new(EmailAddress))
 	return err
 }
-
-// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
-func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
-	accesses := make([]*Access, 0, 10)
-	if err := x.Find(&accesses, &Access{UserID: u.ID}); err != nil {
-		return nil, err
-	}
-
-	repos := make(map[*Repository]AccessMode, len(accesses))
-	for _, access := range accesses {
-		repo, err := GetRepositoryByID(access.RepoID)
-		if err != nil {
-			if IsErrRepoNotExist(err) {
-				log.Error("Failed to get repository by ID: %v", err)
-				continue
-			}
-			return nil, err
-		}
-		if repo.OwnerID == u.ID {
-			continue
-		}
-		repos[repo] = access.Mode
-	}
-	return repos, nil
-}
-
-// GetAccessibleRepositories finds repositories which the user has access but does not own.
-// If limit is smaller than 1 means returns all found results.
-func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) {
-	sess := x.Where("owner_id !=? ", user.ID).Desc("updated_unix")
-	if limit > 0 {
-		sess.Limit(limit)
-		repos = make([]*Repository, 0, limit)
-	} else {
-		repos = make([]*Repository, 0, 10)
-	}
-	return repos, sess.Join("INNER", "access", "access.user_id = ? AND access.repo_id = repository.id", user.ID).Find(&repos)
-}

+ 12 - 12
internal/route/api/v1/repo/repo.go

@@ -116,26 +116,26 @@ func listUserRepositories(c *context.APIContext, username string) {
 		return
 	}
 
-	accessibleRepos, err := user.GetRepositoryAccesses()
+	accessibleRepos, err := db.Repos.GetByCollaboratorIDWithAccessMode(c.Req.Context(), user.ID)
 	if err != nil {
-		c.Error(err, "get repositories accesses")
+		c.Error(err, "get repositories accesses by collaborator")
 		return
 	}
 
 	numOwnRepos := len(ownRepos)
-	repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
-	for i := range ownRepos {
-		repos[i] = ownRepos[i].APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true})
+	repos := make([]*api.Repository, 0, numOwnRepos+len(accessibleRepos))
+	for _, r := range ownRepos {
+		repos = append(repos, r.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
 	}
 
-	i := numOwnRepos
 	for repo, access := range accessibleRepos {
-		repos[i] = repo.APIFormatLegacy(&api.Permission{
-			Admin: access >= db.AccessModeAdmin,
-			Push:  access >= db.AccessModeWrite,
-			Pull:  true,
-		})
-		i++
+		repos = append(repos,
+			repo.APIFormatLegacy(&api.Permission{
+				Admin: access >= db.AccessModeAdmin,
+				Push:  access >= db.AccessModeWrite,
+				Pull:  true,
+			}),
+		)
 	}
 
 	c.JSONSuccess(&repos)

+ 261 - 0
internal/route/lfs/mocks_test.go

@@ -1495,6 +1495,13 @@ type MockReposStore struct {
 	// CreateFunc is an instance of a mock function object controlling the
 	// behavior of the method Create.
 	CreateFunc *ReposStoreCreateFunc
+	// GetByCollaboratorIDFunc is an instance of a mock function object
+	// controlling the behavior of the method GetByCollaboratorID.
+	GetByCollaboratorIDFunc *ReposStoreGetByCollaboratorIDFunc
+	// GetByCollaboratorIDWithAccessModeFunc is an instance of a mock
+	// function object controlling the behavior of the method
+	// GetByCollaboratorIDWithAccessMode.
+	GetByCollaboratorIDWithAccessModeFunc *ReposStoreGetByCollaboratorIDWithAccessModeFunc
 	// GetByNameFunc is an instance of a mock function object controlling
 	// the behavior of the method GetByName.
 	GetByNameFunc *ReposStoreGetByNameFunc
@@ -1512,6 +1519,16 @@ func NewMockReposStore() *MockReposStore {
 				return
 			},
 		},
+		GetByCollaboratorIDFunc: &ReposStoreGetByCollaboratorIDFunc{
+			defaultHook: func(context.Context, int64, int, string) (r0 []*db.Repository, r1 error) {
+				return
+			},
+		},
+		GetByCollaboratorIDWithAccessModeFunc: &ReposStoreGetByCollaboratorIDWithAccessModeFunc{
+			defaultHook: func(context.Context, int64) (r0 map[*db.Repository]db.AccessMode, r1 error) {
+				return
+			},
+		},
 		GetByNameFunc: &ReposStoreGetByNameFunc{
 			defaultHook: func(context.Context, int64, string) (r0 *db.Repository, r1 error) {
 				return
@@ -1534,6 +1551,16 @@ func NewStrictMockReposStore() *MockReposStore {
 				panic("unexpected invocation of MockReposStore.Create")
 			},
 		},
+		GetByCollaboratorIDFunc: &ReposStoreGetByCollaboratorIDFunc{
+			defaultHook: func(context.Context, int64, int, string) ([]*db.Repository, error) {
+				panic("unexpected invocation of MockReposStore.GetByCollaboratorID")
+			},
+		},
+		GetByCollaboratorIDWithAccessModeFunc: &ReposStoreGetByCollaboratorIDWithAccessModeFunc{
+			defaultHook: func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) {
+				panic("unexpected invocation of MockReposStore.GetByCollaboratorIDWithAccessMode")
+			},
+		},
 		GetByNameFunc: &ReposStoreGetByNameFunc{
 			defaultHook: func(context.Context, int64, string) (*db.Repository, error) {
 				panic("unexpected invocation of MockReposStore.GetByName")
@@ -1554,6 +1581,12 @@ func NewMockReposStoreFrom(i db.ReposStore) *MockReposStore {
 		CreateFunc: &ReposStoreCreateFunc{
 			defaultHook: i.Create,
 		},
+		GetByCollaboratorIDFunc: &ReposStoreGetByCollaboratorIDFunc{
+			defaultHook: i.GetByCollaboratorID,
+		},
+		GetByCollaboratorIDWithAccessModeFunc: &ReposStoreGetByCollaboratorIDWithAccessModeFunc{
+			defaultHook: i.GetByCollaboratorIDWithAccessMode,
+		},
 		GetByNameFunc: &ReposStoreGetByNameFunc{
 			defaultHook: i.GetByName,
 		},
@@ -1673,6 +1706,234 @@ func (c ReposStoreCreateFuncCall) Results() []interface{} {
 	return []interface{}{c.Result0, c.Result1}
 }
 
+// ReposStoreGetByCollaboratorIDFunc describes the behavior when the
+// GetByCollaboratorID method of the parent MockReposStore instance is
+// invoked.
+type ReposStoreGetByCollaboratorIDFunc struct {
+	defaultHook func(context.Context, int64, int, string) ([]*db.Repository, error)
+	hooks       []func(context.Context, int64, int, string) ([]*db.Repository, error)
+	history     []ReposStoreGetByCollaboratorIDFuncCall
+	mutex       sync.Mutex
+}
+
+// GetByCollaboratorID delegates to the next hook function in the queue and
+// stores the parameter and result values of this invocation.
+func (m *MockReposStore) GetByCollaboratorID(v0 context.Context, v1 int64, v2 int, v3 string) ([]*db.Repository, error) {
+	r0, r1 := m.GetByCollaboratorIDFunc.nextHook()(v0, v1, v2, v3)
+	m.GetByCollaboratorIDFunc.appendCall(ReposStoreGetByCollaboratorIDFuncCall{v0, v1, v2, v3, r0, r1})
+	return r0, r1
+}
+
+// SetDefaultHook sets function that is called when the GetByCollaboratorID
+// method of the parent MockReposStore instance is invoked and the hook
+// queue is empty.
+func (f *ReposStoreGetByCollaboratorIDFunc) SetDefaultHook(hook func(context.Context, int64, int, string) ([]*db.Repository, error)) {
+	f.defaultHook = hook
+}
+
+// PushHook adds a function to the end of hook queue. Each invocation of the
+// GetByCollaboratorID method of the parent MockReposStore instance invokes
+// the hook at the front of the queue and discards it. After the queue is
+// empty, the default hook function is invoked for any future action.
+func (f *ReposStoreGetByCollaboratorIDFunc) PushHook(hook func(context.Context, int64, int, string) ([]*db.Repository, error)) {
+	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 *ReposStoreGetByCollaboratorIDFunc) SetDefaultReturn(r0 []*db.Repository, r1 error) {
+	f.SetDefaultHook(func(context.Context, int64, int, string) ([]*db.Repository, error) {
+		return r0, r1
+	})
+}
+
+// PushReturn calls PushHook with a function that returns the given values.
+func (f *ReposStoreGetByCollaboratorIDFunc) PushReturn(r0 []*db.Repository, r1 error) {
+	f.PushHook(func(context.Context, int64, int, string) ([]*db.Repository, error) {
+		return r0, r1
+	})
+}
+
+func (f *ReposStoreGetByCollaboratorIDFunc) nextHook() func(context.Context, int64, int, string) ([]*db.Repository, error) {
+	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 *ReposStoreGetByCollaboratorIDFunc) appendCall(r0 ReposStoreGetByCollaboratorIDFuncCall) {
+	f.mutex.Lock()
+	f.history = append(f.history, r0)
+	f.mutex.Unlock()
+}
+
+// History returns a sequence of ReposStoreGetByCollaboratorIDFuncCall
+// objects describing the invocations of this function.
+func (f *ReposStoreGetByCollaboratorIDFunc) History() []ReposStoreGetByCollaboratorIDFuncCall {
+	f.mutex.Lock()
+	history := make([]ReposStoreGetByCollaboratorIDFuncCall, len(f.history))
+	copy(history, f.history)
+	f.mutex.Unlock()
+
+	return history
+}
+
+// ReposStoreGetByCollaboratorIDFuncCall is an object that describes an
+// invocation of method GetByCollaboratorID on an instance of
+// MockReposStore.
+type ReposStoreGetByCollaboratorIDFuncCall struct {
+	// 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 int
+	// Arg3 is the value of the 4th argument passed to this method
+	// invocation.
+	Arg3 string
+	// Result0 is the value of the 1st result returned from this method
+	// invocation.
+	Result0 []*db.Repository
+	// 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 ReposStoreGetByCollaboratorIDFuncCall) Args() []interface{} {
+	return []interface{}{c.Arg0, c.Arg1, c.Arg2, c.Arg3}
+}
+
+// Results returns an interface slice containing the results of this
+// invocation.
+func (c ReposStoreGetByCollaboratorIDFuncCall) Results() []interface{} {
+	return []interface{}{c.Result0, c.Result1}
+}
+
+// ReposStoreGetByCollaboratorIDWithAccessModeFunc describes the behavior
+// when the GetByCollaboratorIDWithAccessMode method of the parent
+// MockReposStore instance is invoked.
+type ReposStoreGetByCollaboratorIDWithAccessModeFunc struct {
+	defaultHook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)
+	hooks       []func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)
+	history     []ReposStoreGetByCollaboratorIDWithAccessModeFuncCall
+	mutex       sync.Mutex
+}
+
+// GetByCollaboratorIDWithAccessMode delegates to the next hook function in
+// the queue and stores the parameter and result values of this invocation.
+func (m *MockReposStore) GetByCollaboratorIDWithAccessMode(v0 context.Context, v1 int64) (map[*db.Repository]db.AccessMode, error) {
+	r0, r1 := m.GetByCollaboratorIDWithAccessModeFunc.nextHook()(v0, v1)
+	m.GetByCollaboratorIDWithAccessModeFunc.appendCall(ReposStoreGetByCollaboratorIDWithAccessModeFuncCall{v0, v1, r0, r1})
+	return r0, r1
+}
+
+// SetDefaultHook sets function that is called when the
+// GetByCollaboratorIDWithAccessMode method of the parent MockReposStore
+// instance is invoked and the hook queue is empty.
+func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) SetDefaultHook(hook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)) {
+	f.defaultHook = hook
+}
+
+// PushHook adds a function to the end of hook queue. Each invocation of the
+// GetByCollaboratorIDWithAccessMode method of the parent MockReposStore
+// instance invokes the hook at the front of the queue and discards it.
+// After the queue is empty, the default hook function is invoked for any
+// future action.
+func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) PushHook(hook func(context.Context, int64) (map[*db.Repository]db.AccessMode, error)) {
+	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 *ReposStoreGetByCollaboratorIDWithAccessModeFunc) SetDefaultReturn(r0 map[*db.Repository]db.AccessMode, r1 error) {
+	f.SetDefaultHook(func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) {
+		return r0, r1
+	})
+}
+
+// PushReturn calls PushHook with a function that returns the given values.
+func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) PushReturn(r0 map[*db.Repository]db.AccessMode, r1 error) {
+	f.PushHook(func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) {
+		return r0, r1
+	})
+}
+
+func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) nextHook() func(context.Context, int64) (map[*db.Repository]db.AccessMode, error) {
+	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 *ReposStoreGetByCollaboratorIDWithAccessModeFunc) appendCall(r0 ReposStoreGetByCollaboratorIDWithAccessModeFuncCall) {
+	f.mutex.Lock()
+	f.history = append(f.history, r0)
+	f.mutex.Unlock()
+}
+
+// History returns a sequence of
+// ReposStoreGetByCollaboratorIDWithAccessModeFuncCall objects describing
+// the invocations of this function.
+func (f *ReposStoreGetByCollaboratorIDWithAccessModeFunc) History() []ReposStoreGetByCollaboratorIDWithAccessModeFuncCall {
+	f.mutex.Lock()
+	history := make([]ReposStoreGetByCollaboratorIDWithAccessModeFuncCall, len(f.history))
+	copy(history, f.history)
+	f.mutex.Unlock()
+
+	return history
+}
+
+// ReposStoreGetByCollaboratorIDWithAccessModeFuncCall is an object that
+// describes an invocation of method GetByCollaboratorIDWithAccessMode on an
+// instance of MockReposStore.
+type ReposStoreGetByCollaboratorIDWithAccessModeFuncCall struct {
+	// 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 map[*db.Repository]db.AccessMode
+	// 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 ReposStoreGetByCollaboratorIDWithAccessModeFuncCall) Args() []interface{} {
+	return []interface{}{c.Arg0, c.Arg1}
+}
+
+// Results returns an interface slice containing the results of this
+// invocation.
+func (c ReposStoreGetByCollaboratorIDWithAccessModeFuncCall) Results() []interface{} {
+	return []interface{}{c.Result0, c.Result1}
+}
+
 // ReposStoreGetByNameFunc describes the behavior when the GetByName method
 // of the parent MockReposStore instance is invoked.
 type ReposStoreGetByNameFunc struct {

+ 2 - 2
internal/route/user/home.go

@@ -125,9 +125,9 @@ func Dashboard(c *context.Context) {
 
 	// Only user can have collaborative repositories.
 	if !ctxUser.IsOrganization() {
-		collaborateRepos, err := c.User.GetAccessibleRepositories(conf.UI.User.RepoPagingNum)
+		collaborateRepos, err := db.Repos.GetByCollaboratorID(c.Req.Context(), c.User.ID, conf.UI.User.RepoPagingNum, "updated_unix DESC")
 		if err != nil {
-			c.Error(err, "get accessible repositories")
+			c.Error(err, "get accessible repositories by collaborator")
 			return
 		} else if err = db.RepositoryList(collaborateRepos).LoadAttributes(); err != nil {
 			c.Error(err, "load attributes")