Skip to content
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,4 @@ fabric.properties
**/appsettings.Internal.json
**/appsettings.Main.json
ArenaService.IntegrationTests/**/*.received.*
.claude/scheduled_tasks.lock
120 changes: 120 additions & 0 deletions ArenaService.Shared/Dtos/AdminRequests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.ComponentModel.DataAnnotations;
using ArenaService.Shared.Constants;

namespace ArenaService.Shared.Dtos;

public class CreateSeasonRequest
{
[Range(typeof(long), "0", "9223372036854775807")]
public long StartBlock { get; set; }

[Range(1, int.MaxValue)]
public int RoundInterval { get; set; }

[Range(1, int.MaxValue)]
public int RoundCount { get; set; }

[Range(1, int.MaxValue)]
public int SeasonGroupId { get; set; }

[EnumDataType(typeof(ArenaType))]
public ArenaType ArenaType { get; set; }

[Range(0, int.MaxValue)]
public int RequiredMedalCount { get; set; }

[Range(0, int.MaxValue)]
public int TotalPrize { get; set; }

[Range(1, int.MaxValue)]
public int BattleTicketPolicyId { get; set; }

[Range(1, int.MaxValue)]
public int RefreshTicketPolicyId { get; set; }
}

public class UpdateSeasonRequest
{
[Range(1, int.MaxValue)]
public int SeasonGroupId { get; set; }

[EnumDataType(typeof(ArenaType))]
public ArenaType ArenaType { get; set; }

[Range(1, int.MaxValue)]
public int RoundInterval { get; set; }

[Range(0, int.MaxValue)]
public int RequiredMedalCount { get; set; }

[Range(0, int.MaxValue)]
public int TotalPrize { get; set; }

[Range(1, int.MaxValue)]
public int BattleTicketPolicyId { get; set; }

[Range(1, int.MaxValue)]
public int RefreshTicketPolicyId { get; set; }
}

public class AdjustEndBlockRequest
{
[Range(typeof(long), "0", "9223372036854775807")]
public long NewEndBlock { get; set; }
}

public class CreateBattleTicketPolicyRequest
{
[Required]
public required string Name { get; set; }

[Range(0, int.MaxValue)]
public int DefaultTicketsPerRound { get; set; }

[Range(1, int.MaxValue)]
public int MaxPurchasableTicketsPerRound { get; set; }

[Range(1, int.MaxValue)]
public int MaxPurchasableTicketsPerSeason { get; set; }

[Required]
[MinLength(1)]
public List<decimal> PurchasePrices { get; set; } = new();
}
Comment thread
longfin marked this conversation as resolved.

public class CreateRefreshTicketPolicyRequest
{
[Required]
public required string Name { get; set; }

[Range(0, int.MaxValue)]
public int DefaultTicketsPerRound { get; set; }

[Range(1, int.MaxValue)]
public int MaxPurchasableTicketsPerRound { get; set; }

[Required]
[MinLength(1)]
public List<decimal> PurchasePrices { get; set; } = new();
}
Comment thread
longfin marked this conversation as resolved.

public class InitializeSeasonRequest
{
[Range(typeof(long), "0", "9223372036854775807")]
public long BlockIndex { get; set; }
}
Comment on lines +101 to +105

public class PrepareNextRoundRequest
{
[Range(typeof(long), "0", "9223372036854775807")]
public long BlockIndex { get; set; }
}
Comment thread
longfin marked this conversation as resolved.
Comment on lines +107 to +111

public class InitializeRankingCacheRequest
{
[Range(1, int.MaxValue)]
public int SeasonId { get; set; }

[Range(1, int.MaxValue)]
public int RoundId { get; set; }
}
55 changes: 55 additions & 0 deletions ArenaService.Shared/Dtos/AdminResponses.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using ArenaService.Shared.Models.Enums;
using Libplanet.Types.Tx;

namespace ArenaService.Shared.Dtos;

public class AdminBattleReviewResponse
{
public int Id { get; set; }
public string AvatarAddress { get; set; } = null!;
public int SeasonId { get; set; }
public int RoundId { get; set; }
public BattleStatus BattleStatus { get; set; }
public TxId? TxId { get; set; }
public TxStatus? TxStatus { get; set; }
public string? ExceptionNames { get; set; }
public bool? Reviewed { get; set; }
}

public class AdminTicketPurchaseReviewResponse
{
public int Id { get; set; }
public string AvatarAddress { get; set; } = null!;
public int SeasonId { get; set; }
public int RoundId { get; set; }
public PurchaseStatus PurchaseStatus { get; set; }
public TxId? TxId { get; set; }
public TxStatus? TxStatus { get; set; }
public string? ExceptionNames { get; set; }
public bool? Reviewed { get; set; }
}

public class AdminTicketPurchaseReviewListResponse
{
public List<AdminTicketPurchaseReviewResponse> BattleTicketPurchases { get; set; } = new();
public List<AdminTicketPurchaseReviewResponse> RefreshTicketPurchases { get; set; } = new();
}

public class AdminPreparationResponse
{
public string Message { get; set; } = null!;
public int? SeasonId { get; set; }
public int? RoundId { get; set; }
}

public class AdminLeaderboardEntryResponse
{
public string AvatarAddress { get; set; } = null!;
public string AgentAddress { get; set; } = null!;
public string NameWithHash { get; set; } = null!;
public int Rank { get; set; }
public int Score { get; set; }
public int TotalWin { get; set; }
public int TotalLose { get; set; }
public int Level { get; set; }
}
6 changes: 5 additions & 1 deletion ArenaService.Shared/Repositories/SeasonRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ public async Task<Season> AddSeasonWithRoundsAsync(
int refreshTicketPolicyId
)
{
long endBlock = startBlock + (roundInterval * roundCount) - 1;
long endBlock;
checked
{
endBlock = startBlock + ((long)roundInterval * roundCount) - 1;
}

bool isOverlapping = await IsBlockRangeOverlappingAsync(startBlock, endBlock);
if (isOverlapping)
Expand Down
67 changes: 67 additions & 0 deletions ArenaService.Tests/Controllers/AdminLeaderboardControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using ArenaService.Controllers;
using ArenaService.Shared.Models;
using ArenaService.Shared.Repositories;
using Libplanet.Crypto;
using Microsoft.AspNetCore.Mvc;
using Moq;

namespace ArenaService.Tests.Controllers;

public class AdminLeaderboardControllerTests
{
private readonly AdminLeaderboardController _controller;
private readonly Mock<ILeaderboardRepository> _leaderboardRepoMock;

public AdminLeaderboardControllerTests()
{
_leaderboardRepoMock = new Mock<ILeaderboardRepository>();
_controller = new AdminLeaderboardController(_leaderboardRepoMock.Object);
}

[Fact]
public async Task GetLeaderboard_ReturnsOk()
{
var address = new Address("0x0000000000000000000000000000000000000000");
var participant = new Participant
{
AvatarAddress = address,
SeasonId = 1,
Score = 1200,
TotalWin = 10,
TotalLose = 5,
User = new User
{
AvatarAddress = address,
AgentAddress = address,
NameWithHash = "TestUser#1234",
Level = 100,
PortraitId = 1,
Cp = 5000L
}
};

var leaderboard = new List<(Participant Participant, int Score, int Rank)>
{
(participant, 1200, 1)
};
_leaderboardRepoMock.Setup(x => x.GetLeaderboardAsync(1)).ReturnsAsync(leaderboard);

var result = await _controller.GetLeaderboard(1);

Assert.IsType<OkObjectResult>(result);
}

[Fact]
public async Task DownloadLeaderboardCsv_ReturnsFile()
{
var csvBytes = "avatar_address,agent_address\ntest,test"u8.ToArray();
_leaderboardRepoMock.Setup(x => x.GenerateLeaderboardCsvAsync(1)).ReturnsAsync(csvBytes);

var result = await _controller.DownloadLeaderboardCsv(1);

var fileResult = Assert.IsType<FileContentResult>(result);
Assert.Equal("text/csv", fileResult.ContentType);
Assert.Equal("leaderboard_season_1.csv", fileResult.FileDownloadName);
Assert.Equal(csvBytes, fileResult.FileContents);
}
}
Loading
Loading