sno-quiz/backend/internal/service/reward_service.go
2025-09-17 22:22:14 +03:00

160 lines
4.7 KiB
Go

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
}