From 0a17cc0acb8f51dcb1d7e7f50a8ece5d37eb6179 Mon Sep 17 00:00:00 2001 From: richardscull <106016833+richardscull@users.noreply.github.com> Date: Mon, 8 Jun 2026 02:38:09 +0300 Subject: [PATCH 1/6] feat: Replace MySql.EntityFrameworkCore with Pomelo.EntityFrameworkCore.MySql --- Sunrise.Server/Bootstrap.cs | 2 +- .../20250327193326_MigrateToMySQL.cs | 50 +++++++++---------- .../20250330181953_AddUserGrades.cs | 8 +-- ...20250403173344_AddCustomBeatmapStatuses.cs | 8 +-- .../20250508213243_AddUserMetadataTable.cs | 8 +-- ...20250509040239_AddUserRelationshipTable.cs | 8 +-- ...617_AddUserInventoryItemAndBeatmapHypes.cs | 12 ++--- .../20250520134458_AddBeatmapEvents.cs | 8 +-- ...ScoreSubmissionRequestAndProcessingTask.cs | 10 ++-- Sunrise.Shared/Database/SunriseDbContext.cs | 2 +- Sunrise.Shared/Sunrise.Shared.csproj | 16 +++--- Sunrise.Tests/SunriseServerFactory.cs | 2 +- 12 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Sunrise.Server/Bootstrap.cs b/Sunrise.Server/Bootstrap.cs index cfa55de0..27dc2f71 100644 --- a/Sunrise.Server/Bootstrap.cs +++ b/Sunrise.Server/Bootstrap.cs @@ -350,7 +350,7 @@ public static void AddSunriseDbContextPool(this WebApplicationBuilder builder) .AddInterceptors(new SlowQueryLoggerInterceptor()); optionsBuilder - .UseMySQL(Configuration.DatabaseConnectionString); + .UseMySql(Configuration.DatabaseConnectionString, ServerVersion.AutoDetect(Configuration.DatabaseConnectionString)); }); } diff --git a/Sunrise.Shared/Database/Migrations/20250327193326_MigrateToMySQL.cs b/Sunrise.Shared/Database/Migrations/20250327193326_MigrateToMySQL.cs index d5925d4c..120558f3 100644 --- a/Sunrise.Shared/Database/Migrations/20250327193326_MigrateToMySQL.cs +++ b/Sunrise.Shared/Database/Migrations/20250327193326_MigrateToMySQL.cs @@ -1,6 +1,6 @@ -using System; +using System; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; #nullable disable @@ -13,14 +13,14 @@ public partial class MigrateToMySQL : Migration protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AlterDatabase() - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "medal_file", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), Path = table.Column(type: "longtext", nullable: false), CreatedAt = table.Column(type: "datetime(6)", nullable: false) }, @@ -28,14 +28,14 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_medal_file", x => x.Id); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "user", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), Username = table.Column(type: "varchar(255)", nullable: false, collation: "utf8mb4_unicode_ci"), Email = table.Column(type: "varchar(255)", nullable: false, collation: "utf8mb4_unicode_ci"), Passhash = table.Column(type: "longtext", nullable: false), @@ -52,14 +52,14 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_user", x => x.Id); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "medal", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), Name = table.Column(type: "longtext", nullable: false), Description = table.Column(type: "longtext", nullable: false), GameMode = table.Column(type: "tinyint unsigned", nullable: true), @@ -77,14 +77,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalTable: "medal_file", principalColumn: "Id"); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "event_user", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), EventType = table.Column(type: "int", nullable: false), Ip = table.Column(type: "varchar(255)", nullable: false), @@ -101,14 +101,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "restriction", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), ExecutorId = table.Column(type: "int", nullable: true), Reason = table.Column(type: "longtext", nullable: false), @@ -130,14 +130,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "user_favourite_beatmap", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), BeatmapSetId = table.Column(type: "int", nullable: false), DateAdded = table.Column(type: "datetime(6)", nullable: false) @@ -152,14 +152,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "user_file", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), OwnerId = table.Column(type: "int", nullable: false), Path = table.Column(type: "longtext", nullable: false), Type = table.Column(type: "int", nullable: false), @@ -176,14 +176,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "user_medals", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), MedalId = table.Column(type: "int", nullable: false), UnlockedAt = table.Column(type: "datetime(6)", nullable: false) @@ -198,14 +198,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "user_stats", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), GameMode = table.Column(type: "tinyint unsigned", nullable: false), Accuracy = table.Column(type: "double", nullable: false), @@ -231,14 +231,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "user_stats_snapshot", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), GameMode = table.Column(type: "tinyint unsigned", nullable: false), SnapshotsJson = table.Column(type: "longtext", nullable: false) @@ -253,14 +253,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "score", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), BeatmapId = table.Column(type: "int", nullable: false), ScoreHash = table.Column(type: "longtext", nullable: false), @@ -303,7 +303,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalTable: "user_file", principalColumn: "Id"); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_event_user_EventType_UserId", diff --git a/Sunrise.Shared/Database/Migrations/20250330181953_AddUserGrades.cs b/Sunrise.Shared/Database/Migrations/20250330181953_AddUserGrades.cs index 5bed77cb..1f24518e 100644 --- a/Sunrise.Shared/Database/Migrations/20250330181953_AddUserGrades.cs +++ b/Sunrise.Shared/Database/Migrations/20250330181953_AddUserGrades.cs @@ -1,5 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -16,7 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), GameMode = table.Column(type: "tinyint unsigned", nullable: false), CountXH = table.Column(type: "int", nullable: false), @@ -38,7 +38,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_user_grades_UserId_GameMode", diff --git a/Sunrise.Shared/Database/Migrations/20250403173344_AddCustomBeatmapStatuses.cs b/Sunrise.Shared/Database/Migrations/20250403173344_AddCustomBeatmapStatuses.cs index 33a710db..a5149d85 100644 --- a/Sunrise.Shared/Database/Migrations/20250403173344_AddCustomBeatmapStatuses.cs +++ b/Sunrise.Shared/Database/Migrations/20250403173344_AddCustomBeatmapStatuses.cs @@ -1,5 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -16,7 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), BeatmapSetId = table.Column(type: "int", nullable: false), BeatmapHash = table.Column(type: "longtext", nullable: false), UpdatedByUserId = table.Column(type: "int", nullable: false), @@ -32,7 +32,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_custom_beatmap_status_UpdatedByUserId", diff --git a/Sunrise.Shared/Database/Migrations/20250508213243_AddUserMetadataTable.cs b/Sunrise.Shared/Database/Migrations/20250508213243_AddUserMetadataTable.cs index 3df092ae..e97a9b4f 100644 --- a/Sunrise.Shared/Database/Migrations/20250508213243_AddUserMetadataTable.cs +++ b/Sunrise.Shared/Database/Migrations/20250508213243_AddUserMetadataTable.cs @@ -1,5 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -16,7 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), Playstyle = table.Column(type: "int", nullable: false), Location = table.Column(type: "varchar(32)", maxLength: 32, nullable: false), @@ -38,7 +38,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_user_metadata_UserId", diff --git a/Sunrise.Shared/Database/Migrations/20250509040239_AddUserRelationshipTable.cs b/Sunrise.Shared/Database/Migrations/20250509040239_AddUserRelationshipTable.cs index 18c568c0..71f264b7 100644 --- a/Sunrise.Shared/Database/Migrations/20250509040239_AddUserRelationshipTable.cs +++ b/Sunrise.Shared/Database/Migrations/20250509040239_AddUserRelationshipTable.cs @@ -1,5 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -16,7 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), TargetId = table.Column(type: "int", nullable: false), Relation = table.Column(type: "int", nullable: false) @@ -37,7 +37,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_user_relationship_TargetId", diff --git a/Sunrise.Shared/Database/Migrations/20250512014617_AddUserInventoryItemAndBeatmapHypes.cs b/Sunrise.Shared/Database/Migrations/20250512014617_AddUserInventoryItemAndBeatmapHypes.cs index 20d00cee..b5a03796 100644 --- a/Sunrise.Shared/Database/Migrations/20250512014617_AddUserInventoryItemAndBeatmapHypes.cs +++ b/Sunrise.Shared/Database/Migrations/20250512014617_AddUserInventoryItemAndBeatmapHypes.cs @@ -1,5 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -16,7 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), BeatmapSetId = table.Column(type: "int", nullable: false), UserId = table.Column(type: "int", nullable: false), Hypes = table.Column(type: "int", nullable: false) @@ -31,14 +31,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "user_inventory_item", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), ItemType = table.Column(type: "int", nullable: false), Quantity = table.Column(type: "int", nullable: false) @@ -53,7 +53,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_beatmap_hype_BeatmapSetId_Hypes", diff --git a/Sunrise.Shared/Database/Migrations/20250520134458_AddBeatmapEvents.cs b/Sunrise.Shared/Database/Migrations/20250520134458_AddBeatmapEvents.cs index 106a2eae..bfecb5e2 100644 --- a/Sunrise.Shared/Database/Migrations/20250520134458_AddBeatmapEvents.cs +++ b/Sunrise.Shared/Database/Migrations/20250520134458_AddBeatmapEvents.cs @@ -1,6 +1,6 @@ -using System; +using System; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; #nullable disable @@ -17,7 +17,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), BeatmapSetId = table.Column(type: "int", nullable: false), ExecutorId = table.Column(type: "int", nullable: false), EventType = table.Column(type: "int", nullable: false), @@ -34,7 +34,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalColumn: "Id", onDelete: ReferentialAction.Cascade); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_event_beatmap_BeatmapSetId", diff --git a/Sunrise.Shared/Database/Migrations/20260419233843_AddScoreSubmissionRequestAndProcessingTask.cs b/Sunrise.Shared/Database/Migrations/20260419233843_AddScoreSubmissionRequestAndProcessingTask.cs index b4c049d4..8195457d 100644 --- a/Sunrise.Shared/Database/Migrations/20260419233843_AddScoreSubmissionRequestAndProcessingTask.cs +++ b/Sunrise.Shared/Database/Migrations/20260419233843_AddScoreSubmissionRequestAndProcessingTask.cs @@ -1,6 +1,6 @@ using System; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; -using MySql.EntityFrameworkCore.Metadata; #nullable disable @@ -25,7 +25,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), UserId = table.Column(type: "int", nullable: false), ScoreHash = table.Column(type: "varchar(255)", nullable: false), ScoreSerialized = table.Column(type: "longtext", nullable: false), @@ -53,14 +53,14 @@ protected override void Up(MigrationBuilder migrationBuilder) principalTable: "user_file", principalColumn: "Id"); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateTable( name: "score_processing_task", columns: table => new { Id = table.Column(type: "int", nullable: false) - .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), TaskType = table.Column(type: "int", nullable: false), ScoreSubmissionRequestId = table.Column(type: "int", nullable: true), ScoreId = table.Column(type: "int", nullable: true), @@ -91,7 +91,7 @@ protected override void Up(MigrationBuilder migrationBuilder) principalTable: "score_submission_request", principalColumn: "Id"); }) - .Annotation("MySQL:Charset", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_score_ScoreHash_Status_Id", diff --git a/Sunrise.Shared/Database/SunriseDbContext.cs b/Sunrise.Shared/Database/SunriseDbContext.cs index f2edc6f9..44c51289 100644 --- a/Sunrise.Shared/Database/SunriseDbContext.cs +++ b/Sunrise.Shared/Database/SunriseDbContext.cs @@ -107,7 +107,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { - optionsBuilder.UseMySQL(Configuration.DatabaseConnectionString); + optionsBuilder.UseMySql(Configuration.DatabaseConnectionString, ServerVersion.AutoDetect(Configuration.DatabaseConnectionString)); } } } \ No newline at end of file diff --git a/Sunrise.Shared/Sunrise.Shared.csproj b/Sunrise.Shared/Sunrise.Shared.csproj index 7b7f9d0c..c7e1d04e 100644 --- a/Sunrise.Shared/Sunrise.Shared.csproj +++ b/Sunrise.Shared/Sunrise.Shared.csproj @@ -18,15 +18,15 @@ - - - - - - - - + + + + + + + + diff --git a/Sunrise.Tests/SunriseServerFactory.cs b/Sunrise.Tests/SunriseServerFactory.cs index 4d182d08..664c8b6c 100644 --- a/Sunrise.Tests/SunriseServerFactory.cs +++ b/Sunrise.Tests/SunriseServerFactory.cs @@ -30,7 +30,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) options.EnableServiceProviderCaching(false); options.EnableSensitiveDataLogging(); - options.UseMySQL(Configuration.DatabaseConnectionString); + options.UseMySql(Configuration.DatabaseConnectionString, ServerVersion.AutoDetect(Configuration.DatabaseConnectionString)); }); var httpClientDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(HttpClientService)); From ea547276262c8b07e5d9cbc72d642bfbc8574553 Mon Sep 17 00:00:00 2001 From: richardscull <106016833+richardscull@users.noreply.github.com> Date: Mon, 8 Jun 2026 02:39:26 +0300 Subject: [PATCH 2/6] fix: usage of sql raw query connection --- .../Database/Repositories/ScoreRepository.cs | 7 +- Sunrise.Shared/Database/Seeders/UserSeeder.cs | 74 +++++++++++-------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/Sunrise.Shared/Database/Repositories/ScoreRepository.cs b/Sunrise.Shared/Database/Repositories/ScoreRepository.cs index 4dedeb32..7034ea4e 100644 --- a/Sunrise.Shared/Database/Repositories/ScoreRepository.cs +++ b/Sunrise.Shared/Database/Repositories/ScoreRepository.cs @@ -258,14 +258,15 @@ public async Task> EnrichScoresWithLeaderboardPosition(List s var scoresIds = string.Join(",", scores.Select(s => s.Id)); - await using var connection = dbContext.Database.GetDbConnection(); - await connection.OpenAsync(ct); + var connection = dbContext.Database.GetDbConnection(); + if (connection.State != System.Data.ConnectionState.Open) + await connection.OpenAsync(ct); var gameModesWithoutScoreMultiplier = GameModeExtensions.GetGameModesWithoutScoreMultiplier(); var orderByValue = gameModesWithoutScoreMultiplier.Contains(scores.FirstOrDefault()?.GameMode ?? GameMode.Standard) ? nameof(Score.PerformancePoints) : nameof(Score.TotalScore); - var command = connection.CreateCommand(); + await using var command = connection.CreateCommand(); command.CommandText = $""" SELECT Id, diff --git a/Sunrise.Shared/Database/Seeders/UserSeeder.cs b/Sunrise.Shared/Database/Seeders/UserSeeder.cs index 5d9e2a0e..13c810db 100644 --- a/Sunrise.Shared/Database/Seeders/UserSeeder.cs +++ b/Sunrise.Shared/Database/Seeders/UserSeeder.cs @@ -120,37 +120,45 @@ private static async Task IncrementUserIds(DbContext context, CancellationToken var foreignKeys = await FetchUserForeignKeys(context); - await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS=0;"); + var shouldCloseConnection = context.Database.GetDbConnection().State != System.Data.ConnectionState.Open; + if (shouldCloseConnection) + await context.Database.OpenConnectionAsync(ct); - var usersCount = await context.Set().CountAsync(ct); - - var pageSize = 50; - - for (var x = 0;; x++) + try { - var users = context.Set() - .OrderByDescending(u => u.Username) - .Skip(x * pageSize) - .Take(pageSize) - .ToList(); + await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS=0;", ct); - var caseStatements = new StringBuilder(); - var ids = new List(); + var usersCount = await context.Set().CountAsync(ct); - foreach (var user in users) + var pageSize = 50; + + for (var x = 0;; x++) { - var oldId = user.Id; - var newId = oldId + 1000; - ids.Add(oldId); + var users = context.Set() + .OrderByDescending(u => u.Username) + .Skip(x * pageSize) + .Take(pageSize) + .ToList(); - caseStatements.Append($" WHEN {oldId} THEN {newId}"); - } + if (users.Count == 0) break; - var idsCsv = string.Join(", ", ids); + var caseStatements = new StringBuilder(); + var ids = new List(); - foreach (var key in foreignKeys) - { - var sql = $@" + foreach (var user in users) + { + var oldId = user.Id; + var newId = oldId + 1000; + ids.Add(oldId); + + caseStatements.Append($" WHEN {oldId} THEN {newId}"); + } + + var idsCsv = string.Join(", ", ids); + + foreach (var key in foreignKeys) + { + var sql = $@" UPDATE {key.Item1} SET {key.Item2} = CASE {key.Item2} {caseStatements} @@ -159,10 +167,10 @@ private static async Task IncrementUserIds(DbContext context, CancellationToken WHERE {key.Item2} IN ({idsCsv}) "; - await context.Database.ExecuteSqlRawAsync(sql); - } + await context.Database.ExecuteSqlRawAsync(sql, ct); + } - var userUpdateSql = $@" + var userUpdateSql = $@" UPDATE `user` SET Id = CASE Id {caseStatements} @@ -171,14 +179,20 @@ ELSE Id WHERE Id IN ({idsCsv}) "; - await context.Database.ExecuteSqlRawAsync(userUpdateSql); + await context.Database.ExecuteSqlRawAsync(userUpdateSql, ct); - logger.Information("Users updated: {usersUpdated} / {usersTotal}.", x * pageSize + users.Count, usersCount); + logger.Information("Users updated: {usersUpdated} / {usersTotal}.", x * pageSize + users.Count, usersCount); - if (users.Count < pageSize) break; + if (users.Count < pageSize) break; + } } + finally + { + await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS=1;", ct); - await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS=1;"); + if (shouldCloseConnection) + await context.Database.CloseConnectionAsync(); + } logger.Information("All user ids were updated to new ID format."); From 622d3cdc112bf3570171ba3e46380748311b7f30 Mon Sep 17 00:00:00 2001 From: richardscull <106016833+richardscull@users.noreply.github.com> Date: Mon, 8 Jun 2026 02:58:24 +0300 Subject: [PATCH 3/6] feat: Update all possible db sync paths to the proper await usages --- Sunrise.API/Controllers/BeatmapController.cs | 6 +++-- Sunrise.API/Controllers/UserController.cs | 22 +++++++++++-------- Sunrise.Server/Bootstrap.cs | 6 ++--- .../Commands/ChatCommands/HelpCommand.cs | 6 ++--- Sunrise.Server/Middlewares/Middleware.cs | 2 +- Sunrise.Server/Program.cs | 10 ++++----- .../Repositories/ChatCommandRepository.cs | 6 ++--- Sunrise.Shared/Application/RecurringJobs.cs | 2 +- Sunrise.Shared/Application/SunriseMetrics.cs | 12 +++++++++- .../Services/Users/UserStatsRanksService.cs | 4 ++-- .../Extensions/Users/UsernameExtensions.cs | 6 ++--- .../Objects/Chat/ChatRateLimiter.cs | 12 +++++----- Sunrise.Shared/Objects/ReplayFile.cs | 4 ++-- Sunrise.Shared/Objects/Sessions/Session.cs | 4 ++-- .../Repositories/ChatChannelRepository.cs | 6 ++--- Sunrise.Tests/Extensions/ScoreExtensions.cs | 4 ++-- 16 files changed, 62 insertions(+), 50 deletions(-) diff --git a/Sunrise.API/Controllers/BeatmapController.cs b/Sunrise.API/Controllers/BeatmapController.cs index 5669dfec..504c1c6f 100644 --- a/Sunrise.API/Controllers/BeatmapController.cs +++ b/Sunrise.API/Controllers/BeatmapController.cs @@ -207,7 +207,7 @@ public async Task GetHypedBeatmapSets( var (beatmapSetIdsWithHypeCount, totalCount) = await database.Beatmaps.Hypes.GetHypedBeatmaps(new QueryOptions(true, new Pagination(page, limit)), ct); - var result = beatmapSetIdsWithHypeCount.Select(async g => + var resultTasks = beatmapSetIdsWithHypeCount.Select(async g => { var (beatmapSetId, hypeCount) = g; @@ -216,7 +216,9 @@ public async Task GetHypedBeatmapSets( return null; return new HypedBeatmapSetResponse(sessions, beatmapSetResult.Value, hypeCount); - }).Select(task => task.Result).Where(x => x != null).Select(x => x!).ToList(); + }); + + var result = (await Task.WhenAll(resultTasks)).Where(x => x != null).Select(x => x!).ToList(); return Ok(new HypedBeatmapSetsResponse(result, totalCount)); } diff --git a/Sunrise.API/Controllers/UserController.cs b/Sunrise.API/Controllers/UserController.cs index 8097f27e..ee9cd87d 100644 --- a/Sunrise.API/Controllers/UserController.cs +++ b/Sunrise.API/Controllers/UserController.cs @@ -408,7 +408,7 @@ public async Task GetUserMostPlayedMaps([Range(1, int.MaxValue)] var (beatmapsIds, totalIdsCount) = await database.Scores.GetUserMostPlayedBeatmapIds(id, mode, new QueryOptions(true, new Pagination(page, limit)), ct); - var parsedBeatmaps = beatmapsIds.Select(async pair => + var parsedBeatmapsTasks = beatmapsIds.Select(async pair => { var bId = pair.Key; var count = pair.Value; @@ -419,10 +419,12 @@ public async Task GetUserMostPlayedMaps([Range(1, int.MaxValue)] var beatmapSet = beatmapSetResult.Value; - var beatmap = beatmapSet?.Beatmaps.FirstOrDefault(b => b.Id == bId); + var beatmap = beatmapSet?.Beatmaps?.FirstOrDefault(b => b.Id == bId); return beatmap == null ? null : new MostPlayedBeatmapResponse(sessions, beatmap, count, beatmapSet); - }).Select(task => task.Result).Where(x => x != null).Select(x => x!).ToList(); + }); + + var parsedBeatmaps = (await Task.WhenAll(parsedBeatmapsTasks)).Where(x => x != null).Select(x => x!).ToList(); return Ok(new MostPlayedResponse(parsedBeatmaps, totalIdsCount)); } @@ -451,7 +453,7 @@ public async Task GetUserFavourites([Range(1, int.MaxValue)] int var (favourites, favouritesCount) = await database.Users.Favourites.GetUserFavouriteBeatmapIds(id, new QueryOptions(true, new Pagination(page, limit)), ct); - var parsedFavourites = favourites.Select(async setId => + var parsedFavouritesTasks = favourites.Select(async setId => { var beatmapSetResult = await beatmapService.GetBeatmapSet(session, setId, ct: ct); if (beatmapSetResult.IsFailure) @@ -460,7 +462,9 @@ public async Task GetUserFavourites([Range(1, int.MaxValue)] int var beatmapSet = beatmapSetResult.Value; return beatmapSet == null ? null : new BeatmapSetResponse(sessions, beatmapSet); - }).Select(task => task.Result).Where(x => x != null).Select(x => x!).ToList(); + }); + + var parsedFavourites = (await Task.WhenAll(parsedFavouritesTasks)).Where(x => x != null).Select(x => x!).ToList(); return Ok(new BeatmapSetsResponse(parsedFavourites, favouritesCount)); } @@ -912,7 +916,7 @@ public async Task SetUserAvatar([Range(1, int.MaxValue)] int id) var ip = RegionService.GetUserIpAddress(Request); - if (Request.HasFormContentType == false) + if (!Request.HasFormContentType) return Problem(title: ApiErrorResponse.Title.UnableToChangeAvatar, detail: ApiErrorResponse.Detail.InvalidContentType, statusCode: StatusCodes.Status400BadRequest); if (Request.Form.Files.Count == 0) @@ -931,7 +935,7 @@ public async Task SetAvatar() var user = HttpContext.GetCurrentUserOrThrow(); var ip = RegionService.GetUserIpAddress(Request); - if (Request.HasFormContentType == false) + if (!Request.HasFormContentType) return Problem(title: ApiErrorResponse.Title.UnableToChangeAvatar, detail: ApiErrorResponse.Detail.InvalidContentType, statusCode: StatusCodes.Status400BadRequest); if (Request.Form.Files.Count == 0) @@ -955,7 +959,7 @@ public async Task SetUserBanner([Range(1, int.MaxValue)] int id) var ip = RegionService.GetUserIpAddress(Request); - if (Request.HasFormContentType == false) + if (!Request.HasFormContentType) return Problem(title: ApiErrorResponse.Title.UnableToChangeBanner, detail: ApiErrorResponse.Detail.InvalidContentType, statusCode: StatusCodes.Status400BadRequest); if (Request.Form.Files.Count == 0) @@ -974,7 +978,7 @@ public async Task SetBanner() var user = HttpContext.GetCurrentUserOrThrow(); var ip = RegionService.GetUserIpAddress(Request); - if (Request.HasFormContentType == false) + if (!Request.HasFormContentType) return Problem(title: ApiErrorResponse.Title.UnableToChangeBanner, detail: ApiErrorResponse.Detail.InvalidContentType, statusCode: StatusCodes.Status400BadRequest); if (Request.Form.Files.Count == 0) diff --git a/Sunrise.Server/Bootstrap.cs b/Sunrise.Server/Bootstrap.cs index 27dc2f71..16d293ee 100644 --- a/Sunrise.Server/Bootstrap.cs +++ b/Sunrise.Server/Bootstrap.cs @@ -525,7 +525,7 @@ public static string GetEnvFilename() }; } - public static void ApplyDatabaseBootstrapping(this WebApplication app) + public static async Task ApplyDatabaseBootstrapping(this WebApplication app) { using var scope = app.Services.GetRequiredService().CreateScope(); var database = scope.ServiceProvider.GetRequiredService(); @@ -533,7 +533,7 @@ public static void ApplyDatabaseBootstrapping(this WebApplication app) if (!Configuration.IsTestingEnv) database.DbContext.Database.Migrate(); - DatabaseSeeder.UseAsyncSeeding(database.DbContext).Wait(); + await DatabaseSeeder.UseAsyncSeeding(database.DbContext); } public static void UseStaticBackgrounds(this WebApplication app) @@ -607,7 +607,7 @@ public static void Setup(this WebApplication app) Configuration.Initialize(); } - private class LazilyResolved : Lazy + private class LazilyResolved : Lazy where T : notnull { public LazilyResolved(IServiceProvider serviceProvider) : base(serviceProvider.GetRequiredService) diff --git a/Sunrise.Server/Commands/ChatCommands/HelpCommand.cs b/Sunrise.Server/Commands/ChatCommands/HelpCommand.cs index 422c3137..a122d2f2 100644 --- a/Sunrise.Server/Commands/ChatCommands/HelpCommand.cs +++ b/Sunrise.Server/Commands/ChatCommands/HelpCommand.cs @@ -9,13 +9,11 @@ namespace Sunrise.Server.Commands.ChatCommands; [ChatCommand("help")] public class HelpCommand : IChatCommand { - public Task Handle(Session session, ChatChannel? channel, string[]? args) + public async Task Handle(Session session, ChatChannel? channel, string[]? args) { var message = $"Available commands: {Configuration.BotPrefix}" + string.Join($", {Configuration.BotPrefix}", - ChatCommandRepository.GetAvailableCommands(session)); + await ChatCommandRepository.GetAvailableCommands(session)); ChatCommandRepository.SendMessage(session, message); - - return Task.CompletedTask; } } \ No newline at end of file diff --git a/Sunrise.Server/Middlewares/Middleware.cs b/Sunrise.Server/Middlewares/Middleware.cs index 1c56ba82..c356c86c 100644 --- a/Sunrise.Server/Middlewares/Middleware.cs +++ b/Sunrise.Server/Middlewares/Middleware.cs @@ -169,7 +169,7 @@ private async Task SetCurrentUserFromRequestIfPossible(HttpContext context) if (user.AccountStatus == UserAccountStatus.Disabled) { - database.Users.Moderation.EnableUser(user.Id).Wait(); + await database.Users.Moderation.EnableUser(user.Id); // TODO: Send message from bot about account being enabled } } diff --git a/Sunrise.Server/Program.cs b/Sunrise.Server/Program.cs index 4dd6ffb5..49c50dbc 100644 --- a/Sunrise.Server/Program.cs +++ b/Sunrise.Server/Program.cs @@ -46,7 +46,7 @@ app.UseHealthChecks("/health"); app.UseScalarApiReference(); -app.ApplyDatabaseBootstrapping(); +await app.ApplyDatabaseBootstrapping(); app.UseStaticBackgrounds(); app.UseExceptionHandler(); @@ -63,14 +63,14 @@ { using var scope = app.Services.CreateScope(); var database = scope.ServiceProvider.GetRequiredService(); - database.FlushAndUpdateRedisCache(false).Wait(); + await database.FlushAndUpdateRedisCache(false); } var sessions = app.Services.GetRequiredService(); -sessions.AddBotToSession().Wait(); -RecurringJobs.RefreshServerBotAccount(CancellationToken.None).Wait(); +await sessions.AddBotToSession(); +await RecurringJobs.RefreshServerBotAccount(CancellationToken.None); -app.Run(); +await app.RunAsync(); namespace Sunrise.Server { diff --git a/Sunrise.Server/Repositories/ChatCommandRepository.cs b/Sunrise.Server/Repositories/ChatCommandRepository.cs index ebf86e4c..e72fb99c 100644 --- a/Sunrise.Server/Repositories/ChatCommandRepository.cs +++ b/Sunrise.Server/Repositories/ChatCommandRepository.cs @@ -52,7 +52,7 @@ public static async Task HandleCommand(BanchoChatMessage message, Session sessio return; case null: { - var possibleCommands = GetAvailableCommands(session) + var possibleCommands = (await GetAvailableCommands(session)) .Where(x => x.Contains(command)) .ToArray(); @@ -94,12 +94,12 @@ public static async Task HandleCommand(BanchoChatMessage message, Session sessio await handler.Handle(session, channel, args); } - public static string[] GetAvailableCommands(Session session) + public static async Task GetAvailableCommands(Session session) { using var scope = ServicesProviderHolder.CreateScope(); var database = scope.ServiceProvider.GetRequiredService(); - var sessionUser = database.Users.GetUser(session.UserId).Result; + var sessionUser = await database.Users.GetUser(session.UserId); if (sessionUser == null) return []; diff --git a/Sunrise.Shared/Application/RecurringJobs.cs b/Sunrise.Shared/Application/RecurringJobs.cs index 86b64d1b..cc2b73c5 100644 --- a/Sunrise.Shared/Application/RecurringJobs.cs +++ b/Sunrise.Shared/Application/RecurringJobs.cs @@ -308,7 +308,7 @@ private static async Task> CreateDatabaseBackup(string databaseBa ct); if (await Task.WhenAny(backupDatabaseTask, Task.Delay(60_000, ct)) == backupDatabaseTask) - return backupDatabaseTask.Result; + return await backupDatabaseTask; return Result.Failure("Database backup operation timed out"); } diff --git a/Sunrise.Shared/Application/SunriseMetrics.cs b/Sunrise.Shared/Application/SunriseMetrics.cs index 1a0f8a07..604f283c 100644 --- a/Sunrise.Shared/Application/SunriseMetrics.cs +++ b/Sunrise.Shared/Application/SunriseMetrics.cs @@ -183,7 +183,17 @@ public class SunriseMetrics public SunriseMetrics() { - _ = RefreshDatabaseMetricsAsync(); + _ = Task.Run(async () => + { + try + { + await RefreshDatabaseMetricsAsync(); + } + catch (Exception ex) + { + Log.Warning(ex, "Failed to refresh database metrics during startup."); + } + }); RecurringJob.AddOrUpdate("Fetch database metrics", () => RefreshDatabaseMetricsAsync(), "*/1 * * * *"); } diff --git a/Sunrise.Shared/Database/Services/Users/UserStatsRanksService.cs b/Sunrise.Shared/Database/Services/Users/UserStatsRanksService.cs index 7bca6da8..a9e3a50b 100644 --- a/Sunrise.Shared/Database/Services/Users/UserStatsRanksService.cs +++ b/Sunrise.Shared/Database/Services/Users/UserStatsRanksService.cs @@ -100,8 +100,8 @@ public async Task> GetUsersGlobalRanks(List user, Ga await Task.WhenAll(globalRankTask, countryRankTask); - var globalRank = globalRankTask.Result; - var countryRank = countryRankTask.Result; + var globalRank = await globalRankTask; + var countryRank = await countryRankTask; try { diff --git a/Sunrise.Shared/Extensions/Users/UsernameExtensions.cs b/Sunrise.Shared/Extensions/Users/UsernameExtensions.cs index 537578ba..c5c1e917 100644 --- a/Sunrise.Shared/Extensions/Users/UsernameExtensions.cs +++ b/Sunrise.Shared/Extensions/Users/UsernameExtensions.cs @@ -32,7 +32,7 @@ public static (bool, string?) IsValidUsername(this string str) return (false, "Username contains unallowed strings, please remove them and try again."); } - if (IsUsernameDisallowed(str).Result) + if (IsUsernameDisallowed(str)) { return (false, "Username contains unallowed strings, try to come up with a harmless and original nickname."); } @@ -55,7 +55,7 @@ public static bool IsValidUsernameCharacters(this string str) return Regex.IsMatch(str, @"^[1-9 0-\[\]a-zA-Z_-]+$"); } - private static async Task IsUsernameDisallowed(string str) + private static bool IsUsernameDisallowed(string str) { var path = Path.Combine(Configuration.DataPath, Configuration.BannedUsernamesName); @@ -64,7 +64,7 @@ private static async Task IsUsernameDisallowed(string str) return false; } - var bannedUsernames = await File.ReadAllLinesAsync(path, Encoding.UTF8); + var bannedUsernames = File.ReadAllLines(path, Encoding.UTF8); for (var i = 0; i < bannedUsernames.Length; i++) { diff --git a/Sunrise.Shared/Objects/Chat/ChatRateLimiter.cs b/Sunrise.Shared/Objects/Chat/ChatRateLimiter.cs index 34901d1a..589a66e4 100644 --- a/Sunrise.Shared/Objects/Chat/ChatRateLimiter.cs +++ b/Sunrise.Shared/Objects/Chat/ChatRateLimiter.cs @@ -16,14 +16,12 @@ public class ChatRateLimiter(int messagesLimit, TimeSpan timeWindow, bool action public new bool CanSend(BaseSession session) { var canSend = base.CanSend(session); - + using var scope = ServicesProviderHolder.CreateScope(); - var database = scope.ServiceProvider.GetRequiredService(); - - var user = database.Users - .GetUser(id: session.UserId, options: new QueryOptions(true)) - .ConfigureAwait(false).GetAwaiter().GetResult(); - + var dbContext = scope.ServiceProvider.GetRequiredService(); + + var user = dbContext.Users.FirstOrDefault(u => u.Id == session.UserId); + if (user == null) return false; diff --git a/Sunrise.Shared/Objects/ReplayFile.cs b/Sunrise.Shared/Objects/ReplayFile.cs index db080629..2376cad0 100644 --- a/Sunrise.Shared/Objects/ReplayFile.cs +++ b/Sunrise.Shared/Objects/ReplayFile.cs @@ -27,8 +27,8 @@ public ReplayFile(Score score, byte[] rawReplay, User? user = null) if (user == null) { using var scope = ServicesProviderHolder.CreateScope(); - var database = scope.ServiceProvider.GetRequiredService(); - User = database.Users.GetUser(score.UserId).Result; + var dbContext = scope.ServiceProvider.GetRequiredService(); + User = dbContext.Users.FirstOrDefault(u => u.Id == score.UserId); } if (User == null) diff --git a/Sunrise.Shared/Objects/Sessions/Session.cs b/Sunrise.Shared/Objects/Sessions/Session.cs index 48874803..1f0789ec 100644 --- a/Sunrise.Shared/Objects/Sessions/Session.cs +++ b/Sunrise.Shared/Objects/Sessions/Session.cs @@ -234,9 +234,9 @@ public void RemoveSpectator(Session session) private User? GetSessionUser() { using var scope = ServicesProviderHolder.CreateScope(); - var database = scope.ServiceProvider.GetRequiredService(); + var dbContext = scope.ServiceProvider.GetRequiredService(); - var user = database.Users.GetUser(UserId, options: new QueryOptions(true)).ConfigureAwait(false).GetAwaiter().GetResult(); + var user = dbContext.Users.FirstOrDefault(u => u.Id == UserId); if (user == null) throw new ApplicationException($"User with id {UserId} not found"); diff --git a/Sunrise.Shared/Repositories/ChatChannelRepository.cs b/Sunrise.Shared/Repositories/ChatChannelRepository.cs index 367e8287..ec1419be 100644 --- a/Sunrise.Shared/Repositories/ChatChannelRepository.cs +++ b/Sunrise.Shared/Repositories/ChatChannelRepository.cs @@ -95,13 +95,13 @@ public List GetChannels(Session? session = null) { return _channels.Values.Where(x => x.IsPublic).ToList(); } - + using var scope = ServicesProviderHolder.CreateScope(); - var database = scope.ServiceProvider.GetRequiredService(); + var dbContext = scope.ServiceProvider.GetRequiredService(); var sessionPrivilege = UserPrivilege.User; - var user = database.Users.GetUser(id: session.UserId, options: new QueryOptions(true)).ConfigureAwait(false).GetAwaiter().GetResult(); + var user = dbContext.Users.FirstOrDefault(u => u.Id == session.UserId); if (user != null) sessionPrivilege = user.Privilege; diff --git a/Sunrise.Tests/Extensions/ScoreExtensions.cs b/Sunrise.Tests/Extensions/ScoreExtensions.cs index 541c262a..4ea25e72 100644 --- a/Sunrise.Tests/Extensions/ScoreExtensions.cs +++ b/Sunrise.Tests/Extensions/ScoreExtensions.cs @@ -18,9 +18,9 @@ public static void EnrichWithSessionData(this Score score, Session session, stri score.UserId = session.UserId; using var scope = ServicesProviderHolder.CreateScope(); - var database = scope.ServiceProvider.GetRequiredService(); + var dbContext = scope.ServiceProvider.GetRequiredService(); - var user = database.Users.GetUser(id: session.UserId).Result; + var user = dbContext.Users.FirstOrDefault(u => u.Id == session.UserId); if (user == null) throw new NullReferenceException("User not found"); From 353dac567821448e1cecd0cc56aba6b95e173faf Mon Sep 17 00:00:00 2001 From: richardscull <106016833+richardscull@users.noreply.github.com> Date: Mon, 8 Jun 2026 03:07:02 +0300 Subject: [PATCH 4/6] feat: Add TryGetSessionAsync for hot path; Add authentication for osu get replay controller --- Sunrise.Server/Controllers/ScoreController.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sunrise.Server/Controllers/ScoreController.cs b/Sunrise.Server/Controllers/ScoreController.cs index 79424a0d..67f6a8ca 100644 --- a/Sunrise.Server/Controllers/ScoreController.cs +++ b/Sunrise.Server/Controllers/ScoreController.cs @@ -37,10 +37,11 @@ public async Task Submit( var clientHash = ServerParsers.ParseRijndaelString(osuVersion, iv, clientHashEncoded); var username = scoreSerialized.Split(':')[1].Trim(); - if (!sessions.TryGetSession(username, passhash, out var session) || session == null) + var session = await sessions.TryGetSessionAsync(username, passhash); + + if (session == null) { Log.Error("Failed to authenticate score submission for user {Username}. Invalid session.", username); - return Ok("error: pass"); } @@ -75,7 +76,8 @@ public async Task GetScores( if (fromEditor == "1" || leaderboardVersion != "4") return Ok("error: pass"); - if (!sessions.TryGetSession(username, passhash, out var session) || session == null) + var session = await sessions.TryGetSessionAsync(username, passhash); + if (session == null) return Ok("error: pass"); var result = @@ -86,9 +88,15 @@ public async Task GetScores( [HttpGet(RequestType.OsuGetReplay)] public async Task GetReplay( + [FromQuery(Name = "u")] string username, + [FromQuery(Name = "h")] string passhash, [FromQuery(Name = "c")] int scoreId, CancellationToken ct = default ) { + var session = await sessions.TryGetSessionAsync(username, passhash); + if (session == null) + return Ok("error: pass"); + var getReplayResult = await assetBanchoService.GetOsuReplayBytes(scoreId, ct); if (getReplayResult.IsFailure) return Ok("error: no-replay"); From 671dee36f42cb1b61552fd0904387d98aa14ab16 Mon Sep 17 00:00:00 2001 From: richardscull <106016833+richardscull@users.noreply.github.com> Date: Mon, 8 Jun 2026 04:22:47 +0300 Subject: [PATCH 5/6] feat: Add EntityFrameworkCore.Locking.MySql --- Sunrise.Server/Bootstrap.cs | 4 ++++ .../Database/Repositories/ScoreRepository.cs | 24 ++++++------------- Sunrise.Shared/Sunrise.Shared.csproj | 1 + Sunrise.Tests/SunriseServerFactory.cs | 4 +++- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Sunrise.Server/Bootstrap.cs b/Sunrise.Server/Bootstrap.cs index 16d293ee..11cc4f69 100644 --- a/Sunrise.Server/Bootstrap.cs +++ b/Sunrise.Server/Bootstrap.cs @@ -2,6 +2,7 @@ using System.Security.Authentication; using System.Transactions; using EFCoreSecondLevelCacheInterceptor; +using EntityFrameworkCore.Locking.MySql; using Hangfire; using Hangfire.MySql; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -349,6 +350,9 @@ public static void AddSunriseDbContextPool(this WebApplicationBuilder builder) optionsBuilder .AddInterceptors(new SlowQueryLoggerInterceptor()); + optionsBuilder + .UseLocking(); + optionsBuilder .UseMySql(Configuration.DatabaseConnectionString, ServerVersion.AutoDetect(Configuration.DatabaseConnectionString)); }); diff --git a/Sunrise.Shared/Database/Repositories/ScoreRepository.cs b/Sunrise.Shared/Database/Repositories/ScoreRepository.cs index 7034ea4e..8e089571 100644 --- a/Sunrise.Shared/Database/Repositories/ScoreRepository.cs +++ b/Sunrise.Shared/Database/Repositories/ScoreRepository.cs @@ -1,4 +1,6 @@ +using System.Data; using CSharpFunctionalExtensions; +using EntityFrameworkCore.Locking; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using osu.Shared; @@ -14,7 +16,6 @@ using Sunrise.Shared.Extensions.Scores; using Sunrise.Shared.Objects; using Sunrise.Shared.Utils; -using SubmissionStatus = Sunrise.Shared.Enums.Scores.SubmissionStatus; using GameMode = Sunrise.Shared.Enums.Beatmaps.GameMode; namespace Sunrise.Shared.Database.Repositories; @@ -259,7 +260,7 @@ public async Task> EnrichScoresWithLeaderboardPosition(List s var scoresIds = string.Join(",", scores.Select(s => s.Id)); var connection = dbContext.Database.GetDbConnection(); - if (connection.State != System.Data.ConnectionState.Open) + if (connection.State != ConnectionState.Open) await connection.OpenAsync(ct); var gameModesWithoutScoreMultiplier = GameModeExtensions.GetGameModesWithoutScoreMultiplier(); @@ -326,23 +327,12 @@ public async Task GetUserBeatmapPeersForUpdate( { var excludeId = excludeScoreId ?? 0; - // TODO: Use EntityFrameworkCore.Locking.MySql instead after move to Pomelo MySQL provider - // TODO: Use FilterPassedScoreableScores to filter scoreable scores var scores = await dbContext.Scores - .FromSqlInterpolated($""" - SELECT * FROM score - WHERE UserId = {userId} - AND BeatmapHash = {beatmapHash} - AND GameMode = {(int)gameMode} - AND IsScoreable = TRUE - AND IsPassed = TRUE - AND SubmissionStatus != {(int)SubmissionStatus.Failed} - AND SubmissionStatus != {(int)SubmissionStatus.Deleted} - AND SubmissionStatus != {(int)SubmissionStatus.Unknown} - AND Id != {excludeId} - FOR UPDATE - """) + .FilterValidScores() + .FilterPassedScoreableScores() + .Where(s => s.UserId == userId && s.BeatmapHash == beatmapHash && s.GameMode == gameMode && s.Id != excludeId) .AsNoTracking() + .ForUpdate() .ToListAsync(ct); foreach (var score in scores) diff --git a/Sunrise.Shared/Sunrise.Shared.csproj b/Sunrise.Shared/Sunrise.Shared.csproj index c7e1d04e..a04ce9df 100644 --- a/Sunrise.Shared/Sunrise.Shared.csproj +++ b/Sunrise.Shared/Sunrise.Shared.csproj @@ -11,6 +11,7 @@ + diff --git a/Sunrise.Tests/SunriseServerFactory.cs b/Sunrise.Tests/SunriseServerFactory.cs index 664c8b6c..99c46936 100644 --- a/Sunrise.Tests/SunriseServerFactory.cs +++ b/Sunrise.Tests/SunriseServerFactory.cs @@ -1,3 +1,4 @@ +using EntityFrameworkCore.Locking.MySql; using Hangfire; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -29,6 +30,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) { options.EnableServiceProviderCaching(false); options.EnableSensitiveDataLogging(); + options.UseLocking(); options.UseMySql(Configuration.DatabaseConnectionString, ServerVersion.AutoDetect(Configuration.DatabaseConnectionString)); }); @@ -58,7 +60,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) protected override void Dispose(bool disposing) { - if (disposing == false) + if (!disposing) { base.Dispose(disposing); return; From ce8572e92898ce62e861b88c60af821799a0f28f Mon Sep 17 00:00:00 2001 From: richardscull <106016833+richardscull@users.noreply.github.com> Date: Tue, 9 Jun 2026 01:54:16 +0300 Subject: [PATCH 6/6] feat: add Pomelo UseMySqlIdentityColumn migration --- ...225230_AddPomeloIdentityColumn.Designer.cs | 1137 +++++++++++++++++ .../20260608225230_AddPomeloIdentityColumn.cs | 379 ++++++ .../SunriseDbContextModelSnapshot.cs | 193 +-- 3 files changed, 1633 insertions(+), 76 deletions(-) create mode 100644 Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.Designer.cs create mode 100644 Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.cs diff --git a/Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.Designer.cs b/Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.Designer.cs new file mode 100644 index 00000000..d17e7045 --- /dev/null +++ b/Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.Designer.cs @@ -0,0 +1,1137 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Sunrise.Shared.Database; + +#nullable disable + +namespace Sunrise.Shared.Database.Migrations +{ + [DbContext(typeof(SunriseDbContext))] + [Migration("20260608225230_AddPomeloIdentityColumn")] + partial class AddPomeloIdentityColumn + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.22") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Beatmap.BeatmapHype", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BeatmapSetId") + .HasColumnType("int"); + + b.Property("Hypes") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("BeatmapSetId", "Hypes"); + + b.HasIndex("BeatmapSetId", "UserId") + .IsUnique(); + + b.ToTable("beatmap_hype"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Beatmap.CustomBeatmapStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BeatmapHash") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("BeatmapSetId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedByUserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BeatmapHash"); + + b.HasIndex("BeatmapSetId"); + + b.HasIndex("UpdatedByUserId"); + + b.ToTable("custom_beatmap_status"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Events.EventBeatmap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BeatmapSetId") + .HasColumnType("int"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("ExecutorId") + .HasColumnType("int"); + + b.Property("JsonData") + .HasColumnType("longtext"); + + b.Property("Time") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("BeatmapSetId"); + + b.HasIndex("ExecutorId"); + + b.ToTable("event_beatmap"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Events.EventUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Time") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("EventType", "Ip"); + + b.HasIndex("EventType", "UserId"); + + b.ToTable("event_user"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Medal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Category") + .HasColumnType("int"); + + b.Property("Condition") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FileId") + .HasColumnType("int"); + + b.Property("FileUrl") + .HasColumnType("longtext"); + + b.Property("GameMode") + .HasColumnType("tinyint unsigned"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("FileId"); + + b.HasIndex("GameMode"); + + b.ToTable("medal"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.MedalFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("medal_file"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Restriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("ExecutorId") + .HasColumnType("int"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ExecutorId"); + + b.HasIndex("UserId"); + + b.ToTable("restriction"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Score", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Accuracy") + .HasColumnType("double"); + + b.Property("BeatmapHash") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("BeatmapId") + .HasColumnType("int"); + + b.Property("BeatmapStatus") + .HasColumnType("int"); + + b.Property("ClientTime") + .HasColumnType("datetime(6)"); + + b.Property("Count100") + .HasColumnType("int"); + + b.Property("Count300") + .HasColumnType("int"); + + b.Property("Count50") + .HasColumnType("int"); + + b.Property("CountGeki") + .HasColumnType("int"); + + b.Property("CountKatu") + .HasColumnType("int"); + + b.Property("CountMiss") + .HasColumnType("int"); + + b.Property("GameMode") + .HasColumnType("tinyint unsigned"); + + b.Property("Grade") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsPassed") + .HasColumnType("tinyint(1)"); + + b.Property("IsScoreable") + .HasColumnType("tinyint(1)"); + + b.Property("MaxCombo") + .HasColumnType("int"); + + b.Property("Mods") + .HasColumnType("int"); + + b.Property("OsuVersion") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Perfect") + .HasColumnType("tinyint(1)"); + + b.Property("PerformancePoints") + .HasColumnType("double"); + + b.Property("ReplayFileId") + .HasColumnType("int"); + + b.Property("ScoreHash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("SubmissionStatus") + .HasColumnType("int"); + + b.Property("TimeElapsed") + .HasColumnType("int"); + + b.Property("TotalScore") + .HasColumnType("BIGINT"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WhenPlayed") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("BeatmapHash"); + + b.HasIndex("ReplayFileId"); + + b.HasIndex("ScoreHash") + .IsUnique(); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "BeatmapId"); + + b.HasIndex("UserId", "SubmissionStatus", "BeatmapStatus"); + + b.HasIndex("BeatmapId", "IsScoreable", "IsPassed", "SubmissionStatus"); + + b.HasIndex("GameMode", "SubmissionStatus", "BeatmapStatus", "WhenPlayed"); + + b.ToTable("score"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreProcessingTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ActiveScoreId") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("int") + .HasComputedColumnSql("CASE WHEN Status IN (0, 1) THEN ScoreId ELSE NULL END", true); + + b.Property("ActiveScoreSubmissionRequestId") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("int") + .HasComputedColumnSql("CASE WHEN Status IN (0, 1) THEN ScoreSubmissionRequestId ELSE NULL END", true); + + b.Property("ClaimToken") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ErrorCode") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasColumnType("longtext"); + + b.Property("LeaseExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("NextRetryAt") + .HasColumnType("datetime(6)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("RetryCount") + .HasColumnType("int"); + + b.Property("ScoreId") + .HasColumnType("int"); + + b.Property("ScoreSubmissionRequestId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TaskType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ActiveScoreId") + .IsUnique() + .HasDatabaseName("UX_score_processing_task_active_score"); + + b.HasIndex("ActiveScoreSubmissionRequestId") + .IsUnique() + .HasDatabaseName("UX_score_processing_task_active_submission_request"); + + b.HasIndex("ScoreId"); + + b.HasIndex("ScoreSubmissionRequestId"); + + b.HasIndex("Status", "LeaseExpiresAt"); + + b.HasIndex("TaskType", "ScoreId"); + + b.HasIndex("Status", "Priority", "NextRetryAt"); + + b.ToTable("score_processing_task", t => + { + t.HasCheckConstraint("CK_score_processing_task_target", "((TaskType = 0 AND ScoreSubmissionRequestId IS NOT NULL AND ScoreId IS NULL) OR (TaskType <> 0 AND ScoreSubmissionRequestId IS NULL AND ScoreId IS NOT NULL))"); + }); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BeatmapHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ClientHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OsuVersion") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ReplayFileId") + .HasColumnType("int"); + + b.Property("ScoreHash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("ScoreSerialized") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StoryboardHash") + .HasColumnType("longtext"); + + b.Property("TimeElapsed") + .HasColumnType("int"); + + b.Property("UserHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WhenPlayed") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ReplayFileId"); + + b.HasIndex("ScoreHash") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("score_submission_request"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccountStatus") + .HasColumnType("int"); + + b.Property("Country") + .HasColumnType("smallint"); + + b.Property("DefaultGameMode") + .HasColumnType("tinyint unsigned"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Email") + .IsRequired() + .HasColumnType("varchar(255)") + .UseCollation("utf8mb4_unicode_ci"); + + b.Property("LastOnlineTime") + .HasColumnType("datetime(6)"); + + b.Property("Passhash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Privilege") + .HasColumnType("int"); + + b.Property("RegisterDate") + .HasColumnType("datetime(6)"); + + b.Property("SilencedUntil") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("varchar(255)") + .UseCollation("utf8mb4_unicode_ci"); + + b.HasKey("Id"); + + b.HasIndex("AccountStatus"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("user"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserFavouriteBeatmap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BeatmapSetId") + .HasColumnType("int"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "BeatmapSetId"); + + b.ToTable("user_favourite_beatmap"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Path") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("OwnerId", "Type"); + + b.ToTable("user_file"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserGrades", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CountA") + .HasColumnType("int"); + + b.Property("CountB") + .HasColumnType("int"); + + b.Property("CountC") + .HasColumnType("int"); + + b.Property("CountD") + .HasColumnType("int"); + + b.Property("CountS") + .HasColumnType("int"); + + b.Property("CountSH") + .HasColumnType("int"); + + b.Property("CountX") + .HasColumnType("int"); + + b.Property("CountXH") + .HasColumnType("int"); + + b.Property("GameMode") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GameMode") + .IsUnique(); + + b.ToTable("user_grades"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserInventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ItemType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemType") + .IsUnique(); + + b.ToTable("user_inventory_item"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserMedals", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("MedalId") + .HasColumnType("int"); + + b.Property("UnlockedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("user_medals"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Discord") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("Interest") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("Occupation") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("Playstyle") + .HasColumnType("int"); + + b.Property("Telegram") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("Twitch") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("Twitter") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Website") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("user_metadata"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserRelationship", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Relation") + .HasColumnType("int"); + + b.Property("TargetId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TargetId"); + + b.HasIndex("UserId", "TargetId"); + + b.ToTable("user_relationship"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Accuracy") + .HasColumnType("double"); + + b.Property("BestCountryRank") + .HasColumnType("BIGINT"); + + b.Property("BestCountryRankDate") + .HasColumnType("datetime(6)"); + + b.Property("BestGlobalRank") + .HasColumnType("BIGINT"); + + b.Property("BestGlobalRankDate") + .HasColumnType("datetime(6)"); + + b.Property("GameMode") + .HasColumnType("tinyint unsigned"); + + b.Property("MaxCombo") + .HasColumnType("int"); + + b.Property("PerformancePoints") + .HasColumnType("double"); + + b.Property("PlayCount") + .HasColumnType("int"); + + b.Property("PlayTime") + .HasColumnType("int"); + + b.Property("RankedScore") + .HasColumnType("BIGINT"); + + b.Property("TotalHits") + .HasColumnType("int"); + + b.Property("TotalScore") + .HasColumnType("BIGINT"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "GameMode") + .IsUnique(); + + b.ToTable("user_stats"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserStatsSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("GameMode") + .HasColumnType("tinyint unsigned"); + + b.Property("SnapshotsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GameMode"); + + b.ToTable("user_stats_snapshot"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Beatmap.BeatmapHype", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Beatmap.CustomBeatmapStatus", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Events.EventBeatmap", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "Executor") + .WithMany() + .HasForeignKey("ExecutorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Executor"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Events.EventUser", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Medal", b => + { + b.HasOne("Sunrise.Shared.Database.Models.MedalFile", "File") + .WithMany() + .HasForeignKey("FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Restriction", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "Executor") + .WithMany() + .HasForeignKey("ExecutorId"); + + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Executor"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Score", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.UserFile", "ReplayFile") + .WithMany() + .HasForeignKey("ReplayFileId"); + + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReplayFile"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreProcessingTask", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Score", "Score") + .WithMany() + .HasForeignKey("ScoreId"); + + b.HasOne("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", "ScoreSubmissionRequest") + .WithMany() + .HasForeignKey("ScoreSubmissionRequestId"); + + b.Navigation("Score"); + + b.Navigation("ScoreSubmissionRequest"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.UserFile", "ReplayFile") + .WithMany() + .HasForeignKey("ReplayFileId"); + + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReplayFile"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserFavouriteBeatmap", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserFile", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany("UserFiles") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserGrades", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserInventoryItem", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany("Inventory") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserMedals", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserMetadata", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserRelationship", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "Target") + .WithMany("UserReceivedRelationships") + .HasForeignKey("TargetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany("UserInitiatedRelationships") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Target"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserStats", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany("UserStats") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserStatsSnapshot", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") + .WithMany("UserStatsSnapshots") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.User", b => + { + b.Navigation("Inventory"); + + b.Navigation("UserFiles"); + + b.Navigation("UserInitiatedRelationships"); + + b.Navigation("UserReceivedRelationships"); + + b.Navigation("UserStats"); + + b.Navigation("UserStatsSnapshots"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.cs b/Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.cs new file mode 100644 index 00000000..3292a963 --- /dev/null +++ b/Sunrise.Shared/Database/Migrations/20260608225230_AddPomeloIdentityColumn.cs @@ -0,0 +1,379 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Sunrise.Shared.Database.Migrations +{ + /// + public partial class AddPomeloIdentityColumn : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Id", + table: "user_stats_snapshot", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_stats", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_relationship", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_metadata", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_medals", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_inventory_item", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_grades", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_file", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_favourite_beatmap", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "score_submission_request", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "score_processing_task", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "score", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "restriction", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "medal_file", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "medal", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "event_user", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "event_beatmap", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "custom_beatmap_status", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "beatmap_hype", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Id", + table: "user_stats_snapshot", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_stats", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_relationship", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_metadata", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_medals", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_inventory_item", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_grades", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_file", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user_favourite_beatmap", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "user", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "score_submission_request", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "score_processing_task", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "score", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "restriction", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "medal_file", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "medal", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "event_user", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "event_beatmap", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "custom_beatmap_status", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + + migrationBuilder.AlterColumn( + name: "Id", + table: "beatmap_hype", + type: "int", + nullable: false, + oldClrType: typeof(int), + oldType: "int") + .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); + } + } +} diff --git a/Sunrise.Shared/Database/Migrations/SunriseDbContextModelSnapshot.cs b/Sunrise.Shared/Database/Migrations/SunriseDbContextModelSnapshot.cs index 7b31cad0..c758db1a 100644 --- a/Sunrise.Shared/Database/Migrations/SunriseDbContextModelSnapshot.cs +++ b/Sunrise.Shared/Database/Migrations/SunriseDbContextModelSnapshot.cs @@ -1,7 +1,8 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Sunrise.Shared.Database; @@ -19,12 +20,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasAnnotation("ProductVersion", "8.0.22") .HasAnnotation("Relational:MaxIdentifierLength", 64); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("Sunrise.Shared.Database.Models.Beatmap.BeatmapHype", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("BeatmapSetId") .HasColumnType("int"); @@ -52,6 +57,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("BeatmapHash") .IsRequired() .HasColumnType("varchar(255)"); @@ -82,6 +89,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("BeatmapSetId") .HasColumnType("int"); @@ -112,6 +121,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("EventType") .HasColumnType("int"); @@ -146,6 +157,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Category") .HasColumnType("int"); @@ -187,6 +200,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("CreatedAt") .HasColumnType("datetime(6)"); @@ -205,6 +220,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Date") .HasColumnType("datetime(6)"); @@ -236,6 +253,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Accuracy") .HasColumnType("double"); @@ -344,70 +363,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("score"); }); - modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("BeatmapHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ClientHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("OsuVersion") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ReplayFileId") - .HasColumnType("int"); - - b.Property("ScoreHash") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("varchar(32)"); - - b.Property("ScoreSerialized") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("StoryboardHash") - .HasColumnType("longtext"); - - b.Property("TimeElapsed") - .HasColumnType("int"); - - b.Property("UserHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("UserId") - .HasColumnType("int"); - - b.Property("WhenPlayed") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("ReplayFileId"); - - b.HasIndex("ScoreHash") - .IsUnique(); - - b.HasIndex("UserId"); - - b.ToTable("score_submission_request"); - }); - modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreProcessingTask", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ActiveScoreId") .ValueGeneratedOnAddOrUpdate() .HasColumnType("int") @@ -480,12 +443,74 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BeatmapHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ClientHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OsuVersion") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ReplayFileId") + .HasColumnType("int"); + + b.Property("ScoreHash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("ScoreSerialized") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StoryboardHash") + .HasColumnType("longtext"); + + b.Property("TimeElapsed") + .HasColumnType("int"); + + b.Property("UserHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("WhenPlayed") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ReplayFileId"); + + b.HasIndex("ScoreHash") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("score_submission_request"); + }); + modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.User", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("AccountStatus") .HasColumnType("int"); @@ -543,6 +568,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("BeatmapSetId") .HasColumnType("int"); @@ -567,6 +594,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("CreatedAt") .HasColumnType("datetime(6)"); @@ -598,6 +627,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("CountA") .HasColumnType("int"); @@ -642,6 +673,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ItemType") .HasColumnType("int"); @@ -665,6 +698,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("MedalId") .HasColumnType("int"); @@ -687,6 +722,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Discord") .IsRequired() .HasMaxLength(32) @@ -746,6 +783,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Relation") .HasColumnType("int"); @@ -770,6 +809,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("Accuracy") .HasColumnType("double"); @@ -828,6 +869,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("GameMode") .HasColumnType("tinyint unsigned"); @@ -932,6 +975,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("User"); }); + modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreProcessingTask", b => + { + b.HasOne("Sunrise.Shared.Database.Models.Score", "Score") + .WithMany() + .HasForeignKey("ScoreId"); + + b.HasOne("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", "ScoreSubmissionRequest") + .WithMany() + .HasForeignKey("ScoreSubmissionRequestId"); + + b.Navigation("Score"); + + b.Navigation("ScoreSubmissionRequest"); + }); + modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", b => { b.HasOne("Sunrise.Shared.Database.Models.Users.UserFile", "ReplayFile") @@ -949,21 +1007,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("User"); }); - modelBuilder.Entity("Sunrise.Shared.Database.Models.Scores.ScoreProcessingTask", b => - { - b.HasOne("Sunrise.Shared.Database.Models.Score", "Score") - .WithMany() - .HasForeignKey("ScoreId"); - - b.HasOne("Sunrise.Shared.Database.Models.Scores.ScoreSubmissionRequest", "ScoreSubmissionRequest") - .WithMany() - .HasForeignKey("ScoreSubmissionRequestId"); - - b.Navigation("Score"); - - b.Navigation("ScoreSubmissionRequest"); - }); - modelBuilder.Entity("Sunrise.Shared.Database.Models.Users.UserFavouriteBeatmap", b => { b.HasOne("Sunrise.Shared.Database.Models.Users.User", "User") @@ -1089,5 +1132,3 @@ protected override void BuildModel(ModelBuilder modelBuilder) } } } - -