Aditya Siregar 155016dec8 Add Campaign
2025-09-17 23:55:11 +07:00

264 lines
7.4 KiB
Go

package validator
import (
"errors"
"regexp"
"strings"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
)
type RewardValidator interface {
ValidateCreateRewardRequest(req *contract.CreateRewardRequest) (error, string)
ValidateUpdateRewardRequest(req *contract.UpdateRewardRequest) (error, string)
ValidateListRewardsRequest(req *contract.ListRewardsRequest) (error, string)
ValidateGetRewardRequest(req *contract.GetRewardRequest) (error, string)
ValidateDeleteRewardRequest(req *contract.DeleteRewardRequest) (error, string)
}
type RewardValidatorImpl struct{}
func NewRewardValidator() RewardValidator {
return &RewardValidatorImpl{}
}
func (v *RewardValidatorImpl) ValidateCreateRewardRequest(req *contract.CreateRewardRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
// Validate name
if strings.TrimSpace(req.Name) == "" {
return errors.New("reward name is required"), constants.ValidationErrorCode
}
if len(req.Name) > 150 {
return errors.New("reward name cannot exceed 150 characters"), constants.ValidationErrorCode
}
// Validate reward type
if !v.isValidRewardType(req.RewardType) {
return errors.New("invalid reward type. Valid types are: VOUCHER, PHYSICAL, DIGITAL, BALANCE"), constants.ValidationErrorCode
}
// Validate cost points
if req.CostPoints <= 0 {
return errors.New("cost points must be greater than 0"), constants.ValidationErrorCode
}
// Validate stock
if req.Stock != nil && *req.Stock < 0 {
return errors.New("stock cannot be negative"), constants.ValidationErrorCode
}
// Validate max per customer
if req.MaxPerCustomer <= 0 {
return errors.New("max per customer must be greater than 0"), constants.ValidationErrorCode
}
// Validate TNC if provided
if req.Tnc != nil {
if err := v.validateTermsAndConditions(req.Tnc); err != nil {
return err, constants.ValidationErrorCode
}
}
// Validate images if provided
if req.Images != nil {
if err := v.validateImages(*req.Images); err != nil {
return err, constants.ValidationErrorCode
}
}
return nil, ""
}
func (v *RewardValidatorImpl) ValidateUpdateRewardRequest(req *contract.UpdateRewardRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
// Validate ID
if req.ID.String() == "" {
return errors.New("reward ID is required"), constants.ValidationErrorCode
}
// Validate name
if strings.TrimSpace(req.Name) == "" {
return errors.New("reward name is required"), constants.ValidationErrorCode
}
if len(req.Name) > 150 {
return errors.New("reward name cannot exceed 150 characters"), constants.ValidationErrorCode
}
// Validate reward type
if !v.isValidRewardType(req.RewardType) {
return errors.New("invalid reward type. Valid types are: VOUCHER, PHYSICAL, DIGITAL, BALANCE"), constants.ValidationErrorCode
}
// Validate cost points
if req.CostPoints <= 0 {
return errors.New("cost points must be greater than 0"), constants.ValidationErrorCode
}
// Validate stock
if req.Stock != nil && *req.Stock < 0 {
return errors.New("stock cannot be negative"), constants.ValidationErrorCode
}
// Validate max per customer
if req.MaxPerCustomer <= 0 {
return errors.New("max per customer must be greater than 0"), constants.ValidationErrorCode
}
// Validate TNC if provided
if req.Tnc != nil {
if err := v.validateTermsAndConditions(req.Tnc); err != nil {
return err, constants.ValidationErrorCode
}
}
// Validate images if provided
if req.Images != nil {
if err := v.validateImages(*req.Images); err != nil {
return err, constants.ValidationErrorCode
}
}
return nil, ""
}
func (v *RewardValidatorImpl) ValidateListRewardsRequest(req *contract.ListRewardsRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
// Validate pagination
if req.Page < 1 {
return errors.New("page must be greater than 0"), constants.ValidationErrorCode
}
if req.Limit < 1 {
return errors.New("limit must be greater than 0"), constants.ValidationErrorCode
}
if req.Limit > 100 {
return errors.New("limit cannot exceed 100"), constants.ValidationErrorCode
}
// Validate reward type filter if provided
if req.RewardType != "" && !v.isValidRewardType(req.RewardType) {
return errors.New("invalid reward type filter. Valid types are: VOUCHER, PHYSICAL, DIGITAL, BALANCE"), constants.ValidationErrorCode
}
// Validate points range if provided
if req.MinPoints != nil && req.MaxPoints != nil {
if *req.MinPoints > *req.MaxPoints {
return errors.New("min points cannot be greater than max points"), constants.ValidationErrorCode
}
if *req.MinPoints < 0 {
return errors.New("min points cannot be negative"), constants.ValidationErrorCode
}
if *req.MaxPoints < 0 {
return errors.New("max points cannot be negative"), constants.ValidationErrorCode
}
}
return nil, ""
}
func (v *RewardValidatorImpl) ValidateGetRewardRequest(req *contract.GetRewardRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
if req.ID.String() == "" {
return errors.New("reward ID is required"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *RewardValidatorImpl) ValidateDeleteRewardRequest(req *contract.DeleteRewardRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
if req.ID.String() == "" {
return errors.New("reward ID is required"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *RewardValidatorImpl) isValidRewardType(rewardType string) bool {
validTypes := []string{"VOUCHER", "PHYSICAL", "DIGITAL", "BALANCE"}
return contains(validTypes, rewardType)
}
func (v *RewardValidatorImpl) validateTermsAndConditions(tnc *contract.TermsAndConditionsStruct) error {
if tnc == nil {
return nil
}
// Validate expiry days
if tnc.ExpiryDays < 0 {
return errors.New("expiry days cannot be negative")
}
// Validate sections
if len(tnc.Sections) == 0 {
return errors.New("terms and conditions must have at least one section")
}
for i, section := range tnc.Sections {
if strings.TrimSpace(section.Title) == "" {
return errors.New("section title is required")
}
if len(section.Rules) == 0 {
return errors.New("section must have at least one rule")
}
for j, rule := range section.Rules {
if strings.TrimSpace(rule) == "" {
return errors.New("rule cannot be empty")
}
if len(rule) > 500 {
return errors.New("rule cannot exceed 500 characters")
}
// Validate rule content (basic validation)
if len(rule) < 10 {
return errors.New("rule must be at least 10 characters long")
}
_ = j // Avoid unused variable warning
}
_ = i // Avoid unused variable warning
}
return nil
}
func (v *RewardValidatorImpl) validateImages(images []string) error {
if len(images) == 0 {
return errors.New("at least one image is required")
}
for i, imgURL := range images {
if strings.TrimSpace(imgURL) == "" {
return errors.New("image URL is required")
}
// Validate URL format (basic validation)
if !v.isValidURL(imgURL) {
return errors.New("invalid image URL format")
}
_ = i // Avoid unused variable warning
}
return nil
}
func (v *RewardValidatorImpl) isValidURL(url string) bool {
// Basic URL validation regex
urlRegex := regexp.MustCompile(`^https?://[^\s/$.?#].[^\s]*$`)
return urlRegex.MatchString(url)
}