Skip to content

Commit 53912d4

Browse files
authored
Merge branch 'main' into pin-platform
2 parents c97da2e + 9a3ba99 commit 53912d4

18 files changed

Lines changed: 580 additions & 29 deletions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Refresh.Common.Extensions;
2+
3+
public static class StringExtensions
4+
{
5+
public static bool IsBlankHash(this string? hash)
6+
{
7+
return string.IsNullOrWhiteSpace(hash) || hash == "0";
8+
}
9+
}

Refresh.Core/Refresh.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<PackageReference Include="NPTicket" Version="3.1.1" />
2727
<PackageReference Include="Pfim" Version="0.11.3" />
2828
<PackageReference Include="SharpZipLib" Version="1.4.2" />
29-
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
29+
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
3030
<PackageReference Include="EasyHotReload" Version="1.0.0" />
3131
</ItemGroup>
3232

Refresh.Database/GameDatabaseContext.Assets.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public partial class GameDatabaseContext // Assets
1010

1111
public GameAsset? GetAssetFromHash(string hash)
1212
{
13-
if (hash == "0" || hash.StartsWith('g')) return null;
13+
if (hash.IsBlankHash() || hash.StartsWith('g')) return null;
1414

1515
return this.GameAssetsIncluded
1616
.FirstOrDefault(a => a.AssetHash == hash);

Refresh.Database/GameDatabaseContext.Levels.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ public void DeleteLevel(GameLevel level)
269269
this.GameReviews.RemoveRange(r => r.Level == level);
270270
this.LevelPlaylistRelations.RemoveRange(r => r.Level == level);
271271

272-
IEnumerable<GameChallenge> challenges = this.GameChallenges.Where(c => c.Level == level);
272+
IEnumerable<GameChallenge> challenges = this.GameChallenges.Where(c => c.Level == level).ToArray();
273273

274274
foreach (GameChallenge challenge in challenges)
275275
{
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Refresh.Database.Models.Users;
2+
using Refresh.Database.Models.Moderation;
3+
using Refresh.Database.Models.Levels;
4+
using Refresh.Database.Models.Levels.Scores;
5+
using Refresh.Database.Models.Photos;
6+
using Refresh.Database.Models.Comments;
7+
using Refresh.Database.Models.Playlists;
8+
using Refresh.Database.Models.Assets;
9+
using Refresh.Database.Models.Levels.Challenges;
10+
11+
namespace Refresh.Database;
12+
13+
public partial class GameDatabaseContext // Moderation
14+
{
15+
private IQueryable<ModerationAction> ModerationActionsIncluded => this.ModerationActions
16+
.Include(s => s.Actor)
17+
.Include(s => s.InvolvedUser);
18+
19+
#region Retrieval
20+
21+
public DatabaseList<ModerationAction> GetModerationActions(int skip, int count)
22+
{
23+
return new(this.ModerationActionsIncluded.OrderByDescending(a => a.Timestamp), skip, count);
24+
}
25+
26+
public DatabaseList<ModerationAction> GetModerationActionsByActor(GameUser actor, int skip, int count)
27+
{
28+
return new(this.ModerationActionsIncluded
29+
.Where(a => a.ActorId == actor.UserId)
30+
.OrderByDescending(a => a.Timestamp), skip, count);
31+
}
32+
33+
public DatabaseList<ModerationAction> GetModerationActionsForInvolvedUser(GameUser involvedUser, int skip, int count)
34+
{
35+
return new(this.ModerationActionsIncluded
36+
.Where(a => a.InvolvedUserId == involvedUser.UserId)
37+
.OrderByDescending(a => a.Timestamp), skip, count);
38+
}
39+
40+
public DatabaseList<ModerationAction> GetModerationActionsForObject(string id, ModerationObjectType objectType, int skip, int count)
41+
{
42+
return new(this.ModerationActionsIncluded
43+
.Where(a => a.ModeratedObjectType == objectType && a.ModeratedObjectId == id)
44+
.OrderByDescending(a => a.Timestamp), skip, count);
45+
}
46+
47+
#endregion
48+
49+
#region Creation
50+
51+
public ModerationAction CreateModerationAction(GameUser user, ModerationActionType actionType, GameUser actor, string description)
52+
=> this.CreateModerationActionInternal(user.UserId.ToString(), ModerationObjectType.User, actionType, actor, user, description);
53+
54+
public ModerationAction CreateModerationAction(GameLevel level, ModerationActionType actionType, GameUser actor, string description)
55+
=> this.CreateModerationActionInternal(level.LevelId.ToString(), ModerationObjectType.Level, actionType, actor, level.Publisher, description);
56+
57+
public ModerationAction CreateModerationAction(GameScore score, ModerationActionType actionType, GameUser actor, string description)
58+
=> this.CreateModerationActionInternal(score.ScoreId.ToString(), ModerationObjectType.Score, actionType, actor, score.Publisher, description);
59+
60+
public ModerationAction CreateModerationAction(GamePhoto photo, ModerationActionType actionType, GameUser actor, string description)
61+
=> this.CreateModerationActionInternal(photo.PhotoId.ToString(), ModerationObjectType.Photo, actionType, actor, photo.Publisher, description);
62+
63+
public ModerationAction CreateModerationAction(GameReview review, ModerationActionType actionType, GameUser actor, string description)
64+
=> this.CreateModerationActionInternal(review.ReviewId.ToString(), ModerationObjectType.Review, actionType, actor, review.Publisher, description);
65+
66+
public ModerationAction CreateModerationAction(GameLevelComment comment, ModerationActionType actionType, GameUser actor, string description)
67+
=> this.CreateModerationActionInternal(comment.SequentialId.ToString(), ModerationObjectType.LevelComment, actionType, actor, comment.Author, description);
68+
69+
public ModerationAction CreateModerationAction(GameProfileComment comment, ModerationActionType actionType, GameUser actor, string description)
70+
=> this.CreateModerationActionInternal(comment.SequentialId.ToString(), ModerationObjectType.UserComment, actionType, actor, comment.Author, description);
71+
72+
public ModerationAction CreateModerationAction(GamePlaylist playlist, ModerationActionType actionType, GameUser actor, string description)
73+
=> this.CreateModerationActionInternal(playlist.PlaylistId.ToString(), ModerationObjectType.Playlist, actionType, actor, playlist.Publisher, description);
74+
75+
public ModerationAction CreateModerationAction(GameAsset asset, ModerationActionType actionType, GameUser actor, string description)
76+
=> this.CreateModerationActionInternal(asset.AssetHash, ModerationObjectType.Asset, actionType, actor, asset.OriginalUploader, description);
77+
78+
public ModerationAction CreateModerationAction(GameChallenge challenge, ModerationActionType actionType, GameUser actor, string description)
79+
=> this.CreateModerationActionInternal(challenge.ChallengeId.ToString(), ModerationObjectType.Challenge, actionType, actor, challenge.Publisher, description);
80+
81+
public ModerationAction CreateModerationAction(GameChallengeScore score, ModerationActionType actionType, GameUser actor, string description)
82+
=> this.CreateModerationActionInternal(score.ScoreId.ToString(), ModerationObjectType.Score, actionType, actor, score.Publisher, description);
83+
84+
private ModerationAction CreateModerationActionInternal(string id, ModerationObjectType objectType, ModerationActionType actionType, GameUser actor, GameUser? involvedUser, string description)
85+
{
86+
ModerationAction moderationAction = new()
87+
{
88+
ModeratedObjectId = id,
89+
ModeratedObjectType = objectType,
90+
ActionType = actionType,
91+
Actor = actor,
92+
InvolvedUser = involvedUser,
93+
Description = description,
94+
Timestamp = this._time.Now,
95+
};
96+
97+
this.ModerationActions.Add(moderationAction);
98+
this.SaveChanges();
99+
100+
return moderationAction;
101+
}
102+
103+
#endregion
104+
}

Refresh.Database/GameDatabaseContext.Users.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,16 @@ public int GetActiveUserCount()
267267
public void SetUserRole(GameUser user, GameUserRole role)
268268
{
269269
if(role == GameUserRole.Banned) throw new InvalidOperationException($"Cannot ban a user with this method. Please use {nameof(this.BanUser)}().");
270-
this.Write(() =>
270+
271+
if (user.Role is GameUserRole.Banned or GameUserRole.Restricted)
271272
{
272-
if (user.Role is GameUserRole.Banned or GameUserRole.Restricted)
273-
{
274-
user.BanReason = null;
275-
user.BanExpiryDate = null;
276-
};
277-
278-
user.Role = role;
279-
});
273+
user.BanReason = null;
274+
user.BanExpiryDate = null;
275+
};
276+
277+
user.Role = role;
278+
this.GameUsers.Update(user);
279+
this.SaveChanges();
280280
}
281281

282282
private void PunishUser(GameUser user, string reason, DateTimeOffset expiryDate, GameUserRole role)

Refresh.Database/GameDatabaseContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using Refresh.Database.Models.Statistics;
2323
using Refresh.Database.Models.Workers;
2424
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
25+
using Refresh.Database.Models.Moderation;
2526

2627
namespace Refresh.Database;
2728

@@ -79,6 +80,7 @@ public partial class GameDatabaseContext : DbContext, IDatabaseContext
7980
internal DbSet<WorkerInfo> Workers { get; set; }
8081
internal DbSet<PersistentJobState> JobStates { get; set; }
8182
internal DbSet<GameLevelRevision> GameLevelRevisions { get; set; }
83+
internal DbSet<ModerationAction> ModerationActions { get; set; }
8284

8385
#pragma warning disable CS8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable.
8486
internal GameDatabaseContext(Logger logger, IDateTimeProvider time, IDatabaseConfig dbConfig)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Microsoft.EntityFrameworkCore.Infrastructure;
2+
using Microsoft.EntityFrameworkCore.Migrations;
3+
4+
#nullable disable
5+
6+
namespace Refresh.Database.Migrations
7+
{
8+
/// <inheritdoc />
9+
[DbContext(typeof(GameDatabaseContext))]
10+
[Migration("20251028183803_AddModerationAction")]
11+
public partial class AddModerationAction : Migration
12+
{
13+
/// <inheritdoc />
14+
protected override void Up(MigrationBuilder migrationBuilder)
15+
{
16+
migrationBuilder.CreateTable(
17+
name: "ModerationActions",
18+
columns: table => new
19+
{
20+
ActionId = table.Column<string>(type: "text", nullable: false),
21+
ActionType = table.Column<byte>(type: "smallint", nullable: false),
22+
ModeratedObjectType = table.Column<byte>(type: "smallint", nullable: false),
23+
ModeratedObjectId = table.Column<string>(type: "text", nullable: false),
24+
ActorId = table.Column<string>(type: "text", nullable: false),
25+
InvolvedUserId = table.Column<string>(type: "text", nullable: true),
26+
Description = table.Column<string>(type: "text", nullable: false),
27+
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
28+
},
29+
constraints: table =>
30+
{
31+
table.PrimaryKey("PK_ModerationActions", x => x.ActionId);
32+
table.ForeignKey(
33+
name: "FK_ModerationActions_GameUsers_ActorId",
34+
column: x => x.ActorId,
35+
principalTable: "GameUsers",
36+
principalColumn: "UserId",
37+
onDelete: ReferentialAction.Cascade);
38+
table.ForeignKey(
39+
name: "FK_ModerationActions_GameUsers_InvolvedUserId",
40+
column: x => x.InvolvedUserId,
41+
principalTable: "GameUsers",
42+
principalColumn: "UserId");
43+
});
44+
45+
migrationBuilder.CreateIndex(
46+
name: "IX_ModerationActions_ActorId",
47+
table: "ModerationActions",
48+
column: "ActorId");
49+
50+
migrationBuilder.CreateIndex(
51+
name: "IX_ModerationActions_InvolvedUserId",
52+
table: "ModerationActions",
53+
column: "InvolvedUserId");
54+
}
55+
56+
/// <inheritdoc />
57+
protected override void Down(MigrationBuilder migrationBuilder)
58+
{
59+
migrationBuilder.DropTable(
60+
name: "ModerationActions");
61+
}
62+
}
63+
}

Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
1818
{
1919
#pragma warning disable 612, 618
2020
modelBuilder
21-
.HasAnnotation("ProductVersion", "9.0.9")
21+
.HasAnnotation("ProductVersion", "9.0.10")
2222
.HasAnnotation("Relational:MaxIdentifierLength", 63);
2323

2424
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -598,6 +598,43 @@ protected override void BuildModel(ModelBuilder modelBuilder)
598598
b.ToTable("GameScores");
599599
});
600600

601+
modelBuilder.Entity("Refresh.Database.Models.Moderation.ModerationAction", b =>
602+
{
603+
b.Property<string>("ActionId")
604+
.HasColumnType("text");
605+
606+
b.Property<byte>("ActionType")
607+
.HasColumnType("smallint");
608+
609+
b.Property<string>("ActorId")
610+
.IsRequired()
611+
.HasColumnType("text");
612+
613+
b.Property<string>("Description")
614+
.HasColumnType("text");
615+
616+
b.Property<string>("InvolvedUserId")
617+
.HasColumnType("text");
618+
619+
b.Property<string>("ModeratedObjectId")
620+
.IsRequired()
621+
.HasColumnType("text");
622+
623+
b.Property<byte>("ModeratedObjectType")
624+
.HasColumnType("smallint");
625+
626+
b.Property<DateTimeOffset>("Timestamp")
627+
.HasColumnType("timestamp with time zone");
628+
629+
b.HasKey("ActionId");
630+
631+
b.HasIndex("ActorId");
632+
633+
b.HasIndex("InvolvedUserId");
634+
635+
b.ToTable("ModerationActions");
636+
});
637+
601638
modelBuilder.Entity("Refresh.Database.Models.Notifications.GameAnnouncement", b =>
602639
{
603640
b.Property<string>("AnnouncementId")
@@ -1918,6 +1955,23 @@ protected override void BuildModel(ModelBuilder modelBuilder)
19181955
b.Navigation("Publisher");
19191956
});
19201957

1958+
modelBuilder.Entity("Refresh.Database.Models.Moderation.ModerationAction", b =>
1959+
{
1960+
b.HasOne("Refresh.Database.Models.Users.GameUser", "Actor")
1961+
.WithMany()
1962+
.HasForeignKey("ActorId")
1963+
.OnDelete(DeleteBehavior.Cascade)
1964+
.IsRequired();
1965+
1966+
b.HasOne("Refresh.Database.Models.Users.GameUser", "InvolvedUser")
1967+
.WithMany()
1968+
.HasForeignKey("InvolvedUserId");
1969+
1970+
b.Navigation("Actor");
1971+
1972+
b.Navigation("InvolvedUser");
1973+
});
1974+
19211975
modelBuilder.Entity("Refresh.Database.Models.Notifications.GameNotification", b =>
19221976
{
19231977
b.HasOne("Refresh.Database.Models.Users.GameUser", "User")
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using MongoDB.Bson;
2+
using Refresh.Database.Models.Users;
3+
4+
namespace Refresh.Database.Models.Moderation;
5+
6+
#nullable disable
7+
8+
public partial class ModerationAction
9+
{
10+
[Key] public ObjectId ActionId { get; set; } = ObjectId.GenerateNewId();
11+
12+
/// <summary>
13+
/// Describes what was done with the object.
14+
/// </summary>
15+
public ModerationActionType ActionType { get; set; }
16+
17+
/// <summary>
18+
/// The type of data/object that was moderated.
19+
/// </summary>
20+
public ModerationObjectType ModeratedObjectType { get; set; }
21+
22+
/// <summary>
23+
/// The ID of the object that was moderated. May be a UUID, sequential ID, or a GameAsset hash,
24+
/// depending on ModeratedObjectType's value
25+
/// </summary>
26+
[Required] public string ModeratedObjectId { get; set; }
27+
28+
/// <summary>
29+
/// The user in question who has moderated the object.
30+
/// </summary>
31+
[Required, ForeignKey(nameof(ActorId))] public GameUser Actor { get; set; }
32+
[Required] public ObjectId ActorId { get; set; }
33+
34+
#nullable restore
35+
36+
/// <summary>
37+
/// Usually the publisher/owner of the object that was moderated. May also see this moderation action.
38+
/// </summary>
39+
[ForeignKey(nameof(InvolvedUserId))] public GameUser? InvolvedUser { get; set; }
40+
public ObjectId? InvolvedUserId { get; set; }
41+
42+
#nullable disable
43+
44+
/// <summary>
45+
/// A description, stating the reason of this moderation action.
46+
/// </summary>
47+
public string Description { get; set; }
48+
49+
public DateTimeOffset Timestamp { get; set; }
50+
}

0 commit comments

Comments
 (0)