feat(backend): allow changing username in PATCH /users/@me
This commit is contained in:
parent
cd7689d0f5
commit
c7f486ca21
|
@ -30,13 +30,13 @@ const (
|
||||||
ErrMemberNameInUse = errors.Sentinel("member name already in use")
|
ErrMemberNameInUse = errors.Sentinel("member name already in use")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *DB) Member(ctx context.Context, id xid.ID) (m Member, err error) {
|
func (db *DB) getMember(ctx context.Context, q pgxscan.Querier, id xid.ID) (m Member, err error) {
|
||||||
sql, args, err := sq.Select("*").From("members").Where("id = ?", id).ToSql()
|
sql, args, err := sq.Select("*").From("members").Where("id = ?", id).ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return m, errors.Wrap(err, "building sql")
|
return m, errors.Wrap(err, "building sql")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pgxscan.Get(ctx, db, &m, sql, args...)
|
err = pgxscan.Get(ctx, q, &m, sql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == pgx.ErrNoRows {
|
if errors.Cause(err) == pgx.ErrNoRows {
|
||||||
return m, ErrMemberNotFound
|
return m, ErrMemberNotFound
|
||||||
|
@ -47,6 +47,10 @@ func (db *DB) Member(ctx context.Context, id xid.ID) (m Member, err error) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) Member(ctx context.Context, id xid.ID) (m Member, err error) {
|
||||||
|
return db.getMember(ctx, db, id)
|
||||||
|
}
|
||||||
|
|
||||||
// UserMember returns a member scoped by user.
|
// UserMember returns a member scoped by user.
|
||||||
func (db *DB) UserMember(ctx context.Context, userID xid.ID, memberRef string) (m Member, err error) {
|
func (db *DB) UserMember(ctx context.Context, userID xid.ID, memberRef string) (m Member, err error) {
|
||||||
sql, args, err := sq.Select("*").From("members").
|
sql, args, err := sq.Select("*").From("members").
|
||||||
|
@ -98,6 +102,7 @@ func (db *DB) CreateMember(ctx context.Context, tx pgx.Tx, userID xid.ID, name s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pge := &pgconn.PgError{}
|
pge := &pgconn.PgError{}
|
||||||
if errors.As(err, &pge) {
|
if errors.As(err, &pge) {
|
||||||
|
// unique constraint violation
|
||||||
if pge.Code == "23505" {
|
if pge.Code == "23505" {
|
||||||
return m, ErrMemberNameInUse
|
return m, ErrMemberNameInUse
|
||||||
}
|
}
|
||||||
|
@ -146,7 +151,7 @@ func (db *DB) UpdateMember(
|
||||||
avatarURLs []string,
|
avatarURLs []string,
|
||||||
) (m Member, err error) {
|
) (m Member, err error) {
|
||||||
if name == nil && displayName == nil && bio == nil && links == nil && avatarURLs == nil {
|
if name == nil && displayName == nil && bio == nil && links == nil && avatarURLs == nil {
|
||||||
return m, ErrNothingToUpdate
|
return db.getMember(ctx, tx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder := sq.Update("members").Where("id = ?", id)
|
builder := sq.Update("members").Where("id = ?", id)
|
||||||
|
|
|
@ -113,9 +113,8 @@ func (u *User) UpdateFromDiscord(ctx context.Context, db pgxscan.Querier, du *di
|
||||||
return pgxscan.Get(ctx, db, u, sql, args...)
|
return pgxscan.Get(ctx, db, u, sql, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// User gets a user by ID.
|
func (db *DB) getUser(ctx context.Context, q pgxscan.Querier, id xid.ID) (u User, err error) {
|
||||||
func (db *DB) User(ctx context.Context, id xid.ID) (u User, err error) {
|
err = pgxscan.Get(ctx, q, &u, "select * from users where id = $1", id)
|
||||||
err = pgxscan.Get(ctx, db, &u, "select * from users where id = $1", id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == pgx.ErrNoRows {
|
if errors.Cause(err) == pgx.ErrNoRows {
|
||||||
return u, ErrUserNotFound
|
return u, ErrUserNotFound
|
||||||
|
@ -127,6 +126,11 @@ func (db *DB) User(ctx context.Context, id xid.ID) (u User, err error) {
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User gets a user by ID.
|
||||||
|
func (db *DB) User(ctx context.Context, id xid.ID) (u User, err error) {
|
||||||
|
return db.getUser(ctx, db, id)
|
||||||
|
}
|
||||||
|
|
||||||
// Username gets a user by username.
|
// Username gets a user by username.
|
||||||
func (db *DB) Username(ctx context.Context, name string) (u User, err error) {
|
func (db *DB) Username(ctx context.Context, name string) (u User, err error) {
|
||||||
err = pgxscan.Get(ctx, db, &u, "select * from users where username = $1", name)
|
err = pgxscan.Get(ctx, db, &u, "select * from users where username = $1", name)
|
||||||
|
@ -151,6 +155,32 @@ func (db *DB) UsernameTaken(ctx context.Context, username string) (valid, taken
|
||||||
return true, taken, err
|
return true, taken, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUsername validates the given username, then updates the given user's name to it if valid.
|
||||||
|
func (db *DB) UpdateUsername(ctx context.Context, tx pgx.Tx, id xid.ID, newName string) error {
|
||||||
|
if !usernameRegex.MatchString(newName) {
|
||||||
|
return ErrInvalidUsername
|
||||||
|
}
|
||||||
|
|
||||||
|
sql, args, err := sq.Update("users").Set("username", newName).Where("id = ?", id).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "building sql")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec(ctx, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
pge := &pgconn.PgError{}
|
||||||
|
if errors.As(err, &pge) {
|
||||||
|
// unique constraint violation
|
||||||
|
if pge.Code == "23505" {
|
||||||
|
return ErrUsernameTaken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Wrap(err, "executing query")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) UpdateUser(
|
func (db *DB) UpdateUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tx pgx.Tx, id xid.ID,
|
tx pgx.Tx, id xid.ID,
|
||||||
|
@ -159,7 +189,7 @@ func (db *DB) UpdateUser(
|
||||||
avatarURLs []string,
|
avatarURLs []string,
|
||||||
) (u User, err error) {
|
) (u User, err error) {
|
||||||
if displayName == nil && bio == nil && links == nil && avatarURLs == nil {
|
if displayName == nil && bio == nil && links == nil && avatarURLs == nil {
|
||||||
return u, ErrNothingToUpdate
|
return db.getUser(ctx, tx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder := sq.Update("users").Where("id = ?", id)
|
builder := sq.Update("users").Where("id = ?", id)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type PatchUserRequest struct {
|
type PatchUserRequest struct {
|
||||||
|
Username *string `json:"username"`
|
||||||
DisplayName *string `json:"display_name"`
|
DisplayName *string `json:"display_name"`
|
||||||
Bio *string `json:"bio"`
|
Bio *string `json:"bio"`
|
||||||
Links *[]string `json:"links"`
|
Links *[]string `json:"links"`
|
||||||
|
@ -34,8 +35,15 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
|
||||||
return server.APIError{Code: server.ErrBadRequest}
|
return server.APIError{Code: server.ErrBadRequest}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get existing user, for comparison later
|
||||||
|
u, err := s.DB.User(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting existing user")
|
||||||
|
}
|
||||||
|
|
||||||
// validate that *something* is set
|
// validate that *something* is set
|
||||||
if req.DisplayName == nil &&
|
if req.Username == nil &&
|
||||||
|
req.DisplayName == nil &&
|
||||||
req.Bio == nil &&
|
req.Bio == nil &&
|
||||||
req.Links == nil &&
|
req.Links == nil &&
|
||||||
req.Fields == nil &&
|
req.Fields == nil &&
|
||||||
|
@ -130,7 +138,22 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
u, err := s.DB.UpdateUser(ctx, tx, claims.UserID, req.DisplayName, req.Bio, req.Links, avatarURLs)
|
// update username
|
||||||
|
if req.Username != nil && *req.Username != u.Username {
|
||||||
|
err = s.DB.UpdateUsername(ctx, tx, claims.UserID, *req.Username)
|
||||||
|
if err != nil {
|
||||||
|
switch err {
|
||||||
|
case db.ErrUsernameTaken:
|
||||||
|
return server.APIError{Code: server.ErrUsernameTaken}
|
||||||
|
case db.ErrInvalidUsername:
|
||||||
|
return server.APIError{Code: server.ErrInvalidUsername}
|
||||||
|
default:
|
||||||
|
return errors.Wrap(err, "updating username")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err = s.DB.UpdateUser(ctx, tx, claims.UserID, req.DisplayName, req.Bio, req.Links, avatarURLs)
|
||||||
if err != nil && errors.Cause(err) != db.ErrNothingToUpdate {
|
if err != nil && errors.Cause(err) != db.ErrNothingToUpdate {
|
||||||
log.Errorf("updating user: %v", err)
|
log.Errorf("updating user: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in New Issue