refactor: move buildable .NET source into src/, update CI/doc paths
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m7s
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m7s
Groups the repo root into src (buildable source), docs (already existed), and everything else (db/, scripts/, tests/, deploy/ - deployment/ops/test assets that aren't compiled, already organized as their own folders). CI now only needs src/ to build: dotnet restore/build/test/publish all point at src/TaxBaik.sln, src/TaxBaik.Web/, src/TaxBaik.Proxy/. - git mv every project (Domain, Infrastructure, Application, Application.Tests, Web, Web.Client, Proxy) and TaxBaik.sln into src/ as a unit, so relative ProjectReference/.sln paths stay valid unchanged. - .gitea/workflows/deploy.yml: 6 dotnet restore/clean/build/test/publish invocations now point at src/. db/migrations and scripts/ stay at root (deploy_gb.sh and browser-e2e.yml only touch published output and the deployed URL, not source paths - verified, no changes needed there). - scripts/validate_admin_render.sh: admin render-mode file paths now src/TaxBaik.Web.Client/... - scripts/validate_kst_timestamps.sh: dropped deploy.sh from its target list - that script was removed in the prior cleanup commit (dead, no CI workflow referenced it) but this validator still expected it to exist. - CLAUDE.md, docs/ENGINEERING_HARNESS.md, docs/ADMIN_PATTERN_CRITIQUE_WBS.md: updated project-structure diagram, dotnet run/build commands, and grep targets to the new src/ paths (also fixed a pre-existing stale path in ADMIN_PATTERN_CRITIQUE_WBS.md that still said TaxBaik.Web/Components/Admin from before that ever moved to TaxBaik.Web.Client). - Added a Repo Root harness rule + Architecture Guardrail entries: new files belong under src/docs/tests/scripts/db/deploy, not loose at root; temp work stays outside the repo (or under a gitignored .scratch/) and is never committed. Verified locally: dotnet build/test src/TaxBaik.sln (26/26 tests), and all three scripts/validate_*.sh pass against the new layout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,176 +0,0 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceNames = assembly.GetManifestResourceNames()
|
||||
.Where(x => x.Contains(".Migrations.V") && x.EndsWith(".sql", StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(x => x);
|
||||
|
||||
foreach (var resourceName in resourceNames)
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
var sql = reader.ReadToEnd();
|
||||
var fileName = Path.GetFileNameWithoutExtension(resourceName);
|
||||
var versionStart = fileName.IndexOf('V');
|
||||
var versionEnd = fileName.IndexOf('_', versionStart + 1);
|
||||
if (versionStart < 0 || versionEnd < 0)
|
||||
continue;
|
||||
|
||||
var version = fileName.Substring(versionStart + 1, versionEnd - versionStart - 1);
|
||||
var description = fileName.Substring(versionEnd + 1);
|
||||
|
||||
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 (Npgsql.PostgresException pgEx) when (pgEx.SqlState == "42P07") // relation already exists
|
||||
{
|
||||
// Already executed previously; mark as done
|
||||
Console.WriteLine($"ℹ Migration {migration.Version} already applied");
|
||||
using var insertCmd = conn.CreateCommand();
|
||||
insertCmd.CommandText =
|
||||
"INSERT INTO schema_migrations (version, description) VALUES (@version, @description) ON CONFLICT (version) DO NOTHING;";
|
||||
insertCmd.Parameters.AddWithValue("@version", migration.Version);
|
||||
insertCmd.Parameters.AddWithValue("@description", migration.Description);
|
||||
await insertCmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"✗ Migration {migration.Version} failed: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private class Migration
|
||||
{
|
||||
public required string Version { get; set; }
|
||||
public required string Description { get; set; }
|
||||
public required string Sql { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user