mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-05-14 13:52:28 +00:00
Fix bug where users can't be deleted (#648)
* Add username to mod cases if user is deleted * Add timezone package to docker container * Remove extra space in migration sql statement * Changes from self-review
This commit is contained in:
parent
2c2f31ad38
commit
4559d26a54
15 changed files with 170 additions and 83 deletions
|
@ -3,7 +3,7 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "7.0.1",
|
||||
"version": "7.0.2",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
|
|
@ -24,7 +24,7 @@ adduser -S lighthouse -G lighthouse -h /lighthouse --uid 1001 && \
|
|||
mkdir -p /lighthouse/data && \
|
||||
mkdir -p /lighthouse/app && \
|
||||
mkdir -p /lighthouse/temp && \
|
||||
apk add --no-cache icu-libs su-exec
|
||||
apk add --no-cache icu-libs su-exec tzdata
|
||||
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ public class ModerationCaseController : ControllerBase
|
|||
|
||||
@case.DismissedAt = DateTime.Now;
|
||||
@case.DismisserId = user.UserId;
|
||||
@case.DismisserUsername = user.Username;
|
||||
|
||||
@case.Processed = false;
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ public class NewCasePage : BaseLayout
|
|||
if (type == null) return this.BadRequest();
|
||||
if (affectedId == null) return this.BadRequest();
|
||||
|
||||
this.Type = (CaseType)type;
|
||||
this.AffectedId = (int)affectedId;
|
||||
this.Type = type.Value;
|
||||
this.AffectedId = affectedId.Value;
|
||||
|
||||
return this.Page();
|
||||
}
|
||||
|
@ -38,19 +38,19 @@ public class NewCasePage : BaseLayout
|
|||
reason ??= string.Empty;
|
||||
modNotes ??= string.Empty;
|
||||
|
||||
// this is fucking ugly
|
||||
// if id is invalid then return bad request
|
||||
if (!(await ((CaseType)type).IsIdValid((int)affectedId, this.Database))) return this.BadRequest();
|
||||
if (!await type.Value.IsIdValid((int)affectedId, this.Database)) return this.BadRequest();
|
||||
|
||||
ModerationCase @case = new()
|
||||
{
|
||||
Type = (CaseType)type,
|
||||
Type = type.Value,
|
||||
Reason = reason,
|
||||
ModeratorNotes = modNotes,
|
||||
ExpiresAt = expires,
|
||||
CreatedAt = DateTime.Now,
|
||||
CreatorId = user.UserId,
|
||||
AffectedId = (int)affectedId,
|
||||
CreatorUsername = user.Username,
|
||||
AffectedId = affectedId.Value,
|
||||
};
|
||||
|
||||
this.Database.Cases.Add(@case);
|
||||
|
|
|
@ -21,24 +21,44 @@
|
|||
|
||||
@if (Model.Dismissed)
|
||||
{
|
||||
Debug.Assert(Model.Dismisser != null);
|
||||
Debug.Assert(Model.DismissedAt != null);
|
||||
|
||||
@if (Model.Dismisser != null)
|
||||
{
|
||||
<h3 class="ui @color header">
|
||||
This case was dismissed by <a href="/user/@Model.Dismisser.UserId">@Model.DismisserUsername</a> on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt").
|
||||
</h3>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h3 class="ui @color header">
|
||||
This case was dismissed by @Model.DismisserUsername on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt").
|
||||
</h3>
|
||||
}
|
||||
|
||||
<h3 class="ui @color header">
|
||||
This case was dismissed by <a href="/user/@Model.Dismisser.UserId">@Model.Dismisser.Username</a> on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt").
|
||||
</h3>
|
||||
}
|
||||
else if (Model.Expired && Model.ExpiresAt != null)
|
||||
else if (Model.Expired)
|
||||
{
|
||||
<h3 class="ui @color header">
|
||||
This case expired on @TimeZoneInfo.ConvertTime(Model.ExpiresAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt").
|
||||
This case expired on @TimeZoneInfo.ConvertTime(Model.ExpiresAt!.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt").
|
||||
</h3>
|
||||
}
|
||||
|
||||
@if (Model.Creator != null && Model.Creator.Username.Length != 0)
|
||||
{
|
||||
<span>
|
||||
Case created by <a href="/user/@Model.Creator.UserId">@Model.Creator.Username</a>
|
||||
on @TimeZoneInfo.ConvertTime(Model.CreatedAt, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt")
|
||||
</span><br>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>
|
||||
Case created by @Model.CreatorUsername
|
||||
on @TimeZoneInfo.ConvertTime(Model.CreatedAt, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt")
|
||||
</span><br>
|
||||
}
|
||||
|
||||
<span>
|
||||
Case created by <a href="/user/@Model.Creator!.UserId">@Model.Creator.Username</a>
|
||||
on @TimeZoneInfo.ConvertTime(Model.CreatedAt, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt")
|
||||
</span><br>
|
||||
|
||||
@if (Model.Type.AffectsLevel())
|
||||
{
|
||||
|
|
|
@ -48,6 +48,6 @@ public static class CaseTypeExtensions
|
|||
if (type.AffectsUser()) return await database.Users.Has(u => u.UserId == affectedId);
|
||||
if (type.AffectsLevel()) return await database.Slots.Has(u => u.SlotId == affectedId);
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
throw new ArgumentOutOfRangeException(nameof(type));
|
||||
}
|
||||
}
|
|
@ -30,14 +30,18 @@ public class ModerationCase
|
|||
|
||||
public DateTime? DismissedAt { get; set; }
|
||||
public bool Dismissed => this.DismissedAt != null;
|
||||
|
||||
public int? DismisserId { get; set; }
|
||||
public string? DismisserUsername { get; set; }
|
||||
|
||||
[ForeignKey(nameof(DismisserId))]
|
||||
public User? Dismisser { get; set; }
|
||||
public virtual User? Dismisser { get; set; }
|
||||
|
||||
public int CreatorId { get; set; }
|
||||
|
||||
public required string CreatorUsername { get; set; }
|
||||
|
||||
[ForeignKey(nameof(CreatorId))]
|
||||
public User? Creator { get; set; }
|
||||
public virtual User? Creator { get; set; }
|
||||
|
||||
public int AffectedId { get; set; }
|
||||
|
||||
|
@ -54,24 +58,4 @@ public class ModerationCase
|
|||
return database.Slots.FirstOrDefaultAsync(u => u.SlotId == this.AffectedId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Case creators
|
||||
#region Level
|
||||
|
||||
#endregion
|
||||
|
||||
#region User
|
||||
public static ModerationCase NewBanCase(int caseCreator, int userId, string reason, string modNotes, DateTime caseExpires)
|
||||
=> new()
|
||||
{
|
||||
Type = CaseType.UserBan,
|
||||
Reason = $"Banned for reason '{reason}'\nModeration notes: {modNotes}",
|
||||
CreatorId = caseCreator,
|
||||
CreatedAt = DateTime.Now,
|
||||
ExpiresAt = caseExpires,
|
||||
AffectedId = userId,
|
||||
};
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
|
@ -517,6 +517,16 @@ public class Database : DbContext
|
|||
LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId);
|
||||
if (lastContact != null) this.LastContacts.Remove(lastContact);
|
||||
|
||||
foreach (ModerationCase modCase in await this.Cases
|
||||
.Where(c => c.CreatorId == user.UserId || c.DismisserId == user.UserId)
|
||||
.ToListAsync())
|
||||
{
|
||||
if(modCase.DismisserId == user.UserId)
|
||||
modCase.DismisserId = null;
|
||||
if(modCase.CreatorId == user.UserId)
|
||||
modCase.CreatorId = await SlotHelper.GetPlaceholderUserId(this);
|
||||
}
|
||||
|
||||
foreach (Slot slot in this.Slots.Where(s => s.CreatorId == user.UserId)) await this.RemoveSlot(slot, false);
|
||||
|
||||
this.HeartedProfiles.RemoveRange(this.HeartedProfiles.Where(h => h.UserId == user.UserId));
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
namespace LBPUnion.ProjectLighthouse.Helpers;
|
||||
|
||||
public static class LocalizationHelper
|
||||
{}
|
|
@ -41,6 +41,56 @@ public static class SlotHelper
|
|||
|
||||
private static readonly SemaphoreSlim semaphore = new(1, 1);
|
||||
|
||||
private static async Task<int> GetPlaceholderLocationId(Database database)
|
||||
{
|
||||
Location? devLocation = await database.Locations.FirstOrDefaultAsync(l => l.Id == 1);
|
||||
|
||||
if (devLocation != null) return devLocation.Id;
|
||||
|
||||
await semaphore.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
try
|
||||
{
|
||||
devLocation = new Location
|
||||
{
|
||||
Id = 1,
|
||||
};
|
||||
database.Locations.Add(devLocation);
|
||||
return devLocation.Id;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<int> GetPlaceholderUserId(Database database)
|
||||
{
|
||||
int devCreatorId = await database.Users.Where(u => u.Username.Length == 0)
|
||||
.Select(u => u.UserId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (devCreatorId != 0) return devCreatorId;
|
||||
|
||||
await semaphore.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
try
|
||||
{
|
||||
User devCreator = new()
|
||||
{
|
||||
Username = "",
|
||||
PermissionLevel = PermissionLevel.Banned,
|
||||
Biography = "Placeholder author of story levels",
|
||||
BannedReason = "Banned to not show in users list",
|
||||
LocationId = await GetPlaceholderLocationId(database),
|
||||
};
|
||||
database.Users.Add(devCreator);
|
||||
await database.SaveChangesAsync();
|
||||
return devCreator.UserId;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<int> GetPlaceholderSlotId(Database database, int guid, SlotType slotType)
|
||||
{
|
||||
int slotId = await database.Slots.Where(s => s.Type == slotType && s.InternalSlotId == guid).Select(s => s.SlotId).FirstOrDefaultAsync();
|
||||
|
@ -58,31 +108,7 @@ public static class SlotHelper
|
|||
|
||||
if (slotId != 0) return slotId;
|
||||
|
||||
Location? devLocation = await database.Locations.FirstOrDefaultAsync(l => l.Id == 1);
|
||||
if (devLocation == null)
|
||||
{
|
||||
devLocation = new Location
|
||||
{
|
||||
Id = 1,
|
||||
};
|
||||
database.Locations.Add(devLocation);
|
||||
}
|
||||
|
||||
int devCreatorId = await database.Users.Where(u => u.Username.Length == 0).Select(u => u.UserId).FirstOrDefaultAsync();
|
||||
if (devCreatorId == 0)
|
||||
{
|
||||
User devCreator = new()
|
||||
{
|
||||
Username = "",
|
||||
PermissionLevel = PermissionLevel.Banned,
|
||||
Biography = "Placeholder author of story levels",
|
||||
BannedReason = "Banned to not show in users list",
|
||||
LocationId = devLocation.Id,
|
||||
};
|
||||
database.Users.Add(devCreator);
|
||||
await database.SaveChangesAsync();
|
||||
devCreatorId = devCreator.UserId;
|
||||
}
|
||||
int devCreatorId = await GetPlaceholderUserId(database);
|
||||
|
||||
Slot slot = new()
|
||||
{
|
||||
|
@ -90,7 +116,7 @@ public static class SlotHelper
|
|||
Description = $"Placeholder for {slotType} type level",
|
||||
CreatorId = devCreatorId,
|
||||
InternalSlotId = guid,
|
||||
LocationId = devLocation.Id,
|
||||
LocationId = await GetPlaceholderLocationId(database),
|
||||
Type = slotType,
|
||||
};
|
||||
|
||||
|
@ -103,5 +129,4 @@ public static class SlotHelper
|
|||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,13 +4,13 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Levels.Categories;
|
||||
|
||||
public class HighestRatedCategory : Category
|
||||
{
|
||||
Random rand = new();
|
||||
public override string Name { get; set; } = "Highest Rated";
|
||||
public override string Description { get; set; } = "Community Highest Rated content";
|
||||
public override string IconHash { get; set; } = "g820603";
|
||||
|
@ -21,7 +21,7 @@ public class HighestRatedCategory : Category
|
|||
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Thumbsup)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.ThenBy(_ => EF.Functions.Random())
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 20));
|
||||
public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.Type == SlotType.User);
|
||||
|
|
|
@ -4,23 +4,23 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Levels.Categories;
|
||||
|
||||
public class MostHeartedCategory : Category
|
||||
{
|
||||
Random rand = new();
|
||||
public override string Name { get; set; } = "Most Hearted";
|
||||
public override string Description { get; set; } = "The Most Hearted Content";
|
||||
public override string IconHash { get; set; } = "g820607";
|
||||
public override string Endpoint { get; set; } = "mostHearted";
|
||||
public override Slot? GetPreviewSlot(Database database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().OrderByDescending(s => s.Hearts).FirstOrDefault();
|
||||
public override Slot? GetPreviewSlot(Database database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().MaxBy(s => s.Hearts);
|
||||
public override IEnumerable<Slot> GetSlots
|
||||
(Database database, int pageStart, int pageSize)
|
||||
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.Hearts)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.ThenBy(_ => EF.Functions.Random())
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 20));
|
||||
public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.Type == SlotType.User);
|
||||
|
|
|
@ -4,24 +4,23 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using LBPUnion.ProjectLighthouse.Extensions;
|
||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LBPUnion.ProjectLighthouse.Levels.Categories;
|
||||
|
||||
public class MostPlayedCategory : Category
|
||||
{
|
||||
Random rand = new();
|
||||
public override string Name { get; set; } = "Most Played";
|
||||
public override string Description { get; set; } = "The most played content";
|
||||
public override string IconHash { get; set; } = "g820608";
|
||||
public override string Endpoint { get; set; } = "mostUniquePlays";
|
||||
public override Slot? GetPreviewSlot(Database database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().OrderByDescending(s => s.PlaysUnique).FirstOrDefault();
|
||||
public override Slot? GetPreviewSlot(Database database) => database.Slots.Where(s => s.Type == SlotType.User).AsEnumerable().MaxBy(s => s.PlaysUnique);
|
||||
public override IEnumerable<Slot> GetSlots
|
||||
(Database database, int pageStart, int pageSize)
|
||||
=> database.Slots.ByGameVersion(GameVersion.LittleBigPlanet3, false, true)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(s => s.PlaysUnique)
|
||||
.ThenBy(_ => rand.Next())
|
||||
.ThenBy(_ => EF.Functions.Random())
|
||||
.Skip(Math.Max(0, pageStart - 1))
|
||||
.Take(Math.Min(pageSize, 20));
|
||||
public override int GetTotalSlots(Database database) => database.Slots.Count(s => s.Type == SlotType.User);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
using LBPUnion.ProjectLighthouse;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ProjectLighthouse.Migrations
|
||||
{
|
||||
[DbContext(typeof(Database))]
|
||||
[Migration("20230127021453_AddUsernameToCaseTable")]
|
||||
public partial class AddUsernameToCaseTable : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CreatorUsername",
|
||||
table: "Cases",
|
||||
type: "longtext",
|
||||
nullable: false,
|
||||
defaultValue: "")
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DismisserUsername",
|
||||
table: "Cases",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.Sql("UPDATE Cases INNER JOIN Users ON Cases.CreatorId = Users.UserId SET Cases.CreatorUsername = Users.Username WHERE Cases.CreatorUsername = '';");
|
||||
migrationBuilder.Sql("UPDATE Cases INNER JOIN Users ON Cases.DismisserId = Users.UserId SET Cases.DismisserUsername = Users.Username WHERE Cases.DismisserUsername is NULL;");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatorUsername",
|
||||
table: "Cases");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DismisserUsername",
|
||||
table: "Cases");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ namespace ProjectLighthouse.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.1")
|
||||
.HasAnnotation("ProductVersion", "7.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Administration.CompletedMigration", b =>
|
||||
|
@ -47,12 +47,19 @@ namespace ProjectLighthouse.Migrations
|
|||
b.Property<int>("CreatorId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("CreatorUsername")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("DismissedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int?>("DismisserId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("DismisserUsername")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime?>("ExpiresAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue