|
@@ -32,130 +32,13 @@ import (
|
|
"gogs.io/gogs/internal/userutil"
|
|
"gogs.io/gogs/internal/userutil"
|
|
)
|
|
)
|
|
|
|
|
|
-// UsersStore is the persistent interface for users.
|
|
|
|
-type UsersStore interface {
|
|
|
|
- // Authenticate validates username and password via given login source ID. It
|
|
|
|
- // returns ErrUserNotExist when the user was not found.
|
|
|
|
- //
|
|
|
|
- // When the "loginSourceID" is negative, it aborts the process and returns
|
|
|
|
- // ErrUserNotExist if the user was not found in the database.
|
|
|
|
- //
|
|
|
|
- // When the "loginSourceID" is non-negative, it returns ErrLoginSourceMismatch
|
|
|
|
- // if the user has different login source ID than the "loginSourceID".
|
|
|
|
- //
|
|
|
|
- // 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.
|
|
|
|
- Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error)
|
|
|
|
- // Create creates a new user and persists to database. It returns
|
|
|
|
- // ErrNameNotAllowed if the given name or pattern of the name is not allowed as
|
|
|
|
- // a username, or ErrUserAlreadyExist when a user with same name already exists,
|
|
|
|
- // or ErrEmailAlreadyUsed if the email has been verified by another user.
|
|
|
|
- Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error)
|
|
|
|
-
|
|
|
|
- // GetByEmail returns the user (not organization) with given email. It ignores
|
|
|
|
- // records with unverified emails and returns ErrUserNotExist when not found.
|
|
|
|
- GetByEmail(ctx context.Context, email string) (*User, error)
|
|
|
|
- // GetByID returns the user with given ID. It returns ErrUserNotExist when not
|
|
|
|
- // found.
|
|
|
|
- GetByID(ctx context.Context, id int64) (*User, error)
|
|
|
|
- // GetByUsername returns the user with given username. It returns
|
|
|
|
- // ErrUserNotExist when not found.
|
|
|
|
- GetByUsername(ctx context.Context, username string) (*User, error)
|
|
|
|
- // GetByKeyID returns the owner of given public key ID. It returns
|
|
|
|
- // ErrUserNotExist when not found.
|
|
|
|
- GetByKeyID(ctx context.Context, keyID int64) (*User, error)
|
|
|
|
- // GetMailableEmailsByUsernames returns a list of verified primary email
|
|
|
|
- // addresses (where email notifications are sent to) of users with given list of
|
|
|
|
- // usernames. Non-existing usernames are ignored.
|
|
|
|
- GetMailableEmailsByUsernames(ctx context.Context, usernames []string) ([]string, 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)
|
|
|
|
-
|
|
|
|
- // IsUsernameUsed returns true if the given username has been used other than
|
|
|
|
- // the excluded user (a non-positive ID effectively meaning check against all
|
|
|
|
- // users).
|
|
|
|
- IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool
|
|
|
|
- // 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
|
|
|
|
- // 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
|
|
|
|
-
|
|
|
|
- // AddEmail adds a new email address to given user. It returns
|
|
|
|
- // ErrEmailAlreadyUsed if the email has been verified by another user.
|
|
|
|
- AddEmail(ctx context.Context, userID int64, email string, isActivated bool) error
|
|
|
|
- // GetEmail returns the email address of the given user. If `needsActivated` is
|
|
|
|
- // true, only activated email will be returned, otherwise, it may return
|
|
|
|
- // inactivated email addresses. It returns ErrEmailNotExist when no qualified
|
|
|
|
- // email is not found.
|
|
|
|
- GetEmail(ctx context.Context, userID int64, email string, needsActivated bool) (*EmailAddress, error)
|
|
|
|
- // ListEmails returns all email addresses of the given user. It always includes
|
|
|
|
- // a primary email address.
|
|
|
|
- ListEmails(ctx context.Context, userID int64) ([]*EmailAddress, error)
|
|
|
|
- // MarkEmailActivated marks the email address of the given user as activated,
|
|
|
|
- // and new rands are generated for the user.
|
|
|
|
- MarkEmailActivated(ctx context.Context, userID int64, email string) error
|
|
|
|
- // MarkEmailPrimary marks the email address of the given user as primary. It
|
|
|
|
- // returns ErrEmailNotExist when the email is not found for the user, and
|
|
|
|
- // ErrEmailNotActivated when the email is not activated.
|
|
|
|
- MarkEmailPrimary(ctx context.Context, userID int64, email string) error
|
|
|
|
- // DeleteEmail deletes the email address of the given user.
|
|
|
|
- DeleteEmail(ctx context.Context, userID int64, email string) 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.
|
|
|
|
- // Results are paginated by given page and page size, and sorted by the time of
|
|
|
|
- // follow in descending order.
|
|
|
|
- ListFollowers(ctx context.Context, userID int64, page, pageSize int) ([]*User, error)
|
|
|
|
- // ListFollowings returns a list of users that are followed by the given user.
|
|
|
|
- // 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)
|
|
|
|
-
|
|
|
|
- // List returns a list of users. Results are paginated by given page and page
|
|
|
|
- // size, and sorted by primary key (id) in ascending order.
|
|
|
|
- List(ctx context.Context, page, pageSize int) ([]*User, error)
|
|
|
|
- // Count returns the total number of users.
|
|
|
|
- Count(ctx context.Context) int64
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-var Users UsersStore
|
|
|
|
-
|
|
|
|
-var _ UsersStore = (*usersStore)(nil)
|
|
|
|
-
|
|
|
|
-type usersStore struct {
|
|
|
|
- *gorm.DB
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// NewUsersStore returns a persistent interface for users with given database
|
|
|
|
-// connection.
|
|
|
|
-func NewUsersStore(db *gorm.DB) UsersStore {
|
|
|
|
- return &usersStore{DB: db}
|
|
|
|
|
|
+// UsersStore is the storage layer for users.
|
|
|
|
+type UsersStore struct {
|
|
|
|
+ db *gorm.DB
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func newUsersStore(db *gorm.DB) *UsersStore {
|
|
|
|
+ return &UsersStore{db: db}
|
|
}
|
|
}
|
|
|
|
|
|
type ErrLoginSourceMismatch struct {
|
|
type ErrLoginSourceMismatch struct {
|
|
@@ -165,18 +48,28 @@ type ErrLoginSourceMismatch struct {
|
|
// IsErrLoginSourceMismatch returns true if the underlying error has the type
|
|
// IsErrLoginSourceMismatch returns true if the underlying error has the type
|
|
// ErrLoginSourceMismatch.
|
|
// ErrLoginSourceMismatch.
|
|
func IsErrLoginSourceMismatch(err error) bool {
|
|
func IsErrLoginSourceMismatch(err error) bool {
|
|
- _, ok := errors.Cause(err).(ErrLoginSourceMismatch)
|
|
|
|
- return ok
|
|
|
|
|
|
+ return errors.As(err, &ErrLoginSourceMismatch{})
|
|
}
|
|
}
|
|
|
|
|
|
func (err ErrLoginSourceMismatch) Error() string {
|
|
func (err ErrLoginSourceMismatch) Error() string {
|
|
return fmt.Sprintf("login source mismatch: %v", err.args)
|
|
return fmt.Sprintf("login source mismatch: %v", err.args)
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) Authenticate(ctx context.Context, login, password string, loginSourceID int64) (*User, error) {
|
|
|
|
|
|
+// Authenticate validates username and password via given login source ID. It
|
|
|
|
+// returns ErrUserNotExist when the user was not found.
|
|
|
|
+//
|
|
|
|
+// When the "loginSourceID" is negative, it aborts the process and returns
|
|
|
|
+// ErrUserNotExist if the user was not found in the database.
|
|
|
|
+//
|
|
|
|
+// When the "loginSourceID" is non-negative, it returns ErrLoginSourceMismatch
|
|
|
|
+// if the user has different login source ID than the "loginSourceID".
|
|
|
|
+//
|
|
|
|
+// 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.
|
|
|
|
+func (s *UsersStore) Authenticate(ctx context.Context, login, password string, loginSourceID int64) (*User, error) {
|
|
login = strings.ToLower(login)
|
|
login = strings.ToLower(login)
|
|
|
|
|
|
- query := s.WithContext(ctx)
|
|
|
|
|
|
+ query := s.db.WithContext(ctx)
|
|
if strings.Contains(login, "@") {
|
|
if strings.Contains(login, "@") {
|
|
query = query.Where("email = ?", login)
|
|
query = query.Where("email = ?", login)
|
|
} else {
|
|
} else {
|
|
@@ -221,7 +114,7 @@ func (s *usersStore) Authenticate(ctx context.Context, login, password string, l
|
|
createNewUser = true
|
|
createNewUser = true
|
|
}
|
|
}
|
|
|
|
|
|
- source, err := newLoginSourcesStore(s.DB, loadedLoginSourceFilesStore).GetByID(ctx, authSourceID)
|
|
|
|
|
|
+ source, err := newLoginSourcesStore(s.db, loadedLoginSourceFilesStore).GetByID(ctx, authSourceID)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "get login source")
|
|
return nil, errors.Wrap(err, "get login source")
|
|
}
|
|
}
|
|
@@ -257,7 +150,11 @@ func (s *usersStore) Authenticate(ctx context.Context, login, password string, l
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) ChangeUsername(ctx context.Context, userID int64, newUsername string) 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.
|
|
|
|
+func (s *UsersStore) ChangeUsername(ctx context.Context, userID int64, newUsername string) error {
|
|
err := isUsernameAllowed(newUsername)
|
|
err := isUsernameAllowed(newUsername)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
@@ -276,7 +173,7 @@ func (s *usersStore) ChangeUsername(ctx context.Context, userID int64, newUserna
|
|
return errors.Wrap(err, "get user")
|
|
return errors.Wrap(err, "get user")
|
|
}
|
|
}
|
|
|
|
|
|
- return s.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
+ return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
err := tx.Model(&User{}).
|
|
err := tx.Model(&User{}).
|
|
Where("id = ?", user.ID).
|
|
Where("id = ?", user.ID).
|
|
Updates(map[string]any{
|
|
Updates(map[string]any{
|
|
@@ -338,9 +235,10 @@ func (s *usersStore) ChangeUsername(ctx context.Context, userID int64, newUserna
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) Count(ctx context.Context) int64 {
|
|
|
|
|
|
+// Count returns the total number of users.
|
|
|
|
+func (s *UsersStore) Count(ctx context.Context) int64 {
|
|
var count int64
|
|
var count int64
|
|
- s.WithContext(ctx).Model(&User{}).Where("type = ?", UserTypeIndividual).Count(&count)
|
|
|
|
|
|
+ s.db.WithContext(ctx).Model(&User{}).Where("type = ?", UserTypeIndividual).Count(&count)
|
|
return count
|
|
return count
|
|
}
|
|
}
|
|
|
|
|
|
@@ -362,8 +260,7 @@ type ErrUserAlreadyExist struct {
|
|
// IsErrUserAlreadyExist returns true if the underlying error has the type
|
|
// IsErrUserAlreadyExist returns true if the underlying error has the type
|
|
// ErrUserAlreadyExist.
|
|
// ErrUserAlreadyExist.
|
|
func IsErrUserAlreadyExist(err error) bool {
|
|
func IsErrUserAlreadyExist(err error) bool {
|
|
- _, ok := errors.Cause(err).(ErrUserAlreadyExist)
|
|
|
|
- return ok
|
|
|
|
|
|
+ return errors.As(err, &ErrUserAlreadyExist{})
|
|
}
|
|
}
|
|
|
|
|
|
func (err ErrUserAlreadyExist) Error() string {
|
|
func (err ErrUserAlreadyExist) Error() string {
|
|
@@ -377,8 +274,7 @@ type ErrEmailAlreadyUsed struct {
|
|
// IsErrEmailAlreadyUsed returns true if the underlying error has the type
|
|
// IsErrEmailAlreadyUsed returns true if the underlying error has the type
|
|
// ErrEmailAlreadyUsed.
|
|
// ErrEmailAlreadyUsed.
|
|
func IsErrEmailAlreadyUsed(err error) bool {
|
|
func IsErrEmailAlreadyUsed(err error) bool {
|
|
- _, ok := errors.Cause(err).(ErrEmailAlreadyUsed)
|
|
|
|
- return ok
|
|
|
|
|
|
+ return errors.As(err, &ErrEmailAlreadyUsed{})
|
|
}
|
|
}
|
|
|
|
|
|
func (err ErrEmailAlreadyUsed) Email() string {
|
|
func (err ErrEmailAlreadyUsed) Email() string {
|
|
@@ -393,7 +289,11 @@ func (err ErrEmailAlreadyUsed) Error() string {
|
|
return fmt.Sprintf("email has been used: %v", err.args)
|
|
return fmt.Sprintf("email has been used: %v", err.args)
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error) {
|
|
|
|
|
|
+// Create creates a new user and persists to database. It returns
|
|
|
|
+// ErrNameNotAllowed if the given name or pattern of the name is not allowed as
|
|
|
|
+// a username, or ErrUserAlreadyExist when a user with same name already exists,
|
|
|
|
+// or ErrEmailAlreadyUsed if the email has been verified by another user.
|
|
|
|
+func (s *UsersStore) Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error) {
|
|
err := isUsernameAllowed(username)
|
|
err := isUsernameAllowed(username)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
@@ -446,17 +346,19 @@ func (s *usersStore) Create(ctx context.Context, username, email string, opts Cr
|
|
}
|
|
}
|
|
user.Password = userutil.EncodePassword(user.Password, user.Salt)
|
|
user.Password = userutil.EncodePassword(user.Password, user.Salt)
|
|
|
|
|
|
- return user, s.WithContext(ctx).Create(user).Error
|
|
|
|
|
|
+ return user, s.db.WithContext(ctx).Create(user).Error
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) DeleteCustomAvatar(ctx context.Context, userID int64) error {
|
|
|
|
|
|
+// DeleteCustomAvatar deletes the current user custom avatar and falls back to
|
|
|
|
+// use look up avatar by email.
|
|
|
|
+func (s *UsersStore) DeleteCustomAvatar(ctx context.Context, userID int64) error {
|
|
_ = os.Remove(userutil.CustomAvatarPath(userID))
|
|
_ = os.Remove(userutil.CustomAvatarPath(userID))
|
|
- return s.WithContext(ctx).
|
|
|
|
|
|
+ return s.db.WithContext(ctx).
|
|
Model(&User{}).
|
|
Model(&User{}).
|
|
Where("id = ?", userID).
|
|
Where("id = ?", userID).
|
|
Updates(map[string]any{
|
|
Updates(map[string]any{
|
|
"use_custom_avatar": false,
|
|
"use_custom_avatar": false,
|
|
- "updated_unix": s.NowFunc().Unix(),
|
|
|
|
|
|
+ "updated_unix": s.db.NowFunc().Unix(),
|
|
}).
|
|
}).
|
|
Error
|
|
Error
|
|
}
|
|
}
|
|
@@ -468,8 +370,7 @@ type ErrUserOwnRepos struct {
|
|
// IsErrUserOwnRepos returns true if the underlying error has the type
|
|
// IsErrUserOwnRepos returns true if the underlying error has the type
|
|
// ErrUserOwnRepos.
|
|
// ErrUserOwnRepos.
|
|
func IsErrUserOwnRepos(err error) bool {
|
|
func IsErrUserOwnRepos(err error) bool {
|
|
- _, ok := errors.Cause(err).(ErrUserOwnRepos)
|
|
|
|
- return ok
|
|
|
|
|
|
+ return errors.As(err, &ErrUserOwnRepos{})
|
|
}
|
|
}
|
|
|
|
|
|
func (err ErrUserOwnRepos) Error() string {
|
|
func (err ErrUserOwnRepos) Error() string {
|
|
@@ -483,15 +384,19 @@ type ErrUserHasOrgs struct {
|
|
// IsErrUserHasOrgs returns true if the underlying error has the type
|
|
// IsErrUserHasOrgs returns true if the underlying error has the type
|
|
// ErrUserHasOrgs.
|
|
// ErrUserHasOrgs.
|
|
func IsErrUserHasOrgs(err error) bool {
|
|
func IsErrUserHasOrgs(err error) bool {
|
|
- _, ok := errors.Cause(err).(ErrUserHasOrgs)
|
|
|
|
- return ok
|
|
|
|
|
|
+ return errors.As(err, &ErrUserHasOrgs{})
|
|
}
|
|
}
|
|
|
|
|
|
func (err ErrUserHasOrgs) Error() string {
|
|
func (err ErrUserHasOrgs) Error() string {
|
|
return fmt.Sprintf("user still has organization membership: %v", err.args)
|
|
return fmt.Sprintf("user still has organization membership: %v", err.args)
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) DeleteByID(ctx context.Context, userID int64, skipRewriteAuthorizedKeys bool) 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.
|
|
|
|
+func (s *UsersStore) DeleteByID(ctx context.Context, userID int64, skipRewriteAuthorizedKeys bool) error {
|
|
user, err := s.GetByID(ctx, userID)
|
|
user, err := s.GetByID(ctx, userID)
|
|
if err != nil {
|
|
if err != nil {
|
|
if IsErrUserNotExist(err) {
|
|
if IsErrUserNotExist(err) {
|
|
@@ -500,17 +405,17 @@ func (s *usersStore) DeleteByID(ctx context.Context, userID int64, skipRewriteAu
|
|
return errors.Wrap(err, "get user")
|
|
return errors.Wrap(err, "get user")
|
|
}
|
|
}
|
|
|
|
|
|
- // Double check the user is not a direct owner of any repository and not a
|
|
|
|
|
|
+ // Double-check the user is not a direct owner of any repository and not a
|
|
// member of any organization.
|
|
// member of any organization.
|
|
var count int64
|
|
var count int64
|
|
- err = s.WithContext(ctx).Model(&Repository{}).Where("owner_id = ?", userID).Count(&count).Error
|
|
|
|
|
|
+ err = s.db.WithContext(ctx).Model(&Repository{}).Where("owner_id = ?", userID).Count(&count).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Wrap(err, "count repositories")
|
|
return errors.Wrap(err, "count repositories")
|
|
} else if count > 0 {
|
|
} else if count > 0 {
|
|
return ErrUserOwnRepos{args: errutil.Args{"userID": userID}}
|
|
return ErrUserOwnRepos{args: errutil.Args{"userID": userID}}
|
|
}
|
|
}
|
|
|
|
|
|
- err = s.WithContext(ctx).Model(&OrgUser{}).Where("uid = ?", userID).Count(&count).Error
|
|
|
|
|
|
+ err = s.db.WithContext(ctx).Model(&OrgUser{}).Where("uid = ?", userID).Count(&count).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Wrap(err, "count organization membership")
|
|
return errors.Wrap(err, "count organization membership")
|
|
} else if count > 0 {
|
|
} else if count > 0 {
|
|
@@ -518,7 +423,7 @@ func (s *usersStore) DeleteByID(ctx context.Context, userID int64, skipRewriteAu
|
|
}
|
|
}
|
|
|
|
|
|
needsRewriteAuthorizedKeys := false
|
|
needsRewriteAuthorizedKeys := false
|
|
- err = s.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
+ err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
/*
|
|
/*
|
|
Equivalent SQL for PostgreSQL:
|
|
Equivalent SQL for PostgreSQL:
|
|
|
|
|
|
@@ -645,7 +550,7 @@ func (s *usersStore) DeleteByID(ctx context.Context, userID int64, skipRewriteAu
|
|
_ = os.Remove(userutil.CustomAvatarPath(userID))
|
|
_ = os.Remove(userutil.CustomAvatarPath(userID))
|
|
|
|
|
|
if needsRewriteAuthorizedKeys {
|
|
if needsRewriteAuthorizedKeys {
|
|
- err = newPublicKeysStore(s.DB).RewriteAuthorizedKeys()
|
|
|
|
|
|
+ err = newPublicKeysStore(s.db).RewriteAuthorizedKeys()
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Wrap(err, `rewrite "authorized_keys" file`)
|
|
return errors.Wrap(err, `rewrite "authorized_keys" file`)
|
|
}
|
|
}
|
|
@@ -653,11 +558,13 @@ func (s *usersStore) DeleteByID(ctx context.Context, userID int64, skipRewriteAu
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// DeleteInactivated deletes all inactivated users.
|
|
|
|
+//
|
|
// NOTE: We do not take context.Context here because this operation in practice
|
|
// NOTE: We do not take context.Context here because this operation in practice
|
|
// could much longer than the general request timeout (e.g. one minute).
|
|
// could much longer than the general request timeout (e.g. one minute).
|
|
-func (s *usersStore) DeleteInactivated() error {
|
|
|
|
|
|
+func (s *UsersStore) DeleteInactivated() error {
|
|
var userIDs []int64
|
|
var userIDs []int64
|
|
- err := s.Model(&User{}).Where("is_active = ?", false).Pluck("id", &userIDs).Error
|
|
|
|
|
|
+ err := s.db.Model(&User{}).Where("is_active = ?", false).Pluck("id", &userIDs).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Wrap(err, "get inactivated user IDs")
|
|
return errors.Wrap(err, "get inactivated user IDs")
|
|
}
|
|
}
|
|
@@ -672,14 +579,14 @@ func (s *usersStore) DeleteInactivated() error {
|
|
return errors.Wrapf(err, "delete user with ID %d", userID)
|
|
return errors.Wrapf(err, "delete user with ID %d", userID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- err = newPublicKeysStore(s.DB).RewriteAuthorizedKeys()
|
|
|
|
|
|
+ err = newPublicKeysStore(s.db).RewriteAuthorizedKeys()
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Wrap(err, `rewrite "authorized_keys" file`)
|
|
return errors.Wrap(err, `rewrite "authorized_keys" file`)
|
|
}
|
|
}
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (*usersStore) recountFollows(tx *gorm.DB, userID, followID int64) error {
|
|
|
|
|
|
+func (*UsersStore) recountFollows(tx *gorm.DB, userID, followID int64) error {
|
|
/*
|
|
/*
|
|
Equivalent SQL for PostgreSQL:
|
|
Equivalent SQL for PostgreSQL:
|
|
|
|
|
|
@@ -722,12 +629,13 @@ func (*usersStore) recountFollows(tx *gorm.DB, userID, followID int64) error {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) Follow(ctx context.Context, userID, followID int64) error {
|
|
|
|
|
|
+// Follow marks the user to follow the other user.
|
|
|
|
+func (s *UsersStore) Follow(ctx context.Context, userID, followID int64) error {
|
|
if userID == followID {
|
|
if userID == followID {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
- return s.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
+ return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
f := &Follow{
|
|
f := &Follow{
|
|
UserID: userID,
|
|
UserID: userID,
|
|
FollowID: followID,
|
|
FollowID: followID,
|
|
@@ -743,12 +651,13 @@ func (s *usersStore) Follow(ctx context.Context, userID, followID int64) error {
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) Unfollow(ctx context.Context, userID, followID int64) error {
|
|
|
|
|
|
+// Unfollow removes the mark the user to follow the other user.
|
|
|
|
+func (s *UsersStore) Unfollow(ctx context.Context, userID, followID int64) error {
|
|
if userID == followID {
|
|
if userID == followID {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
- return s.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
+ return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
err := tx.Where("user_id = ? AND follow_id = ?", userID, followID).Delete(&Follow{}).Error
|
|
err := tx.Where("user_id = ? AND follow_id = ?", userID, followID).Delete(&Follow{}).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Wrap(err, "delete")
|
|
return errors.Wrap(err, "delete")
|
|
@@ -757,8 +666,9 @@ func (s *usersStore) Unfollow(ctx context.Context, userID, followID int64) error
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) IsFollowing(ctx context.Context, userID, followID int64) bool {
|
|
|
|
- return s.WithContext(ctx).Where("user_id = ? AND follow_id = ?", userID, followID).First(&Follow{}).Error == nil
|
|
|
|
|
|
+// IsFollowing returns true if the user is following the other user.
|
|
|
|
+func (s *UsersStore) IsFollowing(ctx context.Context, userID, followID int64) bool {
|
|
|
|
+ return s.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)
|
|
@@ -782,7 +692,9 @@ func (ErrUserNotExist) NotFound() bool {
|
|
return true
|
|
return true
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) GetByEmail(ctx context.Context, email string) (*User, error) {
|
|
|
|
|
|
+// GetByEmail returns the user (not organization) with given email. It ignores
|
|
|
|
+// records with unverified emails and returns ErrUserNotExist when not found.
|
|
|
|
+func (s *UsersStore) GetByEmail(ctx context.Context, email string) (*User, error) {
|
|
if email == "" {
|
|
if email == "" {
|
|
return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
|
|
return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
|
|
}
|
|
}
|
|
@@ -801,17 +713,17 @@ func (s *usersStore) GetByEmail(ctx context.Context, email string) (*User, error
|
|
)
|
|
)
|
|
*/
|
|
*/
|
|
user := new(User)
|
|
user := new(User)
|
|
- err := s.WithContext(ctx).
|
|
|
|
|
|
+ err := s.db.WithContext(ctx).
|
|
Joins(dbutil.Quote("LEFT JOIN email_address ON email_address.uid = %s.id", "user"), true).
|
|
Joins(dbutil.Quote("LEFT JOIN email_address ON email_address.uid = %s.id", "user"), true).
|
|
Where(dbutil.Quote("%s.type = ?", "user"), UserTypeIndividual).
|
|
Where(dbutil.Quote("%s.type = ?", "user"), UserTypeIndividual).
|
|
- Where(s.
|
|
|
|
|
|
+ Where(s.db.
|
|
Where(dbutil.Quote("%[1]s.email = ? AND %[1]s.is_active = ?", "user"), email, true).
|
|
Where(dbutil.Quote("%[1]s.email = ? AND %[1]s.is_active = ?", "user"), email, true).
|
|
Or("email_address.email = ? AND email_address.is_activated = ?", email, true),
|
|
Or("email_address.email = ? AND email_address.is_activated = ?", email, true),
|
|
).
|
|
).
|
|
First(&user).
|
|
First(&user).
|
|
Error
|
|
Error
|
|
if err != nil {
|
|
if err != nil {
|
|
- if err == gorm.ErrRecordNotFound {
|
|
|
|
|
|
+ if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
|
|
return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
|
|
}
|
|
}
|
|
return nil, err
|
|
return nil, err
|
|
@@ -819,11 +731,13 @@ func (s *usersStore) GetByEmail(ctx context.Context, email string) (*User, error
|
|
return user, nil
|
|
return user, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
|
|
|
|
|
+// GetByID returns the user with given ID. It returns ErrUserNotExist when not
|
|
|
|
+// found.
|
|
|
|
+func (s *UsersStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
|
user := new(User)
|
|
user := new(User)
|
|
- err := s.WithContext(ctx).Where("id = ?", id).First(user).Error
|
|
|
|
|
|
+ err := s.db.WithContext(ctx).Where("id = ?", id).First(user).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
- if err == gorm.ErrRecordNotFound {
|
|
|
|
|
|
+ if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrUserNotExist{args: errutil.Args{"userID": id}}
|
|
return nil, ErrUserNotExist{args: errutil.Args{"userID": id}}
|
|
}
|
|
}
|
|
return nil, err
|
|
return nil, err
|
|
@@ -831,11 +745,13 @@ func (s *usersStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
|
return user, nil
|
|
return user, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) GetByUsername(ctx context.Context, username string) (*User, error) {
|
|
|
|
|
|
+// GetByUsername returns the user with given username. It returns
|
|
|
|
+// ErrUserNotExist when not found.
|
|
|
|
+func (s *UsersStore) GetByUsername(ctx context.Context, username string) (*User, error) {
|
|
user := new(User)
|
|
user := new(User)
|
|
- err := s.WithContext(ctx).Where("lower_name = ?", strings.ToLower(username)).First(user).Error
|
|
|
|
|
|
+ err := s.db.WithContext(ctx).Where("lower_name = ?", strings.ToLower(username)).First(user).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
- if err == gorm.ErrRecordNotFound {
|
|
|
|
|
|
+ if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrUserNotExist{args: errutil.Args{"name": username}}
|
|
return nil, ErrUserNotExist{args: errutil.Args{"name": username}}
|
|
}
|
|
}
|
|
return nil, err
|
|
return nil, err
|
|
@@ -843,15 +759,17 @@ func (s *usersStore) GetByUsername(ctx context.Context, username string) (*User,
|
|
return user, nil
|
|
return user, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) GetByKeyID(ctx context.Context, keyID int64) (*User, error) {
|
|
|
|
|
|
+// GetByKeyID returns the owner of given public key ID. It returns
|
|
|
|
+// ErrUserNotExist when not found.
|
|
|
|
+func (s *UsersStore) GetByKeyID(ctx context.Context, keyID int64) (*User, error) {
|
|
user := new(User)
|
|
user := new(User)
|
|
- err := s.WithContext(ctx).
|
|
|
|
|
|
+ err := s.db.WithContext(ctx).
|
|
Joins(dbutil.Quote("JOIN public_key ON public_key.owner_id = %s.id", "user")).
|
|
Joins(dbutil.Quote("JOIN public_key ON public_key.owner_id = %s.id", "user")).
|
|
Where("public_key.id = ?", keyID).
|
|
Where("public_key.id = ?", keyID).
|
|
First(user).
|
|
First(user).
|
|
Error
|
|
Error
|
|
if err != nil {
|
|
if err != nil {
|
|
- if err == gorm.ErrRecordNotFound {
|
|
|
|
|
|
+ if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrUserNotExist{args: errutil.Args{"keyID": keyID}}
|
|
return nil, ErrUserNotExist{args: errutil.Args{"keyID": keyID}}
|
|
}
|
|
}
|
|
return nil, err
|
|
return nil, err
|
|
@@ -859,29 +777,37 @@ func (s *usersStore) GetByKeyID(ctx context.Context, keyID int64) (*User, error)
|
|
return user, nil
|
|
return user, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) GetMailableEmailsByUsernames(ctx context.Context, usernames []string) ([]string, error) {
|
|
|
|
|
|
+// GetMailableEmailsByUsernames returns a list of verified primary email
|
|
|
|
+// addresses (where email notifications are sent to) of users with given list of
|
|
|
|
+// usernames. Non-existing usernames are ignored.
|
|
|
|
+func (s *UsersStore) GetMailableEmailsByUsernames(ctx context.Context, usernames []string) ([]string, error) {
|
|
emails := make([]string, 0, len(usernames))
|
|
emails := make([]string, 0, len(usernames))
|
|
- return emails, s.WithContext(ctx).
|
|
|
|
|
|
+ return emails, s.db.WithContext(ctx).
|
|
Model(&User{}).
|
|
Model(&User{}).
|
|
Select("email").
|
|
Select("email").
|
|
Where("lower_name IN (?) AND is_active = ?", usernames, true).
|
|
Where("lower_name IN (?) AND is_active = ?", usernames, true).
|
|
Find(&emails).Error
|
|
Find(&emails).Error
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool {
|
|
|
|
|
|
+// IsUsernameUsed returns true if the given username has been used other than
|
|
|
|
+// the excluded user (a non-positive ID effectively meaning check against all
|
|
|
|
+// users).
|
|
|
|
+func (s *UsersStore) IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool {
|
|
if username == "" {
|
|
if username == "" {
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
- return s.WithContext(ctx).
|
|
|
|
|
|
+ return s.db.WithContext(ctx).
|
|
Select("id").
|
|
Select("id").
|
|
Where("lower_name = ? AND id != ?", strings.ToLower(username), excludeUserId).
|
|
Where("lower_name = ? AND id != ?", strings.ToLower(username), excludeUserId).
|
|
First(&User{}).
|
|
First(&User{}).
|
|
Error != gorm.ErrRecordNotFound
|
|
Error != gorm.ErrRecordNotFound
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) List(ctx context.Context, page, pageSize int) ([]*User, error) {
|
|
|
|
|
|
+// List returns a list of users. Results are paginated by given page and page
|
|
|
|
+// size, and sorted by primary key (id) in ascending order.
|
|
|
|
+func (s *UsersStore) List(ctx context.Context, page, pageSize int) ([]*User, error) {
|
|
users := make([]*User, 0, pageSize)
|
|
users := make([]*User, 0, pageSize)
|
|
- return users, s.WithContext(ctx).
|
|
|
|
|
|
+ return users, s.db.WithContext(ctx).
|
|
Where("type = ?", UserTypeIndividual).
|
|
Where("type = ?", UserTypeIndividual).
|
|
Limit(pageSize).Offset((page - 1) * pageSize).
|
|
Limit(pageSize).Offset((page - 1) * pageSize).
|
|
Order("id ASC").
|
|
Order("id ASC").
|
|
@@ -889,7 +815,10 @@ func (s *usersStore) List(ctx context.Context, page, pageSize int) ([]*User, err
|
|
Error
|
|
Error
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) ListFollowers(ctx context.Context, userID int64, page, pageSize int) ([]*User, error) {
|
|
|
|
|
|
+// 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
|
|
|
|
+// follow in descending order.
|
|
|
|
+func (s *UsersStore) ListFollowers(ctx context.Context, userID int64, page, pageSize int) ([]*User, error) {
|
|
/*
|
|
/*
|
|
Equivalent SQL for PostgreSQL:
|
|
Equivalent SQL for PostgreSQL:
|
|
|
|
|
|
@@ -900,7 +829,7 @@ func (s *usersStore) ListFollowers(ctx context.Context, userID int64, page, page
|
|
LIMIT @limit OFFSET @offset
|
|
LIMIT @limit OFFSET @offset
|
|
*/
|
|
*/
|
|
users := make([]*User, 0, pageSize)
|
|
users := make([]*User, 0, pageSize)
|
|
- return users, s.WithContext(ctx).
|
|
|
|
|
|
+ return users, s.db.WithContext(ctx).
|
|
Joins(dbutil.Quote("LEFT JOIN follow ON follow.user_id = %s.id", "user")).
|
|
Joins(dbutil.Quote("LEFT JOIN follow ON follow.user_id = %s.id", "user")).
|
|
Where("follow.follow_id = ?", userID).
|
|
Where("follow.follow_id = ?", userID).
|
|
Limit(pageSize).Offset((page - 1) * pageSize).
|
|
Limit(pageSize).Offset((page - 1) * pageSize).
|
|
@@ -909,7 +838,10 @@ func (s *usersStore) ListFollowers(ctx context.Context, userID int64, page, page
|
|
Error
|
|
Error
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) ListFollowings(ctx context.Context, userID int64, page, pageSize int) ([]*User, error) {
|
|
|
|
|
|
+// ListFollowings returns a list of users that are followed by the given user.
|
|
|
|
+// Results are paginated by given page and page size, and sorted by the time of
|
|
|
|
+// follow in descending order.
|
|
|
|
+func (s *UsersStore) ListFollowings(ctx context.Context, userID int64, page, pageSize int) ([]*User, error) {
|
|
/*
|
|
/*
|
|
Equivalent SQL for PostgreSQL:
|
|
Equivalent SQL for PostgreSQL:
|
|
|
|
|
|
@@ -920,7 +852,7 @@ func (s *usersStore) ListFollowings(ctx context.Context, userID int64, page, pag
|
|
LIMIT @limit OFFSET @offset
|
|
LIMIT @limit OFFSET @offset
|
|
*/
|
|
*/
|
|
users := make([]*User, 0, pageSize)
|
|
users := make([]*User, 0, pageSize)
|
|
- return users, s.WithContext(ctx).
|
|
|
|
|
|
+ return users, s.db.WithContext(ctx).
|
|
Joins(dbutil.Quote("LEFT JOIN follow ON follow.follow_id = %s.id", "user")).
|
|
Joins(dbutil.Quote("LEFT JOIN follow ON follow.follow_id = %s.id", "user")).
|
|
Where("follow.user_id = ?", userID).
|
|
Where("follow.user_id = ?", userID).
|
|
Limit(pageSize).Offset((page - 1) * pageSize).
|
|
Limit(pageSize).Offset((page - 1) * pageSize).
|
|
@@ -948,8 +880,13 @@ func searchUserByName(ctx context.Context, db *gorm.DB, userType UserType, keywo
|
|
return users, count, tx.Order(orderBy).Limit(pageSize).Offset((page - 1) * pageSize).Find(&users).Error
|
|
return users, count, tx.Order(orderBy).Limit(pageSize).Offset((page - 1) * pageSize).Find(&users).Error
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*User, int64, error) {
|
|
|
|
- return searchUserByName(ctx, s.DB, UserTypeIndividual, keyword, page, pageSize, orderBy)
|
|
|
|
|
|
+// 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.
|
|
|
|
+func (s *UsersStore) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*User, int64, error) {
|
|
|
|
+ return searchUserByName(ctx, s.db, UserTypeIndividual, keyword, page, pageSize, orderBy)
|
|
}
|
|
}
|
|
|
|
|
|
type UpdateUserOptions struct {
|
|
type UpdateUserOptions struct {
|
|
@@ -979,9 +916,10 @@ type UpdateUserOptions struct {
|
|
AvatarEmail *string
|
|
AvatarEmail *string
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) Update(ctx context.Context, userID int64, opts UpdateUserOptions) error {
|
|
|
|
|
|
+// Update updates fields for the given user.
|
|
|
|
+func (s *UsersStore) Update(ctx context.Context, userID int64, opts UpdateUserOptions) error {
|
|
updates := map[string]any{
|
|
updates := map[string]any{
|
|
- "updated_unix": s.NowFunc().Unix(),
|
|
|
|
|
|
+ "updated_unix": s.db.NowFunc().Unix(),
|
|
}
|
|
}
|
|
|
|
|
|
if opts.LoginSource != nil {
|
|
if opts.LoginSource != nil {
|
|
@@ -1063,26 +1001,29 @@ func (s *usersStore) Update(ctx context.Context, userID int64, opts UpdateUserOp
|
|
updates["avatar_email"] = strutil.Truncate(*opts.AvatarEmail, 255)
|
|
updates["avatar_email"] = strutil.Truncate(*opts.AvatarEmail, 255)
|
|
}
|
|
}
|
|
|
|
|
|
- return s.WithContext(ctx).Model(&User{}).Where("id = ?", userID).Updates(updates).Error
|
|
|
|
|
|
+ return s.db.WithContext(ctx).Model(&User{}).Where("id = ?", userID).Updates(updates).Error
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) UseCustomAvatar(ctx context.Context, userID int64, avatar []byte) error {
|
|
|
|
|
|
+// UseCustomAvatar uses the given avatar as the user custom avatar.
|
|
|
|
+func (s *UsersStore) UseCustomAvatar(ctx context.Context, userID int64, avatar []byte) error {
|
|
err := userutil.SaveAvatar(userID, avatar)
|
|
err := userutil.SaveAvatar(userID, avatar)
|
|
if err != nil {
|
|
if err != nil {
|
|
return errors.Wrap(err, "save avatar")
|
|
return errors.Wrap(err, "save avatar")
|
|
}
|
|
}
|
|
|
|
|
|
- return s.WithContext(ctx).
|
|
|
|
|
|
+ return s.db.WithContext(ctx).
|
|
Model(&User{}).
|
|
Model(&User{}).
|
|
Where("id = ?", userID).
|
|
Where("id = ?", userID).
|
|
Updates(map[string]any{
|
|
Updates(map[string]any{
|
|
"use_custom_avatar": true,
|
|
"use_custom_avatar": true,
|
|
- "updated_unix": s.NowFunc().Unix(),
|
|
|
|
|
|
+ "updated_unix": s.db.NowFunc().Unix(),
|
|
}).
|
|
}).
|
|
Error
|
|
Error
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) AddEmail(ctx context.Context, userID int64, email string, isActivated bool) error {
|
|
|
|
|
|
+// AddEmail adds a new email address to given user. It returns
|
|
|
|
+// ErrEmailAlreadyUsed if the email has been verified by another user.
|
|
|
|
+func (s *UsersStore) AddEmail(ctx context.Context, userID int64, email string, isActivated bool) error {
|
|
email = strings.ToLower(strings.TrimSpace(email))
|
|
email = strings.ToLower(strings.TrimSpace(email))
|
|
_, err := s.GetByEmail(ctx, email)
|
|
_, err := s.GetByEmail(ctx, email)
|
|
if err == nil {
|
|
if err == nil {
|
|
@@ -1095,7 +1036,7 @@ func (s *usersStore) AddEmail(ctx context.Context, userID int64, email string, i
|
|
return errors.Wrap(err, "check user by email")
|
|
return errors.Wrap(err, "check user by email")
|
|
}
|
|
}
|
|
|
|
|
|
- return s.WithContext(ctx).Create(
|
|
|
|
|
|
+ return s.db.WithContext(ctx).Create(
|
|
&EmailAddress{
|
|
&EmailAddress{
|
|
UserID: userID,
|
|
UserID: userID,
|
|
Email: email,
|
|
Email: email,
|
|
@@ -1125,8 +1066,12 @@ func (ErrEmailNotExist) NotFound() bool {
|
|
return true
|
|
return true
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) GetEmail(ctx context.Context, userID int64, email string, needsActivated bool) (*EmailAddress, error) {
|
|
|
|
- tx := s.WithContext(ctx).Where("uid = ? AND email = ?", userID, email)
|
|
|
|
|
|
+// GetEmail returns the email address of the given user. If `needsActivated` is
|
|
|
|
+// true, only activated email will be returned, otherwise, it may return
|
|
|
|
+// inactivated email addresses. It returns ErrEmailNotExist when no qualified
|
|
|
|
+// email is not found.
|
|
|
|
+func (s *UsersStore) GetEmail(ctx context.Context, userID int64, email string, needsActivated bool) (*EmailAddress, error) {
|
|
|
|
+ tx := s.db.WithContext(ctx).Where("uid = ? AND email = ?", userID, email)
|
|
if needsActivated {
|
|
if needsActivated {
|
|
tx = tx.Where("is_activated = ?", true)
|
|
tx = tx.Where("is_activated = ?", true)
|
|
}
|
|
}
|
|
@@ -1134,7 +1079,7 @@ func (s *usersStore) GetEmail(ctx context.Context, userID int64, email string, n
|
|
emailAddress := new(EmailAddress)
|
|
emailAddress := new(EmailAddress)
|
|
err := tx.First(emailAddress).Error
|
|
err := tx.First(emailAddress).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
- if err == gorm.ErrRecordNotFound {
|
|
|
|
|
|
+ if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrEmailNotExist{
|
|
return nil, ErrEmailNotExist{
|
|
args: errutil.Args{
|
|
args: errutil.Args{
|
|
"email": email,
|
|
"email": email,
|
|
@@ -1146,14 +1091,16 @@ func (s *usersStore) GetEmail(ctx context.Context, userID int64, email string, n
|
|
return emailAddress, nil
|
|
return emailAddress, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) ListEmails(ctx context.Context, userID int64) ([]*EmailAddress, error) {
|
|
|
|
|
|
+// ListEmails returns all email addresses of the given user. It always includes
|
|
|
|
+// a primary email address.
|
|
|
|
+func (s *UsersStore) ListEmails(ctx context.Context, userID int64) ([]*EmailAddress, error) {
|
|
user, err := s.GetByID(ctx, userID)
|
|
user, err := s.GetByID(ctx, userID)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "get user")
|
|
return nil, errors.Wrap(err, "get user")
|
|
}
|
|
}
|
|
|
|
|
|
var emails []*EmailAddress
|
|
var emails []*EmailAddress
|
|
- err = s.WithContext(ctx).Where("uid = ?", userID).Order("id ASC").Find(&emails).Error
|
|
|
|
|
|
+ err = s.db.WithContext(ctx).Where("uid = ?", userID).Order("id ASC").Find(&emails).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "list emails")
|
|
return nil, errors.Wrap(err, "list emails")
|
|
}
|
|
}
|
|
@@ -1179,9 +1126,11 @@ func (s *usersStore) ListEmails(ctx context.Context, userID int64) ([]*EmailAddr
|
|
return emails, nil
|
|
return emails, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) MarkEmailActivated(ctx context.Context, userID int64, email string) error {
|
|
|
|
- return s.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
|
|
- err := s.WithContext(ctx).
|
|
|
|
|
|
+// MarkEmailActivated marks the email address of the given user as activated,
|
|
|
|
+// and new rands are generated for the user.
|
|
|
|
+func (s *UsersStore) MarkEmailActivated(ctx context.Context, userID int64, email string) error {
|
|
|
|
+ return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
|
|
+ err := s.db.WithContext(ctx).
|
|
Model(&EmailAddress{}).
|
|
Model(&EmailAddress{}).
|
|
Where("uid = ? AND email = ?", userID, email).
|
|
Where("uid = ? AND email = ?", userID, email).
|
|
Update("is_activated", true).
|
|
Update("is_activated", true).
|
|
@@ -1190,7 +1139,7 @@ func (s *usersStore) MarkEmailActivated(ctx context.Context, userID int64, email
|
|
return errors.Wrap(err, "mark email activated")
|
|
return errors.Wrap(err, "mark email activated")
|
|
}
|
|
}
|
|
|
|
|
|
- return NewUsersStore(tx).Update(ctx, userID, UpdateUserOptions{GenerateNewRands: true})
|
|
|
|
|
|
+ return newUsersStore(tx).Update(ctx, userID, UpdateUserOptions{GenerateNewRands: true})
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1209,11 +1158,14 @@ func (err ErrEmailNotVerified) Error() string {
|
|
return fmt.Sprintf("email has not been verified: %v", err.args)
|
|
return fmt.Sprintf("email has not been verified: %v", err.args)
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) MarkEmailPrimary(ctx context.Context, userID int64, email string) error {
|
|
|
|
|
|
+// MarkEmailPrimary marks the email address of the given user as primary. It
|
|
|
|
+// returns ErrEmailNotExist when the email is not found for the user, and
|
|
|
|
+// ErrEmailNotActivated when the email is not activated.
|
|
|
|
+func (s *UsersStore) MarkEmailPrimary(ctx context.Context, userID int64, email string) error {
|
|
var emailAddress EmailAddress
|
|
var emailAddress EmailAddress
|
|
- err := s.WithContext(ctx).Where("uid = ? AND email = ?", userID, email).First(&emailAddress).Error
|
|
|
|
|
|
+ err := s.db.WithContext(ctx).Where("uid = ? AND email = ?", userID, email).First(&emailAddress).Error
|
|
if err != nil {
|
|
if err != nil {
|
|
- if err == gorm.ErrRecordNotFound {
|
|
|
|
|
|
+ if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrEmailNotExist{args: errutil.Args{"email": email}}
|
|
return ErrEmailNotExist{args: errutil.Args{"email": email}}
|
|
}
|
|
}
|
|
return errors.Wrap(err, "get email address")
|
|
return errors.Wrap(err, "get email address")
|
|
@@ -1228,7 +1180,7 @@ func (s *usersStore) MarkEmailPrimary(ctx context.Context, userID int64, email s
|
|
return errors.Wrap(err, "get user")
|
|
return errors.Wrap(err, "get user")
|
|
}
|
|
}
|
|
|
|
|
|
- return s.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
+ return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
// Make sure the former primary email doesn't disappear.
|
|
// Make sure the former primary email doesn't disappear.
|
|
err = tx.FirstOrCreate(
|
|
err = tx.FirstOrCreate(
|
|
&EmailAddress{
|
|
&EmailAddress{
|
|
@@ -1255,8 +1207,9 @@ func (s *usersStore) MarkEmailPrimary(ctx context.Context, userID int64, email s
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *usersStore) DeleteEmail(ctx context.Context, userID int64, email string) error {
|
|
|
|
- return s.WithContext(ctx).Where("uid = ? AND email = ?", userID, email).Delete(&EmailAddress{}).Error
|
|
|
|
|
|
+// DeleteEmail deletes the email address of the given user.
|
|
|
|
+func (s *UsersStore) DeleteEmail(ctx context.Context, userID int64, email string) error {
|
|
|
|
+ return s.db.WithContext(ctx).Where("uid = ? AND email = ?", userID, email).Delete(&EmailAddress{}).Error
|
|
}
|
|
}
|
|
|
|
|
|
// UserType indicates the type of the user account.
|
|
// UserType indicates the type of the user account.
|
|
@@ -1464,7 +1417,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 Users.IsFollowing(context.TODO(), u.ID, followID)
|
|
|
|
|
|
+ return Handle.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
|