Skip to content

Commit 7ff09cf

Browse files
authored
refactor(db): migrate methods off user.go (gogs#7336)
1 parent 3c43b9b commit 7ff09cf

File tree

9 files changed

+410
-55
lines changed

9 files changed

+410
-55
lines changed

internal/db/actions_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,10 @@ func actionsNewRepo(t *testing.T, db *actions) {
720720
func actionsPushTag(t *testing.T, db *actions) {
721721
ctx := context.Background()
722722

723+
// NOTE: We set a noop mock here to avoid data race with other tests that writes
724+
// to the mock server because this function holds a lock.
725+
conf.SetMockServer(t, conf.ServerOpts{})
726+
723727
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
724728
require.NoError(t, err)
725729
repo, err := NewReposStore(db.DB).Create(ctx,

internal/db/db.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func Init(w logger.Writer) (*gorm.DB, error) {
127127
LFS = &lfs{DB: db}
128128
Orgs = NewOrgsStore(db)
129129
OrgUsers = NewOrgUsersStore(db)
130-
Perms = &perms{DB: db}
130+
Perms = NewPermsStore(db)
131131
Repos = NewReposStore(db)
132132
TwoFactors = &twoFactors{DB: db}
133133
Users = NewUsersStore(db)

internal/db/perms.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ type perms struct {
8282
*gorm.DB
8383
}
8484

85+
// NewPermsStore returns a persistent interface for permissions with given
86+
// database connection.
87+
func NewPermsStore(db *gorm.DB) PermsStore {
88+
return &perms{DB: db}
89+
}
90+
8591
type AccessModeOptions struct {
8692
OwnerID int64 // The ID of the repository owner.
8793
Private bool // Whether the repository is private.

internal/db/repos.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ type ReposStore interface {
2626
// ErrRepoAlreadyExist when a repository with same name already exists for the
2727
// owner.
2828
Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error)
29+
// GetByCollaboratorID returns a list of repositories that the given
30+
// collaborator has access to. Results are limited to the given limit and sorted
31+
// by the given order (e.g. "updated_unix DESC"). Repositories that are owned
32+
// directly by the given collaborator are not included.
33+
GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error)
34+
// GetByCollaboratorIDWithAccessMode returns a list of repositories and
35+
// corresponding access mode that the given collaborator has access to.
36+
// Repositories that are owned directly by the given collaborator are not
37+
// included.
38+
GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error)
2939
// GetByName returns the repository with given owner and name. It returns
3040
// ErrRepoNotExist when not found.
3141
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
170180
return repo, db.WithContext(ctx).Create(repo).Error
171181
}
172182

183+
func (db *repos) GetByCollaboratorID(ctx context.Context, collaboratorID int64, limit int, orderBy string) ([]*Repository, error) {
184+
/*
185+
Equivalent SQL for PostgreSQL:
186+
187+
SELECT * FROM repository
188+
JOIN access ON access.repo_id = repository.id AND access.user_id = @collaboratorID
189+
WHERE access.mode >= @accessModeRead
190+
ORDER BY @orderBy
191+
LIMIT @limit
192+
*/
193+
var repos []*Repository
194+
return repos, db.WithContext(ctx).
195+
Joins("JOIN access ON access.repo_id = repository.id AND access.user_id = ?", collaboratorID).
196+
Where("access.mode >= ?", AccessModeRead).
197+
Order(orderBy).
198+
Limit(limit).
199+
Find(&repos).
200+
Error
201+
}
202+
203+
func (db *repos) GetByCollaboratorIDWithAccessMode(ctx context.Context, collaboratorID int64) (map[*Repository]AccessMode, error) {
204+
/*
205+
Equivalent SQL for PostgreSQL:
206+
207+
SELECT
208+
repository.*,
209+
access.mode
210+
FROM repository
211+
JOIN access ON access.repo_id = repository.id AND access.user_id = @collaboratorID
212+
WHERE access.mode >= @accessModeRead
213+
*/
214+
var reposWithAccessMode []*struct {
215+
*Repository
216+
Mode AccessMode
217+
}
218+
err := db.WithContext(ctx).
219+
Select("repository.*", "access.mode").
220+
Table("repository").
221+
Joins("JOIN access ON access.repo_id = repository.id AND access.user_id = ?", collaboratorID).
222+
Where("access.mode >= ?", AccessModeRead).
223+
Find(&reposWithAccessMode).
224+
Error
225+
if err != nil {
226+
return nil, err
227+
}
228+
229+
repos := make(map[*Repository]AccessMode, len(reposWithAccessMode))
230+
for _, repoWithAccessMode := range reposWithAccessMode {
231+
repos[repoWithAccessMode.Repository] = repoWithAccessMode.Mode
232+
}
233+
return repos, nil
234+
}
235+
173236
var _ errutil.NotFound = (*ErrRepoNotExist)(nil)
174237

175238
type ErrRepoNotExist struct {

internal/db/repos_test.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestRepos(t *testing.T) {
8585
}
8686
t.Parallel()
8787

88-
tables := []any{new(Repository)}
88+
tables := []any{new(Repository), new(Access)}
8989
db := &repos{
9090
DB: dbtest.NewDB(t, "repos", tables...),
9191
}
@@ -95,6 +95,8 @@ func TestRepos(t *testing.T) {
9595
test func(t *testing.T, db *repos)
9696
}{
9797
{"Create", reposCreate},
98+
{"GetByCollaboratorID", reposGetByCollaboratorID},
99+
{"GetByCollaboratorIDWithAccessMode", reposGetByCollaboratorIDWithAccessMode},
98100
{"GetByName", reposGetByName},
99101
{"Touch", reposTouch},
100102
} {
@@ -154,6 +156,64 @@ func reposCreate(t *testing.T, db *repos) {
154156
assert.Equal(t, db.NowFunc().Format(time.RFC3339), repo.Created.UTC().Format(time.RFC3339))
155157
}
156158

159+
func reposGetByCollaboratorID(t *testing.T, db *repos) {
160+
ctx := context.Background()
161+
162+
repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"})
163+
require.NoError(t, err)
164+
repo2, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo2"})
165+
require.NoError(t, err)
166+
167+
permsStore := NewPermsStore(db.DB)
168+
err = permsStore.SetRepoPerms(ctx, repo1.ID, map[int64]AccessMode{3: AccessModeRead})
169+
require.NoError(t, err)
170+
err = permsStore.SetRepoPerms(ctx, repo2.ID, map[int64]AccessMode{4: AccessModeAdmin})
171+
require.NoError(t, err)
172+
173+
t.Run("user 3 is a collaborator of repo1", func(t *testing.T) {
174+
got, err := db.GetByCollaboratorID(ctx, 3, 10, "")
175+
require.NoError(t, err)
176+
require.Len(t, got, 1)
177+
assert.Equal(t, repo1.ID, got[0].ID)
178+
})
179+
180+
t.Run("do not return directly owned repository", func(t *testing.T) {
181+
got, err := db.GetByCollaboratorID(ctx, 1, 10, "")
182+
require.NoError(t, err)
183+
require.Len(t, got, 0)
184+
})
185+
}
186+
187+
func reposGetByCollaboratorIDWithAccessMode(t *testing.T, db *repos) {
188+
ctx := context.Background()
189+
190+
repo1, err := db.Create(ctx, 1, CreateRepoOptions{Name: "repo1"})
191+
require.NoError(t, err)
192+
repo2, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo2"})
193+
require.NoError(t, err)
194+
repo3, err := db.Create(ctx, 2, CreateRepoOptions{Name: "repo3"})
195+
require.NoError(t, err)
196+
197+
permsStore := NewPermsStore(db.DB)
198+
err = permsStore.SetRepoPerms(ctx, repo1.ID, map[int64]AccessMode{3: AccessModeRead})
199+
require.NoError(t, err)
200+
err = permsStore.SetRepoPerms(ctx, repo2.ID, map[int64]AccessMode{3: AccessModeAdmin, 4: AccessModeWrite})
201+
require.NoError(t, err)
202+
err = permsStore.SetRepoPerms(ctx, repo3.ID, map[int64]AccessMode{4: AccessModeWrite})
203+
require.NoError(t, err)
204+
205+
got, err := db.GetByCollaboratorIDWithAccessMode(ctx, 3)
206+
require.NoError(t, err)
207+
require.Len(t, got, 2)
208+
209+
accessModes := make(map[int64]AccessMode)
210+
for repo, mode := range got {
211+
accessModes[repo.ID] = mode
212+
}
213+
assert.Equal(t, AccessModeRead, accessModes[repo1.ID])
214+
assert.Equal(t, AccessModeAdmin, accessModes[repo2.ID])
215+
}
216+
157217
func reposGetByName(t *testing.T, db *repos) {
158218
ctx := context.Background()
159219

internal/db/user.go

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"os"
1111
"time"
1212

13-
log "unknwon.dev/clog/v2"
1413
"xorm.io/xorm"
1514

1615
"gogs.io/gogs/internal/repoutil"
@@ -196,41 +195,3 @@ func DeleteInactivateUsers() (err error) {
196195
_, err = x.Where("is_activated = ?", false).Delete(new(EmailAddress))
197196
return err
198197
}
199-
200-
// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
201-
func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
202-
accesses := make([]*Access, 0, 10)
203-
if err := x.Find(&accesses, &Access{UserID: u.ID}); err != nil {
204-
return nil, err
205-
}
206-
207-
repos := make(map[*Repository]AccessMode, len(accesses))
208-
for _, access := range accesses {
209-
repo, err := GetRepositoryByID(access.RepoID)
210-
if err != nil {
211-
if IsErrRepoNotExist(err) {
212-
log.Error("Failed to get repository by ID: %v", err)
213-
continue
214-
}
215-
return nil, err
216-
}
217-
if repo.OwnerID == u.ID {
218-
continue
219-
}
220-
repos[repo] = access.Mode
221-
}
222-
return repos, nil
223-
}
224-
225-
// GetAccessibleRepositories finds repositories which the user has access but does not own.
226-
// If limit is smaller than 1 means returns all found results.
227-
func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) {
228-
sess := x.Where("owner_id !=? ", user.ID).Desc("updated_unix")
229-
if limit > 0 {
230-
sess.Limit(limit)
231-
repos = make([]*Repository, 0, limit)
232-
} else {
233-
repos = make([]*Repository, 0, 10)
234-
}
235-
return repos, sess.Join("INNER", "access", "access.user_id = ? AND access.repo_id = repository.id", user.ID).Find(&repos)
236-
}

internal/route/api/v1/repo/repo.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,26 +116,26 @@ func listUserRepositories(c *context.APIContext, username string) {
116116
return
117117
}
118118

119-
accessibleRepos, err := user.GetRepositoryAccesses()
119+
accessibleRepos, err := db.Repos.GetByCollaboratorIDWithAccessMode(c.Req.Context(), user.ID)
120120
if err != nil {
121-
c.Error(err, "get repositories accesses")
121+
c.Error(err, "get repositories accesses by collaborator")
122122
return
123123
}
124124

125125
numOwnRepos := len(ownRepos)
126-
repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
127-
for i := range ownRepos {
128-
repos[i] = ownRepos[i].APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true})
126+
repos := make([]*api.Repository, 0, numOwnRepos+len(accessibleRepos))
127+
for _, r := range ownRepos {
128+
repos = append(repos, r.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
129129
}
130130

131-
i := numOwnRepos
132131
for repo, access := range accessibleRepos {
133-
repos[i] = repo.APIFormatLegacy(&api.Permission{
134-
Admin: access >= db.AccessModeAdmin,
135-
Push: access >= db.AccessModeWrite,
136-
Pull: true,
137-
})
138-
i++
132+
repos = append(repos,
133+
repo.APIFormatLegacy(&api.Permission{
134+
Admin: access >= db.AccessModeAdmin,
135+
Push: access >= db.AccessModeWrite,
136+
Pull: true,
137+
}),
138+
)
139139
}
140140

141141
c.JSONSuccess(&repos)

0 commit comments

Comments
 (0)