Sort team picks by the time they were team picked (#980)

This commit is contained in:
Josh 2024-02-23 08:38:02 -06:00 committed by GitHub
parent 983b1e9984
commit a2eaedc85b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 136 additions and 21 deletions

View file

@ -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,

View file

@ -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,

View file

@ -247,7 +247,8 @@ public class SlotsController : ControllerBase
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
SlotSortBuilder<SlotEntity> sortBuilder = new();
sortBuilder.AddSort(new LastUpdatedSort());
sortBuilder.AddSort(new TeamPickSort());
sortBuilder.AddSort(new FirstUploadedSort());
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);

View file

@ -19,5 +19,5 @@ public class TeamPicksCategory : SlotCategory
public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder) =>
database.Slots.Where(queryBuilder.Clone().AddFilter(new TeamPickFilter()).Build())
.ApplyOrdering(new SlotSortBuilder<SlotEntity>().AddSort(new FirstUploadedSort()));
.ApplyOrdering(new SlotSortBuilder<SlotEntity>().AddSort(new TeamPickSort()).AddSort(new FirstUploadedSort()));
}

View file

@ -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,

View file

@ -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();

View file

@ -217,9 +217,9 @@ else
<div class="ui green segment">
<h2>Moderation Options</h2>
@if (Model.Slot?.TeamPick ?? false)
@if ((Model.Slot?.TeamPickTime ?? 0) != 0)
{
<a href="/moderation/slot/@Model.Slot.SlotId/removeTeamPick">
<a href="/moderation/slot/@Model.Slot?.SlotId/removeTeamPick">
<div class="ui pink button">
<i class="star icon"></i>
<span>Remove Team Pick</span>

View file

@ -495,4 +495,62 @@ public class SlotControllerTests
}
}
#endregion
#region Team Picks
[Fact]
public async Task TeamPick_ShouldOnlyIncludeTeamPickedLevels()
{
DatabaseContext db = await MockHelper.GetTestDatabase(new List<SlotEntity>
{
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<OkObjectResult, GenericSlotResponse>();
Assert.Single(slotResponse.Slots);
Assert.Equal(1, slotResponse.Slots.OfType<GameUserSlot>().First().SlotId);
}
[Fact]
public async Task TeamPick_LevelsAreSortedByTimestamp()
{
DatabaseContext db = await MockHelper.GetTestDatabase(new List<SlotEntity>
{
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<OkObjectResult, GenericSlotResponse>();
Assert.Equal(2, slotResponse.Slots.Count);
Assert.Equal(2, slotResponse.Slots.OfType<GameUserSlot>().First().SlotId);
Assert.Equal(1, slotResponse.Slots.OfType<GameUserSlot>().Last().SlotId);
}
#endregion
}

View file

@ -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,
},
};

View file

@ -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));

View file

@ -7,5 +7,5 @@ namespace LBPUnion.ProjectLighthouse.Filter.Filters;
public class TeamPickFilter : ISlotFilter
{
public Expression<Func<SlotEntity, bool>> GetPredicate() => s => s.TeamPick;
public Expression<Func<SlotEntity, bool>> GetPredicate() => s => s.TeamPickTime != 0;
}

View file

@ -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<Func<SlotEntity, dynamic>> GetExpression() => s => s.TeamPickTime;
}

View file

@ -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
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TeamPickTime",
table: "Slots");
migrationBuilder.AddColumn<bool>(
name: "TeamPick",
table: "Slots",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
}
}

View file

@ -472,8 +472,8 @@ namespace ProjectLighthouse.Migrations
b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)");
b.Property<bool>("TeamPick")
.HasColumnType("tinyint(1)");
b.Property<long>("TeamPickTime")
.HasColumnType("bigint");
b.Property<int>("Type")
.HasColumnType("int");

View file

@ -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; }

View file

@ -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,