160 lines
4.7 KiB
Go
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
|
|
}
|