From 0da0c8b8c9834047bda6b9d931af3bd2dd533d0a Mon Sep 17 00:00:00 2001 From: Mazyar Date: Sat, 6 Jun 2026 12:03:04 +0330 Subject: [PATCH] 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. --- internal/customer/handler.go | 2 ++ internal/customer/repository.go | 21 +++++++++++++++++++++ internal/customer/service.go | 13 +++++++++++++ internal/user/repository.go | 20 ++++++++++++++++++++ internal/user/service.go | 19 +++++++++++++++++++ 5 files changed, 75 insertions(+) diff --git a/internal/customer/handler.go b/internal/customer/handler.go index 5431089..b7c4de2 100644 --- a/internal/customer/handler.go +++ b/internal/customer/handler.go @@ -55,6 +55,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error { if err != nil { if err.Error() == "customer with this email 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 registration number 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" || 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 registration number already exists" || err.Error() == "company with this tax ID already exists" { diff --git a/internal/customer/repository.go b/internal/customer/repository.go index ed52842..cc557d4 100644 --- a/internal/customer/repository.go +++ b/internal/customer/repository.go @@ -25,6 +25,7 @@ type Repository interface { GetByID(ctx context.Context, id string) (*Customer, error) GetByEmail(ctx context.Context, email 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) GetByRole(ctx context.Context, role CustomerRole) ([]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{ *orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}), *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 @@ -166,6 +169,24 @@ func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Cus 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 func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) { filter := bson.M{"username": username} diff --git a/internal/customer/service.go b/internal/customer/service.go index a75b4fc..3a88166 100644 --- a/internal/customer/service.go +++ b/internal/customer/service.go @@ -95,6 +95,13 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm 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 if !s.validator.IsValidUsername(form.Username) { 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 != "" { + 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 infoChanged = true } diff --git a/internal/user/repository.go b/internal/user/repository.go index 09796d3..e758a4b 100644 --- a/internal/user/repository.go +++ b/internal/user/repository.go @@ -22,6 +22,7 @@ type Repository interface { UpdatePasswordReset(ctx context.Context, id, hashedPassword, updatedBy string) error GetByEmail(ctx context.Context, email 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) 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 indexes := []orm.Index{ *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 @@ -250,6 +253,23 @@ func (r *userRepository) GetByEmail(ctx context.Context, email string) (*User, e 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 func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) { user, err := r.ormRepo.FindOne(ctx, bson.M{"username": username}) diff --git a/internal/user/service.go b/internal/user/service.go index fbfac0b..8bc6c4d 100644 --- a/internal/user/service.go +++ b/internal/user/service.go @@ -77,6 +77,13 @@ func (s *userService) Register(ctx context.Context, form *CreateUserForm) (*User 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 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost) 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 != "" { + 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 } @@ -574,6 +587,12 @@ func (s *userService) UpdateProfile(ctx context.Context, userID string, form *Up user.Position = form.Position } 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 } if form.ProfileImage != nil {