refactor: move buildable .NET source into src/, update CI/doc paths
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:
2026-07-03 10:37:37 +09:00
parent c00d002972
commit ea447495d3
277 changed files with 36 additions and 29 deletions
@@ -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; }
}
}