owpengram-server/internal/store/postgres/phone_change.go
2026-09-01 12:06:31 +03:00

87 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(&currentPhone); 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
}