mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-06-20 22:41:28 +00:00
Rewrite gameserver slot filter system (#763)
* Initial implementation of new slot sorting and filtering system * Initial implementation of filtering for lbp3 community tab * Add support for organization on lbp3 * Add playlist and user categories * Implement unit tests for all filters Refactor more systems to use PaginationData * Fix PlayerCountFilter test * Add more unit tests and integration tests for the filter system * Fix LBP2 move filter and gameFilterType * Fix sort by likes in LBP3 category * Add sort for total plays * Remove extra whitespace and make styling more consistent * Order hearted and queued levels by primary key ID * Fix query without order warnings
This commit is contained in:
parent
de228cb242
commit
0c1e350fa3
106 changed files with 4040 additions and 1183 deletions
|
@ -1,10 +1,16 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using System.Linq.Expressions;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts.Metadata;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms;
|
||||
using LBPUnion.ProjectLighthouse.Types.Misc;
|
||||
|
@ -23,34 +29,31 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
|||
public class SlotsController : ControllerBase
|
||||
{
|
||||
private readonly DatabaseContext database;
|
||||
|
||||
public SlotsController(DatabaseContext database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("slots/by")]
|
||||
public async Task<IActionResult> SlotsBy([FromQuery(Name = "u")] string username, [FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] bool crosscontrol = false)
|
||||
public async Task<IActionResult> SlotsBy([FromQuery(Name = "u")] string username)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
|
||||
int targetUserId = await this.database.UserIdFromUsername(username);
|
||||
if (targetUserId == 0) return this.NotFound();
|
||||
|
||||
int usedSlots = this.database.Slots.Count(s => s.CreatorId == targetUserId);
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
List<SlotBase> slots = (await this.database.Slots.Where(s => s.CreatorId == targetUserId)
|
||||
.ByGameVersion(token.GameVersion, token.UserId == targetUserId)
|
||||
.Where(match => match.CrossControllerRequired == crosscontrol)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, usedSlots))
|
||||
.ToListAsync()).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, usedSlots);
|
||||
int total = await this.database.Slots.CountAsync(s => s.CreatorId == targetUserId && s.CrossControllerRequired == crosscontrol);
|
||||
pageData.TotalElements = await this.database.Slots.CountAsync(s => s.CreatorId == targetUserId);
|
||||
|
||||
return this.Ok(new GenericSlotResponse("slots", slots, total, start));
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token).AddFilter(new CreatorFilter(targetUserId));
|
||||
|
||||
SlotSortBuilder<SlotEntity> sortBuilder = new SlotSortBuilder<SlotEntity>().AddSort(new FirstUploadedSort());
|
||||
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse("slots", slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slotList")]
|
||||
|
@ -61,7 +64,7 @@ public class SlotsController : ControllerBase
|
|||
List<SlotBase> slots = new();
|
||||
foreach (int slotId in slotIds)
|
||||
{
|
||||
SlotEntity? slot = await this.database.Slots.Include(t => t.Creator).Where(t => t.SlotId == slotId && t.Type == SlotType.User).FirstOrDefaultAsync();
|
||||
SlotEntity? slot = await this.database.Slots.Where(t => t.SlotId == slotId && t.Type == SlotType.User).FirstOrDefaultAsync();
|
||||
if (slot == null)
|
||||
{
|
||||
slot = await this.database.Slots.Where(t => t.InternalSlotId == slotId && t.Type == SlotType.Developer).FirstOrDefaultAsync();
|
||||
|
@ -102,7 +105,7 @@ public class SlotsController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("s/developer/{id:int}")]
|
||||
public async Task<IActionResult> SDev(int id)
|
||||
public async Task<IActionResult> DeveloperSlot(int id)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
|
@ -113,13 +116,12 @@ public class SlotsController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("s/user/{id:int}")]
|
||||
public async Task<IActionResult> SUser(int id)
|
||||
public async Task<IActionResult> UserSlot(int id)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
SlotEntity? slot = await this.database.Slots.ByGameVersion(gameVersion, true, true).FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
SlotEntity? slot = await this.database.Slots.Where(this.GetDefaultFilters(token).Build())
|
||||
.FirstOrDefaultAsync(s => s.SlotId == id);
|
||||
|
||||
if (slot == null) return this.NotFound();
|
||||
|
||||
|
@ -127,66 +129,40 @@ public class SlotsController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("slots/cool")]
|
||||
public async Task<IActionResult> Lbp1CoolSlots([FromQuery] int page)
|
||||
{
|
||||
const int pageSize = 30;
|
||||
return await this.CoolSlots((page - 1) * pageSize, pageSize);
|
||||
}
|
||||
public async Task<IActionResult> Lbp1CoolSlots() => await this.CoolSlots();
|
||||
|
||||
[HttpGet("slots/lbp2cool")]
|
||||
public async Task<IActionResult> CoolSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int players = 1,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] int? page = null,
|
||||
[FromQuery] bool crosscontrol = false
|
||||
)
|
||||
{
|
||||
if (page != null) pageStart = (int)page * 30;
|
||||
// bit of a better placeholder until we can track average user interaction with /stream endpoint
|
||||
return await this.ThumbsSlots(pageStart, Math.Min(pageSize, 30), players, gameFilterType, "thisMonth",
|
||||
labelFilter0, labelFilter1, labelFilter2, move, crosscontrol);
|
||||
}
|
||||
public async Task<IActionResult> CoolSlots() => await this.ThumbsSlots();
|
||||
|
||||
[HttpGet("slots")]
|
||||
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] bool crosscontrol = false)
|
||||
public async Task<IActionResult> NewestSlots()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
List<SlotBase> slots = (await this.database.Slots.ByGameVersion(gameVersion, false, true)
|
||||
.Where(s => s.CrossControllerRequired == crosscontrol)
|
||||
.OrderByDescending(s => s.FirstUploaded)
|
||||
.ThenByDescending(s => s.SlotId)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
SlotSortBuilder<SlotEntity> sortBuilder = new();
|
||||
sortBuilder.AddSort(new FirstUploadedSort());
|
||||
sortBuilder.AddSort(new SlotIdSort());
|
||||
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/like/{slotType}/{slotId:int}")]
|
||||
public async Task<IActionResult> SimilarSlots([FromRoute] string slotType, [FromRoute] int slotId, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
public async Task<IActionResult> SimilarSlots([FromRoute] string slotType, [FromRoute] int slotId)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
if (slotType != "user") return this.BadRequest();
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
|
||||
SlotEntity? targetSlot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (targetSlot == null) return this.BadRequest();
|
||||
|
||||
|
@ -198,275 +174,180 @@ public class SlotsController : ControllerBase
|
|||
.Select(r => r.SlotId)
|
||||
.ToList();
|
||||
|
||||
List<SlotBase> slots = (await this.database.Slots.ByGameVersion(gameVersion, false, true)
|
||||
.Where(s => slotIdsWithTag.Contains(s.SlotId))
|
||||
.OrderByDescending(s => s.PlaysLBP1)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
pageData.TotalElements = slotIdsWithTag.Count;
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = slotIdsWithTag.Count;
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token).AddFilter(0, new SlotIdFilter(slotIdsWithTag));
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
SlotSortBuilder<SlotEntity> sortBuilder = new();
|
||||
sortBuilder.AddSort(new PlaysForGameSort(GameVersion.LittleBigPlanet1));
|
||||
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/highestRated")]
|
||||
public async Task<IActionResult> HighestRatedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
public async Task<IActionResult> HighestRatedSlots()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
List<SlotBase> slots = (await this.database.Slots.ByGameVersion(gameVersion, false, true)
|
||||
.Select(s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
RatingLbp1 = this.database.RatedLevels.Where(r => r.SlotId == s.SlotId).Average(r => (double?)r.RatingLBP1) ?? 3.0,
|
||||
})
|
||||
.OrderByDescending(s => s.RatingLbp1)
|
||||
.Select(s => s.Slot)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = await StatisticsHelper.SlotCount(this.database);
|
||||
SlotSortBuilder<SlotMetadata> sortBuilder = new();
|
||||
sortBuilder.AddSort(new RatingLBP1Sort());
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
Expression<Func<SlotEntity, SlotMetadata>> selectorFunc = s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
RatingLbp1 = this.database.RatedLevels.Where(r => r.SlotId == s.SlotId)
|
||||
.Average(r => (double?)r.RatingLBP1) ?? 3.0,
|
||||
};
|
||||
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder, selectorFunc);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/tag")]
|
||||
public async Task<IActionResult> SimilarSlots([FromQuery] string tag, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
public async Task<IActionResult> SimilarSlots([FromQuery] string tag)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
List<int> slotIdsWithTag = await this.database.RatedLevels.Where(r => r.TagLBP1.Length > 0)
|
||||
.Where(r => r.TagLBP1 == tag)
|
||||
.Select(s => s.SlotId)
|
||||
.ToListAsync();
|
||||
|
||||
List<SlotBase> slots = (await this.database.Slots.Where(s => slotIdsWithTag.Contains(s.SlotId))
|
||||
.ByGameVersion(token.GameVersion, false, true)
|
||||
.OrderByDescending(s => s.PlaysLBP1)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
pageData.TotalElements = slotIdsWithTag.Count;
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = slotIdsWithTag.Count;
|
||||
SlotSortBuilder<SlotEntity> sortBuilder = new();
|
||||
sortBuilder.AddSort(new PlaysForGameSort(GameVersion.LittleBigPlanet1));
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, this.FilterFromRequest(token), pageData, sortBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/mmpicks")]
|
||||
public async Task<IActionResult> TeamPickedSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int players,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] string? dateFilterType = null,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] bool crosscontrol = false
|
||||
)
|
||||
public async Task<IActionResult> TeamPickedSlots()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
List<SlotBase> slots = this.filterSlots((await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.Where(s => s.TeamPick && s.CrossControllerRequired == crosscontrol)
|
||||
.OrderByDescending(s => s.LastUpdated)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()), players, labelFilter0, labelFilter1, labelFilter2, move).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = await StatisticsHelper.TeamPickCountForGame(this.database, token.GameVersion, crosscontrol);
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token).AddFilter(new TeamPickFilter());
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
|
||||
|
||||
SlotSortBuilder<SlotEntity> sortBuilder = new();
|
||||
sortBuilder.AddSort(new LastUpdatedSort());
|
||||
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/lbp2luckydip")]
|
||||
public async Task<IActionResult> LuckyDipSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int seed,
|
||||
[FromQuery] int players = 1,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] string? dateFilterType = null,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] bool crosscontrol = false
|
||||
)
|
||||
public async Task<IActionResult> LuckyDipSlots([FromQuery] int seed)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
const double biasFactor = .8f;
|
||||
List<SlotBase> slots = this.filterSlots((await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.Where(s => s.CrossControllerRequired == crosscontrol)
|
||||
.OrderByDescending(s => EF.Functions.Random() * (s.FirstUploaded * biasFactor))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()), players, labelFilter0, labelFilter1, labelFilter2, move).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
|
||||
SlotSortBuilder<SlotEntity> sortBuilder = new();
|
||||
sortBuilder.AddSort(new RandomFirstUploadedSort());
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/thumbs")]
|
||||
public async Task<IActionResult> ThumbsSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int players,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] string? dateFilterType = null,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] bool crosscontrol = false
|
||||
)
|
||||
public async Task<IActionResult> ThumbsSlots()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
List<SlotBase> slots = this.filterSlots((await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.Where(s => s.CrossControllerRequired == crosscontrol)
|
||||
.Select(s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
ThumbsUp = this.database.RatedLevels.Count(r => r.SlotId == s.SlotId && r.Rating == 1),
|
||||
})
|
||||
.OrderByDescending(s => s.ThumbsUp)
|
||||
.ThenBy(_ => EF.Functions.Random())
|
||||
.Select(s => s.Slot)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()), players, labelFilter0, labelFilter1, labelFilter2, move).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
|
||||
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
SlotSortBuilder<SlotMetadata> sortBuilder = new();
|
||||
sortBuilder.AddSort(new ThumbsUpSort());
|
||||
|
||||
Expression<Func<SlotEntity, SlotMetadata>> selectorFunc = s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
ThumbsUp = this.database.RatedLevels.Count(r => r.SlotId == s.SlotId && r.Rating == 1),
|
||||
};
|
||||
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder, selectorFunc);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/mostUniquePlays")]
|
||||
public async Task<IActionResult> MostUniquePlaysSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int players,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] string? dateFilterType = null,
|
||||
[FromQuery] bool crosscontrol = false
|
||||
)
|
||||
public async Task<IActionResult> MostUniquePlaysSlots()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
string game = getGameFilter(gameFilterType, token.GameVersion) switch
|
||||
{
|
||||
GameVersion.LittleBigPlanet1 => "LBP1",
|
||||
GameVersion.LittleBigPlanet2 => "LBP2",
|
||||
GameVersion.LittleBigPlanet3 => "LBP3",
|
||||
GameVersion.LittleBigPlanetVita => "LBP2",
|
||||
_ => "",
|
||||
};
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
string colName = $"Plays{game}Unique";
|
||||
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
|
||||
|
||||
List<SlotBase> slots = this.filterSlots((await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.Where(s => s.CrossControllerRequired == crosscontrol)
|
||||
.OrderByDescending(s => EF.Property<int>(s, colName))
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()), players, labelFilter0, labelFilter1, labelFilter2, move).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
SlotSortBuilder<SlotEntity> sortBuilder = new();
|
||||
sortBuilder.AddSort(new UniquePlaysForGameSort(token.GameVersion));
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
[HttpGet("slots/mostHearted")]
|
||||
public async Task<IActionResult> MostHeartedSlots
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int players,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] string? dateFilterType = null,
|
||||
[FromQuery] bool crosscontrol = false
|
||||
)
|
||||
public async Task<IActionResult> MostHeartedSlots()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
List<SlotBase> slots = this.filterSlots((await this.filterByRequest(gameFilterType, dateFilterType, token.GameVersion)
|
||||
.Where(s => s.CrossControllerRequired == crosscontrol)
|
||||
.Select(s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
Hearts = this.database.HeartedLevels.Count(r => r.SlotId == s.SlotId),
|
||||
})
|
||||
.OrderByDescending(s => s.Hearts)
|
||||
.Select(s => s.Slot)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()), players, labelFilter0, labelFilter1, labelFilter2, move).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = await StatisticsHelper.SlotCountForGame(this.database, token.GameVersion);
|
||||
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, total, start));
|
||||
SlotSortBuilder<SlotMetadata> sortBuilder = new();
|
||||
sortBuilder.AddSort(new HeartsSort());
|
||||
|
||||
Expression<Func<SlotEntity, SlotMetadata>> selectorFunc = s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
Hearts = this.database.HeartedLevels.Count(r => r.SlotId == s.SlotId),
|
||||
};
|
||||
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder, selectorFunc);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
|
||||
// /slots/busiest?pageStart=1&pageSize=30&gameFilterType=both&players=1&move=true
|
||||
[HttpGet("slots/busiest")]
|
||||
public async Task<IActionResult> BusiestLevels
|
||||
(
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int players = 1,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] bool crosscontrol = false
|
||||
)
|
||||
public async Task<IActionResult> BusiestLevels()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
Dictionary<int, int> playersBySlotId = new();
|
||||
|
||||
|
@ -484,98 +365,15 @@ public class SlotsController : ControllerBase
|
|||
playersBySlotId.Add(room.Slot.SlotId, playerCount);
|
||||
}
|
||||
|
||||
IEnumerable<int> orderedPlayersBySlotId = playersBySlotId
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.OrderByDescending(kvp => kvp.Value)
|
||||
.Select(kvp => kvp.Key);
|
||||
|
||||
List<SlotEntity> slots = new();
|
||||
pageData.TotalElements = playersBySlotId.Count;
|
||||
|
||||
foreach (int slotId in orderedPlayersBySlotId)
|
||||
{
|
||||
SlotEntity? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true)
|
||||
.Where(s => s.SlotId == slotId && s.CrossControllerRequired == crosscontrol)
|
||||
.FirstOrDefaultAsync();
|
||||
if (slot == null) continue; // shouldn't happen ever unless the room is borked
|
||||
|
||||
slots.Add(slot);
|
||||
}
|
||||
List<int> orderedPlayersBySlotId = playersBySlotId.OrderByDescending(kvp => kvp.Value).Select(kvp => kvp.Key).ToList();
|
||||
|
||||
slots = this.filterSlots(slots, players, labelFilter0, labelFilter1, labelFilter2, move);
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
queryBuilder.AddFilter(0, new SlotIdFilter(orderedPlayersBySlotId));
|
||||
|
||||
int start = pageStart + Math.Min(pageSize, ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots);
|
||||
int total = playersBySlotId.Count;
|
||||
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, new SlotSortBuilder<SlotEntity>());
|
||||
|
||||
return this.Ok(new GenericSlotResponse(slots.ToSerializableList(s => SlotBase.CreateFromEntity(s, token)), total, start));
|
||||
}
|
||||
|
||||
private List<SlotEntity> filterSlots(List<SlotEntity> slots, int players, string? labelFilter0 = null, string? labelFilter1 = null, string? labelFilter2 = null, string? move = null)
|
||||
{
|
||||
slots.RemoveAll(s => s.MinimumPlayers != players);
|
||||
|
||||
if (labelFilter0 != null)
|
||||
slots.RemoveAll(s => !s.AuthorLabels.Split(',').ToList().Contains(labelFilter0));
|
||||
if (labelFilter1 != null)
|
||||
slots.RemoveAll(s => !s.AuthorLabels.Split(',').ToList().Contains(labelFilter1));
|
||||
if (labelFilter2 != null)
|
||||
slots.RemoveAll(s => !s.AuthorLabels.Split(',').ToList().Contains(labelFilter2));
|
||||
|
||||
if (move == "false")
|
||||
slots.RemoveAll(s => s.MoveRequired);
|
||||
if (move == "only")
|
||||
slots.RemoveAll(s => !s.MoveRequired);
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
private static GameVersion getGameFilter(string? gameFilterType, GameVersion version)
|
||||
{
|
||||
return version switch
|
||||
{
|
||||
GameVersion.LittleBigPlanetVita => GameVersion.LittleBigPlanetVita,
|
||||
GameVersion.LittleBigPlanetPSP => GameVersion.LittleBigPlanetPSP,
|
||||
_ => gameFilterType switch
|
||||
{
|
||||
"lbp1" => GameVersion.LittleBigPlanet1,
|
||||
"lbp2" => GameVersion.LittleBigPlanet2,
|
||||
"lbp3" => GameVersion.LittleBigPlanet3,
|
||||
"both" => GameVersion.LittleBigPlanet2, // LBP2 default option
|
||||
null => GameVersion.LittleBigPlanet1,
|
||||
_ => GameVersion.Unknown,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private IQueryable<SlotEntity> filterByRequest(string? gameFilterType, string? dateFilterType, GameVersion version)
|
||||
{
|
||||
if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown)
|
||||
{
|
||||
return this.database.Slots.ByGameVersion(version, false, true);
|
||||
}
|
||||
|
||||
string _dateFilterType = dateFilterType ?? "";
|
||||
|
||||
long oldestTime = _dateFilterType switch
|
||||
{
|
||||
"thisWeek" => DateTimeOffset.Now.AddDays(-7).ToUnixTimeMilliseconds(),
|
||||
"thisMonth" => DateTimeOffset.Now.AddDays(-31).ToUnixTimeMilliseconds(),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
GameVersion gameVersion = getGameFilter(gameFilterType, version);
|
||||
|
||||
IQueryable<SlotEntity> whereSlots;
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (gameFilterType == "both")
|
||||
// Get game versions less than the current version
|
||||
// Needs support for LBP3 ("both" = LBP1+2)
|
||||
whereSlots = this.database.Slots.Where(s => s.Type == SlotType.User && !s.Hidden && s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime);
|
||||
else
|
||||
// Get game versions exactly equal to gamefiltertype
|
||||
whereSlots = this.database.Slots.Where(s => s.Type == SlotType.User && !s.Hidden && s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime);
|
||||
|
||||
return whereSlots.Include(s => s.Creator);
|
||||
return this.Ok(new GenericSlotResponse(slots, pageData));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue