diff --git a/cmd/web/main.go b/cmd/web/main.go index 980f6df..1da7d91 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -146,14 +146,15 @@ func main() { "repositories": []string{"customer", "user", "company", "category", "tender", "feedback", "tender_approval", "inquiry"}, }) - // Initialize validation service + // Initialize validation services userValidator := user.NewValidationService() + customerValidator := customer.NewValidationService() // Initialize services with repositories userService := user.NewService(userRepository, logger, userAuthService, userValidator, notificationSDK) categoryService := company_category.NewService(categoryRepository, logger) companyService := company.NewService(companyRepository, categoryService, logger) - customerService := customer.NewService(customerRepository, logger, customerAuthService, companyService, redisClient, notificationSDK) + customerService := customer.NewService(customerRepository, logger, customerAuthService, companyService, redisClient, notificationSDK, customerValidator) tenderService := tender.NewService(tenderRepository, companyService, logger) feedbackService := feedback.NewService(feedbackRepo, tenderService, companyService, logger) tenderApprovalService := tender_approval.NewService(tenderApprovalRepository, tenderService, logger) @@ -166,7 +167,7 @@ func main() { // Initialize handlers with services userHandler := user.NewHandler(userService, logger, userValidator, userAuthService) - customerHandler := customer.NewHandler(customerService, userHandler, customerAuthService, logger) + customerHandler := customer.NewHandler(customerService, userHandler, customerAuthService, logger, customerValidator) companyHandler := company.NewHandler(companyService, userHandler, logger) categoryHandler := company_category.NewHandler(categoryService, logger) tenderHandler := tender.NewHandler(tenderService, logger) diff --git a/internal/customer/form.go b/internal/customer/form.go index 59e8262..086d00b 100644 --- a/internal/customer/form.go +++ b/internal/customer/form.go @@ -13,7 +13,7 @@ type CreateCustomerForm struct { Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"` Role string `json:"role" valid:"required,in(admin|analyst)" example:"analyst"` Companies []string `json:"companies,omitempty" valid:"optional"` - Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"` + Username string `json:"username" valid:"required,username,length(3|30)" example:"user"` Email string `json:"email" valid:"required,email" example:"user@opplens.com"` Password string `json:"password" valid:"required,length(8|128)" example:"User!1234"` Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"` @@ -25,7 +25,7 @@ type UpdateCustomerForm struct { Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"` Role string `json:"role" valid:"required,in(admin|analyst)" example:"analyst"` Companies []string `json:"companies,omitempty" valid:"optional"` - Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"` + Username string `json:"username" valid:"required,username,length(3|30)" example:"user"` Email string `json:"email" valid:"required,email" example:"user@opplens.com"` Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"` } @@ -132,7 +132,7 @@ type ChangePasswordForm struct { type UpdateProfileForm struct { FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"` - Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"` + Username *string `json:"username,omitempty" valid:"optional,username,length(3|30)"` Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` } diff --git a/internal/customer/handler.go b/internal/customer/handler.go index da26268..ba15681 100644 --- a/internal/customer/handler.go +++ b/internal/customer/handler.go @@ -15,15 +15,17 @@ type Handler struct { authService authorization.AuthorizationService userHandler *user.Handler logger logger.Logger + validator ValidationService } // NewHandler creates a new customer handler -func NewHandler(service Service, userHandler *user.Handler, authService authorization.AuthorizationService, logger logger.Logger) *Handler { +func NewHandler(service Service, userHandler *user.Handler, authService authorization.AuthorizationService, logger logger.Logger, validator ValidationService) *Handler { return &Handler{ service: service, authService: authService, userHandler: userHandler, logger: logger, + validator: validator, } } diff --git a/internal/customer/repository.go b/internal/customer/repository.go index 036edaf..afea8d7 100644 --- a/internal/customer/repository.go +++ b/internal/customer/repository.go @@ -43,7 +43,7 @@ func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Re // Create indexes using the ORM's index management indexes := []orm.Index{ *orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}), - *orm.CreateTextIndex("search_idx", "full_name", "email", "username", "phone"), + *orm.CreateTextIndex("search_idx", "email", "username"), } // Create indexes diff --git a/internal/customer/service.go b/internal/customer/service.go index 8607e8b..a0f6146 100644 --- a/internal/customer/service.go +++ b/internal/customer/service.go @@ -60,10 +60,11 @@ type customerService struct { companyService company.Service redisClient redis.Client notification notification.SDK + validator ValidationService } // New creates a new customer service -func NewService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client, notificationSDK notification.SDK) Service { +func NewService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client, notificationSDK notification.SDK, validator ValidationService) Service { return &customerService{ repository: repository, logger: logger, @@ -71,6 +72,7 @@ func NewService(repository Repository, logger logger.Logger, authService authori companyService: companyService, redisClient: redisClient, notification: notificationSDK, + validator: validator, } } @@ -82,6 +84,11 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm return nil, errors.New("customer with this email already exists") } + // Check validator + if !s.validator.IsValidUsername(form.Username) { + return nil, errors.New("invalid username format") + } + // Verify that all company IDs exist var Companies []string if len(form.Companies) > 0 { @@ -307,6 +314,11 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus customer.Email = form.Email } + // Check validator + if !s.validator.IsValidUsername(form.Username) { + return nil, errors.New("invalid username format") + } + // Check if username already exists (if changing username) if form.Username != "" && strings.ToLower(form.Username) != customer.Username { existingCustomer, _ := s.repository.GetByUsername(ctx, strings.ToLower(form.Username)) diff --git a/internal/customer/validation.go b/internal/customer/validation.go new file mode 100644 index 0000000..f80a283 --- /dev/null +++ b/internal/customer/validation.go @@ -0,0 +1,41 @@ +package customer + +import ( + "regexp" + + "github.com/asaskevich/govalidator" +) + +// ValidationService defines validation methods for customer operations +type ValidationService interface { + IsValidUsername(username string) bool +} + +// validationService implements the ValidationService interface +type validationService struct { + usernameRegex *regexp.Regexp +} + +// NewValidationService creates a new validation service +func NewValidationService() ValidationService { + // Compile the username regex pattern + usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9._]*[a-zA-Z0-9])?$`) + + // Register custom validator with govalidator + govalidator.CustomTypeTagMap.Set("username", govalidator.CustomTypeValidator(func(i interface{}, o interface{}) bool { + username, ok := i.(string) + if !ok { + return false + } + return usernameRegex.MatchString(username) + })) + + return &validationService{ + usernameRegex: usernameRegex, + } +} + +// IsValidUsername checks if a username matches the required pattern +func (v *validationService) IsValidUsername(username string) bool { + return v.usernameRegex.MatchString(username) +}