Forgot unversioned xd
This commit is contained in:
175
SVSim.CardImport/Program.cs
Normal file
175
SVSim.CardImport/Program.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.CardImport;
|
||||
|
||||
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 < 1 || args[0] is "--help" or "-h")
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"Usage: svsim-card-import <cards.json> [connection-string]\n" +
|
||||
"\n" +
|
||||
" cards.json Path to the loader's card dump (LitJson array of CardCSVData)\n" +
|
||||
" connection-string Postgres connection (falls back to NPGSQL_CONNECTION env var,\n" +
|
||||
$" then \"{DefaultConnectionString}\")");
|
||||
return 1;
|
||||
}
|
||||
|
||||
string path = args[0];
|
||||
string connection = args.Length > 1
|
||||
? args[1]
|
||||
: Environment.GetEnvironmentVariable("NPGSQL_CONNECTION") ?? DefaultConnectionString;
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Console.Error.WriteLine($"File not found: {path}");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Reading {path} ({new FileInfo(path).Length / 1024} KiB)...");
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
};
|
||||
|
||||
List<CardInput>? input;
|
||||
await using (var fs = File.OpenRead(path))
|
||||
{
|
||||
input = await JsonSerializer.DeserializeAsync<List<CardInput>>(fs, jsonOptions);
|
||||
}
|
||||
if (input is null || input.Count == 0)
|
||||
{
|
||||
Console.Error.WriteLine("No card records parsed from input.");
|
||||
return 3;
|
||||
}
|
||||
Console.WriteLine($"Parsed {input.Count} card records.");
|
||||
|
||||
var dbOptions = new DbContextOptionsBuilder<SVSimDbContext>()
|
||||
.UseNpgsql(connection)
|
||||
.Options;
|
||||
|
||||
await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions);
|
||||
|
||||
// Apply any pending migrations first — bootstraps a fresh DB so CardImport can be the
|
||||
// very first thing run after `dotnet ef migrations add` (no need to run the server too).
|
||||
// Migration files have InsertData rows for the seeded master data already; runtime seeder
|
||||
// skip is fine.
|
||||
await context.Database.MigrateAsync();
|
||||
|
||||
var classesById = await context.Classes.ToDictionaryAsync(c => c.Id);
|
||||
var existingSets = (await context.CardSets.ToListAsync()).ToDictionary(s => s.Id);
|
||||
var existingCards = (await context.Cards.ToListAsync()).ToDictionary(c => c.Id);
|
||||
Console.WriteLine(
|
||||
$"DB state before: {existingCards.Count} cards, {existingSets.Count} card sets, " +
|
||||
$"{classesById.Count} classes seeded.");
|
||||
|
||||
int created = 0, updated = 0, skipped = 0, setsCreated = 0;
|
||||
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (!long.TryParse(c.CardId, out long id) || id == 0)
|
||||
{
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int setId = ParseInt(c.CardSetId, 0);
|
||||
int clan = ParseInt(c.Clan, 0);
|
||||
int rarity = ParseInt(c.Rarity, 0);
|
||||
|
||||
if (!existingSets.TryGetValue(setId, out var set))
|
||||
{
|
||||
set = new ShadowverseCardSetEntry
|
||||
{
|
||||
Id = setId,
|
||||
Name = $"Card Set {setId}",
|
||||
IsInRotation = true,
|
||||
IsBasic = false
|
||||
};
|
||||
context.CardSets.Add(set);
|
||||
existingSets[setId] = set;
|
||||
setsCreated++;
|
||||
}
|
||||
|
||||
ClassEntry? classEntry = clan > 0 && classesById.TryGetValue(clan, out var ce) ? ce : null;
|
||||
var collection = new CardCollectionInfo
|
||||
{
|
||||
CraftCost = ParseInt(c.UseRedEther, 0),
|
||||
DustReward = ParseInt(c.GetRedEther, 0)
|
||||
};
|
||||
|
||||
if (existingCards.TryGetValue(id, out var card))
|
||||
{
|
||||
card.Rarity = (Rarity)rarity;
|
||||
card.PrimaryResourceCost = ParseNullableInt(c.Cost);
|
||||
card.Attack = ParseNullableInt(c.Atk);
|
||||
card.Defense = ParseNullableInt(c.Life);
|
||||
card.Class = classEntry;
|
||||
card.CollectionInfo = collection;
|
||||
updated++;
|
||||
}
|
||||
else
|
||||
{
|
||||
card = new ShadowverseCardEntry
|
||||
{
|
||||
Id = id,
|
||||
Name = $"Card {id}",
|
||||
Rarity = (Rarity)rarity,
|
||||
PrimaryResourceCost = ParseNullableInt(c.Cost),
|
||||
Attack = ParseNullableInt(c.Atk),
|
||||
Defense = ParseNullableInt(c.Life),
|
||||
Class = classEntry,
|
||||
CollectionInfo = collection
|
||||
};
|
||||
set.Cards.Add(card);
|
||||
existingCards[id] = card;
|
||||
created++;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"Saving: +{created} cards, ~{updated} updated, +{setsCreated} card sets, " +
|
||||
$"skipped {skipped} (bad/missing card_id)...");
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
Console.WriteLine("Done.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int ParseInt(string? raw, int fallback) =>
|
||||
int.TryParse(raw, out int v) ? v : fallback;
|
||||
|
||||
private static int? ParseNullableInt(string? raw) =>
|
||||
int.TryParse(raw, out int v) ? v : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight projection over the CardCSVData fields we care about. The dump has many more
|
||||
/// fields (PascalCase metadata + effect/voice/visual paths) — we ignore them; only the
|
||||
/// snake_case CSV columns map here via the SnakeCaseLower naming policy.
|
||||
/// </summary>
|
||||
public class CardInput
|
||||
{
|
||||
public string? CardId { get; set; }
|
||||
public string? CardSetId { get; set; }
|
||||
public string? Clan { get; set; }
|
||||
public string? Cost { get; set; }
|
||||
public string? Atk { get; set; }
|
||||
public string? Life { get; set; }
|
||||
public string? Rarity { get; set; }
|
||||
public string? GetRedEther { get; set; }
|
||||
public string? UseRedEther { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user