376 lines
11 KiB
Go
376 lines
11 KiB
Go
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)
|
|
}
|