apskel-pos-backend/internal/validator/campaign_validator.go
Aditya Siregar 155016dec8 Add Campaign
2025-09-17 23:55:11 +07:00

337 lines
10 KiB
Go

package validator
import (
"errors"
"strings"
"time"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
)
type CampaignValidator interface {
ValidateCreateCampaignRequest(req *contract.CreateCampaignRequest) (error, string)
ValidateUpdateCampaignRequest(req *contract.UpdateCampaignRequest) (error, string)
ValidateListCampaignsRequest(req *contract.ListCampaignsRequest) (error, string)
ValidateGetCampaignRequest(req *contract.GetCampaignRequest) (error, string)
ValidateDeleteCampaignRequest(req *contract.DeleteCampaignRequest) (error, string)
ValidateCreateCampaignRuleRequest(req *contract.CreateCampaignRuleRequest) (error, string)
ValidateUpdateCampaignRuleRequest(req *contract.UpdateCampaignRuleRequest) (error, string)
ValidateListCampaignRulesRequest(req *contract.ListCampaignRulesRequest) (error, string)
ValidateGetCampaignRuleRequest(req *contract.GetCampaignRuleRequest) (error, string)
ValidateDeleteCampaignRuleRequest(req *contract.DeleteCampaignRuleRequest) (error, string)
}
type CampaignValidatorImpl struct{}
func NewCampaignValidator() CampaignValidator {
return &CampaignValidatorImpl{}
}
func (v *CampaignValidatorImpl) ValidateCreateCampaignRequest(req *contract.CreateCampaignRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
// Validate name
if strings.TrimSpace(req.Name) == "" {
return errors.New("campaign name is required"), constants.ValidationErrorCode
}
if len(req.Name) > 150 {
return errors.New("campaign name cannot exceed 150 characters"), constants.ValidationErrorCode
}
// Validate campaign type
if !v.isValidCampaignType(req.Type) {
return errors.New("invalid campaign type. Valid types are: REWARD, POINTS, TOKENS, MIXED"), constants.ValidationErrorCode
}
// Validate date range
if err := v.validateDateRange(req.StartDate, req.EndDate); err != nil {
return err, constants.ValidationErrorCode
}
// Validate position
if req.Position < 0 {
return errors.New("position cannot be negative"), constants.ValidationErrorCode
}
// Validate rules
if len(req.Rules) == 0 {
return errors.New("at least one rule is required"), constants.ValidationErrorCode
}
for i, rule := range req.Rules {
if err := v.validateCampaignRule(&rule, i+1); err != nil {
return err, constants.ValidationErrorCode
}
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateUpdateCampaignRequest(req *contract.UpdateCampaignRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
// Validate ID
if req.ID.String() == "" {
return errors.New("campaign ID is required"), constants.ValidationErrorCode
}
// Validate name
if strings.TrimSpace(req.Name) == "" {
return errors.New("campaign name is required"), constants.ValidationErrorCode
}
if len(req.Name) > 150 {
return errors.New("campaign name cannot exceed 150 characters"), constants.ValidationErrorCode
}
// Validate campaign type
if !v.isValidCampaignType(req.Type) {
return errors.New("invalid campaign type. Valid types are: REWARD, POINTS, TOKENS, MIXED"), constants.ValidationErrorCode
}
// Validate date range
if err := v.validateDateRange(req.StartDate, req.EndDate); err != nil {
return err, constants.ValidationErrorCode
}
// Validate position
if req.Position < 0 {
return errors.New("position cannot be negative"), constants.ValidationErrorCode
}
// Validate rules
if len(req.Rules) == 0 {
return errors.New("at least one rule is required"), constants.ValidationErrorCode
}
for i, rule := range req.Rules {
if err := v.validateCampaignRule(&rule, i+1); err != nil {
return err, constants.ValidationErrorCode
}
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateListCampaignsRequest(req *contract.ListCampaignsRequest) (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 campaign type filter if provided
if req.Type != "" && !v.isValidCampaignType(req.Type) {
return errors.New("invalid campaign type filter. Valid types are: REWARD, POINTS, TOKENS, MIXED"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateGetCampaignRequest(req *contract.GetCampaignRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
if req.ID.String() == "" {
return errors.New("campaign ID is required"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateDeleteCampaignRequest(req *contract.DeleteCampaignRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
if req.ID.String() == "" {
return errors.New("campaign ID is required"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateCreateCampaignRuleRequest(req *contract.CreateCampaignRuleRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
// Validate campaign ID
if req.CampaignID.String() == "" {
return errors.New("campaign ID is required"), constants.ValidationErrorCode
}
// Validate rule
if err := v.validateCampaignRule(&contract.CampaignRuleStruct{
RuleType: req.RuleType,
ConditionValue: req.ConditionValue,
RewardType: req.RewardType,
RewardValue: req.RewardValue,
RewardSubtype: req.RewardSubtype,
RewardRefID: req.RewardRefID,
Metadata: req.Metadata,
}, 1); err != nil {
return err, constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateUpdateCampaignRuleRequest(req *contract.UpdateCampaignRuleRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
// Validate ID
if req.ID.String() == "" {
return errors.New("campaign rule ID is required"), constants.ValidationErrorCode
}
// Validate campaign ID
if req.CampaignID.String() == "" {
return errors.New("campaign ID is required"), constants.ValidationErrorCode
}
// Validate rule
if err := v.validateCampaignRule(&contract.CampaignRuleStruct{
RuleType: req.RuleType,
ConditionValue: req.ConditionValue,
RewardType: req.RewardType,
RewardValue: req.RewardValue,
RewardSubtype: req.RewardSubtype,
RewardRefID: req.RewardRefID,
Metadata: req.Metadata,
}, 1); err != nil {
return err, constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateListCampaignRulesRequest(req *contract.ListCampaignRulesRequest) (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 rule type filter if provided
if req.RuleType != "" && !v.isValidRuleType(req.RuleType) {
return errors.New("invalid rule type filter. Valid types are: TIER, SPEND, PRODUCT, CATEGORY, DAY, LOCATION"), 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: POINTS, TOKENS, REWARD"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateGetCampaignRuleRequest(req *contract.GetCampaignRuleRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
if req.ID.String() == "" {
return errors.New("campaign rule ID is required"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) ValidateDeleteCampaignRuleRequest(req *contract.DeleteCampaignRuleRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.ValidationErrorCode
}
if req.ID.String() == "" {
return errors.New("campaign rule ID is required"), constants.ValidationErrorCode
}
return nil, ""
}
func (v *CampaignValidatorImpl) isValidCampaignType(campaignType string) bool {
validTypes := []string{"REWARD", "POINTS", "TOKENS", "MIXED"}
return contains(validTypes, campaignType)
}
func (v *CampaignValidatorImpl) isValidRuleType(ruleType string) bool {
validTypes := []string{"TIER", "SPEND", "PRODUCT", "CATEGORY", "DAY", "LOCATION"}
return contains(validTypes, ruleType)
}
func (v *CampaignValidatorImpl) isValidRewardType(rewardType string) bool {
validTypes := []string{"POINTS", "TOKENS", "REWARD"}
return contains(validTypes, rewardType)
}
func (v *CampaignValidatorImpl) validateDateRange(startDate, endDate time.Time) error {
if startDate.IsZero() {
return errors.New("start date is required")
}
if endDate.IsZero() {
return errors.New("end date is required")
}
if startDate.After(endDate) {
return errors.New("start date cannot be after end date")
}
return nil
}
func (v *CampaignValidatorImpl) validateCampaignRule(rule *contract.CampaignRuleStruct, ruleNumber int) error {
if rule == nil {
return errors.New("rule is required")
}
// Validate rule type
if !v.isValidRuleType(rule.RuleType) {
return errors.New("invalid rule type in rule " + string(rune(ruleNumber)) + ". Valid types are: TIER, SPEND, PRODUCT, CATEGORY, DAY, LOCATION")
}
// Validate reward type
if !v.isValidRewardType(rule.RewardType) {
return errors.New("invalid reward type in rule " + string(rune(ruleNumber)) + ". Valid types are: POINTS, TOKENS, REWARD")
}
// Validate reward value based on reward type
if rule.RewardType == "POINTS" || rule.RewardType == "TOKENS" {
if rule.RewardValue == nil || *rule.RewardValue <= 0 {
return errors.New("reward value must be positive for " + rule.RewardType + " type in rule " + string(rune(ruleNumber)))
}
}
// Validate reward reference ID for REWARD type
if rule.RewardType == "REWARD" {
if rule.RewardRefID == nil {
return errors.New("reward reference ID is required for REWARD type in rule " + string(rune(ruleNumber)))
}
}
return nil
}