♻️ Move the lotteries logic to the wallet service
This commit is contained in:
@@ -1,80 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Pass.Permission;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Lotteries;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/lotteries")]
|
||||
public class LotteryController(AppDatabase db, LotteryService lotteryService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<SnLottery>>> GetLotteries(
|
||||
[FromQuery] int offset = 0,
|
||||
[FromQuery] int limit = 20)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var lotteries = await lotteryService.GetUserTicketsAsync(currentUser.Id, offset, limit);
|
||||
var total = await lotteryService.GetUserTicketCountAsync(currentUser.Id);
|
||||
|
||||
Response.Headers["X-Total"] = total.ToString();
|
||||
|
||||
return Ok(lotteries);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<SnLottery>> GetLottery(Guid id)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var lottery = await lotteryService.GetTicketAsync(id);
|
||||
if (lottery == null || lottery.AccountId != currentUser.Id)
|
||||
return NotFound();
|
||||
|
||||
return Ok(lottery);
|
||||
}
|
||||
|
||||
[HttpPost("draw")]
|
||||
[Authorize]
|
||||
[AskPermission("lotteries.draw.perform")]
|
||||
public async Task<IActionResult> PerformLotteryDraw()
|
||||
{
|
||||
await lotteryService.DrawLotteries();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("records")]
|
||||
public async Task<ActionResult<List<SnLotteryRecord>>> GetLotteryRecords(
|
||||
[FromQuery] Instant? startDate = null,
|
||||
[FromQuery] Instant? endDate = null,
|
||||
[FromQuery] int offset = 0,
|
||||
[FromQuery] int limit = 20)
|
||||
{
|
||||
var query = db.LotteryRecords
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.AsQueryable();
|
||||
|
||||
if (startDate.HasValue)
|
||||
query = query.Where(r => r.DrawDate >= startDate.Value);
|
||||
if (endDate.HasValue)
|
||||
query = query.Where(r => r.DrawDate <= endDate.Value);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
Response.Headers["X-Total"] = total.ToString();
|
||||
|
||||
var records = await query
|
||||
.Skip(offset)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(records);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Quartz;
|
||||
|
||||
namespace DysonNetwork.Pass.Lotteries;
|
||||
|
||||
public class LotteryDrawJob(LotteryService lotteryService, ILogger<LotteryDrawJob> logger) : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
logger.LogInformation("Starting daily lottery draw...");
|
||||
|
||||
try
|
||||
{
|
||||
await lotteryService.DrawLotteries();
|
||||
logger.LogInformation("Daily lottery draw completed successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error occurred during daily lottery draw.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Lotteries;
|
||||
|
||||
public class LotteryOrderMetaData
|
||||
{
|
||||
public Guid AccountId { get; set; }
|
||||
public List<int> RegionOneNumbers { get; set; } = new();
|
||||
public int RegionTwoNumber { get; set; }
|
||||
public int Multiplier { get; set; } = 1;
|
||||
}
|
||||
|
||||
public class LotteryService(
|
||||
AppDatabase db,
|
||||
ILogger<LotteryService> logger)
|
||||
{
|
||||
private static bool ValidateNumbers(List<int> region1, int region2)
|
||||
{
|
||||
if (region1.Count != 5 || region1.Distinct().Count() != 5)
|
||||
return false;
|
||||
if (region1.Any(n => n < 0 || n > 99))
|
||||
return false;
|
||||
if (region2 < 0 || region2 > 99)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<SnLottery> CreateTicketAsync(Guid accountId, List<int> region1, int region2, int multiplier = 1)
|
||||
{
|
||||
if (!ValidateNumbers(region1, region2))
|
||||
throw new ArgumentException("Invalid lottery numbers");
|
||||
|
||||
var lottery = new SnLottery
|
||||
{
|
||||
AccountId = accountId,
|
||||
RegionOneNumbers = region1,
|
||||
RegionTwoNumber = region2,
|
||||
Multiplier = multiplier
|
||||
};
|
||||
|
||||
db.Lotteries.Add(lottery);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return lottery;
|
||||
}
|
||||
|
||||
public async Task<List<SnLottery>> GetUserTicketsAsync(Guid accountId, int offset = 0, int limit = 20)
|
||||
{
|
||||
return await db.Lotteries
|
||||
.Where(l => l.AccountId == accountId)
|
||||
.OrderByDescending(l => l.CreatedAt)
|
||||
.Skip(offset)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<SnLottery?> GetTicketAsync(Guid id)
|
||||
{
|
||||
return await db.Lotteries.FirstOrDefaultAsync(l => l.Id == id);
|
||||
}
|
||||
|
||||
public async Task<int> GetUserTicketCountAsync(Guid accountId)
|
||||
{
|
||||
return await db.Lotteries.CountAsync(l => l.AccountId == accountId);
|
||||
}
|
||||
|
||||
private static decimal CalculateLotteryPrice(int multiplier)
|
||||
{
|
||||
return 10 + (multiplier - 1) * 10;
|
||||
}
|
||||
|
||||
private static int CalculateReward(int region1Matches, bool region2Match)
|
||||
{
|
||||
var reward = region1Matches switch
|
||||
{
|
||||
0 => 0,
|
||||
1 => 10,
|
||||
2 => 100,
|
||||
3 => 500,
|
||||
4 => 1000,
|
||||
5 => 10000,
|
||||
_ => 0
|
||||
};
|
||||
if (region2Match) reward *= 10;
|
||||
return reward;
|
||||
}
|
||||
|
||||
private static List<int> GenerateUniqueRandomNumbers(int count, int min, int max)
|
||||
{
|
||||
var numbers = new List<int>();
|
||||
var random = new Random();
|
||||
while (numbers.Count < count)
|
||||
{
|
||||
var num = random.Next(min, max + 1);
|
||||
if (!numbers.Contains(num)) numbers.Add(num);
|
||||
}
|
||||
|
||||
return numbers.OrderBy(n => n).ToList();
|
||||
}
|
||||
|
||||
private int CountMatches(List<int> playerNumbers, List<int> winningNumbers)
|
||||
{
|
||||
return playerNumbers.Intersect(winningNumbers).Count();
|
||||
}
|
||||
|
||||
public async Task DrawLotteries()
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Starting drawing lotteries...");
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
// All pending lottery tickets
|
||||
var tickets = await db.Lotteries
|
||||
.Where(l => l.DrawStatus == LotteryDrawStatus.Pending)
|
||||
.ToListAsync();
|
||||
|
||||
if (tickets.Count == 0)
|
||||
{
|
||||
logger.LogInformation("No pending lottery tickets");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation("Found {Count} pending lottery tickets for draw", tickets.Count);
|
||||
|
||||
// Generate winning numbers
|
||||
var winningRegion1 = GenerateUniqueRandomNumbers(5, 0, 99);
|
||||
var winningRegion2 = GenerateUniqueRandomNumbers(1, 0, 99)[0];
|
||||
|
||||
logger.LogInformation("Winning numbers generated: Region1 [{Region1}], Region2 [{Region2}]",
|
||||
string.Join(",", winningRegion1), winningRegion2);
|
||||
|
||||
var drawDate = Instant.FromDateTimeUtc(new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month,
|
||||
DateTime.UtcNow.Day, 0, 0, 0, DateTimeKind.Utc).AddDays(-1)); // Yesterday's date
|
||||
|
||||
var totalPrizesAwarded = 0;
|
||||
long totalPrizeAmount = 0;
|
||||
|
||||
// Process each ticket
|
||||
foreach (var ticket in tickets)
|
||||
{
|
||||
var region1Matches = CountMatches(ticket.RegionOneNumbers, winningRegion1);
|
||||
var region2Match = ticket.RegionTwoNumber == winningRegion2;
|
||||
var reward = CalculateReward(region1Matches, region2Match);
|
||||
|
||||
// Record match results
|
||||
ticket.MatchedRegionOneNumbers = ticket.RegionOneNumbers.Intersect(winningRegion1).ToList();
|
||||
ticket.MatchedRegionTwoNumber = region2Match ? (int?)winningRegion2 : null;
|
||||
|
||||
if (reward > 0)
|
||||
{
|
||||
// Note: Prize awarding is now handled by the Wallet service
|
||||
// The Wallet service will process lottery results and award prizes
|
||||
logger.LogInformation(
|
||||
"Lottery prize of {Amount} to account {AccountId} for {Matches} matches needs to be awarded via Wallet service",
|
||||
reward, ticket.AccountId, region1Matches);
|
||||
totalPrizesAwarded++;
|
||||
totalPrizeAmount += reward;
|
||||
}
|
||||
|
||||
ticket.DrawStatus = LotteryDrawStatus.Drawn;
|
||||
ticket.DrawDate = drawDate;
|
||||
}
|
||||
|
||||
// Save the draw record
|
||||
var lotteryRecord = new SnLotteryRecord
|
||||
{
|
||||
DrawDate = drawDate,
|
||||
WinningRegionOneNumbers = winningRegion1,
|
||||
WinningRegionTwoNumber = winningRegion2,
|
||||
TotalTickets = tickets.Count,
|
||||
TotalPrizesAwarded = totalPrizesAwarded,
|
||||
TotalPrizeAmount = totalPrizeAmount
|
||||
};
|
||||
|
||||
db.LotteryRecords.Add(lotteryRecord);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
logger.LogInformation("Daily lottery draw completed: {Prizes} prizes awarded, total amount {Amount}",
|
||||
totalPrizesAwarded, totalPrizeAmount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred during the daily lottery draw");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,6 @@ public static class ScheduledJobsConfiguration
|
||||
.RepeatForever())
|
||||
);
|
||||
|
||||
q.AddJob<Lotteries.LotteryDrawJob>(opts => opts.WithIdentity("LotteryDraw"));
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob("LotteryDraw")
|
||||
.WithIdentity("LotteryDrawTrigger")
|
||||
.WithCronSchedule("0 0 0 * * ?"));
|
||||
|
||||
q.AddJob<SocialCreditValidationJob>(opts => opts.WithIdentity("SocialCreditValidation"));
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob("SocialCreditValidation")
|
||||
|
||||
@@ -16,7 +16,6 @@ using DysonNetwork.Pass.Auth.OidcProvider.Services;
|
||||
using DysonNetwork.Pass.Credit;
|
||||
using DysonNetwork.Pass.Handlers;
|
||||
using DysonNetwork.Pass.Leveling;
|
||||
using DysonNetwork.Pass.Lotteries;
|
||||
using DysonNetwork.Pass.Mailer;
|
||||
using DysonNetwork.Pass.Realm;
|
||||
using DysonNetwork.Pass.Rewind;
|
||||
@@ -165,7 +164,6 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<SocialCreditService>();
|
||||
services.AddScoped<ExperienceService>();
|
||||
services.AddScoped<RealmService>();
|
||||
services.AddScoped<LotteryService>();
|
||||
services.AddScoped<AffiliationSpellService>();
|
||||
|
||||
services.AddScoped<SpotifyPresenceService>();
|
||||
|
||||
Reference in New Issue
Block a user