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 {