package validator import ( "errors" "regexp" "strings" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" ) type CustomerAuthValidator interface { ValidateCheckPhoneRequest(req *contract.CheckPhoneRequest) (error, string) ValidateRegisterStartRequest(req *contract.RegisterStartRequest) (error, string) ValidateRegisterVerifyOtpRequest(req *contract.RegisterVerifyOtpRequest) (error, string) ValidateRegisterSetPasswordRequest(req *contract.RegisterSetPasswordRequest) (error, string) ValidateCustomerLoginRequest(req *contract.CustomerLoginRequest) (error, string) ValidateResendOtpRequest(req *contract.ResendOtpRequest) (error, string) } type CustomerAuthValidatorImpl struct{} func NewCustomerAuthValidator() CustomerAuthValidator { return &CustomerAuthValidatorImpl{} } func (v *CustomerAuthValidatorImpl) ValidateCheckPhoneRequest(req *contract.CheckPhoneRequest) (error, string) { if req == nil { return errors.New("request is required"), constants.ValidationErrorCode } // Validate phone number if strings.TrimSpace(req.PhoneNumber) == "" { return errors.New("phone number is required"), constants.ValidationErrorCode } if !v.isValidPhoneNumber(req.PhoneNumber) { return errors.New("invalid phone number format"), constants.ValidationErrorCode } return nil, "" } func (v *CustomerAuthValidatorImpl) ValidateRegisterStartRequest(req *contract.RegisterStartRequest) (error, string) { if req == nil { return errors.New("request is required"), constants.ValidationErrorCode } // Validate phone number if strings.TrimSpace(req.PhoneNumber) == "" { return errors.New("phone number is required"), constants.ValidationErrorCode } if !v.isValidPhoneNumber(req.PhoneNumber) { return errors.New("invalid phone number format"), constants.ValidationErrorCode } // Validate name if strings.TrimSpace(req.Name) == "" { return errors.New("name is required"), constants.ValidationErrorCode } if len(req.Name) < 2 { return errors.New("name must be at least 2 characters long"), constants.ValidationErrorCode } if len(req.Name) > 100 { return errors.New("name cannot exceed 100 characters"), constants.ValidationErrorCode } // Validate birth date if strings.TrimSpace(req.BirthDate) == "" { return errors.New("birth date is required"), constants.ValidationErrorCode } if !v.isValidDateFormat(req.BirthDate) { return errors.New("invalid birth date format (YYYY-MM-DD)"), constants.ValidationErrorCode } return nil, "" } func (v *CustomerAuthValidatorImpl) ValidateRegisterVerifyOtpRequest(req *contract.RegisterVerifyOtpRequest) (error, string) { if req == nil { return errors.New("request is required"), constants.ValidationErrorCode } // Validate registration token if strings.TrimSpace(req.RegistrationToken) == "" { return errors.New("registration token is required"), constants.ValidationErrorCode } // Validate OTP code if strings.TrimSpace(req.OtpCode) == "" { return errors.New("OTP code is required"), constants.ValidationErrorCode } if !v.isValidOtpCode(req.OtpCode) { return errors.New("invalid OTP code format"), constants.ValidationErrorCode } return nil, "" } func (v *CustomerAuthValidatorImpl) ValidateRegisterSetPasswordRequest(req *contract.RegisterSetPasswordRequest) (error, string) { if req == nil { return errors.New("request is required"), constants.ValidationErrorCode } // Validate registration token if strings.TrimSpace(req.RegistrationToken) == "" { return errors.New("registration token is required"), constants.ValidationErrorCode } // Validate password if strings.TrimSpace(req.Password) == "" { return errors.New("password is required"), constants.ValidationErrorCode } if len(req.Password) < 8 { return errors.New("password must be at least 8 characters long"), constants.ValidationErrorCode } if len(req.Password) > 128 { return errors.New("password cannot exceed 128 characters"), constants.ValidationErrorCode } // Validate confirm password if strings.TrimSpace(req.ConfirmPassword) == "" { return errors.New("confirm password is required"), constants.ValidationErrorCode } if req.Password != req.ConfirmPassword { return errors.New("passwords do not match"), constants.ValidationErrorCode } // Validate password strength if !v.isStrongPassword(req.Password) { return errors.New("password must contain at least one uppercase letter, one lowercase letter, and one number"), constants.ValidationErrorCode } return nil, "" } func (v *CustomerAuthValidatorImpl) ValidateCustomerLoginRequest(req *contract.CustomerLoginRequest) (error, string) { if req == nil { return errors.New("request is required"), constants.ValidationErrorCode } // Validate phone number if strings.TrimSpace(req.PhoneNumber) == "" { return errors.New("phone number is required"), constants.ValidationErrorCode } if !v.isValidPhoneNumber(req.PhoneNumber) { return errors.New("invalid phone number format"), constants.ValidationErrorCode } // Validate password if strings.TrimSpace(req.Password) == "" { return errors.New("password is required"), constants.ValidationErrorCode } return nil, "" } // Helper validation functions func (v *CustomerAuthValidatorImpl) isValidPhoneNumber(phoneNumber string) bool { // Basic phone number validation - adjust regex based on your requirements phoneRegex := regexp.MustCompile(`^\+?[1-9]\d{1,14}$`) return phoneRegex.MatchString(phoneNumber) } func (v *CustomerAuthValidatorImpl) isValidDateFormat(date string) bool { // Basic date format validation for YYYY-MM-DD dateRegex := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) if !dateRegex.MatchString(date) { return false } // You can add more sophisticated date validation here if needed return true } func (v *CustomerAuthValidatorImpl) isValidOtpCode(code string) bool { // OTP code should be 4-8 digits otpRegex := regexp.MustCompile(`^\d{4,8}$`) return otpRegex.MatchString(code) } func (v *CustomerAuthValidatorImpl) ValidateResendOtpRequest(req *contract.ResendOtpRequest) (error, string) { if req == nil { return errors.New("request is required"), constants.CustomerEntity } // Validate phone number if req.PhoneNumber == "" { return errors.New("phone number is required"), constants.CustomerEntity } // Validate phone number format if !v.isValidPhoneNumber(req.PhoneNumber) { return errors.New("invalid phone number format"), constants.CustomerEntity } // Validate purpose if req.Purpose == "" { return errors.New("purpose is required"), constants.CustomerEntity } // Validate purpose values validPurposes := []string{"login", "registration"} if !v.contains(validPurposes, req.Purpose) { return errors.New("purpose must be either 'login' or 'registration'"), constants.CustomerEntity } return nil, "" } func (v *CustomerAuthValidatorImpl) isStrongPassword(password string) bool { // Password must contain at least one uppercase, one lowercase, and one number hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password) hasLower := regexp.MustCompile(`[a-z]`).MatchString(password) hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password) return hasUpper && hasLower && hasNumber } func (v *CustomerAuthValidatorImpl) contains(slice []string, item string) bool { for _, s := range slice { if s == item { return true } } return false }