87 lines
2.9 KiB
Go
87 lines
2.9 KiB
Go
package postgres
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
|
||
"github.com/jackc/pgx/v5"
|
||
|
||
"telesrv/internal/domain"
|
||
"telesrv/internal/store/postgres/sqlcgen"
|
||
)
|
||
|
||
// PhoneChangeStore 在事务内更新 users.phone。updateUserPhone 没有
|
||
// pts/pts_count,因此这里不得分配账号 PTS 或写 durable event/outbox。
|
||
type PhoneChangeStore struct {
|
||
db sqlcgen.DBTX
|
||
q *sqlcgen.Queries
|
||
}
|
||
|
||
func NewPhoneChangeStore(db sqlcgen.DBTX) *PhoneChangeStore {
|
||
return &PhoneChangeStore{db: db, q: sqlcgen.New(db)}
|
||
}
|
||
|
||
func (s *PhoneChangeStore) ChangePhone(ctx context.Context, req domain.PhoneChangeRequest) (domain.PhoneChangeResult, error) {
|
||
if s == nil || req.UserID == 0 || !domain.ValidPhone(req.Phone) {
|
||
return domain.PhoneChangeResult{}, domain.ErrPhoneNumberInvalid
|
||
}
|
||
beginner, ok := s.db.(txBeginner)
|
||
if !ok {
|
||
return domain.PhoneChangeResult{}, fmt.Errorf("change phone: db does not support transactions")
|
||
}
|
||
tx, err := beginner.Begin(ctx)
|
||
if err != nil {
|
||
return domain.PhoneChangeResult{}, fmt.Errorf("begin change phone: %w", err)
|
||
}
|
||
committed := false
|
||
defer func() {
|
||
if !committed {
|
||
_ = tx.Rollback(ctx)
|
||
}
|
||
}()
|
||
qtx := s.q.WithTx(tx)
|
||
|
||
var currentPhone string
|
||
if err := tx.QueryRow(ctx, `SELECT phone FROM users WHERE id = $1 FOR UPDATE`, req.UserID).Scan(¤tPhone); err != nil {
|
||
if errors.Is(err, pgx.ErrNoRows) {
|
||
return domain.PhoneChangeResult{}, domain.ErrUserNotFound
|
||
}
|
||
return domain.PhoneChangeResult{}, fmt.Errorf("lock user for phone change: %w", err)
|
||
}
|
||
if currentPhone == req.Phone {
|
||
row, err := qtx.GetUserByID(ctx, req.UserID)
|
||
if err != nil {
|
||
return domain.PhoneChangeResult{}, fmt.Errorf("reload unchanged phone user: %w", err)
|
||
}
|
||
if err := tx.Commit(ctx); err != nil {
|
||
return domain.PhoneChangeResult{}, fmt.Errorf("commit unchanged phone: %w", err)
|
||
}
|
||
committed = true
|
||
return domain.PhoneChangeResult{User: userFromModel(row)}, nil
|
||
}
|
||
|
||
var row sqlcgen.User
|
||
if req.SignupEmail != "" {
|
||
row, err = qtx.UpdateUserPhoneAndSignupEmail(ctx, sqlcgen.UpdateUserPhoneAndSignupEmailParams{ID: req.UserID, Phone: req.Phone, SignupEmail: req.SignupEmail})
|
||
} else {
|
||
row, err = qtx.UpdateUserPhone(ctx, sqlcgen.UpdateUserPhoneParams{ID: req.UserID, Phone: req.Phone})
|
||
}
|
||
if err != nil {
|
||
if isUniqueConstraint(err, "users_phone_unique_idx") || isUniqueConstraint(err, "users_signup_email_lower_unique_idx") {
|
||
return domain.PhoneChangeResult{}, domain.ErrPhoneNumberOccupied
|
||
}
|
||
if errors.Is(err, pgx.ErrNoRows) {
|
||
return domain.PhoneChangeResult{}, domain.ErrUserNotFound
|
||
}
|
||
return domain.PhoneChangeResult{}, fmt.Errorf("update user phone: %w", err)
|
||
}
|
||
if err := tx.Commit(ctx); err != nil {
|
||
if isUniqueConstraint(err, "users_phone_unique_idx") {
|
||
return domain.PhoneChangeResult{}, domain.ErrPhoneNumberOccupied
|
||
}
|
||
return domain.PhoneChangeResult{}, fmt.Errorf("commit phone change: %w", err)
|
||
}
|
||
committed = true
|
||
return domain.PhoneChangeResult{User: userFromModel(row), Changed: true}, nil
|
||
}
|