Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDX-356: Move POST DP API to GET /arena/rankings API #8

Merged
merged 8 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,5 @@ obj/

# StrawberryShake
Generated

appsettings.local.json
13 changes: 11 additions & 2 deletions NineChroniclesUtilBackend/Controllers/ArenaController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,22 @@
using NineChroniclesUtilBackend.Models.Arena;
using NineChroniclesUtilBackend.Services;
using NineChroniclesUtilBackend.Arena;
using NineChroniclesUtilBackend.Repositories;

namespace NineChroniclesUtilBackend.Controllers;

[ApiController]
[Route("arena")]
public class ArenaController : ControllerBase
public class ArenaController(ArenaRankingRepository arenaRankingRepository) : ControllerBase
{
[HttpGet("ranking")]
public async Task<List<ArenaRanking>> GetRanking(int limit, int offset, string avatarAddress)
{
var addressOffset = await arenaRankingRepository.GetRankByAvatarAddress(avatarAddress) - 1;

return await arenaRankingRepository.GetRanking(limit, offset + addressOffset);
}

[HttpPost("simulate")]
public async Task<ArenaSimulateResponse> Simulate([FromBody] ArenaSimulateRequest arenaSimulateRequest, IStateService stateService)
{
Expand Down Expand Up @@ -110,7 +119,7 @@ async Task<List<RuneState>> GetRuneStates(Address avatarAddress)
myAvatarState,
myAvatarItemSlotState,
myAvatarRuneStates
),
),
new AvatarStatesForArena(
enemyAvatarState,
enemyAvatarItemSlotState,
Expand Down
27 changes: 27 additions & 0 deletions NineChroniclesUtilBackend/Models/Arena/ArenaRanking.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NineChroniclesUtilBackend.Models.Agent;

namespace NineChroniclesUtilBackend.Models.Arena;

public class ArenaRanking(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like a case to use record type. (maybe)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first, I thought adding cp calculation part to getter (or static method) of ArenaRanking or Avatar. Because it's kind of (Domain) Entity and cp calculation code is domain logic. I will apply these refactor on following Pull Request and ticket.

string AvatarAddress,
string ArenaAddress,
int Win,
int Lose,
long Rank,
int Ticket,
int TicketResetCount,
int PurchasedTicketCount,
int Score,
Avatar Avatar)
{
public string AvatarAddress { get; set; } = AvatarAddress;
public string ArenaAddress { get; set; } = ArenaAddress;
public int Win { get; set; } = Win;
public int Lose { get; set; } = Lose;
public long Rank { get; set; } = Rank;
public int Ticket { get; set; } = Ticket;
public int TicketResetCount { get; set; } = TicketResetCount;
public int PurchasedTicketCount { get; set; } = PurchasedTicketCount;
public int Score { get; set; } = Score;
public Avatar Avatar { get; set; } = Avatar;
}
1 change: 1 addition & 0 deletions NineChroniclesUtilBackend/NineChroniclesUtilBackend.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.3.1" />
<PackageReference Include="MongoDB.Driver" Version="2.24.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Lib9c" Version="1.8.0-dev.4e2f5d9bfed5366a94e67b88c4b0e7171c0cd0d6" />
<PackageReference Include="Libplanet" Version="4.1.0-dev.20242745721" />
Expand Down
7 changes: 7 additions & 0 deletions NineChroniclesUtilBackend/Options/DatabaseOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NineChroniclesUtilBackend.Options;

public class DatabaseOption
{
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
4 changes: 4 additions & 0 deletions NineChroniclesUtilBackend/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
using Microsoft.Extensions.Options;
using NineChroniclesUtilBackend.Services;
using NineChroniclesUtilBackend.Options;
using NineChroniclesUtilBackend.Repositories;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEnvironmentVariables();

builder.Services.Configure<HeadlessStateServiceOption>(builder.Configuration.GetRequiredSection("StateService"));
builder.Services.Configure<DataProviderOption>(builder.Configuration.GetRequiredSection("DataProvider"));
builder.Services.Configure<DatabaseOption>(builder.Configuration.GetRequiredSection("Database"));
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()));

Expand All @@ -22,6 +24,8 @@
options.SupportNonNullableReferenceTypes();
});
builder.Services.AddSingleton<IStateService, HeadlessStateService>();
builder.Services.AddSingleton<MongoDBCollectionService>();
builder.Services.AddSingleton<ArenaRankingRepository>();
builder.Services.AddControllers();
builder.Services.AddHeadlessGQLClient()
.ConfigureHttpClient((provider, client) =>
Expand Down
110 changes: 110 additions & 0 deletions NineChroniclesUtilBackend/Repositories/ArenaRankingRespository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using MongoDB.Bson;
using MongoDB.Driver;
using NineChroniclesUtilBackend.Models.Agent;
using NineChroniclesUtilBackend.Models.Arena;
using NineChroniclesUtilBackend.Services;

namespace NineChroniclesUtilBackend.Repositories;

public class ArenaRankingRepository(MongoDBCollectionService mongoDBCollectionService)
{
private readonly IMongoCollection<dynamic> ArenaCollection = mongoDBCollectionService.GetCollection<dynamic>("arena_0_10");

public async Task<long> GetRankByAvatarAddress(string avatarAddress)
{
var pipelines = new BsonDocument[]
{
new("$sort", new BsonDocument("ArenaScore.Score", -1)),
new("$group", new BsonDocument
{
{ "_id", BsonNull.Value },
{ "docs", new BsonDocument("$push", "$$ROOT") },
}),
new("$unwind", new BsonDocument
{
{ "path", "$docs" },
{ "includeArrayIndex", "Rank" },
}),
new("$match", new BsonDocument("docs.AvatarAddress", avatarAddress)),
new("$replaceRoot", new BsonDocument
{
{ "newRoot", new BsonDocument
{
{ "$mergeObjects", new BsonArray
{
"$docs",
new BsonDocument("Rank", "$Rank"),
} },
} },
}),
};

var aggregation = await ArenaCollection.Aggregate<dynamic>(pipelines).ToListAsync();

return aggregation.First().Rank + 1;
}

public async Task<List<ArenaRanking>> GetRanking(long limit, long offset)
{
// TODO: Implement CP Calculation
var pipelines = new BsonDocument[]
{
new("$sort", new BsonDocument("ArenaScore.Score", -1)),
new("$group", new BsonDocument
{
{ "_id", BsonNull.Value },
{ "docs", new BsonDocument("$push", "$$ROOT") },
}),
new("$unwind", new BsonDocument
{
{ "path", "$docs" },
{ "includeArrayIndex", "Rank" },
}),
new("$skip", offset),
new("$limit", limit),
new("$replaceRoot", new BsonDocument
{
{ "newRoot", new BsonDocument
{
{ "$mergeObjects", new BsonArray
{
"$docs",
new BsonDocument("Rank", "$Rank"),
} },
} },
}),
new("$lookup", new BsonDocument
{
{ "from", "avatars" },
{ "localField", "AvatarAddress" },
{ "foreignField", "address" },
{ "as", "Avatar" },
}),
new("$unwind", new BsonDocument
{
{ "path", "$Avatar" },
}),
};


var aggregation = await ArenaCollection.Aggregate<dynamic>(pipelines).ToListAsync();
var result = aggregation.Select(x => new ArenaRanking(
x.AvatarAddress,
x.ArenaInfo.Address,
x.ArenaInfo.Win,
x.ArenaInfo.Lose,
x.Rank + 1,
x.ArenaInfo.Ticket,
x.ArenaInfo.TicketResetCount,
x.ArenaInfo.PurchasedTicketCount,
x.ArenaScore.Score,
new Avatar(
x.Avatar.address,
x.Avatar.name,
x.Avatar.level
)
)).ToList();

return result;
}
}
17 changes: 17 additions & 0 deletions NineChroniclesUtilBackend/Services/MongoDBCollectionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using NineChroniclesUtilBackend.Options;

namespace NineChroniclesUtilBackend.Services;

public class MongoDBCollectionService(IOptions<DatabaseOption> databaseOption)
{
private readonly IOptions<DatabaseOption> _databaseOption = databaseOption;

public IMongoCollection<T> GetCollection<T>(string collectionName)
{
var client = new MongoClient(_databaseOption.Value.ConnectionString);
var database = client.GetDatabase(_databaseOption.Value.DatabaseName);
return database.GetCollection<T>(collectionName);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please insert newline at the EOFs.