apskel-pos-backend/internal/validator/customer_auth_validator.go
2025-09-18 01:32:01 +07:00

237 lines
7.2 KiB
Go

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
}