package service import ( "context" "errors" "fmt" "sno/internal/models" "sno/internal/repository" "github.com/jackc/pgx/v5/pgxpool" ) // RewardService defines the interface for reward-related business logic. type RewardService interface { CreateReward(ctx context.Context, reward *models.Reward) (*models.Reward, error) ListActiveRewards(ctx context.Context) ([]models.Reward, error) PurchaseReward(ctx context.Context, userID int64, rewardID int) (*models.Purchase, error) UpdateReward(ctx context.Context, id int, reward *models.Reward) (*models.Reward, error) DeleteReward(ctx context.Context, id int) error } // rewardService implements the RewardService interface. type rewardService struct { db *pgxpool.Pool rewardRepo repository.RewardRepository userRepo repository.UserRepository purchaseRepo repository.PurchaseRepository } // NewRewardService creates a new instance of a reward service. func NewRewardService(db *pgxpool.Pool, rewardRepo repository.RewardRepository, userRepo repository.UserRepository, purchaseRepo repository.PurchaseRepository) RewardService { return &rewardService{ db: db, rewardRepo: rewardRepo, userRepo: userRepo, purchaseRepo: purchaseRepo, } } // CreateReward handles the business logic for creating a new reward. func (s *rewardService) CreateReward(ctx context.Context, reward *models.Reward) (*models.Reward, error) { if reward.Title == "" { return nil, errors.New("reward title cannot be empty") } if reward.PriceStars <= 0 { return nil, errors.New("reward price must be positive") } return s.rewardRepo.Create(ctx, reward) } // ListActiveRewards retrieves a list of all active rewards. func (s *rewardService) ListActiveRewards(ctx context.Context) ([]models.Reward, error) { return s.rewardRepo.GetAllActive(ctx) } // PurchaseReward handles the logic for a user purchasing a reward. func (s *rewardService) PurchaseReward(ctx context.Context, userID int64, rewardID int) (*models.Purchase, error) { // 1. Get reward and user details reward, err := s.rewardRepo.GetByID(ctx, rewardID) if err != nil { return nil, fmt.Errorf("reward not found: %w", err) } user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, fmt.Errorf("user not found: %w", err) } // 2. Validate purchase if !reward.IsActive { return nil, errors.New("reward is not active") } if reward.Stock == 0 { // -1 is infinite return nil, errors.New("reward is out of stock") } if user.StarsBalance < reward.PriceStars { return nil, errors.New("not enough stars") } // 3. Start transaction tx, err := s.db.Begin(ctx) if err != nil { return nil, fmt.Errorf("failed to begin transaction: %w", err) } defer tx.Rollback(ctx) txCtx := context.WithValue(ctx, "tx", tx) // 4. Perform operations // a. Create purchase record purchase := &models.Purchase{ UserID: userID, RewardID: rewardID, StarsSpent: reward.PriceStars, Status: models.Pending, } createdPurchase, err := s.purchaseRepo.Create(txCtx, purchase) if err != nil { return nil, fmt.Errorf("failed to create purchase record: %w", err) } // b. Decrement user stars if err := s.userRepo.UpdateStarsBalance(txCtx, userID, -reward.PriceStars); err != nil { return nil, fmt.Errorf("failed to update user balance: %w", err) } // c. Decrement stock (if not infinite) if reward.Stock != -1 { if err := s.rewardRepo.UpdateStock(txCtx, rewardID, 1); err != nil { return nil, fmt.Errorf("failed to update reward stock: %w", err) } } // 5. Commit transaction if err := tx.Commit(ctx); err != nil { return nil, fmt.Errorf("failed to commit transaction: %w", err) } return createdPurchase, nil } // UpdateReward updates an existing reward func (s *rewardService) UpdateReward(ctx context.Context, id int, reward *models.Reward) (*models.Reward, error) { // Check if reward exists existingReward, err := s.rewardRepo.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("failed to get existing reward: %w", err) } if existingReward == nil { return nil, errors.New("reward not found") } // Update reward updatedReward, err := s.rewardRepo.Update(ctx, id, reward) if err != nil { return nil, fmt.Errorf("failed to update reward: %w", err) } return updatedReward, nil } // DeleteReward deletes a reward func (s *rewardService) DeleteReward(ctx context.Context, id int) error { // Check if reward exists existingReward, err := s.rewardRepo.GetByID(ctx, id) if err != nil { return fmt.Errorf("failed to get existing reward: %w", err) } if existingReward == nil { return errors.New("reward not found") } // Delete reward err = s.rewardRepo.Delete(ctx, id) if err != nil { return fmt.Errorf("failed to delete reward: %w", err) } return nil }