feat: use six-digit numeric member invite codes
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -39,7 +41,7 @@ var (
|
||||
|
||||
var (
|
||||
phonePattern = regexp.MustCompile(`^1[3-9][0-9]{9}$`)
|
||||
referralPattern = regexp.MustCompile(`^[A-Za-z0-9]{1,32}$`)
|
||||
referralPattern = regexp.MustCompile(`^[0-9]{6}$`)
|
||||
tradePasswordPattern = regexp.MustCompile(`^[0-9]{6}$`)
|
||||
)
|
||||
|
||||
@@ -170,26 +172,40 @@ func (s *Service) Register(ctx context.Context, input RegisterInput) (*Result, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inviteCode, err := newPublicID("", 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account, err := s.repository.RegisterAccount(ctx, store.AccountRegistration{
|
||||
TenantID: s.cfg.DefaultTenantID,
|
||||
MemberNo: memberNo,
|
||||
Username: input.Username,
|
||||
Mobile: input.Phone,
|
||||
PasswordHash: string(passwordHash),
|
||||
ReferralCode: input.ReferralCode,
|
||||
InviteCode: inviteCode,
|
||||
})
|
||||
if err != nil {
|
||||
for attempt := 0; attempt < 10; attempt++ {
|
||||
inviteCode, err := newInviteCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account, err := s.repository.RegisterAccount(ctx, store.AccountRegistration{
|
||||
TenantID: s.cfg.DefaultTenantID,
|
||||
MemberNo: memberNo,
|
||||
Username: input.Username,
|
||||
Mobile: input.Phone,
|
||||
PasswordHash: string(passwordHash),
|
||||
ReferralCode: input.ReferralCode,
|
||||
InviteCode: inviteCode,
|
||||
})
|
||||
if errors.Is(err, store.ErrInviteCodeExists) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, store.ErrReferralInvalid) {
|
||||
return nil, ErrReferralInvalid
|
||||
}
|
||||
return nil, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.issueSession(ctx, account, input.DeviceID)
|
||||
}
|
||||
return s.issueSession(ctx, account, input.DeviceID)
|
||||
return nil, ErrUnavailable
|
||||
}
|
||||
|
||||
func newInviteCode() (string, error) {
|
||||
number, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%06d", number.Int64()), nil
|
||||
}
|
||||
|
||||
func validMemberName(value string) bool {
|
||||
|
||||
@@ -48,7 +48,8 @@ func TestRegisterValidation(t *testing.T) {
|
||||
{name: "password", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "123", PrivacyAccepted: true}, want: ErrPasswordInvalid},
|
||||
{name: "referral required", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", PrivacyAccepted: true}, want: ErrReferralInvalid},
|
||||
{name: "referral", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", ReferralCode: "bad!", PrivacyAccepted: true}, want: ErrReferralInvalid},
|
||||
{name: "privacy", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", ReferralCode: "ABC123"}, want: ErrPrivacyNotAccepted},
|
||||
{name: "referral length", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", ReferralCode: "12345", PrivacyAccepted: true}, want: ErrReferralInvalid},
|
||||
{name: "privacy", input: RegisterInput{Username: "alice", Phone: "13800138000", Password: "secret1", ReferralCode: "123456"}, want: ErrPrivacyNotAccepted},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -60,6 +61,15 @@ func TestRegisterValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInviteCodeIsSixDigits(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
code, err := newInviteCode()
|
||||
if err != nil || !referralPattern.MatchString(code) {
|
||||
t.Fatalf("newInviteCode() = %q, %v", code, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidMemberNameSupportsChinese(t *testing.T) {
|
||||
for _, name := range []string{"杨情", "欧阳娜娜", "Alicia", "阿依古丽·买买提"} {
|
||||
if !validMemberName(name) {
|
||||
|
||||
@@ -67,6 +67,9 @@ func (s *Store) RegisterAccount(ctx context.Context, input AccountRegistration)
|
||||
levelID, parentID, teamLevel, tree, input.InviteCode)
|
||||
if err != nil {
|
||||
if isDuplicate(err) {
|
||||
if strings.Contains(err.Error(), "uk_app_member_invite_code") {
|
||||
return nil, ErrInviteCodeExists
|
||||
}
|
||||
return nil, ErrAccountExists
|
||||
}
|
||||
return nil, err
|
||||
|
||||
@@ -4,6 +4,7 @@ import "errors"
|
||||
|
||||
var (
|
||||
ErrAccountExists = errors.New("account already exists")
|
||||
ErrInviteCodeExists = errors.New("invite code already exists")
|
||||
ErrAccountNotFound = errors.New("account not found")
|
||||
ErrAccountRestricted = errors.New("account restricted")
|
||||
ErrAttachmentInvalid = errors.New("attachment invalid")
|
||||
|
||||
Reference in New Issue
Block a user