From ff8e9cc952cea6ddf57bcbcfd31edb506d3b3337 Mon Sep 17 00:00:00 2001 From: Saphire Date: Sat, 16 Nov 2024 16:10:17 +0600 Subject: [PATCH 1/2] Fix signed 32 bit int overflow --- ReplayBrowser/Data/Models/Replay.cs | 7 +++++-- ReplayBrowser/Models/Ingested/YamlReplay.cs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ReplayBrowser/Data/Models/Replay.cs b/ReplayBrowser/Data/Models/Replay.cs index f5d94e2..ee41c57 100644 --- a/ReplayBrowser/Data/Models/Replay.cs +++ b/ReplayBrowser/Data/Models/Replay.cs @@ -28,11 +28,14 @@ public class Replay : IEntityTypeConfiguration public List? RoundParticipants { get; set; } public string? RoundEndText { get; set; } public required string ServerId { get; set; } + // Doesn't need to be a long because at 30TPS that's 828 days. + // Unless we get round saving and it restores the tick count? + // - November 2024 public int EndTick { get; set; } public required string Duration { get; set; } public int FileCount { get; set; } - public int Size { get; set; } - public int UncompressedSize { get; set; } + public long Size { get; set; } + public long UncompressedSize { get; set; } public required string EndTime { get; set; } [JsonIgnore] diff --git a/ReplayBrowser/Models/Ingested/YamlReplay.cs b/ReplayBrowser/Models/Ingested/YamlReplay.cs index 98e7b89..b068f2f 100644 --- a/ReplayBrowser/Models/Ingested/YamlReplay.cs +++ b/ReplayBrowser/Models/Ingested/YamlReplay.cs @@ -25,7 +25,7 @@ public class YamlReplay { public int EndTick { get; set; } public required string Duration { get; set; } public int FileCount { get; set; } - public int Size { get; set; } - public int UncompressedSize { get; set; } + public long Size { get; set; } + public long UncompressedSize { get; set; } public required string EndTime { get; set; } } \ No newline at end of file From ce87547132818e5458a16b62423e6980a2d1d330 Mon Sep 17 00:00:00 2001 From: Saphire Date: Sat, 16 Nov 2024 16:43:28 +0600 Subject: [PATCH 2/2] Add migration for int overflow fix --- .../20241116104038_FixIntOverflow.Designer.cs | 650 ++++++++++++++++++ .../20241116104038_FixIntOverflow.cs | 50 ++ .../ReplayDbContextModelSnapshot.cs | 8 +- ReplayBrowser/Models/ReplayResult.cs | 4 +- 4 files changed, 706 insertions(+), 6 deletions(-) create mode 100644 ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.Designer.cs create mode 100644 ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.cs diff --git a/ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.Designer.cs b/ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.Designer.cs new file mode 100644 index 0000000..f1a7c3b --- /dev/null +++ b/ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.Designer.cs @@ -0,0 +1,650 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; +using ReplayBrowser.Data; + +#nullable disable + +namespace Server.Migrations +{ + [DbContext(typeof(ReplayDbContext))] + [Migration("20241116104038_FixIntOverflow")] + partial class FixIntOverflow + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property>("FavoriteReplays") + .IsRequired() + .HasColumnType("integer[]"); + + b.Property("Guid") + .HasColumnType("uuid"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("Protected") + .HasColumnType("boolean"); + + b.Property>("SavedProfiles") + .IsRequired() + .HasColumnType("uuid[]"); + + b.Property("SettingsId") + .HasColumnType("integer"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Guid") + .IsUnique(); + + b.HasIndex("SettingsId"); + + b.HasIndex("Username"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.AccountSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property>("Friends") + .IsRequired() + .HasColumnType("uuid[]"); + + b.Property("RedactInformation") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("AccountSettings"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.HistoryEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("Details") + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("HistoryEntry"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.Webhook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Servers") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Webhook"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.WebhookHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ResponseBody") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResponseCode") + .HasColumnType("integer"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WebhookId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("WebhookId"); + + b.ToTable("WebhookHistory"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.CharacterData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("CollectedPlayerDataPlayerGuid") + .HasColumnType("uuid"); + + b.Property("LastPlayed") + .HasColumnType("timestamp with time zone"); + + b.Property("RoundsPlayed") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CollectedPlayerDataPlayerGuid"); + + b.ToTable("CharacterData"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.CollectedPlayerData", b => + { + b.Property("PlayerGuid") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GeneratedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsWatched") + .HasColumnType("boolean"); + + b.Property("LastSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("PlayerDataId") + .HasColumnType("integer"); + + b.Property("TotalAntagRoundsPlayed") + .HasColumnType("integer"); + + b.Property("TotalEstimatedPlaytime") + .HasColumnType("interval"); + + b.Property("TotalRoundsPlayed") + .HasColumnType("integer"); + + b.HasKey("PlayerGuid"); + + b.HasIndex("PlayerDataId"); + + b.HasIndex("PlayerGuid") + .IsUnique(); + + b.ToTable("PlayerProfiles"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.GdprRequest", b => + { + b.Property("Guid") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.HasKey("Guid"); + + b.ToTable("GdprRequests"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.JobCountData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CollectedPlayerDataPlayerGuid") + .HasColumnType("uuid"); + + b.Property("JobPrototype") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastPlayed") + .HasColumnType("timestamp with time zone"); + + b.Property("RoundsPlayed") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CollectedPlayerDataPlayerGuid"); + + b.ToTable("JobCountData"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.JobDepartment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Department") + .IsRequired() + .HasColumnType("text"); + + b.Property("Job") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Job") + .IsUnique(); + + b.ToTable("JobDepartments"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Notice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Notices"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.ParsedReplay", b => + { + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Name"); + + b.ToTable("ParsedReplays"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Antag") + .HasColumnType("boolean"); + + b.Property>("AntagPrototypes") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("EffectiveJobId") + .HasColumnType("integer"); + + b.Property>("JobPrototypes") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("ParticipantId") + .HasColumnType("integer"); + + b.Property("PlayerIcName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("EffectiveJobId"); + + b.HasIndex("ParticipantId"); + + b.HasIndex("PlayerIcName"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.PlayerData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerGuid") + .HasColumnType("uuid"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PlayerData"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Replay", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Duration") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndTick") + .HasColumnType("integer"); + + b.Property("EndTime") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileCount") + .HasColumnType("integer"); + + b.Property("Gamemode") + .IsRequired() + .HasColumnType("text"); + + b.Property("Link") + .IsRequired() + .HasColumnType("text"); + + b.Property("Map") + .HasColumnType("text"); + + b.Property>("Maps") + .HasColumnType("text[]"); + + b.Property("RoundEndText") + .HasColumnType("text"); + + b.Property("RoundEndTextSearchVector") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("tsvector") + .HasAnnotation("Npgsql:TsVectorConfig", "english") + .HasAnnotation("Npgsql:TsVectorProperties", new[] { "RoundEndText" }); + + b.Property("RoundId") + .HasColumnType("integer"); + + b.Property("ServerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServerName") + .HasColumnType("text"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("UncompressedSize") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Gamemode"); + + b.HasIndex("Map"); + + b.HasIndex("RoundEndTextSearchVector"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("RoundEndTextSearchVector"), "GIN"); + + b.HasIndex("ServerId"); + + b.HasIndex("ServerName"); + + b.ToTable("Replays"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.ReplayParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerGuid") + .HasColumnType("uuid"); + + b.Property("ReplayId") + .HasColumnType("integer"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ReplayId"); + + b.HasIndex("Username"); + + b.HasIndex("PlayerGuid", "ReplayId") + .IsUnique(); + + b.ToTable("ReplayParticipants"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.ServerToken", b => + { + b.Property("Token") + .HasColumnType("text"); + + b.HasKey("Token"); + + b.HasIndex("Token"); + + b.ToTable("ServerTokens"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.Account", b => + { + b.HasOne("ReplayBrowser.Data.Models.Account.AccountSettings", "Settings") + .WithMany() + .HasForeignKey("SettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Settings"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.HistoryEntry", b => + { + b.HasOne("ReplayBrowser.Data.Models.Account.Account", "Account") + .WithMany("History") + .HasForeignKey("AccountId"); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.Webhook", b => + { + b.HasOne("ReplayBrowser.Data.Models.Account.Account", null) + .WithMany("Webhooks") + .HasForeignKey("AccountId"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.WebhookHistory", b => + { + b.HasOne("ReplayBrowser.Data.Models.Account.Webhook", "Webhook") + .WithMany("Logs") + .HasForeignKey("WebhookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Webhook"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.CharacterData", b => + { + b.HasOne("ReplayBrowser.Data.Models.CollectedPlayerData", null) + .WithMany("Characters") + .HasForeignKey("CollectedPlayerDataPlayerGuid"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.CollectedPlayerData", b => + { + b.HasOne("ReplayBrowser.Data.Models.PlayerData", "PlayerData") + .WithMany() + .HasForeignKey("PlayerDataId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlayerData"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.JobCountData", b => + { + b.HasOne("ReplayBrowser.Data.Models.CollectedPlayerData", null) + .WithMany("JobCount") + .HasForeignKey("CollectedPlayerDataPlayerGuid"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Player", b => + { + b.HasOne("ReplayBrowser.Data.Models.JobDepartment", "EffectiveJob") + .WithMany() + .HasForeignKey("EffectiveJobId"); + + b.HasOne("ReplayBrowser.Data.Models.ReplayParticipant", "Participant") + .WithMany("Players") + .HasForeignKey("ParticipantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EffectiveJob"); + + b.Navigation("Participant"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.ReplayParticipant", b => + { + b.HasOne("ReplayBrowser.Data.Models.Replay", "Replay") + .WithMany("RoundParticipants") + .HasForeignKey("ReplayId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Replay"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.Account", b => + { + b.Navigation("History"); + + b.Navigation("Webhooks"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Account.Webhook", b => + { + b.Navigation("Logs"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.CollectedPlayerData", b => + { + b.Navigation("Characters"); + + b.Navigation("JobCount"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.Replay", b => + { + b.Navigation("RoundParticipants"); + }); + + modelBuilder.Entity("ReplayBrowser.Data.Models.ReplayParticipant", b => + { + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.cs b/ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.cs new file mode 100644 index 0000000..8b38de8 --- /dev/null +++ b/ReplayBrowser/Data/Migrations/20241116104038_FixIntOverflow.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.Migrations +{ + /// + public partial class FixIntOverflow : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "UncompressedSize", + table: "Replays", + type: "bigint", // this actually means long, 8 byte number + nullable: false, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AlterColumn( + name: "Size", + table: "Replays", + type: "bigint", + nullable: false, + oldClrType: typeof(int), + oldType: "integer"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "UncompressedSize", + table: "Replays", + type: "integer", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AlterColumn( + name: "Size", + table: "Replays", + type: "integer", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + } + } +} diff --git a/ReplayBrowser/Data/Migrations/ReplayDbContextModelSnapshot.cs b/ReplayBrowser/Data/Migrations/ReplayDbContextModelSnapshot.cs index 658f96c..b5811e2 100644 --- a/ReplayBrowser/Data/Migrations/ReplayDbContextModelSnapshot.cs +++ b/ReplayBrowser/Data/Migrations/ReplayDbContextModelSnapshot.cs @@ -457,11 +457,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ServerName") .HasColumnType("text"); - b.Property("Size") - .HasColumnType("integer"); + b.Property("Size") + .HasColumnType("bigint"); - b.Property("UncompressedSize") - .HasColumnType("integer"); + b.Property("UncompressedSize") + .HasColumnType("bigint"); b.HasKey("Id"); diff --git a/ReplayBrowser/Models/ReplayResult.cs b/ReplayBrowser/Models/ReplayResult.cs index 450a060..497d353 100644 --- a/ReplayBrowser/Models/ReplayResult.cs +++ b/ReplayBrowser/Models/ReplayResult.cs @@ -17,8 +17,8 @@ public class ReplayResult public required string Duration { get; set; } public DateTime? Date { get; set; } public int? RoundId { get; set; } - public int Size { get; set; } - public int UncompressedSize { get; set; } + public long Size { get; set; } + public long UncompressedSize { get; set; } public bool IsFavorite { get; set; } } \ No newline at end of file