mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-29 08:28:39 +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
|
@ -0,0 +1,169 @@
|
|||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Sorts;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Misc;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("LITTLEBIGPLANETPS3_XML/")]
|
||||
[Produces("text/xml")]
|
||||
public class CategoryController : ControllerBase
|
||||
{
|
||||
private readonly DatabaseContext database;
|
||||
|
||||
public CategoryController(DatabaseContext database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
[HttpGet("searches")]
|
||||
[HttpGet("genres")]
|
||||
public async Task<IActionResult> GenresAndSearches()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
UserEntity? user = await this.database.UserFromGameToken(token);
|
||||
if (user == null) return this.Forbid();
|
||||
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
pageData.TotalElements = CategoryHelper.Categories.Count;
|
||||
|
||||
if (!int.TryParse(this.Request.Query["num_categories_with_results"], out int results)) results = 5;
|
||||
|
||||
List<GameCategory> categories = new();
|
||||
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
foreach (Category category in CategoryHelper.Categories.Skip(Math.Max(0, pageData.PageStart - 1))
|
||||
.Take(Math.Min(pageData.PageSize, pageData.MaxElements))
|
||||
.ToList())
|
||||
{
|
||||
int numResults = results > 0 ? 1 : 0;
|
||||
categories.Add(await category.Serialize(this.database, token, queryBuilder, numResults));
|
||||
results--;
|
||||
}
|
||||
|
||||
return this.Ok(new CategoryListResponse(categories, pageData.TotalElements, "", pageData.HintStart));
|
||||
}
|
||||
|
||||
[HttpGet("searches/{endpointName}")]
|
||||
public async Task<IActionResult> GetCategorySlots(string endpointName)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
UserEntity? user = await this.database.UserFromGameToken(token);
|
||||
if (user == null) return this.Forbid();
|
||||
|
||||
Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
|
||||
if (category == null) return this.NotFound();
|
||||
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
Logger.Debug("Found category " + category, LogArea.Category);
|
||||
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
GenericSerializableList returnList = category switch
|
||||
{
|
||||
SlotCategory gc => await this.GetSlotCategory(gc, token, queryBuilder, pageData),
|
||||
PlaylistCategory pc => await this.GetPlaylistCategory(pc, token, pageData),
|
||||
UserCategory uc => await this.GetUserCategory(uc, token, pageData),
|
||||
_ => new GenericSerializableList(),
|
||||
};
|
||||
|
||||
return this.Ok(returnList);
|
||||
}
|
||||
|
||||
private async Task<GenericSerializableList> GetUserCategory(UserCategory userCategory, GameTokenEntity token, PaginationData pageData)
|
||||
{
|
||||
int totalUsers = await userCategory.GetItems(this.database, token).CountAsync();
|
||||
pageData.TotalElements = totalUsers;
|
||||
IQueryable<UserEntity> userQuery = userCategory.GetItems(this.database, token).ApplyPagination(pageData);
|
||||
|
||||
List<ILbpSerializable> users =
|
||||
(await userQuery.ToListAsync()).ToSerializableList<UserEntity, ILbpSerializable>(GameUser
|
||||
.CreateFromEntity);
|
||||
return new GenericSerializableList(users, pageData);
|
||||
}
|
||||
|
||||
private async Task<GenericSerializableList> GetPlaylistCategory(PlaylistCategory playlistCategory, GameTokenEntity token, PaginationData pageData)
|
||||
{
|
||||
int totalPlaylists = await playlistCategory.GetItems(this.database, token).CountAsync();
|
||||
pageData.TotalElements = totalPlaylists;
|
||||
IQueryable<PlaylistEntity> playlistQuery = playlistCategory.GetItems(this.database, token).ApplyPagination(pageData);
|
||||
|
||||
List<ILbpSerializable> playlists =
|
||||
(await playlistQuery.ToListAsync()).ToSerializableList<PlaylistEntity, ILbpSerializable>(GamePlaylist
|
||||
.CreateFromEntity);
|
||||
return new GenericSerializableList(playlists, pageData);
|
||||
}
|
||||
|
||||
private async Task<GenericSerializableList> GetSlotCategory(SlotCategory slotCategory, GameTokenEntity token, SlotQueryBuilder queryBuilder, PaginationData pageData)
|
||||
{
|
||||
int totalSlots = await slotCategory.GetItems(this.database, token, queryBuilder).CountAsync();
|
||||
pageData.TotalElements = totalSlots;
|
||||
IQueryable<SlotEntity> slotQuery = slotCategory.GetItems(this.database, token, queryBuilder).ApplyPagination(pageData);
|
||||
|
||||
if (bool.TryParse(this.Request.Query["includePlayed"], out bool includePlayed) && !includePlayed)
|
||||
{
|
||||
slotQuery = slotQuery.Select(s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
Played = this.database.VisitedLevels.Any(v => v.SlotId == s.SlotId && v.UserId == token.UserId),
|
||||
})
|
||||
.Where(s => !s.Played)
|
||||
.Select(s => s.Slot);
|
||||
}
|
||||
|
||||
if (this.Request.Query.ContainsKey("sort"))
|
||||
{
|
||||
string sort = (string?)this.Request.Query["sort"] ?? "";
|
||||
slotQuery = sort switch
|
||||
{
|
||||
"relevance" => slotQuery.ApplyOrdering(new SlotSortBuilder<SlotEntity>()
|
||||
.AddSort(new UniquePlaysTotalSort())
|
||||
.AddSort(new LastUpdatedSort())),
|
||||
"likes" => slotQuery.Select(s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
ThumbsUp = this.database.RatedLevels.Count(r => r.SlotId == s.SlotId && r.Rating == 1),
|
||||
})
|
||||
.OrderByDescending(s => s.ThumbsUp)
|
||||
.Select(s => s.Slot),
|
||||
"hearts" => slotQuery.Select(s => new SlotMetadata
|
||||
{
|
||||
Slot = s,
|
||||
Hearts = this.database.HeartedLevels.Count(h => h.SlotId == s.SlotId),
|
||||
})
|
||||
.OrderByDescending(s => s.Hearts)
|
||||
.Select(s => s.Slot),
|
||||
"date" => slotQuery.ApplyOrdering(new SlotSortBuilder<SlotEntity>().AddSort(new FirstUploadedSort())),
|
||||
"plays" => slotQuery.ApplyOrdering(
|
||||
new SlotSortBuilder<SlotEntity>().AddSort(new UniquePlaysTotalSort()).AddSort(new TotalPlaysSort())),
|
||||
_ => slotQuery,
|
||||
};
|
||||
}
|
||||
|
||||
List<ILbpSerializable> slots =
|
||||
(await slotQuery.ToListAsync()).ToSerializableList<SlotEntity, ILbpSerializable>(s =>
|
||||
SlotBase.CreateFromEntity(s, token));
|
||||
return new GenericSerializableList(slots, pageData);
|
||||
}
|
||||
}
|
|
@ -2,13 +2,8 @@
|
|||
using LBPUnion.ProjectLighthouse.Configuration;
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Logging;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -148,95 +143,4 @@ public class CollectionController : ControllerBase
|
|||
|
||||
return this.Ok(await this.GetUserPlaylists(targetUserId));
|
||||
}
|
||||
|
||||
[HttpGet("searches")]
|
||||
[HttpGet("genres")]
|
||||
public async Task<IActionResult> GenresAndSearches()
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
UserEntity? user = await this.database.UserFromGameToken(token);
|
||||
if (user == null) return this.Forbid();
|
||||
|
||||
List<GameCategory> categories = new();
|
||||
|
||||
foreach (Category category in CategoryHelper.Categories.ToList())
|
||||
{
|
||||
if(category is CategoryWithUser categoryWithUser) categories.Add(categoryWithUser.Serialize(this.database, user));
|
||||
else categories.Add(category.Serialize(this.database));
|
||||
}
|
||||
|
||||
return this.Ok(new CategoryListResponse(categories, CategoryHelper.Categories.Count, 0, 1));
|
||||
}
|
||||
|
||||
[HttpGet("searches/{endpointName}")]
|
||||
public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize,
|
||||
[FromQuery] int players = 0,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? labelFilter3 = null,
|
||||
[FromQuery] string? labelFilter4 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] string? adventure = null
|
||||
)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
UserEntity? user = await this.database.UserFromGameToken(token);
|
||||
if (user == null) return this.Forbid();
|
||||
|
||||
Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
|
||||
if (category == null) return this.NotFound();
|
||||
|
||||
Logger.Debug("Found category " + category, LogArea.Category);
|
||||
|
||||
List<SlotEntity> slots;
|
||||
int totalSlots;
|
||||
|
||||
if (category is CategoryWithUser categoryWithUser)
|
||||
{
|
||||
slots = (await categoryWithUser.GetSlots(this.database, user, pageStart, pageSize)
|
||||
.ToListAsync());
|
||||
totalSlots = categoryWithUser.GetTotalSlots(this.database, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
slots = category.GetSlots(this.database, pageStart, pageSize)
|
||||
.ToList();
|
||||
totalSlots = category.GetTotalSlots(this.database);
|
||||
}
|
||||
|
||||
slots = this.filterSlots(slots, players + 1, labelFilter0, labelFilter1, labelFilter2, labelFilter3, labelFilter4, move, adventure);
|
||||
|
||||
return this.Ok(new GenericSlotResponse("results", slots.ToSerializableList(s => SlotBase.CreateFromEntity(s, token)), totalSlots, pageStart + pageSize));
|
||||
}
|
||||
|
||||
private List<SlotEntity> filterSlots(List<SlotEntity> slots, int players, string? labelFilter0 = null, string? labelFilter1 = null, string? labelFilter2 = null, string? labelFilter3 = null, string? labelFilter4 = null, string? move = null, string? adventure = 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 (labelFilter3 != null)
|
||||
slots.RemoveAll(s => !s.AuthorLabels.Split(',').ToList().Contains(labelFilter3));
|
||||
if (labelFilter4 != null)
|
||||
slots.RemoveAll(s => !s.AuthorLabels.Split(',').ToList().Contains(labelFilter4));
|
||||
|
||||
if (move == "noneCan")
|
||||
slots.RemoveAll(s => s.MoveRequired);
|
||||
if (move == "allMust")
|
||||
slots.RemoveAll(s => !s.MoveRequired);
|
||||
|
||||
if (adventure == "noneCan")
|
||||
slots.RemoveAll(s => s.IsAdventurePlanet);
|
||||
if (adventure == "allMust")
|
||||
slots.RemoveAll(s => !s.IsAdventurePlanet);
|
||||
|
||||
return slots;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Helpers;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
|
@ -22,6 +24,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
|||
public class ListController : ControllerBase
|
||||
{
|
||||
private readonly DatabaseContext database;
|
||||
|
||||
public ListController(DatabaseContext database)
|
||||
{
|
||||
this.database = database;
|
||||
|
@ -32,31 +35,30 @@ public class ListController : ControllerBase
|
|||
#region Level Queue (lolcatftw)
|
||||
|
||||
[HttpGet("slots/lolcatftw/{username}")]
|
||||
public async Task<IActionResult> GetQueuedLevels
|
||||
(
|
||||
string username,
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
public async Task<IActionResult> GetQueuedLevels(string username)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
List<SlotBase> queuedLevels = (await this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.Queue)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync())
|
||||
.ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
int targetUserId = await this.database.Users.Where(u => u.Username == username)
|
||||
.Select(u => u.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (targetUserId == 0) return this.BadRequest();
|
||||
|
||||
int total = await this.database.QueuedLevels.CountAsync(q => q.UserId == token.UserId);
|
||||
int start = pageStart + Math.Min(pageSize, 30);
|
||||
pageData.TotalElements = await this.database.QueuedLevels.CountAsync(q => q.UserId == targetUserId);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(queuedLevels, total, start));
|
||||
IQueryable<SlotEntity> baseQuery = this.database.QueuedLevels.Where(h => h.UserId == targetUserId)
|
||||
.OrderByDescending(q => q.QueuedLevelId)
|
||||
.Include(q => q.Slot)
|
||||
.Select(q => q.Slot);
|
||||
|
||||
List<SlotBase> queuedLevels = await baseQuery.GetSlots(token,
|
||||
this.FilterFromRequest(token),
|
||||
pageData,
|
||||
new SlotSortBuilder<SlotEntity>());
|
||||
|
||||
return this.Ok(new GenericSlotResponse(queuedLevels, pageData));
|
||||
}
|
||||
|
||||
[HttpPost("lolcatftw/add/user/{id:int}")]
|
||||
|
@ -102,37 +104,33 @@ public class ListController : ControllerBase
|
|||
#region Hearted Levels
|
||||
|
||||
[HttpGet("favouriteSlots/{username}")]
|
||||
public async Task<IActionResult> GetFavouriteSlots
|
||||
(
|
||||
string username,
|
||||
[FromQuery] int pageStart,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] string? gameFilterType = null,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] bool? move = null,
|
||||
[FromQuery] string? dateFilterType = null
|
||||
)
|
||||
public async Task<IActionResult> GetFavouriteSlots(string username)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (targetUser == null) return this.Forbid();
|
||||
int targetUserId = await this.database.Users.Where(u => u.Username == username)
|
||||
.Select(u => u.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (targetUserId == 0) return this.BadRequest();
|
||||
|
||||
List<SlotBase> heartedLevels = (await this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.FavouriteSlots)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync()).ToSerializableList(s => SlotBase.CreateFromEntity(s, token));
|
||||
|
||||
pageData.TotalElements = await this.database.HeartedLevels.CountAsync(h => h.UserId == targetUserId);
|
||||
|
||||
int total = await this.database.HeartedLevels.CountAsync(q => q.UserId == targetUser.UserId);
|
||||
int start = pageStart + Math.Min(pageSize, 30);
|
||||
IQueryable<SlotEntity> baseQuery = this.database.HeartedLevels.Where(h => h.UserId == targetUserId)
|
||||
.OrderByDescending(h => h.HeartedLevelId)
|
||||
.Include(h => h.Slot)
|
||||
.Select(h => h.Slot);
|
||||
|
||||
return this.Ok(new GenericSlotResponse("favouriteSlots", heartedLevels, total, start));
|
||||
List<SlotBase> heartedLevels = await baseQuery.GetSlots(token,
|
||||
this.FilterFromRequest(token),
|
||||
pageData,
|
||||
new SlotSortBuilder<SlotEntity>());
|
||||
|
||||
return this.Ok(new GenericSlotResponse("favouriteSlots", heartedLevels, pageData));
|
||||
}
|
||||
|
||||
private const int FirstLbp2DeveloperSlotId = 124806; // This is the first known level slot GUID in LBP2. Feel free to change it if a lower one is found.
|
||||
private const int firstLbp2DeveloperSlotId = 124806; // This is the first known level slot GUID in LBP2. Feel free to change it if a lower one is found.
|
||||
|
||||
[HttpPost("favourite/slot/{slotType}/{id:int}")]
|
||||
public async Task<IActionResult> AddFavouriteSlot(string slotType, int id)
|
||||
|
@ -148,7 +146,7 @@ public class ListController : ControllerBase
|
|||
|
||||
if (slotType == "developer")
|
||||
{
|
||||
GameVersion slotGameVersion = (slot.InternalSlotId < FirstLbp2DeveloperSlotId) ? GameVersion.LittleBigPlanet1 : token.GameVersion;
|
||||
GameVersion slotGameVersion = (slot.InternalSlotId < firstLbp2DeveloperSlotId) ? GameVersion.LittleBigPlanet1 : token.GameVersion;
|
||||
slot.GameVersion = slotGameVersion;
|
||||
}
|
||||
|
||||
|
@ -171,7 +169,7 @@ public class ListController : ControllerBase
|
|||
|
||||
if (slotType == "developer")
|
||||
{
|
||||
GameVersion slotGameVersion = (slot.InternalSlotId < FirstLbp2DeveloperSlotId) ? GameVersion.LittleBigPlanet1 : token.GameVersion;
|
||||
GameVersion slotGameVersion = (slot.InternalSlotId < firstLbp2DeveloperSlotId) ? GameVersion.LittleBigPlanet1 : token.GameVersion;
|
||||
slot.GameVersion = slotGameVersion;
|
||||
}
|
||||
|
||||
|
@ -185,26 +183,27 @@ public class ListController : ControllerBase
|
|||
#region Hearted Playlists
|
||||
|
||||
[HttpGet("favouritePlaylists/{username}")]
|
||||
public async Task<IActionResult> GetFavouritePlaylists(string username, [FromQuery] int pageStart, [FromQuery] int pageSize)
|
||||
public async Task<IActionResult> GetFavouritePlaylists(string username)
|
||||
{
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
|
||||
int targetUserId = await this.database.UserIdFromUsername(username);
|
||||
if (targetUserId == 0) return this.Forbid();
|
||||
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
List<GamePlaylist> heartedPlaylists = (await this.database.HeartedPlaylists.Where(p => p.UserId == targetUserId)
|
||||
.Include(p => p.Playlist)
|
||||
.Include(p => p.Playlist.Creator)
|
||||
.OrderByDescending(p => p.HeartedPlaylistId)
|
||||
.Select(p => p.Playlist)
|
||||
.ApplyPagination(pageData)
|
||||
.ToListAsync()).ToSerializableList(GamePlaylist.CreateFromEntity);
|
||||
|
||||
int total = await this.database.HeartedPlaylists.CountAsync(p => p.UserId == targetUserId);
|
||||
pageData.TotalElements = await this.database.HeartedPlaylists.CountAsync(p => p.UserId == targetUserId);
|
||||
|
||||
return this.Ok(new GenericPlaylistResponse<GamePlaylist>("favouritePlaylists", heartedPlaylists)
|
||||
{
|
||||
Total = total,
|
||||
HintStart = pageStart + Math.Min(pageSize, 30),
|
||||
Total = pageData.TotalElements,
|
||||
HintStart = pageData.HintStart,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -241,26 +240,27 @@ public class ListController : ControllerBase
|
|||
#region Users
|
||||
|
||||
[HttpGet("favouriteUsers/{username}")]
|
||||
public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
|
||||
public async Task<IActionResult> GetFavouriteUsers(string username)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (targetUser == null) return this.Forbid();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
int targetUserId = await this.database.Users.Where(u => u.Username == username)
|
||||
.Select(u => u.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (targetUserId == 0) return this.BadRequest();
|
||||
|
||||
pageData.TotalElements = await this.database.HeartedProfiles.CountAsync(h => h.UserId == targetUserId);
|
||||
|
||||
List<GameUser> heartedProfiles = (await this.database.HeartedProfiles.Include(h => h.HeartedUser)
|
||||
.OrderBy(h => h.HeartedProfileId)
|
||||
.Where(h => h.UserId == targetUser.UserId)
|
||||
.Where(h => h.UserId == targetUserId)
|
||||
.Select(h => h.HeartedUser)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ApplyPagination(pageData)
|
||||
.ToListAsync()).ToSerializableList(u => GameUser.CreateFromEntity(u, token.GameVersion));
|
||||
|
||||
int total = await this.database.HeartedProfiles.CountAsync(h => h.UserId == targetUser.UserId);
|
||||
|
||||
return this.Ok(new GenericUserResponse<GameUser>("favouriteUsers", heartedProfiles, total, pageStart + Math.Min(pageSize, 30)));
|
||||
return this.Ok(new GenericUserResponse<GameUser>("favouriteUsers", heartedProfiles, pageData));
|
||||
}
|
||||
|
||||
[HttpPost("favourite/user/{username}")]
|
||||
|
@ -288,85 +288,5 @@ public class ListController : ControllerBase
|
|||
|
||||
return this.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Filtering
|
||||
internal enum ListFilterType // used to collapse code that would otherwise be two separate functions
|
||||
{
|
||||
Queue,
|
||||
FavouriteSlots,
|
||||
}
|
||||
|
||||
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> filterListByRequest(string? gameFilterType, string? dateFilterType, GameVersion version, string username, ListFilterType filterType)
|
||||
{
|
||||
if (version is GameVersion.LittleBigPlanetPSP or GameVersion.Unknown)
|
||||
{
|
||||
return this.database.Slots.ByGameVersion(version, false, true);
|
||||
}
|
||||
|
||||
long oldestTime = dateFilterType switch
|
||||
{
|
||||
"thisWeek" => DateTimeOffset.Now.AddDays(-7).ToUnixTimeMilliseconds(),
|
||||
"thisMonth" => DateTimeOffset.Now.AddDays(-31).ToUnixTimeMilliseconds(),
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
GameVersion gameVersion = getGameFilter(gameFilterType, version);
|
||||
|
||||
// The filtering only cares if this isn't equal to 'both'
|
||||
if (version == GameVersion.LittleBigPlanetVita) gameFilterType = "lbp2";
|
||||
|
||||
if (filterType == ListFilterType.Queue)
|
||||
{
|
||||
IQueryable<QueuedLevelEntity> whereQueuedLevels;
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (gameFilterType == "both")
|
||||
// Get game versions less than the current version
|
||||
// Needs support for LBP3 ("both" = LBP1+2)
|
||||
whereQueuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username)
|
||||
.Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= gameVersion && q.Slot.FirstUploaded >= oldestTime);
|
||||
else
|
||||
// Get game versions exactly equal to gamefiltertype
|
||||
whereQueuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username)
|
||||
.Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion == gameVersion && q.Slot.FirstUploaded >= oldestTime);
|
||||
|
||||
return whereQueuedLevels.OrderByDescending(q => q.QueuedLevelId).Include(q => q.Slot.Creator).Select(q => q.Slot).ByGameVersion(gameVersion, false, false, true);
|
||||
}
|
||||
|
||||
IQueryable<HeartedLevelEntity> whereHeartedLevels;
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (gameFilterType == "both")
|
||||
// Get game versions less than the current version
|
||||
// Needs support for LBP3 ("both" = LBP1+2)
|
||||
whereHeartedLevels = this.database.HeartedLevels.Where(h => h.User.Username == username)
|
||||
.Where(h => (h.Slot.Type == SlotType.User || h.Slot.Type == SlotType.Developer) && !h.Slot.Hidden && h.Slot.GameVersion <= gameVersion && h.Slot.FirstUploaded >= oldestTime);
|
||||
else
|
||||
// Get game versions exactly equal to gamefiltertype
|
||||
whereHeartedLevels = this.database.HeartedLevels.Where(h => h.User.Username == username)
|
||||
.Where(h => (h.Slot.Type == SlotType.User || h.Slot.Type == SlotType.Developer) && !h.Slot.Hidden && h.Slot.GameVersion == gameVersion && h.Slot.FirstUploaded >= oldestTime);
|
||||
|
||||
return whereHeartedLevels.OrderByDescending(h => h.HeartedLevelId).Include(h => h.Slot.Creator).Select(h => h.Slot).ByGameVersion(gameVersion, false, false, true);
|
||||
}
|
||||
#endregion Filtering
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ using LBPUnion.ProjectLighthouse.Helpers;
|
|||
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using LBPUnion.ProjectLighthouse.Types.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -28,7 +28,7 @@ public class ReviewController : ControllerBase
|
|||
|
||||
// LBP1 rating
|
||||
[HttpPost("rate/user/{slotId:int}")]
|
||||
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
|
||||
public async Task<IActionResult> Rate(int slotId, int rating)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
|
@ -57,7 +57,7 @@ public class ReviewController : ControllerBase
|
|||
|
||||
// LBP2 and beyond rating
|
||||
[HttpPost("dpadrate/user/{slotId:int}")]
|
||||
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
|
||||
public async Task<IActionResult> DPadRate(int slotId, int rating)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
|
@ -142,54 +142,47 @@ public class ReviewController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("reviewsFor/user/{slotId:int}")]
|
||||
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
|
||||
public async Task<IActionResult> ReviewsFor(int slotId)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
|
||||
if (slot == null) return this.BadRequest();
|
||||
|
||||
List<GameReview> reviews = (await this.database.Reviews.ByGameVersion(gameVersion, true)
|
||||
List<GameReview> reviews = (await this.database.Reviews
|
||||
.Where(r => r.SlotId == slotId)
|
||||
.OrderByDescending(r => r.ThumbsUp - r.ThumbsDown)
|
||||
.ThenByDescending(r => r.Timestamp)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ApplyPagination(pageData)
|
||||
.ToListAsync()).ToSerializableList(r => GameReview.CreateFromEntity(r, token));
|
||||
|
||||
|
||||
return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageStart + Math.Min(pageSize, 30)));
|
||||
return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageData.HintStart));
|
||||
}
|
||||
|
||||
[HttpGet("reviewsBy/{username}")]
|
||||
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
|
||||
public async Task<IActionResult> ReviewsBy(string username)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
|
||||
GameVersion gameVersion = token.GameVersion;
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
int targetUserId = await this.database.UserIdFromUsername(username);
|
||||
|
||||
if (targetUserId == 0) return this.BadRequest();
|
||||
|
||||
List<GameReview> reviews = (await this.database.Reviews.ByGameVersion(gameVersion, true)
|
||||
List<GameReview> reviews = (await this.database.Reviews
|
||||
.Where(r => r.ReviewerId == targetUserId)
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ApplyPagination(pageData)
|
||||
.ToListAsync()).ToSerializableList(r => GameReview.CreateFromEntity(r, token));
|
||||
|
||||
return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageStart));
|
||||
return this.Ok(new ReviewResponse(reviews, reviews.LastOrDefault()?.Timestamp ?? TimeHelper.TimestampMillis, pageData.HintStart));
|
||||
}
|
||||
|
||||
[HttpPost("rateReview/user/{slotId:int}/{username}")]
|
||||
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
|
||||
public async Task<IActionResult> RateReview(int slotId, string username, int rating = 0)
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
#nullable enable
|
||||
using LBPUnion.ProjectLighthouse.Database;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Filter.Filters;
|
||||
using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
|
||||
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
|
||||
using LBPUnion.ProjectLighthouse.Types.Levels;
|
||||
using LBPUnion.ProjectLighthouse.Types.Filter;
|
||||
using LBPUnion.ProjectLighthouse.Types.Serialization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
|
||||
|
||||
|
@ -25,102 +27,29 @@ public class SearchController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpGet("searchLBP3")]
|
||||
public Task<IActionResult> SearchSlotsLBP3([FromQuery] int pageSize, [FromQuery] int pageStart, [FromQuery] string textFilter,
|
||||
[FromQuery] int? players = 0,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? labelFilter3 = null,
|
||||
[FromQuery] string? labelFilter4 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] string? adventure = null)
|
||||
=> this.SearchSlots(textFilter, pageSize, pageStart, "results", false, players+1, labelFilter0, labelFilter1, labelFilter2, labelFilter3, labelFilter4, move, adventure);
|
||||
public Task<IActionResult> SearchSlotsLBP3([FromQuery] string textFilter)
|
||||
=> this.SearchSlots(textFilter, "results");
|
||||
|
||||
[HttpGet("search")]
|
||||
public async Task<IActionResult> SearchSlots(
|
||||
[FromQuery] string query,
|
||||
[FromQuery] int pageSize,
|
||||
[FromQuery] int pageStart,
|
||||
string? keyName = "slots",
|
||||
bool crosscontrol = false,
|
||||
[FromQuery] int? players = null,
|
||||
[FromQuery] string? labelFilter0 = null,
|
||||
[FromQuery] string? labelFilter1 = null,
|
||||
[FromQuery] string? labelFilter2 = null,
|
||||
[FromQuery] string? labelFilter3 = null,
|
||||
[FromQuery] string? labelFilter4 = null,
|
||||
[FromQuery] string? move = null,
|
||||
[FromQuery] string? adventure = null
|
||||
)
|
||||
public async Task<IActionResult> SearchSlots([FromQuery] string query, string? keyName = "slots")
|
||||
{
|
||||
GameTokenEntity token = this.GetToken();
|
||||
|
||||
if (pageSize <= 0) return this.BadRequest();
|
||||
PaginationData pageData = this.Request.GetPaginationData();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query)) return this.BadRequest();
|
||||
|
||||
query = query.ToLower();
|
||||
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
|
||||
|
||||
string[] keywords = query.Split(" ");
|
||||
queryBuilder.AddFilter(new TextFilter(query));
|
||||
|
||||
IQueryable<SlotEntity> dbQuery = this.database.Slots.ByGameVersion(token.GameVersion, false, true)
|
||||
.Where(s => s.Type == SlotType.User && s.CrossControllerRequired == crosscontrol)
|
||||
.OrderBy(s => !s.TeamPick)
|
||||
.ThenByDescending(s => s.FirstUploaded)
|
||||
.Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable
|
||||
pageData.TotalElements = await this.database.Slots.Where(queryBuilder.Build()).CountAsync();
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (string keyword in keywords)
|
||||
dbQuery = dbQuery.Where
|
||||
(
|
||||
s => s.Name.ToLower().Contains(keyword) ||
|
||||
s.Description.ToLower().Contains(keyword) ||
|
||||
s.Creator!.Username.ToLower().Contains(keyword) ||
|
||||
s.SlotId.ToString().Equals(keyword)
|
||||
);
|
||||
List<SlotBase> slots = await this.database.Slots.Include(s => s.Creator)
|
||||
.GetSlots(token, queryBuilder, pageData, new SlotSortBuilder<SlotEntity>());
|
||||
|
||||
List<SlotEntity> slots = (await dbQuery.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 30))
|
||||
.ToListAsync());
|
||||
|
||||
slots = filterSlots(slots, players, labelFilter0, labelFilter1, labelFilter2, labelFilter3, labelFilter4, move, adventure);
|
||||
|
||||
return this.Ok(new GenericSlotResponse(keyName, slots.ToSerializableList(s => SlotBase.CreateFromEntity(s, token)), await dbQuery.CountAsync(), 0));
|
||||
return this.Ok(new GenericSlotResponse(keyName, slots, pageData));
|
||||
}
|
||||
|
||||
// /LITTLEBIGPLANETPS3_XML?pageStart=1&pageSize=10&resultTypes[]=slot&resultTypes[]=playlist&resultTypes[]=user&adventure=dontCare&textFilter=qwer
|
||||
|
||||
private List<SlotEntity> filterSlots(List<SlotEntity> slots, int? players = null, string? labelFilter0 = null, string? labelFilter1 = null, string? labelFilter2 = null, string? labelFilter3 = null, string? labelFilter4 = null, string? move = null, string? adventure = null)
|
||||
{
|
||||
if (players != 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 (labelFilter3 != null)
|
||||
slots.RemoveAll(s => !s.AuthorLabels.Split(',').ToList().Contains(labelFilter3));
|
||||
if (labelFilter4 != null)
|
||||
slots.RemoveAll(s => !s.AuthorLabels.Split(',').ToList().Contains(labelFilter4));
|
||||
|
||||
if (move == "false")
|
||||
slots.RemoveAll(s => s.MoveRequired);
|
||||
if (move == "only")
|
||||
slots.RemoveAll(s => !s.MoveRequired);
|
||||
|
||||
if (move == "noneCan")
|
||||
slots.RemoveAll(s => s.MoveRequired);
|
||||
if (move == "allMust")
|
||||
slots.RemoveAll(s => !s.MoveRequired);
|
||||
|
||||
if (adventure == "noneCan")
|
||||
slots.RemoveAll(s => s.IsAdventurePlanet);
|
||||
if (adventure == "allMust")
|
||||
slots.RemoveAll(s => !s.IsAdventurePlanet);
|
||||
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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