using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Data.Misc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace API.Data.ManualMigrations; /// /// v0.8.9 - AppUserRating is missing Created/CreatedUtc/LastModified/LastModifiedUtc on old installs /// public class MigrateMissingAppUserRatingDateColumns : ManualMigration { protected override string MigrationName => nameof(MigrateMissingAppUserRatingDateColumns ); protected override async Task ExecuteAsync(DataContext context, ILogger logger) { logger.LogDebug("Checking for missing date columns on AppUserRating table"); // Check which columns are missing var missingColumns = await GetMissingColumnsAsync(context); if (missingColumns.Count == 0) { logger.LogDebug("All date columns already exist on AppUserRating table. Skipping migration."); return; } logger.LogDebug("Missing columns: {Columns}", string.Join(", ", missingColumns)); // Add missing columns foreach (var column in missingColumns) { logger.LogDebug("Adding column {Column} to AppUserRating", column); await AddColumnAsync(context, column); } // Backfill only the columns we just added await BackfillDateColumnsAsync(context, logger, missingColumns); logger.LogDebug("Migration completed successfully"); } private static async Task> GetMissingColumnsAsync(DataContext dataContext) { var requiredColumns = new[] { "Created", "CreatedUtc", "LastModified", "LastModifiedUtc" }; var missingColumns = new List(); foreach (var column in requiredColumns) { var exists = await ColumnExistsAsync(dataContext, column); if (!exists) { missingColumns.Add(column); } } return missingColumns; } private static async Task ColumnExistsAsync(DataContext dataContext, string columnName) { var sql = "SELECT COUNT(*) FROM pragma_table_info('AppUserRating') WHERE name = @columnName"; var connection = dataContext.Database.GetDbConnection(); await using var command = connection.CreateCommand(); command.CommandText = sql; command.Parameters.Add(new Microsoft.Data.Sqlite.SqliteParameter("@columnName", columnName)); if (connection.State != System.Data.ConnectionState.Open) { await connection.OpenAsync(); } var result = await command.ExecuteScalarAsync(); return Convert.ToInt32(result) > 0; } private static async Task AddColumnAsync(DataContext dataContext, string columnName) { var sql = $"ALTER TABLE AppUserRating ADD COLUMN {columnName} TEXT NOT NULL DEFAULT '0001-01-01 00:00:00'"; await dataContext.Database.ExecuteSqlRawAsync(sql); } private static async Task BackfillDateColumnsAsync(DataContext dataContext, ILogger logger, List columnsToBackfill) { if (columnsToBackfill.Count == 0) { return; } logger.LogDebug("Backfilling newly added date columns with current timestamp"); // Build dynamic SET clause only for columns we just added var setClause = string.Join(", ", columnsToBackfill.Select(c => $"{c} = datetime('now')")); var updateSql = $@" UPDATE AppUserRating SET {setClause} WHERE {columnsToBackfill[0]} = '0001-01-01 00:00:00'"; var rowsAffected = await dataContext.Database.ExecuteSqlRawAsync(updateSql); logger.LogDebug("Updated {Rows} records", rowsAffected); } }