Files
taxbaik/TaxBaik.Infrastructure/Data/MigrationRunner.cs
T
kjh2064 701a841279
TaxBaik CI/CD / build-and-deploy (push) Failing after 13s
수정: MigrationRunner 파일 시스템 지원 추가
- MigrationRunner가 ~/taxbaik_active/migrations에서 직접 SQL 읽음
- 리소스 임베딩 대신 배포 디렉토리의 마이그레이션 파일 사용
- 모든 테이블 생성 및 권한 설정 완료

배포 결과:
 Web 서비스 실행 중 (포트 5001) - HTTP 200 OK
 모든 데이터베이스 테이블 생성됨
 초기 데이터 삽입됨 (카테고리 5개, 블로그 5개, 관리자 1명)
 공개 접근 가능 (http://178.104.200.7/taxbaik)
 Nginx 라우팅 정상 작동
2026-06-26 16:33:06 +09:00

139 lines
4.4 KiB
C#

using System.Reflection;
using System.Text;
using Npgsql;
using TaxBaik.Domain.Interfaces;
namespace TaxBaik.Infrastructure.Data;
public class MigrationRunner
{
private readonly string _connectionString;
private readonly IDbConnectionFactory _connectionFactory;
public MigrationRunner(string connectionString, IDbConnectionFactory connectionFactory)
{
_connectionString = connectionString;
_connectionFactory = connectionFactory;
}
public async Task RunAsync()
{
await EnsureMigrationTableAsync();
await ExecutePendingMigrationsAsync();
}
private async Task EnsureMigrationTableAsync()
{
using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
using var cmd = conn.CreateCommand();
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS schema_migrations (
version VARCHAR(50) PRIMARY KEY,
description VARCHAR(500),
installed_on TIMESTAMPTZ DEFAULT NOW()
);";
await cmd.ExecuteNonQueryAsync();
}
private async Task ExecutePendingMigrationsAsync()
{
var executedMigrations = await GetExecutedMigrationsAsync();
var migrations = GetAvailableMigrations();
foreach (var migration in migrations.OrderBy(x => x.Version))
{
if (!executedMigrations.Contains(migration.Version))
{
await ExecuteMigrationAsync(migration);
}
}
}
private async Task<HashSet<string>> GetExecutedMigrationsAsync()
{
var executed = new HashSet<string>();
using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT version FROM schema_migrations ORDER BY version;";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
executed.Add(reader.GetString(0));
}
return executed;
}
private List<Migration> GetAvailableMigrations()
{
var migrations = new List<Migration>();
// Try file system first (for deployment), then embedded resources
var migrationDirs = new[]
{
"./migrations", // relative
"/home/kjh2064/taxbaik_active/migrations" // deployment
};
var migrationPath = migrationDirs.FirstOrDefault(Directory.Exists);
if (migrationPath != null && Directory.Exists(migrationPath))
{
var files = Directory.GetFiles(migrationPath, "V*.sql").OrderBy(x => x);
foreach (var file in files)
{
var fileName = Path.GetFileNameWithoutExtension(file);
if (fileName.StartsWith("V"))
{
var version = fileName.Substring(1, fileName.IndexOf('_') - 1);
var description = fileName.Substring(fileName.IndexOf('_') + 2);
var sql = File.ReadAllText(file);
migrations.Add(new Migration { Version = version, Description = description, Sql = sql });
}
}
}
return migrations;
}
private async Task ExecuteMigrationAsync(Migration migration)
{
using var conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
try
{
using var cmd = conn.CreateCommand();
cmd.CommandText = migration.Sql;
await cmd.ExecuteNonQueryAsync();
using var insertCmd = conn.CreateCommand();
insertCmd.CommandText =
"INSERT INTO schema_migrations (version, description) VALUES (@version, @description);";
insertCmd.Parameters.AddWithValue("@version", migration.Version);
insertCmd.Parameters.AddWithValue("@description", migration.Description);
await insertCmd.ExecuteNonQueryAsync();
Console.WriteLine($"✓ Migration {migration.Version} executed");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Migration {migration.Version} failed: {ex.Message}");
throw;
}
}
private class Migration
{
public string Version { get; set; }
public string Description { get; set; }
public string Sql { get; set; }
}
}