diff --git a/Refresh.Database/GameDatabaseContext.Reports.cs b/Refresh.Database/GameDatabaseContext.Reports.cs index 59dca13cd..b9efeb553 100644 --- a/Refresh.Database/GameDatabaseContext.Reports.cs +++ b/Refresh.Database/GameDatabaseContext.Reports.cs @@ -7,7 +7,7 @@ namespace Refresh.Database; public partial class GameDatabaseContext // Reports { - private IQueryable ReportsIncluded => this.Reports + private IQueryable GameGriefReportsIncluded => this.GameGriefReports .Include(r => r.Reporter) .Include(r => r.Reporter.Statistics) .Include(r => r.Level) @@ -16,20 +16,19 @@ public partial class GameDatabaseContext // Reports .Include(r => r.ReviewedBy) .Include(r => r.ReviewedBy!.Statistics) .Include(r => r.Players) - .ThenInclude(p => p.User) - .Include(r => r.Players) - .ThenInclude(p => p.User.Statistics); + .ThenInclude(p => p.User!) + .ThenInclude(p => p.Statistics); - public void CreateReport(GriefReport griefReport) + public void CreateReport(GameGriefReport griefReport) { this.Write(() => { griefReport.ReportDate = this._time.Now; - this.Reports.Add(griefReport); + this.GameGriefReports.Add(griefReport); }); } - public void UpdateReportStatus(GriefReport griefReport, GriefReportStatus status, GameUser? reviewer = null, string? notes = null) + public void UpdateReportStatus(GameGriefReport griefReport, GriefReportStatus status, GameUser? reviewer = null, string? notes = null) { this.Write(() => { @@ -40,41 +39,41 @@ public void UpdateReportStatus(GriefReport griefReport, GriefReportStatus status }); } - public void RemoveReport(GriefReport griefReport) + public void RemoveReport(GameGriefReport griefReport) { this.Write(() => { - this.Reports.Remove(griefReport); + this.GameGriefReports.Remove(griefReport); }); } [Pure] - public GriefReport? GetReportById(int id) => - this.ReportsIncluded.FirstOrDefault(r => r.ReportId == id); + public GameGriefReport? GetReportById(int id) => + this.GameGriefReportsIncluded.FirstOrDefault(r => r.ReportId == id); [Pure] - public DatabaseList GetReportsByStatus(GriefReportStatus status, int count, int skip) => - new(this.ReportsIncluded + public DatabaseList GetReportsByStatus(GriefReportStatus status, int count, int skip) => + new(this.GameGriefReportsIncluded .Where(r => r.Status == status) .OrderByDescending(r => r.ReportDate), skip, count); [Pure] - public DatabaseList GetReportsByReporter(GameUser reporter, int count, int skip) => - new(this.ReportsIncluded - .Where(r => r.Reporter == reporter) + public DatabaseList GetReportsByReporter(GameUser reporter, int count, int skip) => + new(this.GameGriefReportsIncluded + .Where(r => r.ReporterUserId == reporter.UserId) .OrderByDescending(r => r.ReportDate), skip, count); [Pure] - public DatabaseList GetReportsForLevel(GameLevel level, int count, int skip) => - new(this.ReportsIncluded - .Where(r => r.Level == level) + public DatabaseList GetReportsForLevel(GameLevel level, int count, int skip) => + new(this.GameGriefReportsIncluded + .Where(r => r.LevelId == level.LevelId) .OrderByDescending(r => r.ReportDate), skip, count); [Pure] - public DatabaseList GetStaleReports(TimeSpan maxAge, int count, int skip) + public DatabaseList GetStaleReports(TimeSpan maxAge, int count, int skip) { DateTimeOffset cutoff = this._time.Now - maxAge; - return new(this.ReportsIncluded + return new(this.GameGriefReportsIncluded .Where(r => r.Status == GriefReportStatus.Pending && r.ReportDate < cutoff) .OrderBy(r => r.ReportDate), skip, count); } diff --git a/Refresh.Database/GameDatabaseContext.cs b/Refresh.Database/GameDatabaseContext.cs index c8c5e69b9..ecd71569c 100644 --- a/Refresh.Database/GameDatabaseContext.cs +++ b/Refresh.Database/GameDatabaseContext.cs @@ -80,8 +80,8 @@ public partial class GameDatabaseContext : DbContext, IDatabaseContext internal DbSet PinProgressRelations { get; set; } internal DbSet ProfilePinRelations { get; set; } internal DbSet GameSkillRewards { get; set; } - internal DbSet Reports { get; set; } - internal DbSet ReportPlayersRelations { get; set; } + internal DbSet GameGriefReports { get; set; } + internal DbSet ReportPlayerRelations { get; set; } internal DbSet Workers { get; set; } internal DbSet JobStates { get; set; } internal DbSet GameLevelRevisions { get; set; } diff --git a/Refresh.Database/Migrations/20261013102558_MiscGriefReportQualityUpdates.cs b/Refresh.Database/Migrations/20261013102558_MiscGriefReportQualityUpdates.cs new file mode 100644 index 000000000..0e21c2cd4 --- /dev/null +++ b/Refresh.Database/Migrations/20261013102558_MiscGriefReportQualityUpdates.cs @@ -0,0 +1,144 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refresh.Database.Migrations +{ + /// + [DbContext(typeof(GameDatabaseContext))] + [Migration("20261013102558_MiscGriefReportQualityUpdates")] + public partial class MiscGriefReportQualityUpdates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PlayerRect", + table: "ReportPlayersRelations", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "Username", + table: "ReportPlayersRelations", + type: "text", + nullable: false, + defaultValue: ""); + + // ReportPlayerRelations PK changes (since we're altering UserId, which is part of PK at this point) + + migrationBuilder.DropPrimaryKey( + name: "PK_ReportPlayersRelations", + table: "ReportPlayersRelations"); + + migrationBuilder.AlterColumn( + name: "UserId", + table: "ReportPlayersRelations", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + // (Re)init the player number column so we can ensure it only has unique values per report ID, + // so we can safely use it to replace UserId in the primary key + // https://stackoverflow.com/questions/65231516/add-and-update-a-column-with-sequential-numbering-based-on-grouping + migrationBuilder.Sql + (""" + UPDATE "ReportPlayersRelations" SET "PlayerNumber" = u.newNum + FROM ( + SELECT *, row_number() OVER ( + PARTITION BY "ReportId" + ORDER BY "ReportId" + ) AS newNum + FROM "ReportPlayersRelations" + ) AS u + WHERE "ReportPlayersRelations"."ReportId" = u."ReportId" + AND "ReportPlayersRelations"."UserId" = u."UserId" + """); + + migrationBuilder.AddPrimaryKey( + name: "PK_ReportPlayerRelations", + table: "ReportPlayersRelations", + columns: new[] { "ReportId", "PlayerNumber" }); + + // Doing this in the middle caused random issues with the following DB commands, + // so rename the tables after everything. + + migrationBuilder.RenameTable( + name: "Reports", + newName: "GameGriefReports"); + + // thank you for this typo smh + migrationBuilder.RenameTable( + name: "ReportPlayersRelations", + newName: "ReportPlayerRelations"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + migrationBuilder.AlterColumn( + name: "UserId", + table: "ReportPlayerRelations", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "PlayerRect", + table: "ReportPlayerRelations", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.DropColumn( + name: "Username", + table: "ReportPlayerRelations"); + + // Just remove all with duplicate user IDs. Usually we won't have duplicate IDs anyway, + // but we should ensure we don't anyway; also, this should be enough for a rollback on this, + // better than dropping the whole table or something. + // We can just use PlayerNumber to uniquely identify rows per ReportId, that and ReportId + // are the only PK attributes after the update above. + migrationBuilder.Sql + (""" + DELETE FROM "ReportPlayerRelations" + USING ( + SELECT min ("PlayerNumber") as keepNumber, "ReportId", "UserId" + FROM "ReportPlayerRelations" + GROUP BY "UserId", "ReportId" + ) AS d + WHERE "PlayerNumber" != d.keepNumber + AND "ReportPlayerRelations"."ReportId" = d."ReportId" + AND "ReportPlayerRelations"."UserId" = d."UserId" + """); + + migrationBuilder.DropPrimaryKey( + name: "PK_ReportPlayerRelations", + table: "ReportPlayerRelations"); + + migrationBuilder.AddPrimaryKey( + name: "PK_ReportPlayersRelations", + table: "ReportPlayerRelations", + columns: new[] { "ReportId", "UserId" }); + + migrationBuilder.RenameTable( + name: "GameGriefReports", + newName: "Reports"); + + migrationBuilder.RenameTable( + name: "ReportPlayerRelations", + newName: "ReportPlayersRelations"); + } + } +} diff --git a/Refresh.Database/Migrations/20261013122831_DropReportPlayerRelationTimestamp.cs b/Refresh.Database/Migrations/20261013122831_DropReportPlayerRelationTimestamp.cs new file mode 100644 index 000000000..bdc02d426 --- /dev/null +++ b/Refresh.Database/Migrations/20261013122831_DropReportPlayerRelationTimestamp.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refresh.Database.Migrations +{ + /// + [DbContext(typeof(GameDatabaseContext))] + [Migration("20261013122831_DropReportPlayerRelationTimestamp")] + public partial class DropReportPlayerRelationTimestamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // This column had no setters at all, meaning all rows had this set to -infinity. + // The report uploads also don't actually contain any player-specific timestamps, + // so this is safe to drop. + migrationBuilder.DropColumn( + name: "Timestamp", + table: "ReportPlayerRelations"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Timestamp", + table: "ReportPlayerRelations", + type: "timestamp with time zone", + nullable: false, + defaultValue: new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero)); + } + } +} diff --git a/Refresh.Database/Migrations/20261017093718_MakeReviewedByUserIdNotRequiredAgain.cs b/Refresh.Database/Migrations/20261017093718_MakeReviewedByUserIdNotRequiredAgain.cs new file mode 100644 index 000000000..ef3c761f3 --- /dev/null +++ b/Refresh.Database/Migrations/20261017093718_MakeReviewedByUserIdNotRequiredAgain.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refresh.Database.Migrations +{ + /// + [DbContext(typeof(GameDatabaseContext))] + [Migration("20261017093718_MakeReviewedByUserIdNotRequiredAgain")] + public partial class MakeReviewedByUserIdNotRequiredAgain : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ReviewedByUserId", + table: "GameGriefReports", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // Nothing needed, just leave it at null since we already want it like that, else SQL exception + } + } +} diff --git a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs index 86774e7d0..4ff17febb 100644 --- a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs +++ b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs @@ -1200,8 +1200,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ReportId") .HasColumnType("integer"); - b.Property("UserId") - .HasColumnType("text"); + b.Property("PlayerNumber") + .HasColumnType("integer"); b.Property("IsInGameNow") .HasColumnType("boolean"); @@ -1209,20 +1209,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsReporter") .HasColumnType("boolean"); - b.Property("PlayerNumber") - .HasColumnType("integer"); - b.Property("PlayerRect") + .IsRequired() .HasColumnType("text"); - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); + b.Property("UserId") + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); - b.HasKey("ReportId", "UserId"); + b.HasKey("ReportId", "PlayerNumber"); b.HasIndex("UserId"); - b.ToTable("ReportPlayersRelations"); + b.ToTable("ReportPlayerRelations"); }); modelBuilder.Entity("Refresh.Database.Models.Relations.SubPlaylistRelation", b => @@ -1284,7 +1286,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("UniquePlayLevelRelations"); }); - modelBuilder.Entity("Refresh.Database.Models.Reports.GriefReport", b => + modelBuilder.Entity("Refresh.Database.Models.Reports.GameGriefReport", b => { b.Property("ReportId") .ValueGeneratedOnAdd() @@ -1351,7 +1353,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ReviewedByUserId"); - b.ToTable("Reports"); + b.ToTable("GameGriefReports"); }); modelBuilder.Entity("Refresh.Database.Models.Statistics.GameLevelStatistics", b => @@ -2426,7 +2428,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Refresh.Database.Models.Relations.ReportPlayerRelation", b => { - b.HasOne("Refresh.Database.Models.Reports.GriefReport", "GriefReport") + b.HasOne("Refresh.Database.Models.Reports.GameGriefReport", "GameGriefReport") .WithMany("Players") .HasForeignKey("ReportId") .OnDelete(DeleteBehavior.Cascade) @@ -2434,11 +2436,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("Refresh.Database.Models.Users.GameUser", "User") .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("UserId"); - b.Navigation("GriefReport"); + b.Navigation("GameGriefReport"); b.Navigation("User"); }); @@ -2500,7 +2500,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Refresh.Database.Models.Reports.GriefReport", b => + modelBuilder.Entity("Refresh.Database.Models.Reports.GameGriefReport", b => { b.HasOne("Refresh.Database.Models.Levels.GameLevel", "Level") .WithMany() @@ -2581,7 +2581,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Subjects"); }); - modelBuilder.Entity("Refresh.Database.Models.Reports.GriefReport", b => + modelBuilder.Entity("Refresh.Database.Models.Reports.GameGriefReport", b => { b.Navigation("Players"); }); diff --git a/Refresh.Database/Models/Relations/ReportPlayerRelation.cs b/Refresh.Database/Models/Relations/ReportPlayerRelation.cs index bf09357fa..bc3fa9e59 100644 --- a/Refresh.Database/Models/Relations/ReportPlayerRelation.cs +++ b/Refresh.Database/Models/Relations/ReportPlayerRelation.cs @@ -3,27 +3,24 @@ using Refresh.Database.Models.Users; namespace Refresh.Database.Models.Relations; -#nullable disable -[PrimaryKey(nameof(ReportId), nameof(UserId))] +[PrimaryKey(nameof(ReportId), nameof(PlayerNumber))] public partial class ReportPlayerRelation { [Required] [ForeignKey(nameof(ReportId))] - public GriefReport GriefReport { get; set; } - - [Required] - [ForeignKey(nameof(UserId))] - public GameUser User { get; set; } - + public GameGriefReport GameGriefReport { get; set; } = null!; [Required] public int ReportId { get; set; } - [Required] public ObjectId UserId { get; set; } + [ForeignKey(nameof(UserId))] + public GameUser? User { get; set; } + public ObjectId? UserId { get; set; } + public string Username { get; set; } = ""; + public bool IsReporter { get; set; } public bool IsInGameNow { get; set; } // i think something about whether the player left during the report, could rename it if confirmed public int PlayerNumber { get; set; } + // TODO this and photo subject bounds should be stored in a more atomic way public string PlayerRect { get; set; } = string.Empty; // normalized "l,t,r,b" - - public DateTimeOffset Timestamp { get; set; } } \ No newline at end of file diff --git a/Refresh.Database/Models/Reports/GriefReport.cs b/Refresh.Database/Models/Reports/GameGriefReport.cs similarity index 83% rename from Refresh.Database/Models/Reports/GriefReport.cs rename to Refresh.Database/Models/Reports/GameGriefReport.cs index d2a737cdc..1ca9b9ecb 100644 --- a/Refresh.Database/Models/Reports/GriefReport.cs +++ b/Refresh.Database/Models/Reports/GameGriefReport.cs @@ -1,24 +1,27 @@ -using System.Drawing; +using MongoDB.Bson; using Refresh.Database.Models.Authentication; using Refresh.Database.Models.Levels; -using Refresh.Database.Models.Photos; using Refresh.Database.Models.Relations; using Refresh.Database.Models.Users; namespace Refresh.Database.Models.Reports; -public class GriefReport +public class GameGriefReport { [Key] public int ReportId { get; set; } + [Required, ForeignKey(nameof(ReporterUserId))] public GameUser Reporter { get; set; } = null!; + [Required] public ObjectId ReporterUserId { get; set; } + public DateTimeOffset ReportDate { get; set; } public TokenGame GameVersion { get; set; } // Level data public int? LevelId { get; set; } - [ForeignKey("LevelId")] + [ForeignKey(nameof(LevelId))] public GameLevel? Level { get; set; } // Level reference only provided if it's an uploaded or story level + public string? LevelType { get; set; } public string InitialStateHash { get; set; } = "0"; // Hash for asset of initial level public string GriefStateHash { get; set; } = "0"; // Hash for asset of griefed level @@ -33,7 +36,10 @@ public class GriefReport public string Description { get; set; } = string.Empty; // Moderation + [ForeignKey(nameof(ReviewedByUserId))] public GameUser? ReviewedBy { get; set; } + public ObjectId? ReviewedByUserId { get; set; } + public DateTimeOffset? ReviewedDate { get; set; } public string? ModeratorNotes { get; set; } public GriefReportStatus Status { get; set; } = GriefReportStatus.Pending; diff --git a/Refresh.Interfaces.Game/Endpoints/ReportingEndpoints.cs b/Refresh.Interfaces.Game/Endpoints/ReportingEndpoints.cs index 01180d52e..8dce50c58 100644 --- a/Refresh.Interfaces.Game/Endpoints/ReportingEndpoints.cs +++ b/Refresh.Interfaces.Game/Endpoints/ReportingEndpoints.cs @@ -26,7 +26,7 @@ public class ReportingEndpoints : EndpointGroup [GameEndpoint("grief", HttpMethods.Post, ContentType.Xml)] [RequireEmailVerified] [EndpointRateLimit(EndpointBucketId.GameUploadGriefReport)] - public Response UploadReport(RequestContext context, GameDatabaseContext database, GameReport body, GameUser user, + public Response UploadReport(RequestContext context, GameDatabaseContext database, SerializedGriefReport body, GameUser user, IDateTimeProvider time, Token token, GameServerConfig config) { if (user.IsWriteBlocked(config)) @@ -100,25 +100,34 @@ public Response UploadReport(RequestContext context, GameDatabaseContext databas List players = new(); if (body.Players != null) { - players.AddRange(body.Players.Select(player => + // We don't preserve player numbers since bugged or forged requests could cause issues otherwise, + // but we will still use them to preserve the player order, for our own player numbers. + int newPlayerNumber = 1; + foreach (SerializedReportPlayer player in body.Players.OrderBy(p => p.PlayerNumber)) { - GameUser? playerUser = database.GetUserByUsername(player.Username); // what if user doesnt exist - return new ReportPlayerRelation + // Even if the user doesn't exist (local guest or user from another server), + // we should save their presence in this report anyway, just like with photos. + GameUser? playerUser = database.GetUserByUsername(player.Username); + + players.Add(new ReportPlayerRelation { - User = playerUser, + User = playerUser, + Username = player.Username, + PlayerNumber = newPlayerNumber, IsReporter = player.Reporter, - IsInGameNow = player.IngameNow, - PlayerNumber = player.PlayerNumber, + IsInGameNow = player.IngameNow, PlayerRect = player.Rectangle == null ? string.Empty : NormalizeRectangle(player.Rectangle, imageSize) - }; - })); + }); + + newPlayerNumber++; + } } // Trim description if (body.Description != null && body.Description.Length > UgcLimits.DescriptionLimit) body.Description = body.Description[..UgcLimits.DescriptionLimit]; - GriefReport griefReport = new() + GameGriefReport griefReport = new() { Reporter = user, GameVersion = token.TokenGame, @@ -138,7 +147,7 @@ public Response UploadReport(RequestContext context, GameDatabaseContext databas return OK; } - private static string NormalizeRectangle(Rect rect, Size imageSize) + private static string NormalizeRectangle(SerializedReportRect rect, Size imageSize) { float halfWidth = imageSize.Width / 2f; float halfHeight = imageSize.Height / 2f; diff --git a/Refresh.Interfaces.Game/Types/Report/GameReport.cs b/Refresh.Interfaces.Game/Types/Report/SerializedGriefReport.cs similarity index 73% rename from Refresh.Interfaces.Game/Types/Report/GameReport.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedGriefReport.cs index 7e9991cb8..1cc0d2b9e 100644 --- a/Refresh.Interfaces.Game/Types/Report/GameReport.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedGriefReport.cs @@ -6,13 +6,13 @@ namespace Refresh.Interfaces.Game.Types.Report; #nullable disable [XmlRoot("griefReport")] -public class GameReport +public class SerializedGriefReport { [XmlIgnore] - private List InternalInfoBubble { get; } = []; + private List InternalInfoBubble { get; } = []; [XmlElement("infoBubble")] - public InfoBubble[] InfoBubble + public SerializedReportInfoBubble[] InfoBubble { get => this.InternalInfoBubble.ToArray(); set @@ -24,7 +24,7 @@ public InfoBubble[] InfoBubble return; } - foreach (InfoBubble infoBubble in value) + foreach (SerializedReportInfoBubble infoBubble in value) this.InternalInfoBubble.Add(infoBubble); } } @@ -36,7 +36,7 @@ public InfoBubble[] InfoBubble public int InternalType { get; set; } [XmlElement("marqee")] - public Marqee Marqee { get; set; } + public SerializedReportMarqee Marqee { get; set; } [XmlElement("levelOwner")] public string LevelOwner { get; set; } @@ -44,6 +44,7 @@ public InfoBubble[] InfoBubble [XmlElement("initialStateHash")] public string InitialStateHash { get; set; } + // Actually the slot type, but they named it this anyway... [XmlElement("levelType")] public string LevelType { get; set; } @@ -60,10 +61,10 @@ public InfoBubble[] InfoBubble public string JpegHash { get; set; } [XmlIgnore] - private List InternalPlayers { get; } = []; + private List InternalPlayers { get; } = []; [XmlElement("player")] - public Player[] Players + public SerializedReportPlayer[] Players { get => this.InternalPlayers.ToArray(); set @@ -75,11 +76,11 @@ public Player[] Players return; } - foreach (Player player in value) + foreach (SerializedReportPlayer player in value) this.InternalPlayers.Add(player); } } [XmlElement("screenElements")] - public ScreenElements ScreenElements { get; set; } + public SerializedReportScreenElements ScreenElements { get; set; } } \ No newline at end of file diff --git a/Refresh.Interfaces.Game/Types/Report/InfoBubble.cs b/Refresh.Interfaces.Game/Types/Report/SerializedReportInfoBubble.cs similarity index 82% rename from Refresh.Interfaces.Game/Types/Report/InfoBubble.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedReportInfoBubble.cs index 818130286..6f0b22de1 100644 --- a/Refresh.Interfaces.Game/Types/Report/InfoBubble.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedReportInfoBubble.cs @@ -5,10 +5,10 @@ namespace Refresh.Interfaces.Game.Types.Report; #nullable disable [XmlRoot("infoBubble")] -public class InfoBubble +public class SerializedReportInfoBubble { [XmlElement("slot")] - public Slot Slot { get; set; } + public SerializedReportSlot Slot { get; set; } [XmlElement("name")] public string Name { get; set; } diff --git a/Refresh.Interfaces.Game/Types/Report/Marqee.cs b/Refresh.Interfaces.Game/Types/Report/SerializedReportMarqee.cs similarity index 63% rename from Refresh.Interfaces.Game/Types/Report/Marqee.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedReportMarqee.cs index df014d7cc..1336c6461 100644 --- a/Refresh.Interfaces.Game/Types/Report/Marqee.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedReportMarqee.cs @@ -5,8 +5,8 @@ namespace Refresh.Interfaces.Game.Types.Report; #nullable disable [XmlRoot("marqee")] -public class Marqee +public class SerializedReportMarqee { [XmlElement("rect")] - public Rect Rect { get; set; } + public SerializedReportRect Rect { get; set; } } \ No newline at end of file diff --git a/Refresh.Interfaces.Game/Types/Report/Player.cs b/Refresh.Interfaces.Game/Types/Report/SerializedReportPlayer.cs similarity index 77% rename from Refresh.Interfaces.Game/Types/Report/Player.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedReportPlayer.cs index 90a4ae69d..cfaa3b1f4 100644 --- a/Refresh.Interfaces.Game/Types/Report/Player.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedReportPlayer.cs @@ -5,13 +5,13 @@ namespace Refresh.Interfaces.Game.Types.Report; #nullable disable [XmlRoot("player")] -public class Player +public class SerializedReportPlayer { [XmlElement("id")] public string Username { get; set; } [XmlElement("rect")] - public Rect Rectangle { get; set; } + public SerializedReportRect Rectangle { get; set; } [XmlAttribute("reporter")] public bool Reporter { get; set; } @@ -26,5 +26,5 @@ public class Player public string Text { get; set; } [XmlElement("screenRect")] - public ScreenRect ScreenRect { get; set; } + public SerializedReportScreenRect ScreenRect { get; set; } } \ No newline at end of file diff --git a/Refresh.Interfaces.Game/Types/Report/Rect.cs b/Refresh.Interfaces.Game/Types/Report/SerializedReportRect.cs similarity index 92% rename from Refresh.Interfaces.Game/Types/Report/Rect.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedReportRect.cs index 84b73fa8f..9a8ec0ff9 100644 --- a/Refresh.Interfaces.Game/Types/Report/Rect.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedReportRect.cs @@ -3,7 +3,7 @@ namespace Refresh.Interfaces.Game.Types.Report; [XmlRoot("rect")] -public class Rect +public class SerializedReportRect { [XmlAttribute(AttributeName="t")] public long Top { get; set; } diff --git a/Refresh.Interfaces.Game/Types/Report/ScreenElements.cs b/Refresh.Interfaces.Game/Types/Report/SerializedReportScreenElements.cs similarity index 66% rename from Refresh.Interfaces.Game/Types/Report/ScreenElements.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedReportScreenElements.cs index c53e0dab1..eb46e7005 100644 --- a/Refresh.Interfaces.Game/Types/Report/ScreenElements.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedReportScreenElements.cs @@ -5,13 +5,13 @@ namespace Refresh.Interfaces.Game.Types.Report; #nullable disable [XmlRoot("screenElements")] -public class ScreenElements +public class SerializedReportScreenElements { [XmlIgnore] - private List InternalSlot { get; } = []; + private List InternalSlot { get; } = []; [XmlElement("slot")] - public Slot[] Slot + public SerializedReportSlot[] Slot { get => this.InternalSlot.ToArray(); set @@ -23,16 +23,16 @@ public Slot[] Slot return; } - foreach (Slot slot in value) + foreach (SerializedReportSlot slot in value) this.InternalSlot.Add(slot); } } [XmlIgnore] - private List InternalPlayer { get; } = []; + private List InternalPlayer { get; } = []; [XmlElement("player")] - public Player[] Player + public SerializedReportPlayer[] Player { get => this.InternalPlayer.ToArray(); set @@ -44,7 +44,7 @@ public Player[] Player return; } - foreach (Player player in value) + foreach (SerializedReportPlayer player in value) this.InternalPlayer.Add(player); } } diff --git a/Refresh.Interfaces.Game/Types/Report/ScreenRect.cs b/Refresh.Interfaces.Game/Types/Report/SerializedReportScreenRect.cs similarity index 62% rename from Refresh.Interfaces.Game/Types/Report/ScreenRect.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedReportScreenRect.cs index 97dbf59f4..5880dac7a 100644 --- a/Refresh.Interfaces.Game/Types/Report/ScreenRect.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedReportScreenRect.cs @@ -5,8 +5,8 @@ namespace Refresh.Interfaces.Game.Types.Report; #nullable disable [XmlRoot("screenRect")] -public class ScreenRect +public class SerializedReportScreenRect { [XmlElement("rect")] - public Rect Rect { get; set; } + public SerializedReportRect Rect { get; set; } } \ No newline at end of file diff --git a/Refresh.Interfaces.Game/Types/Report/Slot.cs b/Refresh.Interfaces.Game/Types/Report/SerializedReportSlot.cs similarity index 84% rename from Refresh.Interfaces.Game/Types/Report/Slot.cs rename to Refresh.Interfaces.Game/Types/Report/SerializedReportSlot.cs index fbc0d75e6..d0d5e371e 100644 --- a/Refresh.Interfaces.Game/Types/Report/Slot.cs +++ b/Refresh.Interfaces.Game/Types/Report/SerializedReportSlot.cs @@ -5,7 +5,7 @@ namespace Refresh.Interfaces.Game.Types.Report; #nullable disable [XmlRoot("slot")] -public class Slot +public class SerializedReportSlot { [XmlElement("id")] public int Id { get; set; } @@ -17,7 +17,7 @@ public class Slot public string Text { get; set; } [XmlElement("screenRect")] - public ScreenRect ScreenRect { get; set; } + public SerializedReportScreenRect ScreenRect { get; set; } [XmlElement("slotId")] public string SlotId { get; set; } diff --git a/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs b/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs index 1cf9aa1ff..327d77e88 100644 --- a/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs +++ b/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs @@ -29,14 +29,14 @@ public void UploadReport() HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - GameReport report = new() + SerializedGriefReport report = new() { - InfoBubble = new InfoBubble[] + InfoBubble = new SerializedReportInfoBubble[] {}, Type = GriefReportType.TermsOfService, - Marqee = new Marqee + Marqee = new SerializedReportMarqee { - Rect = new Rect + Rect = new SerializedReportRect { Top = 10, Left = 10, @@ -51,12 +51,12 @@ public void UploadReport() Description = "This is a description, sent by LBP3", GriefStateHash = null, JpegHash = TEST_ASSET_HASH, - Players = new Player[] + Players = new SerializedReportPlayer[] { new() { Username = user.Username, - Rectangle = new Rect + Rectangle = new SerializedReportRect { Top = 10, Left = 10, @@ -67,9 +67,9 @@ public void UploadReport() IngameNow = true, PlayerNumber = 0, Text = "Some text", - ScreenRect = new ScreenRect + ScreenRect = new SerializedReportScreenRect { - Rect = new Rect + Rect = new SerializedReportRect { Top = 10, Left = 10, @@ -79,7 +79,7 @@ public void UploadReport() }, }, }, - ScreenElements = new ScreenElements(), + ScreenElements = new SerializedReportScreenElements(), }; HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; @@ -87,10 +87,10 @@ public void UploadReport() context.Database.Refresh(); - DatabaseList reports = context.Database.GetReportsByReporter(user, 10, 0); + DatabaseList reports = context.Database.GetReportsByReporter(user, 10, 0); Assert.That(reports.TotalItems, Is.EqualTo(1)); - GriefReport createdReport = reports.Items.First(); + GameGriefReport createdReport = reports.Items.First(); Assert.That(createdReport.Level?.LevelId, Is.EqualTo(level.LevelId)); Assert.That(createdReport.Type, Is.EqualTo(GriefReportType.TermsOfService)); Assert.That(createdReport.Description, Is.EqualTo("This is a description, sent by LBP3")); @@ -111,7 +111,7 @@ public void CanUploadReportWithBadLevel() HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - GameReport report = new() + SerializedGriefReport report = new() { LevelId = int.MaxValue, JpegHash = TEST_ASSET_HASH, @@ -122,7 +122,7 @@ public void CanUploadReportWithBadLevel() context.Database.Refresh(); - DatabaseList reports = context.Database.GetReportsByReporter(user, 10, 0); + DatabaseList reports = context.Database.GetReportsByReporter(user, 10, 0); Assert.That(reports.TotalItems, Is.EqualTo(1)); Assert.That(reports.Items.First().Level, Is.Null); @@ -142,36 +142,36 @@ public void CantUploadReportWithBadPlayerCount(bool psp) HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - GameReport report = new() + SerializedGriefReport report = new() { LevelId = 0, JpegHash = TEST_ASSET_HASH, - Players = new Player[] + Players = new SerializedReportPlayer[] { new() { Username = "hee", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "hawk", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "2uh", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "burger", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "er", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, }, }; @@ -194,38 +194,38 @@ public void CantUploadReportWithBadScreenElementPlayerCount(bool psp) HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - GameReport report = new() + SerializedGriefReport report = new() { LevelId = 0, JpegHash = TEST_ASSET_HASH, - ScreenElements = new ScreenElements + ScreenElements = new SerializedReportScreenElements { - Player = new Player[] + Player = new SerializedReportPlayer[] { new() { Username = "hee", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "haw", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "ham", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "burg", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, new() { Username = "er", - Rectangle = new Rect(), + Rectangle = new SerializedReportRect(), }, }, }, @@ -255,14 +255,14 @@ public void RedirectGriefReportToPhoto(TokenGame game) HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new SerializedGriefReport { - Players = new Player[] + Players = new SerializedReportPlayer[] { new() { Username = user.Username, - Rectangle = new Rect + Rectangle = new SerializedReportRect { Top = 10, Left = 10, @@ -310,14 +310,14 @@ public void RedirectGriefReportToPhotoDeveloperLevel(TokenGame game) HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new SerializedGriefReport { - Players = new Player[] + Players = new SerializedReportPlayer[] { new() { Username = user.Username, - Rectangle = new Rect + Rectangle = new SerializedReportRect { Top = 10, Left = 10, @@ -364,14 +364,14 @@ public void RedirectGriefReportToPhotoWhenNotUserOrDeveloperSlot(TokenGame game) HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new SerializedGriefReport { - Players = new Player[] + Players = new SerializedReportPlayer[] { new() { Username = user.Username, - Rectangle = new Rect + Rectangle = new SerializedReportRect { Top = 10, Left = 10, @@ -418,14 +418,14 @@ public void RedirectGriefReportToPhotoWhenMissingLevel(TokenGame game) HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; Assert.That(message.StatusCode, Is.EqualTo(OK)); - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new SerializedGriefReport { - Players = new Player[] + Players = new SerializedReportPlayer[] { new() { Username = user.Username, - Rectangle = new Rect + Rectangle = new SerializedReportRect { Top = 10, Left = 10, @@ -463,14 +463,14 @@ public void CantRedirectGriefReportToPhotoWhenWebsiteToken() using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.Website, TokenPlatform.PS3, user); - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new SerializedGriefReport { - Players = new Player[] + Players = new SerializedReportPlayer[] { new() { Username = user.Username, - Rectangle = new Rect + Rectangle = new SerializedReportRect { Top = 10, Left = 10,