From 86e3f699896b1c3afe0135195d8ab8ab9b16dc98 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Fri, 17 Oct 2025 01:52:43 +0200 Subject: [PATCH] Fixed migrations --- CS2-SimpleAdmin/Database/Migration.cs | 105 ++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 CS2-SimpleAdmin/Database/Migration.cs diff --git a/CS2-SimpleAdmin/Database/Migration.cs b/CS2-SimpleAdmin/Database/Migration.cs new file mode 100644 index 0000000..1f368be --- /dev/null +++ b/CS2-SimpleAdmin/Database/Migration.cs @@ -0,0 +1,105 @@ +using System.Data.Common; +using Microsoft.Extensions.Logging; + +namespace CS2_SimpleAdmin.Database; + +public class Migration(string migrationsPath) +{ + /// + /// Executes all migration scripts found in the configured migrations path that have not been applied yet. + /// Creates a migration tracking table if it does not exist. + /// Applies migration scripts in filename order and logs successes or failures. + /// + public async Task ExecuteMigrationsAsync() + { + if (CS2_SimpleAdmin.DatabaseProvider == null) return; + var files = Directory.GetFiles(migrationsPath, "*.sql").OrderBy(f => f).ToList(); + if (files.Count == 0) return; + + await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); + await using (var cmd = connection.CreateCommand()) + { + if (migrationsPath.Contains("sqlite", StringComparison.CurrentCultureIgnoreCase)) + { + cmd.CommandText = """ + CREATE TABLE IF NOT EXISTS sa_migrations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + version TEXT NOT NULL + ); + + """; + } + else + { + cmd.CommandText = """ + CREATE TABLE IF NOT EXISTS sa_migrations ( + id INT PRIMARY KEY AUTO_INCREMENT, + version VARCHAR(128) NOT NULL + ); + """; + } + + await cmd.ExecuteNonQueryAsync(); + } + + var lastAppliedVersion = await GetLastAppliedVersionAsync(connection); + + foreach (var file in files) + { + var version = Path.GetFileNameWithoutExtension(file); + if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0) + continue; + + try + { + var sqlScript = await File.ReadAllTextAsync(file); + + await using (var cmdMigration = connection.CreateCommand()) + { + cmdMigration.CommandText = sqlScript; + await cmdMigration.ExecuteNonQueryAsync(); + } + + await UpdateLastAppliedVersionAsync(connection, version); + + CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied."); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError(ex, $"Error applying migration \"{version}\"."); + break; + } + } + } + + /// + /// Retrieves the version string of the last applied migration from the database. + /// + /// The open database connection. + /// The version string of the last applied migration, or empty string if none. + private static async Task GetLastAppliedVersionAsync(DbConnection connection) + { + await using var cmd = connection.CreateCommand(); + cmd.CommandText = "SELECT version FROM sa_migrations ORDER BY id DESC LIMIT 1;"; + var result = await cmd.ExecuteScalarAsync(); + return result?.ToString() ?? string.Empty; + } + + /// + /// Inserts a record tracking the successful application of a migration version. + /// + /// The open database connection. + /// The version string of the migration applied. + private static async Task UpdateLastAppliedVersionAsync(DbConnection connection, string version) + { + await using var cmd = connection.CreateCommand(); + cmd.CommandText = "INSERT INTO sa_migrations (version) VALUES (@Version);"; + + var param = cmd.CreateParameter(); + param.ParameterName = "@Version"; + param.Value = version; + cmd.Parameters.Add(param); + + await cmd.ExecuteNonQueryAsync(); + } +}