package service import ( "context" "fmt" "time" "apskel-pos-be/internal/contract" "apskel-pos-be/internal/models" "apskel-pos-be/internal/processor" ) type CampaignService interface { CreateCampaign(ctx context.Context, req *contract.CreateCampaignRequest) (*models.CampaignResponse, error) GetCampaign(ctx context.Context, id string) (*models.CampaignResponse, error) ListCampaigns(ctx context.Context, req *contract.ListCampaignsRequest) (*models.ListCampaignsResponse, error) UpdateCampaign(ctx context.Context, req *contract.UpdateCampaignRequest) (*models.CampaignResponse, error) DeleteCampaign(ctx context.Context, id string) error GetActiveCampaigns(ctx context.Context) ([]models.CampaignResponse, error) GetCampaignsForApp(ctx context.Context) ([]models.CampaignResponse, error) // Campaign Rules methods CreateCampaignRule(ctx context.Context, req *contract.CreateCampaignRuleRequest) (*models.CampaignRuleResponse, error) GetCampaignRule(ctx context.Context, id string) (*models.CampaignRuleResponse, error) ListCampaignRules(ctx context.Context, req *contract.ListCampaignRulesRequest) (*models.ListCampaignRulesResponse, error) UpdateCampaignRule(ctx context.Context, req *contract.UpdateCampaignRuleRequest) (*models.CampaignRuleResponse, error) DeleteCampaignRule(ctx context.Context, id string) error GetCampaignRulesByCampaignID(ctx context.Context, campaignID string) ([]models.CampaignRuleResponse, error) } type campaignService struct { campaignProcessor processor.CampaignProcessor campaignRuleProcessor processor.CampaignRuleProcessor } func NewCampaignService(campaignProcessor processor.CampaignProcessor, campaignRuleProcessor processor.CampaignRuleProcessor) CampaignService { return &campaignService{ campaignProcessor: campaignProcessor, campaignRuleProcessor: campaignRuleProcessor, } } func (s *campaignService) CreateCampaign(ctx context.Context, req *contract.CreateCampaignRequest) (*models.CampaignResponse, error) { // Validate campaign type if err := s.validateCampaignType(req.Type); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate date range if err := s.validateDateRange(req.StartDate, req.EndDate); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate position if req.Position < 0 { return nil, fmt.Errorf("position cannot be negative") } // Create campaign response, err := s.campaignProcessor.CreateCampaign(ctx, req) if err != nil { return nil, fmt.Errorf("failed to create campaign: %w", err) } return response, nil } func (s *campaignService) GetCampaign(ctx context.Context, id string) (*models.CampaignResponse, error) { if id == "" { return nil, fmt.Errorf("campaign ID is required") } response, err := s.campaignProcessor.GetCampaign(ctx, id) if err != nil { return nil, fmt.Errorf("failed to get campaign: %w", err) } return response, nil } func (s *campaignService) ListCampaigns(ctx context.Context, req *contract.ListCampaignsRequest) (*models.ListCampaignsResponse, error) { // Set default pagination if req.Page <= 0 { req.Page = 1 } if req.Limit <= 0 { req.Limit = 10 } if req.Limit > 100 { req.Limit = 100 } // Validate campaign type filter if provided if req.Type != "" { if err := s.validateCampaignType(req.Type); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } } response, err := s.campaignProcessor.ListCampaigns(ctx, req) if err != nil { return nil, fmt.Errorf("failed to list campaigns: %w", err) } return response, nil } func (s *campaignService) UpdateCampaign(ctx context.Context, req *contract.UpdateCampaignRequest) (*models.CampaignResponse, error) { if req.ID.String() == "" { return nil, fmt.Errorf("campaign ID is required") } // Validate campaign type if err := s.validateCampaignType(req.Type); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate date range if err := s.validateDateRange(req.StartDate, req.EndDate); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate position if req.Position < 0 { return nil, fmt.Errorf("position cannot be negative") } response, err := s.campaignProcessor.UpdateCampaign(ctx, req) if err != nil { return nil, fmt.Errorf("failed to update campaign: %w", err) } return response, nil } func (s *campaignService) DeleteCampaign(ctx context.Context, id string) error { if id == "" { return fmt.Errorf("campaign ID is required") } err := s.campaignProcessor.DeleteCampaign(ctx, id) if err != nil { return fmt.Errorf("failed to delete campaign: %w", err) } return nil } func (s *campaignService) GetActiveCampaigns(ctx context.Context) ([]models.CampaignResponse, error) { campaigns, err := s.campaignProcessor.GetActiveCampaigns(ctx) if err != nil { return nil, fmt.Errorf("failed to get active campaigns: %w", err) } return campaigns, nil } func (s *campaignService) GetCampaignsForApp(ctx context.Context) ([]models.CampaignResponse, error) { campaigns, err := s.campaignProcessor.GetCampaignsForApp(ctx) if err != nil { return nil, fmt.Errorf("failed to get campaigns for app: %w", err) } return campaigns, nil } // Campaign Rules methods func (s *campaignService) CreateCampaignRule(ctx context.Context, req *contract.CreateCampaignRuleRequest) (*models.CampaignRuleResponse, error) { // Validate rule type if err := s.validateRuleType(req.RuleType); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate reward type if err := s.validateRewardType(req.RewardType); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate reward value based on reward type if req.RewardType == "POINTS" || req.RewardType == "TOKENS" { if req.RewardValue == nil || *req.RewardValue <= 0 { return nil, fmt.Errorf("reward value must be positive for %s type", req.RewardType) } } // Validate reward reference ID for REWARD type if req.RewardType == "REWARD" { if req.RewardRefID == nil { return nil, fmt.Errorf("reward reference ID is required for REWARD type") } } response, err := s.campaignRuleProcessor.CreateCampaignRule(ctx, req) if err != nil { return nil, fmt.Errorf("failed to create campaign rule: %w", err) } return response, nil } func (s *campaignService) GetCampaignRule(ctx context.Context, id string) (*models.CampaignRuleResponse, error) { if id == "" { return nil, fmt.Errorf("campaign rule ID is required") } response, err := s.campaignRuleProcessor.GetCampaignRule(ctx, id) if err != nil { return nil, fmt.Errorf("failed to get campaign rule: %w", err) } return response, nil } func (s *campaignService) ListCampaignRules(ctx context.Context, req *contract.ListCampaignRulesRequest) (*models.ListCampaignRulesResponse, error) { // Set default pagination if req.Page <= 0 { req.Page = 1 } if req.Limit <= 0 { req.Limit = 10 } if req.Limit > 100 { req.Limit = 100 } response, err := s.campaignRuleProcessor.ListCampaignRules(ctx, req) if err != nil { return nil, fmt.Errorf("failed to list campaign rules: %w", err) } return response, nil } func (s *campaignService) UpdateCampaignRule(ctx context.Context, req *contract.UpdateCampaignRuleRequest) (*models.CampaignRuleResponse, error) { if req.ID.String() == "" { return nil, fmt.Errorf("campaign rule ID is required") } // Validate rule type if err := s.validateRuleType(req.RuleType); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate reward type if err := s.validateRewardType(req.RewardType); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Validate reward value based on reward type if req.RewardType == "POINTS" || req.RewardType == "TOKENS" { if req.RewardValue == nil || *req.RewardValue <= 0 { return nil, fmt.Errorf("reward value must be positive for %s type", req.RewardType) } } // Validate reward reference ID for REWARD type if req.RewardType == "REWARD" { if req.RewardRefID == nil { return nil, fmt.Errorf("reward reference ID is required for REWARD type") } } response, err := s.campaignRuleProcessor.UpdateCampaignRule(ctx, req) if err != nil { return nil, fmt.Errorf("failed to update campaign rule: %w", err) } return response, nil } func (s *campaignService) DeleteCampaignRule(ctx context.Context, id string) error { if id == "" { return fmt.Errorf("campaign rule ID is required") } err := s.campaignRuleProcessor.DeleteCampaignRule(ctx, id) if err != nil { return fmt.Errorf("failed to delete campaign rule: %w", err) } return nil } func (s *campaignService) GetCampaignRulesByCampaignID(ctx context.Context, campaignID string) ([]models.CampaignRuleResponse, error) { if campaignID == "" { return nil, fmt.Errorf("campaign ID is required") } rules, err := s.campaignRuleProcessor.GetCampaignRulesByCampaignID(ctx, campaignID) if err != nil { return nil, fmt.Errorf("failed to get campaign rules by campaign ID: %w", err) } return rules, nil } func (s *campaignService) validateCampaignType(campaignType string) error { validTypes := []string{"REWARD", "POINTS", "TOKENS", "MIXED"} for _, validType := range validTypes { if campaignType == validType { return nil } } return fmt.Errorf("invalid campaign type: %s. Valid types are: REWARD, POINTS, TOKENS, MIXED", campaignType) } func (s *campaignService) validateDateRange(startDate, endDate time.Time) error { if startDate.IsZero() { return fmt.Errorf("start date is required") } if endDate.IsZero() { return fmt.Errorf("end date is required") } if startDate.After(endDate) { return fmt.Errorf("start date cannot be after end date") } if startDate.Before(time.Now()) { return fmt.Errorf("start date cannot be in the past") } return nil } func (s *campaignService) validateCampaignRules(rules []contract.CampaignRuleStruct) error { if len(rules) == 0 { return fmt.Errorf("at least one rule is required") } for i, rule := range rules { if err := s.validateRuleType(rule.RuleType); err != nil { return fmt.Errorf("invalid rule type in rule %d: %w", i+1, err) } if err := s.validateRewardType(rule.RewardType); err != nil { return fmt.Errorf("invalid reward type in rule %d: %w", i+1, err) } // Validate reward value based on reward type if rule.RewardType == "POINTS" || rule.RewardType == "TOKENS" { if rule.RewardValue == nil || *rule.RewardValue <= 0 { return fmt.Errorf("reward value must be positive for %s type in rule %d", rule.RewardType, i+1) } } // Validate reward reference ID for REWARD type if rule.RewardType == "REWARD" { if rule.RewardRefID == nil { return fmt.Errorf("reward reference ID is required for REWARD type in rule %d", i+1) } } _ = i // Avoid unused variable warning } return nil } func (s *campaignService) validateRuleType(ruleType string) error { validTypes := []string{"TIER", "SPEND", "PRODUCT", "CATEGORY", "DAY", "LOCATION"} for _, validType := range validTypes { if ruleType == validType { return nil } } return fmt.Errorf("invalid rule type: %s. Valid types are: TIER, SPEND, PRODUCT, CATEGORY, DAY, LOCATION", ruleType) } func (s *campaignService) validateRewardType(rewardType string) error { validTypes := []string{"POINTS", "TOKENS", "REWARD"} for _, validType := range validTypes { if rewardType == validType { return nil } } return fmt.Errorf("invalid reward type: %s. Valid types are: POINTS, TOKENS, REWARD", rewardType) }