Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TEMP_DB_URL=postgresql://heimdall_arena:uqwu-EDfz.!3KT!2WmV3@postgresql9c.sldb.iwinv.net/heimdall_arena_db?statusColor=F8F8F8&enviroment=production
60 changes: 25 additions & 35 deletions ArenaService.Shared/Repositories/AvailableOpponentRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,48 +99,38 @@ public async Task<List<AvailableOpponent>> RefreshAvailableOpponents(
List<(Address, int)> opponents
)
{
using var transaction = await _context.Database.BeginTransactionAsync();
// Runs in the caller's transaction so the refresh commits together with the ticket deduction.
var existOpponents = await GetAvailableOpponents(avatarAddress, roundId);

try
foreach (var existOpponent in existOpponents)
{
var existOpponents = await GetAvailableOpponents(avatarAddress, roundId);
existOpponent.DeletedAt = DateTime.UtcNow;

foreach (var existOpponent in existOpponents)
{
existOpponent.DeletedAt = DateTime.UtcNow;

_context.AvailableOpponents.Update(existOpponent);
}
_context.AvailableOpponents.Update(existOpponent);
}

var newOpponents = new List<AvailableOpponent>();
var newOpponents = new List<AvailableOpponent>();

foreach (var opponent in opponents)
{
var newOpponent = new AvailableOpponent
{
AvatarAddress = avatarAddress,
SeasonId = seasonId,
RoundId = roundId,
GroupId = opponent.Item2,
OpponentAvatarAddress = opponent.Item1,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};

newOpponents.Add(newOpponent);
}

await _context.AvailableOpponents.AddRangeAsync(newOpponents);
await _context.SaveChangesAsync();
await transaction.CommitAsync();

return newOpponents;
}
catch
foreach (var opponent in opponents)
{
await transaction.RollbackAsync();
throw;
var newOpponent = new AvailableOpponent
{
AvatarAddress = avatarAddress,
SeasonId = seasonId,
RoundId = roundId,
GroupId = opponent.Item2,
OpponentAvatarAddress = opponent.Item1,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};

newOpponents.Add(newOpponent);
}

await _context.AvailableOpponents.AddRangeAsync(newOpponents);
await _context.SaveChangesAsync();

return newOpponents;
}

public async Task<AvailableOpponent> UpdateAvailableOpponent(
Expand Down
14 changes: 14 additions & 0 deletions ArenaService.Shared/Repositories/TicketRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ Task<bool> DeductBattleTicket(
bool isVictory
);

Task<bool> TryDeductRefreshTicket(int refreshTicketStatusPerRoundId);


Task<bool> TryUpdateBattleTicketStatusPerRound(
int roundId,
Expand Down Expand Up @@ -696,6 +698,18 @@ bool isVictory
return false;
}

public async Task<bool> TryDeductRefreshTicket(int refreshTicketStatusPerRoundId)
{
var rowsAffected = await _context.RefreshTicketStatusesPerRound
.Where(rts => rts.Id == refreshTicketStatusPerRoundId && rts.RemainingCount > 0)
.ExecuteUpdateAsync(setters => setters
.SetProperty(rts => rts.RemainingCount, rts => rts.RemainingCount - 1)
.SetProperty(rts => rts.UsedCount, rts => rts.UsedCount + 1)
.SetProperty(rts => rts.UpdatedAt, DateTime.UtcNow));

return rowsAffected > 0;
}

public async Task<bool> TryUpdateBattleTicketStatusPerRound(
int roundId,
Address avatarAddress,
Expand Down
45 changes: 27 additions & 18 deletions ArenaService/Controllers/AvailableOpponentController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace ArenaService.Controllers;

using System.Data;
using ArenaService.Shared.Constants;
using ArenaService.Shared.Data;
using ArenaService.Shared.Dtos;
using ArenaService.Shared.Exceptions;
using ArenaService.Shared.Extensions;
Expand All @@ -22,6 +24,7 @@ public class AvailableOpponentController : ControllerBase
private readonly IParticipateService _participateService;
private readonly IRankingRepository _rankingRepo;
private readonly IAllClanRankingRepository _allClanRankingRepo;
private readonly ArenaDbContext _dbContext;

public AvailableOpponentController(
IAvailableOpponentRepository availableOpponentRepo,
Expand All @@ -30,7 +33,8 @@ public AvailableOpponentController(
ISeasonCacheRepository seasonCacheRepo,
IParticipateService participateService,
IAllClanRankingRepository allClanRankingRepo,
IRankingRepository rankingRepo
IRankingRepository rankingRepo,
ArenaDbContext dbContext
)
{
_availableOpponentRepo = availableOpponentRepo;
Expand All @@ -40,6 +44,7 @@ IRankingRepository rankingRepo
_participateService = participateService;
_allClanRankingRepo = allClanRankingRepo;
_rankingRepo = rankingRepo;
_dbContext = dbContext;
}

[HttpGet]
Expand Down Expand Up @@ -211,26 +216,30 @@ public async Task<IActionResult> RequestFreeRefresh()
cachedSeason.StartBlock == cachedRound.StartBlock
);

var availableOpponents = await _availableOpponentRepo.RefreshAvailableOpponents(
cachedSeason.Id,
cachedRound.Id,
avatarAddress,
opponents.Select(o => (o.Value.AvatarAddress, o.Key)).ToList()
);

await _ticketRepo.UpdateRefreshTicketStatusPerRound(
refreshTicketStatusPerRound,
rts =>
// Deducting first locks the ticket status row, so concurrent refreshes by the same
// avatar are serialized and each one sees the opponents committed by the previous one.
using (var transaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted))
{
if (!await _ticketRepo.TryDeductRefreshTicket(refreshTicketStatusPerRound.Id))
{
rts.RemainingCount -= 1;
rts.UsedCount += 1;
await transaction.RollbackAsync();
return BadRequest("NO_REFRESH_TICKETS");
}
);

await _ticketRepo.AddRefreshTicketUsageLog(
refreshTicketStatusPerRound.Id,
availableOpponents.Select(o => o.Id).ToList()
);
var availableOpponents = await _availableOpponentRepo.RefreshAvailableOpponents(
cachedSeason.Id,
cachedRound.Id,
avatarAddress,
opponents.Select(o => (o.Value.AvatarAddress, o.Key)).ToList()
);

await _ticketRepo.AddRefreshTicketUsageLog(
refreshTicketStatusPerRound.Id,
availableOpponents.Select(o => o.Id).ToList()
);

await transaction.CommitAsync();
}

var availableOpponentsResponses = new List<AvailableOpponentResponse>();

Expand Down
Loading