Files
sale/backend/internal/api/handlers/lottery.go
T

292 lines
7.7 KiB
Go

package handlers
import (
"crypto/rand"
"math/big"
"net/http"
"strconv"
"time"
"sale/internal/models"
"sale/internal/schemas"
"sale/internal/utils"
"github.com/gin-gonic/gin"
)
type LotteryHandler struct{}
func NewLotteryHandler() *LotteryHandler {
return &LotteryHandler{}
}
func (h *LotteryHandler) List(c *gin.Context) {
var lotteries []models.Lottery
utils.DB.Where("is_active = ?", true).Preload("Prizes").Order("created_at DESC").Find(&lotteries)
c.JSON(http.StatusOK, gin.H{"data": lotteries})
}
func (h *LotteryHandler) GetByID(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var lottery models.Lottery
if err := utils.DB.Preload("Prizes").First(&lottery, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": lottery})
}
func (h *LotteryHandler) Register(c *gin.Context) {
userID := c.GetUint("user_id")
id, _ := strconv.Atoi(c.Param("id"))
var lottery models.Lottery
if err := utils.DB.First(&lottery, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
return
}
now := time.Now()
if now.Before(lottery.StartTime) || now.After(lottery.EndTime) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery is not in registration period"})
return
}
if !lottery.IsActive {
c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery is not active"})
return
}
if lottery.TotalQuota != nil && *lottery.TotalQuota > 0 {
var participantCount int64
utils.DB.Model(&models.LotteryParticipant{}).Where("lottery_id = ?", id).Count(&participantCount)
if int(participantCount) >= *lottery.TotalQuota {
c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery quota is full"})
return
}
}
if lottery.DailyQuota != nil && *lottery.DailyQuota > 0 {
today := time.Now().Truncate(24 * time.Hour)
tomorrow := today.Add(24 * time.Hour)
var dailyCount int64
utils.DB.Model(&models.LotteryParticipant{}).Where("lottery_id = ? AND registered_at >= ? AND registered_at < ?", id, today, tomorrow).Count(&dailyCount)
if int(dailyCount) >= *lottery.DailyQuota {
c.JSON(http.StatusBadRequest, gin.H{"error": "Daily quota is full"})
return
}
}
var existing models.LotteryParticipant
if err := utils.DB.Where("lottery_id = ? AND user_id = ?", id, userID).First(&existing).Error; err == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Already registered"})
return
}
var totalSpent float64
utils.DB.Model(&models.Order{}).
Where("user_id = ? AND status = ?", userID, models.OrderStatusCompleted).
Select("COALESCE(SUM(total_amount), 0)").
Scan(&totalSpent)
participant := models.LotteryParticipant{
LotteryID: lottery.ID,
UserID: userID,
PurchaseWeight: int(totalSpent),
}
utils.DB.Create(&participant)
c.JSON(http.StatusOK, gin.H{"message": "Registered successfully"})
}
func (h *LotteryHandler) Create(c *gin.Context) {
var req schemas.CreateLotteryRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
startTime, _ := time.Parse(time.RFC3339, req.StartTime)
endTime, _ := time.Parse(time.RFC3339, req.EndTime)
lottery := models.Lottery{
Name: req.Name,
Description: req.Description,
StartTime: startTime,
EndTime: endTime,
Cycle: req.Cycle,
DailyQuota: req.DailyQuota,
TotalQuota: req.TotalQuota,
RegistrationValidity: req.RegistrationValidity,
IsActive: true,
}
utils.DB.Create(&lottery)
c.JSON(http.StatusCreated, gin.H{"data": lottery})
}
func (h *LotteryHandler) Update(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var lottery models.Lottery
if err := utils.DB.First(&lottery, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
return
}
var req schemas.UpdateLotteryRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
updates := make(map[string]interface{})
if req.Name != nil {
updates["name"] = *req.Name
}
if req.Description != nil {
updates["description"] = *req.Description
}
if req.StartTime != nil {
t, _ := time.Parse(time.RFC3339, *req.StartTime)
updates["start_time"] = t
}
if req.EndTime != nil {
t, _ := time.Parse(time.RFC3339, *req.EndTime)
updates["end_time"] = t
}
if req.Cycle != nil {
updates["cycle"] = *req.Cycle
}
if req.DailyQuota != nil {
updates["daily_quota"] = *req.DailyQuota
}
if req.TotalQuota != nil {
updates["total_quota"] = *req.TotalQuota
}
if req.RegistrationValidity != nil {
updates["registration_validity"] = *req.RegistrationValidity
}
if req.IsActive != nil {
updates["is_active"] = *req.IsActive
}
utils.DB.Model(&lottery).Updates(updates)
c.JSON(http.StatusOK, gin.H{"data": lottery})
}
func (h *LotteryHandler) Delete(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
utils.DB.Delete(&models.Lottery{}, id)
c.JSON(http.StatusOK, gin.H{"message": "Lottery deleted successfully"})
}
func (h *LotteryHandler) AddPrize(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req schemas.AddLotteryPrizeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
prize := models.LotteryPrize{
LotteryID: uint(id),
Name: req.Name,
Type: req.Type,
Quantity: req.Quantity,
Weight: req.Weight,
CreditReward: req.CreditReward,
DrawMode: req.DrawMode,
}
utils.DB.Create(&prize)
c.JSON(http.StatusCreated, gin.H{"data": prize})
}
func (h *LotteryHandler) Draw(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var lottery models.Lottery
if err := utils.DB.Preload("Prizes").First(&lottery, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
return
}
var participants []models.LotteryParticipant
utils.DB.Where("lottery_id = ?", id).Find(&participants)
if len(participants) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "No participants"})
return
}
var winners []models.LotteryWinner
for _, prize := range lottery.Prizes {
remaining := prize.Quantity
var weightParticipants []models.LotteryParticipant
var randomParticipants []models.LotteryParticipant
if prize.DrawMode == models.DrawModeWeight {
weightParticipants = participants
} else {
randomParticipants = participants
}
selectedParticipants := weightParticipants
if len(selectedParticipants) == 0 {
selectedParticipants = randomParticipants
}
totalWeight := 0
for _, p := range selectedParticipants {
w := p.PurchaseWeight
if w < 1 {
w = 1
}
totalWeight += w
}
selected := make(map[uint]bool)
for remaining > 0 && len(selected) < len(selectedParticipants) {
r, err := rand.Int(rand.Reader, big.NewInt(int64(totalWeight)))
if err != nil {
break
}
cumWeight := 0
for _, p := range selectedParticipants {
if selected[p.UserID] {
continue
}
w := p.PurchaseWeight
if w < 1 {
w = 1
}
cumWeight += w
if cumWeight > int(r.Int64()) {
winner := models.LotteryWinner{
LotteryID: lottery.ID,
PrizeID: prize.ID,
UserID: p.UserID,
DrawnAt: time.Now(),
}
winners = append(winners, winner)
selected[p.UserID] = true
remaining--
if prize.Type == models.PrizeTypeCredit && prize.CreditReward != nil {
utils.DB.Model(&models.User{}).Where("id = ?", p.UserID).
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", *prize.CreditReward))
}
break
}
}
}
}
if len(winners) > 0 {
utils.DB.Create(&winners)
}
c.JSON(http.StatusOK, gin.H{"data": winners, "message": "Draw completed successfully"})
}