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

This commit is contained in:
Joe Chen
2023-02-04 00:02:34 +08:00
committed by GitHub
parent c53a1998c5
commit cc4d4eacad
9 changed files with 288 additions and 81 deletions

View File

@@ -79,6 +79,18 @@ func (issue *Issue) AfterSet(colName string, _ xorm.Cell) {
}
}
// Deprecated: Use Users.GetByID instead.
func getUserByID(e Engine, id int64) (*User, error) {
u := new(User)
has, err := e.ID(id).Get(u)
if err != nil {
return nil, err
} else if !has {
return nil, ErrUserNotExist{args: errutil.Args{"userID": id}}
}
return u, nil
}
func (issue *Issue) loadAttributes(e Engine) (err error) {
if issue.Repo == nil {
issue.Repo, err = getRepositoryByID(e, issue.RepoID)

View File

@@ -19,6 +19,12 @@ import (
type OrgsStore interface {
// List returns a list of organizations filtered by options.
List(ctx context.Context, opts ListOrgsOptions) ([]*Organization, error)
// SearchByName returns a list of organizations whose username or full name
// matches the given keyword case-insensitively. Results are paginated by given
// page and page size, and sorted by the given order (e.g. "id DESC"). A total
// count of all results is also returned. If the order is not given, it's up to
// the database to decide.
SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error)
}
var Orgs OrgsStore
@@ -69,6 +75,10 @@ func (db *orgs) List(ctx context.Context, opts ListOrgsOptions) ([]*Organization
return orgs, tx.Find(&orgs).Error
}
func (db *orgs) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error) {
return searchUserByName(ctx, db.DB, UserTypeOrganization, keyword, page, pageSize, orderBy)
}
type Organization = User
func (o *Organization) TableName() string {

View File

@@ -31,6 +31,7 @@ func TestOrgs(t *testing.T) {
test func(t *testing.T, db *orgs)
}{
{"List", orgsList},
{"SearchByName", orgsSearchByName},
} {
t.Run(tc.name, func(t *testing.T) {
t.Cleanup(func() {
@@ -57,16 +58,11 @@ func orgsList(t *testing.T, db *orgs) {
// TODO: Use Orgs.Create to replace SQL hack when the method is available.
org1, err := usersStore.Create(ctx, "org1", "org1@example.com", CreateUserOptions{})
require.NoError(t, err)
err = db.Exec(
dbutil.Quote("UPDATE %s SET type = ? WHERE id = ?", "user"),
UserTypeOrganization, org1.ID,
).Error
require.NoError(t, err)
org2, err := usersStore.Create(ctx, "org2", "org2@example.com", CreateUserOptions{})
require.NoError(t, err)
err = db.Exec(
dbutil.Quote("UPDATE %s SET type = ? WHERE id = ?", "user"),
UserTypeOrganization, org2.ID,
dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?, ?)", "user"),
UserTypeOrganization, org1.ID, org2.ID,
).Error
require.NoError(t, err)
@@ -121,3 +117,50 @@ func orgsList(t *testing.T, db *orgs) {
})
}
}
func orgsSearchByName(t *testing.T, db *orgs) {
ctx := context.Background()
// TODO: Use Orgs.Create to replace SQL hack when the method is available.
usersStore := NewUsersStore(db.DB)
org1, err := usersStore.Create(ctx, "org1", "org1@example.com", CreateUserOptions{FullName: "Acme Corp"})
require.NoError(t, err)
org2, err := usersStore.Create(ctx, "org2", "org2@example.com", CreateUserOptions{FullName: "Acme Corp 2"})
require.NoError(t, err)
err = db.Exec(
dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?, ?)", "user"),
UserTypeOrganization, org1.ID, org2.ID,
).Error
require.NoError(t, err)
t.Run("search for username org1", func(t *testing.T) {
orgs, count, err := db.SearchByName(ctx, "G1", 1, 1, "")
require.NoError(t, err)
require.Len(t, orgs, int(count))
assert.Equal(t, int64(1), count)
assert.Equal(t, org1.ID, orgs[0].ID)
})
t.Run("search for username org2", func(t *testing.T) {
orgs, count, err := db.SearchByName(ctx, "G2", 1, 1, "")
require.NoError(t, err)
require.Len(t, orgs, int(count))
assert.Equal(t, int64(1), count)
assert.Equal(t, org2.ID, orgs[0].ID)
})
t.Run("search for full name acme", func(t *testing.T) {
orgs, count, err := db.SearchByName(ctx, "ACME", 1, 10, "")
require.NoError(t, err)
require.Len(t, orgs, int(count))
assert.Equal(t, int64(2), count)
})
t.Run("search for full name acme ORDER BY id DESC LIMIT 1", func(t *testing.T) {
orgs, count, err := db.SearchByName(ctx, "ACME", 1, 1, "id DESC")
require.NoError(t, err)
require.Len(t, orgs, 1)
assert.Equal(t, int64(2), count)
assert.Equal(t, org2.ID, orgs[0].ID)
})
}

View File

@@ -9,7 +9,6 @@ import (
"fmt"
_ "image/jpeg"
"os"
"strings"
"time"
log "unknwon.dev/clog/v2"
@@ -17,7 +16,6 @@ import (
"github.com/gogs/git-module"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/repoutil"
"gogs.io/gogs/internal/userutil"
)
@@ -202,17 +200,6 @@ func DeleteInactivateUsers() (err error) {
return err
}
func getUserByID(e Engine, id int64) (*User, error) {
u := new(User)
has, err := e.ID(id).Get(u)
if err != nil {
return nil, err
} else if !has {
return nil, ErrUserNotExist{args: map[string]any{"userID": id}}
}
return u, nil
}
// GetUserEmailsByNames returns a list of e-mails corresponds to names.
func GetUserEmailsByNames(names []string) []string {
mails := make([]string, 0, len(names))
@@ -264,48 +251,6 @@ func ValidateCommitsWithEmails(oldCommits []*git.Commit) []*UserCommit {
return newCommits
}
type SearchUserOptions struct {
Keyword string
Type UserType
OrderBy string
Page int
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
}
// SearchUserByName takes keyword and part of user name to search,
// it returns results in given range and number of total results.
func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
if opts.Keyword == "" {
return users, 0, nil
}
opts.Keyword = strings.ToLower(opts.Keyword)
if opts.PageSize <= 0 || opts.PageSize > conf.UI.ExplorePagingNum {
opts.PageSize = conf.UI.ExplorePagingNum
}
if opts.Page <= 0 {
opts.Page = 1
}
searchQuery := "%" + opts.Keyword + "%"
users = make([]*User, 0, opts.PageSize)
// Append conditions
sess := x.Where("LOWER(lower_name) LIKE ?", searchQuery).
Or("LOWER(full_name) LIKE ?", searchQuery).
And("type = ?", opts.Type)
countSess := *sess
count, err := countSess.Count(new(User))
if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err)
}
if len(opts.OrderBy) > 0 {
sess.OrderBy(opts.OrderBy)
}
return users, count, sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&users)
}
// 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)

View File

@@ -90,6 +90,12 @@ type UsersStore interface {
// Results are paginated by given page and page size, and sorted by the time of
// follow in descending order.
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
// 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)
// 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.
@@ -570,6 +576,29 @@ func (db *users) ListFollowings(ctx context.Context, userID int64, page, pageSiz
Error
}
func searchUserByName(ctx context.Context, db *gorm.DB, userType UserType, keyword string, page, pageSize int, orderBy string) ([]*User, int64, error) {
if keyword == "" {
return []*User{}, 0, nil
}
keyword = "%" + strings.ToLower(keyword) + "%"
tx := db.WithContext(ctx).
Where("type = ? AND (lower_name LIKE ? OR LOWER(full_name) LIKE ?)", userType, keyword, keyword)
var count int64
err := tx.Model(&User{}).Count(&count).Error
if err != nil {
return nil, 0, errors.Wrap(err, "count")
}
users := make([]*User, 0, pageSize)
return users, count, tx.Order(orderBy).Limit(pageSize).Offset((page - 1) * pageSize).Find(&users).Error
}
func (db *users) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*User, int64, error) {
return searchUserByName(ctx, db.DB, UserTypeIndividual, keyword, page, pageSize, orderBy)
}
type UpdateUserOptions struct {
LoginSource *int64
LoginName *string

View File

@@ -105,6 +105,7 @@ func TestUsers(t *testing.T) {
{"List", usersList},
{"ListFollowers", usersListFollowers},
{"ListFollowings", usersListFollowings},
{"SearchByName", usersSearchByName},
{"Update", usersUpdate},
{"UseCustomAvatar", usersUseCustomAvatar},
} {
@@ -756,6 +757,46 @@ func usersListFollowings(t *testing.T, db *users) {
assert.Equal(t, alice.ID, got[0].ID)
}
func usersSearchByName(t *testing.T, db *users) {
ctx := context.Background()
alice, err := db.Create(ctx, "alice", "alice@example.com", CreateUserOptions{FullName: "Alice Jordan"})
require.NoError(t, err)
bob, err := db.Create(ctx, "bob", "bob@example.com", CreateUserOptions{FullName: "Bob Jordan"})
require.NoError(t, err)
t.Run("search for username alice", func(t *testing.T) {
users, count, err := db.SearchByName(ctx, "Li", 1, 1, "")
require.NoError(t, err)
require.Len(t, users, int(count))
assert.Equal(t, int64(1), count)
assert.Equal(t, alice.ID, users[0].ID)
})
t.Run("search for username bob", func(t *testing.T) {
users, count, err := db.SearchByName(ctx, "oB", 1, 1, "")
require.NoError(t, err)
require.Len(t, users, int(count))
assert.Equal(t, int64(1), count)
assert.Equal(t, bob.ID, users[0].ID)
})
t.Run("search for full name jordan", func(t *testing.T) {
users, count, err := db.SearchByName(ctx, "Jo", 1, 10, "")
require.NoError(t, err)
require.Len(t, users, int(count))
assert.Equal(t, int64(2), count)
})
t.Run("search for full name jordan ORDER BY id DESC LIMIT 1", func(t *testing.T) {
users, count, err := db.SearchByName(ctx, "Jo", 1, 1, "id DESC")
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, int64(2), count)
assert.Equal(t, bob.ID, users[0].ID)
})
}
func usersUpdate(t *testing.T, db *users) {
ctx := context.Background()