Seeding updated
This commit is contained in:
161
SVSim.Bootstrap/Program.cs
Normal file
161
SVSim.Bootstrap/Program.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using SVSim.Bootstrap.Importers;
|
||||
using SVSim.Database;
|
||||
|
||||
namespace SVSim.Bootstrap;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
private const string DefaultConnectionString =
|
||||
"Host=localhost;Database=svsim;Username=postgres;password=postgres";
|
||||
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0 && (args[0] is "--help" or "-h"))
|
||||
{
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
var opts = ParseArgs(args);
|
||||
if (opts is null)
|
||||
{
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (opts.SkipCards && opts.SkipGlobals)
|
||||
{
|
||||
Console.Error.WriteLine("Both --skip-cards and --skip-globals set; nothing to do.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[Bootstrap] Connection: {RedactPassword(opts.ConnectionString)}");
|
||||
Console.WriteLine($"[Bootstrap] Cards file: {opts.CardsFile}");
|
||||
Console.WriteLine($"[Bootstrap] Captures: {opts.CapturesDir}");
|
||||
|
||||
var dbOptions = new DbContextOptionsBuilder<SVSimDbContext>()
|
||||
.UseNpgsql(opts.ConnectionString)
|
||||
.Options;
|
||||
|
||||
await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions);
|
||||
|
||||
// Bootstrap applies pending migrations first so it can be the very first thing run after
|
||||
// `dotnet ef migrations add` — no need to start the server too.
|
||||
Console.WriteLine("[Bootstrap] Applying pending migrations...");
|
||||
await context.Database.MigrateAsync();
|
||||
|
||||
if (!opts.SkipCards)
|
||||
{
|
||||
await new CardImporter().ImportAsync(context, opts.CardsFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[Bootstrap] --skip-cards set; skipping card import.");
|
||||
}
|
||||
|
||||
if (!opts.SkipGlobals)
|
||||
{
|
||||
await new GlobalsImporter().ImportAllAsync(context, opts.CapturesDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[Bootstrap] --skip-globals set; skipping globals import.");
|
||||
}
|
||||
|
||||
Console.WriteLine("[Bootstrap] Complete.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static BootstrapOptions? ParseArgs(string[] args)
|
||||
{
|
||||
string? dataDir = null;
|
||||
string? cards = null;
|
||||
string? captures = null;
|
||||
string? connection = null;
|
||||
bool skipCards = false;
|
||||
bool skipGlobals = false;
|
||||
string? positionalCards = null;
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
string a = args[i];
|
||||
switch (a)
|
||||
{
|
||||
case "--data-dir": dataDir = NextArg(args, ref i); break;
|
||||
case "--cards": cards = NextArg(args, ref i); break;
|
||||
case "--captures": captures = NextArg(args, ref i); break;
|
||||
case "--connection-string": connection = NextArg(args, ref i); break;
|
||||
case "--skip-cards": skipCards = true; break;
|
||||
case "--skip-globals": skipGlobals = true; break;
|
||||
default:
|
||||
// Back-compat: legacy positional form `svsim-card-import <cards.json> [connection]`.
|
||||
if (positionalCards is null && !a.StartsWith('-')) positionalCards = a;
|
||||
else if (connection is null && !a.StartsWith('-')) connection = a;
|
||||
else { Console.Error.WriteLine($"Unknown argument: {a}"); return null; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolution order:
|
||||
// --cards beats --data-dir/cards.json beats legacy positional;
|
||||
// --captures beats --data-dir/prod-captures beats Bootstrap/Data/prod-captures (shipped default).
|
||||
string baseDir = AppContext.BaseDirectory;
|
||||
string shippedCaptures = Path.Combine(baseDir, "Data", "prod-captures");
|
||||
|
||||
string cardsFile = cards
|
||||
?? (dataDir is not null ? Path.Combine(dataDir, "cards.json") : null)
|
||||
?? positionalCards
|
||||
?? "data_dumps/cards.json";
|
||||
|
||||
// Resolve captures dir, falling back to the shipped copy if the data-dir path is unset
|
||||
// OR points at a missing folder. (Common case: user has cards.json in data_dumps/ but
|
||||
// hasn't copied prod-captures/ there — the shipped snapshot is the source of truth.)
|
||||
string? capturesCandidate = captures
|
||||
?? (dataDir is not null ? Path.Combine(dataDir, "prod-captures") : null);
|
||||
string capturesDir = capturesCandidate is not null && Directory.Exists(capturesCandidate)
|
||||
? capturesCandidate
|
||||
: shippedCaptures;
|
||||
|
||||
string connStr = connection
|
||||
?? Environment.GetEnvironmentVariable("NPGSQL_CONNECTION")
|
||||
?? DefaultConnectionString;
|
||||
|
||||
return new BootstrapOptions(cardsFile, capturesDir, connStr, skipCards, skipGlobals);
|
||||
}
|
||||
|
||||
private static string NextArg(string[] args, ref int i)
|
||||
{
|
||||
if (i + 1 >= args.Length) throw new ArgumentException($"Missing value for {args[i]}");
|
||||
return args[++i];
|
||||
}
|
||||
|
||||
private static string RedactPassword(string conn) =>
|
||||
System.Text.RegularExpressions.Regex.Replace(conn, "(?i)(password=)[^;]+", "$1***");
|
||||
|
||||
private static void PrintUsage()
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"Usage: svsim-bootstrap [options]\n" +
|
||||
"\n" +
|
||||
" --data-dir <path> Directory containing cards.json and prod-captures/\n" +
|
||||
" (default: ./data_dumps relative to working dir)\n" +
|
||||
" --cards <file> Override path to cards.json\n" +
|
||||
" --captures <dir> Override path to prod-captures directory\n" +
|
||||
" (default: shipped Data/prod-captures next to the binary)\n" +
|
||||
" --connection-string <conn> Postgres connection (or NPGSQL_CONNECTION env var,\n" +
|
||||
$" then \"{DefaultConnectionString}\")\n" +
|
||||
" --skip-cards Skip card import (re-run globals only)\n" +
|
||||
" --skip-globals Skip globals import (cards only — legacy behavior)\n" +
|
||||
"\n" +
|
||||
"Back-compat: `svsim-bootstrap <cards.json> [connection]` still works (positional).");
|
||||
}
|
||||
|
||||
private sealed record BootstrapOptions(
|
||||
string CardsFile,
|
||||
string CapturesDir,
|
||||
string ConnectionString,
|
||||
bool SkipCards,
|
||||
bool SkipGlobals);
|
||||
}
|
||||
Reference in New Issue
Block a user