astral-api/Astral.DAL/Infrastructure/DatabaseMigrator.cs
2024-12-11 20:36:30 +00:00

95 lines
3.5 KiB
C#

// <copyright file="DatabaseMigrator.cs" company="alveus.dev">
// Copyright (c) alveus.dev. All rights reserved. Licensed under the MIT License.
// </copyright>
using Astral.Core.Exceptions;
using Astral.Core.Infrastructure;
using Dapper;
using Injectio.Attributes;
using Microsoft.Extensions.Logging;
namespace Astral.DAL.Infrastructure;
/// <inheritdoc />
[RegisterScoped]
public class DatabaseMigrator : IDatabaseMigrator
{
private readonly IDbConnectionProvider _dbConnectionProvider;
private readonly ILogger<DatabaseMigrator> _logger;
private readonly ITransactionProvider _transactionProvider;
/// <summary>
/// Initializes a new instance of the <see cref="DatabaseMigrator" /> class.
/// </summary>
/// <param name="dbConnectionProvider">Instance of <see cref="IDbConnectionProvider" />.</param>
/// <param name="transactionProvider">Instance of <see cref="ITransactionProvider" />.</param>
/// <param name="logger">Instance of <see cref="ILogger" />.</param>
public DatabaseMigrator(
IDbConnectionProvider dbConnectionProvider,
ITransactionProvider transactionProvider,
ILogger<DatabaseMigrator> logger)
{
_dbConnectionProvider = dbConnectionProvider;
_transactionProvider = transactionProvider;
_logger = logger;
}
/// <inheritdoc />
public async Task MigrateDatabaseAsync(string migrationsPath)
{
var connection = await _dbConnectionProvider.OpenConnectionAsync();
_logger.LogInformation("Beginning database migrations");
using var transaction = await _transactionProvider.BeginTransactionAsync();
await connection.ExecuteAsync("""
CREATE TABLE IF NOT EXISTS migrations (
date_added TIMESTAMP NOT NULL,
filename VARCHAR(128) NOT NULL,
CONSTRAINT migrations_filename_pk PRIMARY KEY (filename)
);
""");
var appliedMigrationsEnumerable = await
connection.QueryAsync<string>("SELECT filename FROM migrations ORDER BY date_added;");
// Prevent multiple enumeration.
var appliedMigrations = appliedMigrationsEnumerable.ToList();
var files = Directory.GetFiles(migrationsPath, "*.sql").ToList();
files.Sort();
foreach (var filename in files)
{
var file = filename.Replace(migrationsPath, string.Empty)
.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
if (appliedMigrations.Contains(file))
{
_logger.LogInformation("Already applied: {file}", file);
continue;
}
_logger.LogInformation("Applying {file}...", file);
try
{
var sql = await File.ReadAllTextAsync(filename);
await connection.ExecuteAsync(sql);
await connection.ExecuteAsync(
"INSERT INTO migrations (date_added, filename) SELECT CURRENT_TIMESTAMP, @pFilename ;",
new
{
pFilename = file
});
}
catch (Exception e)
{
throw new MigrationException(file, e.Message);
}
}
transaction.Commit();
_logger.LogInformation("Database migrations complete");
}
}