Implement phone number validation for customer and user registration and updates

Added functionality to check for existing customers and users by phone number during registration and updates. Updated the handler, service, and repository layers to include phone number checks, ensuring unique phone numbers across customers and users.
This commit is contained in:
Mazyar
2026-06-06 12:03:04 +03:30
parent 5af3bfaa1a
commit 0da0c8b8c9
5 changed files with 75 additions and 0 deletions
+2
View File
@@ -55,6 +55,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
if err != nil { if err != nil {
if err.Error() == "customer with this email already exists" || if err.Error() == "customer with this email already exists" ||
err.Error() == "customer with this username already exists" || err.Error() == "customer with this username already exists" ||
err.Error() == "customer with this phone number already exists" ||
err.Error() == "company with this name already exists" || err.Error() == "company with this name already exists" ||
err.Error() == "company with this registration number already exists" || err.Error() == "company with this registration number already exists" ||
err.Error() == "company with this tax ID already exists" { err.Error() == "company with this tax ID already exists" {
@@ -128,6 +129,7 @@ func (h *Handler) UpdateCustomer(c echo.Context) error {
} }
if err.Error() == "customer with this email already exists" || if err.Error() == "customer with this email already exists" ||
err.Error() == "customer with this username already exists" || err.Error() == "customer with this username already exists" ||
err.Error() == "customer with this phone number already exists" ||
err.Error() == "company with this name already exists" || err.Error() == "company with this name already exists" ||
err.Error() == "company with this registration number already exists" || err.Error() == "company with this registration number already exists" ||
err.Error() == "company with this tax ID already exists" { err.Error() == "company with this tax ID already exists" {
+21
View File
@@ -25,6 +25,7 @@ type Repository interface {
GetByID(ctx context.Context, id string) (*Customer, error) GetByID(ctx context.Context, id string) (*Customer, error)
GetByEmail(ctx context.Context, email string) (*Customer, error) GetByEmail(ctx context.Context, email string) (*Customer, error)
GetByUsername(ctx context.Context, username string) (*Customer, error) GetByUsername(ctx context.Context, username string) (*Customer, error)
GetByPhone(ctx context.Context, phone string) (*Customer, error)
GetByIDs(ctx context.Context, ids []string) ([]Customer, error) GetByIDs(ctx context.Context, ids []string) ([]Customer, error)
GetByRole(ctx context.Context, role CustomerRole) ([]Customer, error) GetByRole(ctx context.Context, role CustomerRole) ([]Customer, error)
GetByCompanies(ctx context.Context, companies []string) ([]Customer, error) GetByCompanies(ctx context.Context, companies []string) ([]Customer, error)
@@ -50,6 +51,8 @@ func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Re
indexes := []orm.Index{ indexes := []orm.Index{
*orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}), *orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
*orm.CreateTextIndex("search_idx", "email", "username"), *orm.CreateTextIndex("search_idx", "email", "username"),
*orm.CreateUniqueIndex("phone_idx", bson.D{{Key: "phone", Value: 1}}).
WithPartialFilterExpression(bson.M{"phone": bson.M{"$type": "string", "$ne": ""}}),
} }
// Create indexes // Create indexes
@@ -166,6 +169,24 @@ func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Cus
return customer, nil return customer, nil
} }
// GetByPhone retrieves a customer by phone number
func (r *customerRepository) GetByPhone(ctx context.Context, phone string) (*Customer, error) {
filter := bson.M{"phone": phone}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, ErrCustomerNotFound
}
r.logger.Error("Failed to get customer by phone", map[string]interface{}{
"error": err.Error(),
"phone": phone,
})
return nil, err
}
return customer, nil
}
// GetByUsername retrieves a customer by username // GetByUsername retrieves a customer by username
func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) { func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) {
filter := bson.M{"username": username} filter := bson.M{"username": username}
+13
View File
@@ -95,6 +95,13 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm
return nil, errors.New("customer with this username already exists") return nil, errors.New("customer with this username already exists")
} }
if form.Phone != nil && *form.Phone != "" {
existingCustomer, _ = s.repository.GetByPhone(ctx, *form.Phone)
if existingCustomer != nil {
return nil, errors.New("customer with this phone number already exists")
}
}
// Check validator // Check validator
if !s.validator.IsValidUsername(form.Username) { if !s.validator.IsValidUsername(form.Username) {
return nil, errors.New("invalid username format") return nil, errors.New("invalid username format")
@@ -373,6 +380,12 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
} }
if form.Phone != nil && (customer.Phone == nil || *form.Phone != *customer.Phone) { if form.Phone != nil && (customer.Phone == nil || *form.Phone != *customer.Phone) {
if *form.Phone != "" {
existingCustomer, _ := s.repository.GetByPhone(ctx, *form.Phone)
if existingCustomer != nil && existingCustomer.ID.Hex() != id {
return nil, errors.New("customer with this phone number already exists")
}
}
customer.Phone = form.Phone customer.Phone = form.Phone
infoChanged = true infoChanged = true
} }
+20
View File
@@ -22,6 +22,7 @@ type Repository interface {
UpdatePasswordReset(ctx context.Context, id, hashedPassword, updatedBy string) error UpdatePasswordReset(ctx context.Context, id, hashedPassword, updatedBy string) error
GetByEmail(ctx context.Context, email string) (*User, error) GetByEmail(ctx context.Context, email string) (*User, error)
GetByUsername(ctx context.Context, username string) (*User, error) GetByUsername(ctx context.Context, username string) (*User, error)
GetByPhone(ctx context.Context, phone string) (*User, error)
GetByIDs(ctx context.Context, userIDs []string) ([]User, error) GetByIDs(ctx context.Context, userIDs []string) ([]User, error)
Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*orm.PaginatedResult[User], error) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*orm.PaginatedResult[User], error)
} }
@@ -38,6 +39,8 @@ func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Re
// Create indexes using the ORM's index management // Create indexes using the ORM's index management
indexes := []orm.Index{ indexes := []orm.Index{
*orm.CreateTextIndex("search_idx", "full_name", "email", "username"), *orm.CreateTextIndex("search_idx", "full_name", "email", "username"),
*orm.CreateUniqueIndex("phone_idx", bson.D{{Key: "phone", Value: 1}}).
WithPartialFilterExpression(bson.M{"phone": bson.M{"$type": "string", "$ne": ""}}),
} }
// Create indexes // Create indexes
@@ -250,6 +253,23 @@ func (r *userRepository) GetByEmail(ctx context.Context, email string) (*User, e
return user, nil return user, nil
} }
// GetByPhone retrieves a user by phone number
func (r *userRepository) GetByPhone(ctx context.Context, phone string) (*User, error) {
user, err := r.ormRepo.FindOne(ctx, bson.M{"phone": phone})
if err != nil {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("user not found")
}
r.logger.Error("Failed to get user by phone", map[string]interface{}{
"error": err.Error(),
"phone": phone,
})
return nil, err
}
return user, nil
}
// GetByUsername retrieves a user by username // GetByUsername retrieves a user by username
func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) { func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) {
user, err := r.ormRepo.FindOne(ctx, bson.M{"username": username}) user, err := r.ormRepo.FindOne(ctx, bson.M{"username": username})
+19
View File
@@ -77,6 +77,13 @@ func (s *userService) Register(ctx context.Context, form *CreateUserForm) (*User
return nil, errors.New("username already exists") return nil, errors.New("username already exists")
} }
if form.Phone != nil && *form.Phone != "" {
existingUser, _ = s.repository.GetByPhone(ctx, *form.Phone)
if existingUser != nil {
return nil, errors.New("phone number already exists")
}
}
// Hash password // Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost)
if err != nil { if err != nil {
@@ -185,6 +192,12 @@ func (s *userService) Update(ctx context.Context, id string, form *UpdateUserFor
} }
if form.Phone != nil { if form.Phone != nil {
if *form.Phone != "" {
existingUser, _ := s.repository.GetByPhone(ctx, *form.Phone)
if existingUser != nil && existingUser.ID.Hex() != id {
return nil, errors.New("phone number already exists")
}
}
user.Phone = form.Phone user.Phone = form.Phone
} }
@@ -574,6 +587,12 @@ func (s *userService) UpdateProfile(ctx context.Context, userID string, form *Up
user.Position = form.Position user.Position = form.Position
} }
if form.Phone != nil { if form.Phone != nil {
if *form.Phone != "" {
existingUser, _ := s.repository.GetByPhone(ctx, *form.Phone)
if existingUser != nil && existingUser.ID.Hex() != userID {
return errors.New("phone number already exists")
}
}
user.Phone = form.Phone user.Phone = form.Phone
} }
if form.ProfileImage != nil { if form.ProfileImage != nil {