refactor(bootstrap): finalize load-index migration; GlobalsImporter is now a stub
Stage 9C of the bootstrap-seed-refactor: - Add 6 seed DTOs for the card-id-keyed load-index tables (SpotCard, ReprintedCard, UnlimitedRestriction, LoadingExclusionCard, MaintenanceCard, FeatureMaintenance). - Add CardListsImporter: idempotent upsert of the 6 tables, sharing one Cards FK set for orphan-warning. FeatureMaintenances clear-and-rewrites (synthetic ordinal Id; no natural key). - Add RotationFlagUpdater: reads RotationConfig.RotationCardSetIds from the GameConfigs section (populated by RotationConfigImporter) and flips CardSet.IsInRotation to match. - Add RotationConfig.RotationCardSetIds list property + wire it through RotationConfigImporter. No migration needed (sections are JSON blobs). - RotationConfigImporter: use legacy local-kind DateTime parse for schedule windows so the JSON round-trip stays byte-equivalent to GlobalsImporter. - Strip GlobalsImporter down to a no-op stub (Task 10 will delete it). - Wire all 9 new importers into Program.cs and SVSimTestFactory.SeedGlobalsAsync, in the order RotationConfigImporter -> ... -> CardListsImporter -> RotationFlagUpdater. - Delete prod-captures/load-index-2026-05-23.json. - Add CardListsImporterTests covering each sub-table, idempotency, empty-seed handling, orphan-warning, and the clear-and-rewrite path. Tests: 391 passing (382 baseline + 9 new). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ public class RotationConfigImporter
|
||||
c.IsBattlePassPeriod = rot.IsBattlePassPeriod;
|
||||
c.IsBeginnerMission = rot.IsBeginnerMission;
|
||||
c.CardSetIdForResourceDlView = rot.CardSetIdForResourceDlView;
|
||||
c.RotationCardSetIds = rot.RotationCardSetIds ?? new List<int>();
|
||||
});
|
||||
touched++;
|
||||
}
|
||||
@@ -48,10 +49,15 @@ public class RotationConfigImporter
|
||||
var schedule = SeedLoader.LoadObject<MyRotationScheduleSeed>(Path.Combine(seedDir, "my-rotation-schedule.json"));
|
||||
if (schedule?.Gathering is not null && schedule.FreeBattle is not null)
|
||||
{
|
||||
var gBegin = ParseWireDateTime(schedule.Gathering.Begin);
|
||||
var gEnd = ParseWireDateTime(schedule.Gathering.End);
|
||||
var fBegin = ParseWireDateTime(schedule.FreeBattle.Begin);
|
||||
var fEnd = ParseWireDateTime(schedule.FreeBattle.End);
|
||||
// Schedule windows are intentionally parsed WITHOUT AssumeUniversal because the seed
|
||||
// strings ("2024-05-01 20:00:00") are timezone-less and the rest of the pipeline (the
|
||||
// [ConfigSection] JSON round-trip + LoadController's wire mapping) treats them as
|
||||
// local-kind ticks. Mirrors the legacy GlobalsImporter.TryParseScheduleWindow behavior
|
||||
// — see GlobalsRepositoryTests for the round-trip assertion.
|
||||
var gBegin = ParseScheduleWireDateTime(schedule.Gathering.Begin);
|
||||
var gEnd = ParseScheduleWireDateTime(schedule.Gathering.End);
|
||||
var fBegin = ParseScheduleWireDateTime(schedule.FreeBattle.Begin);
|
||||
var fEnd = ParseScheduleWireDateTime(schedule.FreeBattle.End);
|
||||
// Only commit when both windows parsed to real DateTimes — a malformed/0001 value
|
||||
// would silently lock the MyRotation feature off (the original bug the section fixed).
|
||||
if (gBegin != DateTime.MinValue && gEnd != DateTime.MinValue
|
||||
@@ -75,6 +81,15 @@ public class RotationConfigImporter
|
||||
return touched;
|
||||
}
|
||||
|
||||
// Legacy schedule-window parse: default styles (AssumeLocal), matching the original
|
||||
// GlobalsImporter.TryParseScheduleWindow. The schedule strings are timezone-less; preserving
|
||||
// legacy local-kind ticks keeps the wire output byte-equivalent across the migration.
|
||||
private static DateTime ParseScheduleWireDateTime(string? s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s)) return DateTime.MinValue;
|
||||
return DateTime.TryParse(s, out var dt) ? dt : DateTime.MinValue;
|
||||
}
|
||||
|
||||
// Verbatim copy of GlobalsImporter.UpsertSection<T>. Kept private-static here so this
|
||||
// importer can stand alone after Stage 9C strips the GlobalsImporter copy.
|
||||
private static async Task UpsertSection<T>(SVSimDbContext context, Func<T> shippedDefaults, Action<T> mutate)
|
||||
|
||||
Reference in New Issue
Block a user