refactor(bootstrap): migrate mypage-index globals to seed files

This commit is contained in:
gamer147
2026-05-26 14:31:25 -04:00
parent 0da8ebe1c1
commit a5e4f35c32
16 changed files with 561 additions and 368 deletions

View File

@@ -27,7 +27,6 @@ public class GlobalsImporter
Console.WriteLine($"[GlobalsImporter] Loading captures from {capturesDir}...");
JsonElement? loadIndex = LoadCapture(capturesDir, "load-index");
JsonElement? mypageIndex = LoadCapture(capturesDir, "mypage-index");
JsonElement? deckInfo = LoadCapture(capturesDir, "deck-info");
JsonElement? packInfo = LoadCapture(capturesDir, "pack-info");
@@ -51,15 +50,6 @@ public class GlobalsImporter
total += await UpdateRotationCardSetFlags(context, loadIndex.Value);
}
if (mypageIndex.HasValue)
{
total += await ImportBanners(context, mypageIndex.Value);
total += await ImportColosseum(context, mypageIndex.Value);
total += await ImportSealed(context, mypageIndex.Value);
total += await ImportMasterPointRankingPeriod(context, mypageIndex.Value);
total += await ImportRoomTypeInSession(context, mypageIndex.Value);
}
if (deckInfo.HasValue)
{
total += await ImportDefaultDecks(context, deckInfo.Value);
@@ -521,135 +511,6 @@ public class GlobalsImporter
return updated;
}
// ---------- Mypage: Banners ----------
private async Task<int> ImportBanners(SVSimDbContext context, JsonElement mypage)
{
if (!mypage.TryGetProperty("banner", out var arr) || arr.ValueKind != JsonValueKind.Array) return 0;
// Banners have no wire ID; we treat the capture as authoritative — clear and rewrite.
var existing = await context.Banners.ToListAsync();
context.Banners.RemoveRange(existing);
int created = 0;
int idx = 1;
foreach (var el in arr.EnumerateArray())
{
context.Banners.Add(new BannerEntry
{
Id = idx++,
ImageName = GetString(el, "image_name"),
Click = GetString(el, "click"),
Status = GetString(el, "status"),
ChangeTime = GetInt(el, "change_time"),
RemainingTime = GetInt(el, "remaining_time"),
ImagePaths = el.TryGetProperty("image_paths", out var ip) ? Serialize(ip) : "[]"
});
created++;
}
Console.WriteLine($"[GlobalsImporter] Banners: {(existing.Count > 0 ? $"-{existing.Count}/" : "")}+{created}");
return created;
}
// ---------- Mypage: Colosseum (singleton) ----------
private async Task<int> ImportColosseum(SVSimDbContext context, JsonElement mypage)
{
if (!mypage.TryGetProperty("colosseum_info", out var info) || info.ValueKind != JsonValueKind.Object) return 0;
var existing = await context.Colosseums.FirstOrDefaultAsync(e => e.Id == 1);
var entry = existing ?? new ColosseumConfig { Id = 1 };
entry.ColosseumId = GetString(info, "colosseum_id");
entry.ColosseumName = GetString(info, "colosseum_name");
entry.CardPoolName = GetString(info, "card_pool_name");
entry.DeckFormat = GetString(info, "deck_format");
entry.StartTime = ParseWireDateTime(GetString(info, "start_time"));
entry.EndTime = ParseWireDateTime(GetString(info, "end_time"));
entry.NowRound = GetString(info, "now_round");
entry.IsDisplayTips = GetString(info, "is_display_tips");
entry.TipsId = GetString(info, "tips_id");
entry.IsColosseumPeriod = GetBool(info, "is_colosseum_period");
entry.IsRoundPeriod = GetBool(info, "is_round_period");
entry.IsNormalTwoPick = GetString(info, "is_normal_two_pick");
entry.IsSpecialMode = GetString(info, "is_special_mode");
entry.IsAllCardEnabled = GetInt(info, "is_all_card_enabled");
entry.SalesPeriodInfo = info.TryGetProperty("sales_period_info", out var sp) ? Serialize(sp) : "{}";
if (existing is null) context.Colosseums.Add(entry);
Console.WriteLine($"[GlobalsImporter] Colosseum: {(existing is null ? "+1" : "~1")}");
return 1;
}
// ---------- Mypage: Sealed (singleton) ----------
private async Task<int> ImportSealed(SVSimDbContext context, JsonElement mypage)
{
if (!mypage.TryGetProperty("sealed_info", out var info) || info.ValueKind != JsonValueKind.Object) return 0;
var existing = await context.SealedSeasons.FirstOrDefaultAsync(e => e.Id == 1);
var entry = existing ?? new SealedConfig { Id = 1 };
entry.Enable = GetInt(info, "enable");
entry.CrystalCost = GetInt(info, "crystal_cost");
entry.RupyCost = GetInt(info, "rupy_cost");
entry.TicketCost = GetInt(info, "ticket_cost");
entry.DeckUsingNumMin = GetInt(info, "deck_using_num_min");
entry.ScheduleId = GetInt(info, "schedule_id");
entry.IsJoin = GetBool(info, "is_join");
entry.IsDeckCodeMaintenance = GetBool(info, "is_deck_code_maintenance");
entry.PackInfo = info.TryGetProperty("pack_info", out var pi) ? Serialize(pi) : "[]";
entry.SalesPeriodInfo = info.TryGetProperty("sales_period_info", out var sp) ? Serialize(sp) : "{}";
if (existing is null) context.SealedSeasons.Add(entry);
Console.WriteLine($"[GlobalsImporter] Sealed: {(existing is null ? "+1" : "~1")}");
return 1;
}
// ---------- Mypage: Master Point Ranking Period ----------
private async Task<int> ImportMasterPointRankingPeriod(SVSimDbContext context, JsonElement mypage)
{
if (!mypage.TryGetProperty("master_point_ranking_period", out var info) || info.ValueKind != JsonValueKind.Object) return 0;
int id = GetInt(info, "id");
if (id == 0) return 0;
var existing = await context.MasterPointRankingPeriods.FirstOrDefaultAsync(e => e.Id == id);
var entry = existing ?? new MasterPointRankingPeriodEntry { Id = id };
entry.PeriodNum = GetInt(info, "period_num");
entry.NecessaryScore = GetLong(info, "necessary_score");
entry.BeginTime = ParseWireDateTime(GetString(info, "begin_time"));
entry.EndTime = ParseWireDateTime(GetString(info, "end_time"));
if (existing is null) context.MasterPointRankingPeriods.Add(entry);
Console.WriteLine($"[GlobalsImporter] MasterPointRankingPeriod (id={id}): {(existing is null ? "+1" : "~1")}");
return 1;
}
// ---------- Mypage: Room Type In Session (special deck formats) ----------
private async Task<int> ImportRoomTypeInSession(SVSimDbContext context, JsonElement mypage)
{
if (!mypage.TryGetProperty("room_type_in_session", out var rt) || rt.ValueKind != JsonValueKind.Object) return 0;
if (!rt.TryGetProperty("special_deck_format_list", out var arr) || arr.ValueKind != JsonValueKind.Array) return 0;
// Same shape semantics as Banners — the wire has no stable id, treat the capture as
// authoritative and clear-and-rewrite with a synthetic ordinal.
var existing = await context.SpecialDeckFormats.ToListAsync();
context.SpecialDeckFormats.RemoveRange(existing);
int created = 0;
int idx = 1;
foreach (var el in arr.EnumerateArray())
{
context.SpecialDeckFormats.Add(new SpecialDeckFormatEntry
{
Id = idx++,
DeckFormat = GetString(el, "deck_format"),
EndTime = ParseWireDateTime(GetString(el, "end_time"))
});
created++;
}
Console.WriteLine($"[GlobalsImporter] SpecialDeckFormats: {(existing.Count > 0 ? $"-{existing.Count}/" : "")}+{created}");
return created;
}
// ---------- Deck/info: Default Decks ----------
private async Task<int> ImportDefaultDecks(SVSimDbContext context, JsonElement deckInfo)

View File

@@ -0,0 +1,185 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using SVSim.Bootstrap.Models.Seed;
using SVSim.Database;
using SVSim.Database.Models;
namespace SVSim.Bootstrap.Importers;
/// <summary>
/// Idempotent upsert of /mypage/index-derived globals from per-table seed files.
/// Banners and SpecialDeckFormats use CLEAR-AND-REWRITE semantics (no stable wire ID, capture is authoritative).
/// Colosseum and SealedSeason are singletons (Id=1). MasterPointRankingPeriod upserts by wire id.
/// </summary>
public class MyPageGlobalsImporter
{
public async Task<int> ImportBannersAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<BannerSeed>(Path.Combine(seedDir, "banners.json"));
if (seed.Count == 0)
{
Console.WriteLine("[MyPageGlobalsImporter] No banner seed rows; skipping.");
return 0;
}
// Clear-and-rewrite: banners have no stable wire ID, the capture is authoritative.
var existing = await context.Banners.ToListAsync();
context.Banners.RemoveRange(existing);
foreach (var s in seed)
{
context.Banners.Add(new BannerEntry
{
Id = s.Id,
ImageName = s.ImageName,
Click = s.Click,
Status = s.Status,
ChangeTime = s.ChangeTime,
RemainingTime = s.RemainingTime,
ImagePaths = s.ImagePaths.ValueKind == JsonValueKind.Undefined
? "[]"
: JsonSerializer.Serialize(s.ImagePaths),
});
}
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] Banners: -{existing.Count}/+{seed.Count}");
return seed.Count;
}
public async Task<int> ImportColosseumAsync(SVSimDbContext context, string seedDir)
{
var s = SeedLoader.LoadObject<ColosseumSeed>(Path.Combine(seedDir, "colosseum.json"));
if (s is null)
{
Console.WriteLine("[MyPageGlobalsImporter] No colosseum seed; skipping.");
return 0;
}
var existing = await context.Colosseums.FirstOrDefaultAsync(e => e.Id == 1);
var entry = existing ?? new ColosseumConfig { Id = 1 };
entry.ColosseumId = s.ColosseumId;
entry.ColosseumName = s.ColosseumName;
entry.CardPoolName = s.CardPoolName;
entry.DeckFormat = s.DeckFormat;
entry.StartTime = ImporterBase.ParseWireDateTime(s.StartTime);
entry.EndTime = ImporterBase.ParseWireDateTime(s.EndTime);
entry.NowRound = s.NowRound;
entry.IsDisplayTips = s.IsDisplayTips;
entry.TipsId = s.TipsId;
entry.IsColosseumPeriod = s.IsColosseumPeriod;
entry.IsRoundPeriod = s.IsRoundPeriod;
entry.IsNormalTwoPick = s.IsNormalTwoPick;
entry.IsSpecialMode = s.IsSpecialMode;
entry.IsAllCardEnabled = s.IsAllCardEnabled;
entry.SalesPeriodInfo = s.SalesPeriodInfo.ValueKind == JsonValueKind.Undefined
? "{}"
: JsonSerializer.Serialize(s.SalesPeriodInfo);
if (existing is null) context.Colosseums.Add(entry);
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] Colosseum: {(existing is null ? "+1" : "~1")}");
return 1;
}
public async Task<int> ImportSealedAsync(SVSimDbContext context, string seedDir)
{
var s = SeedLoader.LoadObject<SealedSeasonSeed>(Path.Combine(seedDir, "sealed-season.json"));
if (s is null)
{
Console.WriteLine("[MyPageGlobalsImporter] No sealed-season seed; skipping.");
return 0;
}
var existing = await context.SealedSeasons.FirstOrDefaultAsync(e => e.Id == 1);
var entry = existing ?? new SealedConfig { Id = 1 };
entry.Enable = s.Enable;
entry.CrystalCost = s.CrystalCost;
entry.RupyCost = s.RupyCost;
entry.TicketCost = s.TicketCost;
entry.DeckUsingNumMin = s.DeckUsingNumMin;
entry.ScheduleId = s.ScheduleId;
entry.IsJoin = s.IsJoin;
entry.IsDeckCodeMaintenance = s.IsDeckCodeMaintenance;
entry.PackInfo = s.PackInfo.ValueKind == JsonValueKind.Undefined
? "[]"
: JsonSerializer.Serialize(s.PackInfo);
entry.SalesPeriodInfo = s.SalesPeriodInfo.ValueKind == JsonValueKind.Undefined
? "{}"
: JsonSerializer.Serialize(s.SalesPeriodInfo);
if (existing is null) context.SealedSeasons.Add(entry);
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] SealedSeason: {(existing is null ? "+1" : "~1")}");
return 1;
}
public async Task<int> ImportMasterPointRankingPeriodAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<MasterPointRankingPeriodSeed>(
Path.Combine(seedDir, "master-point-ranking-periods.json"));
if (seed.Count == 0)
{
Console.WriteLine("[MyPageGlobalsImporter] No master-point-ranking-period seed rows; skipping.");
return 0;
}
var existing = await context.MasterPointRankingPeriods.ToDictionaryAsync(e => e.Id);
int created = 0, updated = 0;
foreach (var s in seed)
{
if (s.Id == 0) continue;
var entry = existing.TryGetValue(s.Id, out var ex)
? ex : new MasterPointRankingPeriodEntry { Id = s.Id };
entry.PeriodNum = s.PeriodNum;
entry.NecessaryScore = s.NecessaryScore;
entry.BeginTime = ImporterBase.ParseWireDateTime(s.BeginTime);
entry.EndTime = ImporterBase.ParseWireDateTime(s.EndTime);
if (ex is null)
{
context.MasterPointRankingPeriods.Add(entry);
existing[s.Id] = entry;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] MasterPointRankingPeriod: +{created}/~{updated}");
return created + updated;
}
public async Task<int> ImportSpecialDeckFormatsAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<SpecialDeckFormatSeed>(
Path.Combine(seedDir, "special-deck-formats.json"));
if (seed.Count == 0)
{
Console.WriteLine("[MyPageGlobalsImporter] No special-deck-format seed rows; skipping.");
return 0;
}
// Clear-and-rewrite: same semantics as banners — no stable wire ID, capture is authoritative.
var existing = await context.SpecialDeckFormats.ToListAsync();
context.SpecialDeckFormats.RemoveRange(existing);
foreach (var s in seed)
{
context.SpecialDeckFormats.Add(new SpecialDeckFormatEntry
{
Id = s.Id,
DeckFormat = s.DeckFormat,
EndTime = ImporterBase.ParseWireDateTime(s.EndTime),
});
}
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] SpecialDeckFormats: -{existing.Count}/+{seed.Count}");
return seed.Count;
}
}