diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs index ed152b9a..84744a7c 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs @@ -1,5 +1,6 @@ #nullable enable using LBPUnion.ProjectLighthouse.Extensions; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; @@ -25,7 +26,16 @@ public class ListController : ControllerBase #region Level Queue (lolcatftw) [HttpGet("slots/lolcatftw/{username}")] - public async Task GetQueuedLevels(string username, [FromQuery] int pageSize, [FromQuery] int pageStart) + public async Task 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 + ) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); @@ -34,11 +44,7 @@ public class ListController : ControllerBase GameVersion gameVersion = token.GameVersion; - IEnumerable queuedLevels = this.database.QueuedLevels.Where(q => q.User.Username == username) - .Include(q => q.Slot.Creator) - .Include(q => q.Slot.Location) - .Select(q => q.Slot) - .ByGameVersion(gameVersion) + IEnumerable queuedLevels = this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.Queue) .Skip(Math.Max(0, pageStart - 1)) .Take(Math.Min(pageSize, 30)) .AsEnumerable(); @@ -98,7 +104,16 @@ public class ListController : ControllerBase #region Hearted Levels [HttpGet("favouriteSlots/{username}")] - public async Task GetFavouriteSlots(string username, [FromQuery] int pageSize, [FromQuery] int pageStart) + public async Task 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 + ) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); @@ -110,11 +125,7 @@ public class ListController : ControllerBase User? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); if (targetUser == null) return this.StatusCode(403, ""); - IEnumerable heartedLevels = this.database.HeartedLevels.Where(q => q.UserId == targetUser.UserId) - .Include(q => q.Slot.Creator) - .Include(q => q.Slot.Location) - .Select(q => q.Slot) - .ByGameVersion(gameVersion) + IEnumerable heartedLevels = this.filterListByRequest(gameFilterType, dateFilterType, token.GameVersion, username, ListFilterType.FavouriteSlots) .Skip(Math.Max(0, pageStart - 1)) .Take(Math.Min(pageSize, 30)) .AsEnumerable(); @@ -131,29 +142,51 @@ public class ListController : ControllerBase ); } - [HttpPost("favourite/slot/user/{id:int}")] - public async Task AddFavouriteSlot(int id) + 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 AddFavouriteSlot(string slotType, int id) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); + if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); + + if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); if (slot == null) return this.NotFound(); + if (slotType == "developer") + { + GameVersion slotGameVersion = (slot.InternalSlotId < FirstLbp2DeveloperSlotId) ? GameVersion.LittleBigPlanet1 : token.GameVersion; + slot.GameVersion = slotGameVersion; + } + await this.database.HeartLevel(token.UserId, slot); return this.Ok(); } - [HttpPost("unfavourite/slot/user/{id:int}")] - public async Task RemoveFavouriteSlot(int id) + [HttpPost("unfavourite/slot/{slotType}/{id:int}")] + public async Task RemoveFavouriteSlot(string slotType, int id) { GameToken? token = await this.database.GameTokenFromRequest(this.Request); if (token == null) return this.StatusCode(403, ""); + if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); + + if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); if (slot == null) return this.NotFound(); + if (slotType == "developer") + { + GameVersion slotGameVersion = (slot.InternalSlotId < FirstLbp2DeveloperSlotId) ? GameVersion.LittleBigPlanet1 : token.GameVersion; + slot.GameVersion = slotGameVersion; + } + await this.database.UnheartLevel(token.UserId, slot); return this.Ok(); @@ -178,9 +211,10 @@ public class ListController : ControllerBase IEnumerable heartedProfiles = this.database.HeartedProfiles.Include (q => q.HeartedUser) + .OrderBy(q => q.HeartedProfileId) + .Where(q => q.UserId == targetUser.UserId) .Include(q => q.HeartedUser.Location) .Select(q => q.HeartedUser) - .Where(q => q.UserId == targetUser.UserId) .Skip(Math.Max(0, pageStart - 1)) .Take(Math.Min(pageSize, 30)) .AsEnumerable(); @@ -227,4 +261,80 @@ public class ListController : ControllerBase #endregion -} \ No newline at end of file + #region Filtering + enum ListFilterType // used to collapse code that would otherwise be two separate functions + { + Queue, + FavouriteSlots, + } + + private GameVersion getGameFilter(string? gameFilterType, GameVersion version) + { + if (version == GameVersion.LittleBigPlanetVita) return GameVersion.LittleBigPlanetVita; + if (version == GameVersion.LittleBigPlanetPSP) return GameVersion.LittleBigPlanetPSP; + + return gameFilterType switch + { + "lbp1" => GameVersion.LittleBigPlanet1, + "lbp2" => GameVersion.LittleBigPlanet2, + "lbp3" => GameVersion.LittleBigPlanet3, + "both" => GameVersion.LittleBigPlanet2, // LBP2 default option + null => GameVersion.LittleBigPlanet1, + _ => GameVersion.Unknown, + }; + } + + private IQueryable filterListByRequest(string? gameFilterType, string? dateFilterType, GameVersion version, string username, ListFilterType filterType) + { + 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 = this.getGameFilter(gameFilterType, version); + + if (filterType == ListFilterType.Queue) + { + IQueryable 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).Include(q => q.Slot.Location).Select(q => q.Slot).ByGameVersion(gameVersion, false, false, true); + } else + { + IQueryable 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).Include(h => h.Slot.Location).Select(h => h.Slot).ByGameVersion(gameVersion, false, false, true); + } + } + #endregion Filtering +} diff --git a/ProjectLighthouse/Extensions/DatabaseExtensions.cs b/ProjectLighthouse/Extensions/DatabaseExtensions.cs index b1335ca1..319c65ea 100644 --- a/ProjectLighthouse/Extensions/DatabaseExtensions.cs +++ b/ProjectLighthouse/Extensions/DatabaseExtensions.cs @@ -16,9 +16,9 @@ public static class DatabaseExtensions => set.AsQueryable().ByGameVersion(gameVersion, includeSublevels, includeCreatorAndLocation); public static IQueryable ByGameVersion - (this IQueryable query, GameVersion gameVersion, bool includeSublevels = false, bool includeCreatorAndLocation = false) + (this IQueryable query, GameVersion gameVersion, bool includeSublevels = false, bool includeCreatorAndLocation = false, bool includeDeveloperLevels = false) { - query = query.Where(s => s.Type == SlotType.User); + query = query.Where(s => (s.Type == SlotType.User) || (s.Type == SlotType.Developer && includeDeveloperLevels)); if (includeCreatorAndLocation) { diff --git a/ProjectLighthouse/Levels/Categories/HeartedCategory.cs b/ProjectLighthouse/Levels/Categories/HeartedCategory.cs index 556a4a29..b7e344d9 100644 --- a/ProjectLighthouse/Levels/Categories/HeartedCategory.cs +++ b/ProjectLighthouse/Levels/Categories/HeartedCategory.cs @@ -15,13 +15,26 @@ public class HeartedCategory : CategoryWithUser public override string Description { get; set; } = "Levels you've hearted in the past"; public override string IconHash { get; set; } = "g820607"; public override string Endpoint { get; set; } = "hearted"; - public override Slot? GetPreviewSlot(Database database, User user) => database.HeartedLevels.FirstOrDefault(h => h.UserId == user.UserId)?.Slot; - public override int GetTotalSlots(Database database, User user) => database.HeartedLevels.Count(h => h.UserId == user.UserId); + public override Slot? GetPreviewSlot(Database database, User user) // note: developer slots act up in LBP3 when listed here, so I omitted it + => database.HeartedLevels.Where(h => h.UserId == user.UserId) + .Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3) + .OrderByDescending(h => h.HeartedLevelId) + .Include(h => h.Slot.Creator) + .Include(h => h.Slot.Location) + .Select(h => h.Slot) + .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) + .FirstOrDefault(); + public override IEnumerable GetSlots(Database database, User user, int pageStart, int pageSize) => database.HeartedLevels.Where(h => h.UserId == user.UserId) - .Include(h => h.Slot) + .Where(h => h.Slot.Type == SlotType.User && !h.Slot.Hidden && h.Slot.GameVersion <= GameVersion.LittleBigPlanet3) + .OrderByDescending(h => h.HeartedLevelId) + .Include(h => h.Slot.Creator) + .Include(h => h.Slot.Location) .Select(h => h.Slot) - .ByGameVersion(GameVersion.LittleBigPlanet3) + .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .Skip(Math.Max(0, pageStart)) .Take(Math.Min(pageSize, 20)); + + public override int GetTotalSlots(Database database, User user) => database.HeartedLevels.Count(h => h.UserId == user.UserId); } \ No newline at end of file diff --git a/ProjectLighthouse/Levels/Categories/QueueCategory.cs b/ProjectLighthouse/Levels/Categories/QueueCategory.cs index 62941c6e..231ef8a3 100644 --- a/ProjectLighthouse/Levels/Categories/QueueCategory.cs +++ b/ProjectLighthouse/Levels/Categories/QueueCategory.cs @@ -14,17 +14,25 @@ public class QueueCategory : CategoryWithUser public override string Name { get; set; } = "My Queue"; public override string Description { get; set; } = "Your queued levels"; public override string IconHash { get; set; } = "g820614"; - public override string Endpoint { get; set; } = "queue"; - public override Slot? GetPreviewSlot(Database database, User user) - => database.QueuedLevels.Include(q => q.Slot).FirstOrDefault(q => q.UserId == user.UserId)?.Slot; - public override IEnumerable GetSlots(Database database, User user, int pageStart, int pageSize) - => database.QueuedLevels.Include - (q => q.Slot) + => database.QueuedLevels.Where(q => q.UserId == user.UserId) + .Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3) + .OrderByDescending(q => q.QueuedLevelId) + .Include(q => q.Slot.Creator) .Include(q => q.Slot.Location) .Select(q => q.Slot) - .ByGameVersion(GameVersion.LittleBigPlanet3) + .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) + .FirstOrDefault(); + + public override IEnumerable GetSlots(Database database, User user, int pageStart, int pageSize) + => database.QueuedLevels.Where(q => q.UserId == user.UserId) + .Where(q => q.Slot.Type == SlotType.User && !q.Slot.Hidden && q.Slot.GameVersion <= GameVersion.LittleBigPlanet3) + .OrderByDescending(q => q.QueuedLevelId) + .Include(q => q.Slot.Creator) + .Include(q => q.Slot.Location) + .Select(q => q.Slot) + .ByGameVersion(GameVersion.LittleBigPlanet3, false, false, true) .Skip(Math.Max(0, pageStart - 1)) .Take(Math.Min(pageSize, 20)); diff --git a/ProjectLighthouse/PlayerData/Profiles/HeartedProfile.cs b/ProjectLighthouse/PlayerData/Profiles/HeartedProfile.cs index 04a2dc92..a62c2015 100644 --- a/ProjectLighthouse/PlayerData/Profiles/HeartedProfile.cs +++ b/ProjectLighthouse/PlayerData/Profiles/HeartedProfile.cs @@ -6,8 +6,6 @@ namespace LBPUnion.ProjectLighthouse.PlayerData.Profiles; public class HeartedProfile { - // ReSharper disable once UnusedMember.Global - [Obsolete($"Use {nameof(HeartedUserId)} instead, this is a key which you should never need to use.")] [Key] public int HeartedProfileId { get; set; }