From a2eaedc85b8fbbebec407362393304f5d7403f06 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 23 Feb 2024 08:38:02 -0600 Subject: [PATCH] Sort team picks by the time they were team picked (#980) --- .../Responses/ApiSlot.cs | 2 +- .../Responses/MinimalApiSlot.cs | 2 +- .../Controllers/Slots/SlotsController.cs | 3 +- .../Types/Categories/TeamPicksCategory.cs | 2 +- .../Moderator/ModerationSlotController.cs | 4 +- .../Pages/LandingPage.cshtml.cs | 5 +- .../Pages/SlotPage.cshtml | 4 +- .../Unit/Controllers/SlotControllerTests.cs | 58 +++++++++++++++++++ .../Controllers/StatisticsControllerTests.cs | 8 +-- ProjectLighthouse.Tests/Unit/FilterTests.cs | 4 +- .../Filter/Filters/TeamPickFilter.cs | 2 +- .../Filter/Sorts/TeamPickSort.cs | 11 ++++ ...214031744_UpdateTeamPickBoolToTimestamp.cs | 44 ++++++++++++++ .../DatabaseContextModelSnapshot.cs | 4 +- .../Types/Entities/Level/SlotEntity.cs | 2 +- .../Types/Serialization/SlotBase.cs | 2 +- 16 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 ProjectLighthouse/Filter/Sorts/TeamPickSort.cs create mode 100644 ProjectLighthouse/Migrations/20240214031744_UpdateTeamPickBoolToTimestamp.cs diff --git a/ProjectLighthouse.Servers.API/Responses/ApiSlot.cs b/ProjectLighthouse.Servers.API/Responses/ApiSlot.cs index b859d8fe..c33aaf40 100644 --- a/ProjectLighthouse.Servers.API/Responses/ApiSlot.cs +++ b/ProjectLighthouse.Servers.API/Responses/ApiSlot.cs @@ -61,7 +61,7 @@ public struct ApiSlot MoveRequired = slot.MoveRequired, FirstUploaded = slot.FirstUploaded, LastUpdated = slot.LastUpdated, - TeamPick = slot.TeamPick, + TeamPick = slot.TeamPickTime != 0, Location = slot.Location, GameVersion = slot.GameVersion, Plays = slot.Plays, diff --git a/ProjectLighthouse.Servers.API/Responses/MinimalApiSlot.cs b/ProjectLighthouse.Servers.API/Responses/MinimalApiSlot.cs index a7bfe963..a5d6701e 100644 --- a/ProjectLighthouse.Servers.API/Responses/MinimalApiSlot.cs +++ b/ProjectLighthouse.Servers.API/Responses/MinimalApiSlot.cs @@ -31,7 +31,7 @@ public struct MinimalApiSlot Type = slot.Type, Name = slot.Name, IconHash = slot.IconHash, - TeamPick = slot.TeamPick, + TeamPick = slot.TeamPickTime != 0, IsAdventure = slot.IsAdventurePlanet, Location = slot.Location, GameVersion = slot.GameVersion, diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs index 519c6742..13f9afa8 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs @@ -247,7 +247,8 @@ public class SlotsController : ControllerBase pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder); SlotSortBuilder sortBuilder = new(); - sortBuilder.AddSort(new LastUpdatedSort()); + sortBuilder.AddSort(new TeamPickSort()); + sortBuilder.AddSort(new FirstUploadedSort()); List slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder); diff --git a/ProjectLighthouse.Servers.GameServer/Types/Categories/TeamPicksCategory.cs b/ProjectLighthouse.Servers.GameServer/Types/Categories/TeamPicksCategory.cs index 4386bb17..606557a7 100644 --- a/ProjectLighthouse.Servers.GameServer/Types/Categories/TeamPicksCategory.cs +++ b/ProjectLighthouse.Servers.GameServer/Types/Categories/TeamPicksCategory.cs @@ -19,5 +19,5 @@ public class TeamPicksCategory : SlotCategory public override IQueryable GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder) => database.Slots.Where(queryBuilder.Clone().AddFilter(new TeamPickFilter()).Build()) - .ApplyOrdering(new SlotSortBuilder().AddSort(new FirstUploadedSort())); + .ApplyOrdering(new SlotSortBuilder().AddSort(new TeamPickSort()).AddSort(new FirstUploadedSort())); } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationSlotController.cs b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationSlotController.cs index 27aabd01..f115dbca 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationSlotController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationSlotController.cs @@ -28,7 +28,7 @@ public class ModerationSlotController : ControllerBase SlotEntity? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id); if (slot == null) return this.NotFound(); - slot.TeamPick = true; + slot.TeamPickTime = TimeHelper.TimestampMillis; // Send webhook with slot.Name and slot.Creator.Username await WebhookHelper.SendWebhook("New Team Pick!", $"The level [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId}) by **{slot.Creator?.Username}** has been team picked"); @@ -51,7 +51,7 @@ public class ModerationSlotController : ControllerBase SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); if (slot == null) return this.NotFound(); - slot.TeamPick = false; + slot.TeamPickTime = 0; // Send a notification to the creator await this.database.SendNotification(slot.CreatorId, diff --git a/ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml.cs index 4b9b2041..f866557b 100644 --- a/ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/LandingPage.cshtml.cs @@ -41,8 +41,9 @@ public class LandingPage : BaseLayout const int maxShownLevels = 5; this.LatestTeamPicks = await this.Database.Slots.Where(s => s.Type == SlotType.User && !s.SubLevel && !s.Hidden) - .Where(s => s.TeamPick) - .OrderByDescending(s => s.FirstUploaded) + .Where(s => s.TeamPickTime != 0) + .OrderByDescending(s => s.TeamPickTime) + .ThenByDescending(s => s.FirstUploaded) .Take(maxShownLevels) .Include(s => s.Creator) .ToListAsync(); diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml index 5bff4e62..44ade1fb 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml @@ -217,9 +217,9 @@ else

Moderation Options

- @if (Model.Slot?.TeamPick ?? false) + @if ((Model.Slot?.TeamPickTime ?? 0) != 0) { - +
Remove Team Pick diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs index f55c7514..7408fcf5 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs @@ -495,4 +495,62 @@ public class SlotControllerTests } } #endregion + + #region Team Picks + [Fact] + public async Task TeamPick_ShouldOnlyIncludeTeamPickedLevels() + { + DatabaseContext db = await MockHelper.GetTestDatabase(new List + { + new() + { + SlotId = 1, + CreatorId = 1, + TeamPickTime = 1, + }, + new() + { + SlotId = 2, + CreatorId = 1, + TeamPickTime = 0, + }, + }); + SlotsController controller = new(db); + controller.SetupTestController(); + + IActionResult result = await controller.TeamPickedSlots(); + GenericSlotResponse slotResponse = result.CastTo(); + Assert.Single(slotResponse.Slots); + Assert.Equal(1, slotResponse.Slots.OfType().First().SlotId); + } + + [Fact] + public async Task TeamPick_LevelsAreSortedByTimestamp() + { + DatabaseContext db = await MockHelper.GetTestDatabase(new List + { + new() + { + SlotId = 1, + CreatorId = 1, + TeamPickTime = 1, + }, + new() + { + SlotId = 2, + CreatorId = 1, + TeamPickTime = 5, + }, + }); + SlotsController controller = new(db); + controller.SetupTestController(); + + IActionResult result = await controller.TeamPickedSlots(); + + GenericSlotResponse slotResponse = result.CastTo(); + Assert.Equal(2, slotResponse.Slots.Count); + Assert.Equal(2, slotResponse.Slots.OfType().First().SlotId); + Assert.Equal(1, slotResponse.Slots.OfType().Last().SlotId); + } + #endregion } diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs index 26598f67..5335b2cf 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs @@ -51,7 +51,7 @@ public class StatisticsControllerTests { SlotId = 3, CreatorId = 1, - TeamPick = true, + TeamPickTime = 1, }, }; await using DatabaseContext db = await MockHelper.GetTestDatabase(slots); @@ -90,7 +90,7 @@ public class StatisticsControllerTests { SlotId = 3, CreatorId = 1, - TeamPick = true, + TeamPickTime = 1, GameVersion = GameVersion.LittleBigPlanet2, }, }; @@ -130,7 +130,7 @@ public class StatisticsControllerTests { SlotId = 3, CreatorId = 1, - TeamPick = true, + TeamPickTime = 1, GameVersion = GameVersion.LittleBigPlanet1, }, }; @@ -168,7 +168,7 @@ public class StatisticsControllerTests { SlotId = 3, CreatorId = 1, - TeamPick = true, + TeamPickTime = 1, GameVersion = GameVersion.LittleBigPlanet2, }, }; diff --git a/ProjectLighthouse.Tests/Unit/FilterTests.cs b/ProjectLighthouse.Tests/Unit/FilterTests.cs index 2e8941fa..a3eae2e9 100644 --- a/ProjectLighthouse.Tests/Unit/FilterTests.cs +++ b/ProjectLighthouse.Tests/Unit/FilterTests.cs @@ -729,7 +729,7 @@ public class FilterTests SlotEntity slot = new() { - TeamPick = true, + TeamPickTime = 1, }; Assert.True(teamPickFunc(slot)); @@ -743,7 +743,7 @@ public class FilterTests SlotEntity slot = new() { - TeamPick = false, + TeamPickTime = 0, }; Assert.False(teamPickFunc(slot)); diff --git a/ProjectLighthouse/Filter/Filters/TeamPickFilter.cs b/ProjectLighthouse/Filter/Filters/TeamPickFilter.cs index 7a80d050..eb77c5ea 100644 --- a/ProjectLighthouse/Filter/Filters/TeamPickFilter.cs +++ b/ProjectLighthouse/Filter/Filters/TeamPickFilter.cs @@ -7,5 +7,5 @@ namespace LBPUnion.ProjectLighthouse.Filter.Filters; public class TeamPickFilter : ISlotFilter { - public Expression> GetPredicate() => s => s.TeamPick; + public Expression> GetPredicate() => s => s.TeamPickTime != 0; } \ No newline at end of file diff --git a/ProjectLighthouse/Filter/Sorts/TeamPickSort.cs b/ProjectLighthouse/Filter/Sorts/TeamPickSort.cs new file mode 100644 index 00000000..4c9e32ef --- /dev/null +++ b/ProjectLighthouse/Filter/Sorts/TeamPickSort.cs @@ -0,0 +1,11 @@ +using System; +using System.Linq.Expressions; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Filter.Sorts; + +namespace LBPUnion.ProjectLighthouse.Filter.Sorts; + +public class TeamPickSort : ISlotSort +{ + public Expression> GetExpression() => s => s.TeamPickTime; +} \ No newline at end of file diff --git a/ProjectLighthouse/Migrations/20240214031744_UpdateTeamPickBoolToTimestamp.cs b/ProjectLighthouse/Migrations/20240214031744_UpdateTeamPickBoolToTimestamp.cs new file mode 100644 index 00000000..262f57e6 --- /dev/null +++ b/ProjectLighthouse/Migrations/20240214031744_UpdateTeamPickBoolToTimestamp.cs @@ -0,0 +1,44 @@ +using LBPUnion.ProjectLighthouse.Database; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240214031744_UpdateTeamPickBoolToTimestamp")] + public partial class UpdateTeamPickBoolToTimestamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn(name: "TeamPickTime", + table: "Slots", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.Sql("UPDATE `Slots` SET TeamPickTime = 1 WHERE TeamPick = 1"); + + migrationBuilder.DropColumn( + name: "TeamPick", + table: "Slots"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TeamPickTime", + table: "Slots"); + + migrationBuilder.AddColumn( + name: "TeamPick", + table: "Slots", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs index 4374c7c0..5da0015e 100644 --- a/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs @@ -472,8 +472,8 @@ namespace ProjectLighthouse.Migrations b.Property("SubLevel") .HasColumnType("tinyint(1)"); - b.Property("TeamPick") - .HasColumnType("tinyint(1)"); + b.Property("TeamPickTime") + .HasColumnType("bigint"); b.Property("Type") .HasColumnType("int"); diff --git a/ProjectLighthouse/Types/Entities/Level/SlotEntity.cs b/ProjectLighthouse/Types/Entities/Level/SlotEntity.cs index 94716f19..693bf4aa 100644 --- a/ProjectLighthouse/Types/Entities/Level/SlotEntity.cs +++ b/ProjectLighthouse/Types/Entities/Level/SlotEntity.cs @@ -102,7 +102,7 @@ public class SlotEntity public long LastUpdated { get; set; } - public bool TeamPick { get; set; } + public long TeamPickTime { get; set; } public GameVersion GameVersion { get; set; } diff --git a/ProjectLighthouse/Types/Serialization/SlotBase.cs b/ProjectLighthouse/Types/Serialization/SlotBase.cs index ea22a5c7..49d3ce60 100644 --- a/ProjectLighthouse/Types/Serialization/SlotBase.cs +++ b/ProjectLighthouse/Types/Serialization/SlotBase.cs @@ -84,7 +84,7 @@ public abstract class SlotBase : ILbpSerializable InitiallyLocked = slot.InitiallyLocked, RootLevel = slot.RootLevel, IsShareable = slot.Shareable, - IsTeamPicked = slot.TeamPick, + IsTeamPicked = slot.TeamPickTime != 0, FirstUploaded = slot.FirstUploaded, LastUpdated = slot.LastUpdated, IsCrossControlRequired = slot.CrossControllerRequired,