Pack logic cleanup

This commit is contained in:
gamer147
2026-05-24 09:27:10 -04:00
parent 79209bd70b
commit d9ef9fe1fc
33 changed files with 71175 additions and 245 deletions

View File

@@ -1,4 +1,7 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.Logging;
using SVSim.Database.Common;
using SVSim.Database.DataSeeders;
@@ -112,6 +115,55 @@ public class SVSimDbContext : DbContext
modelBuilder.Entity<PackConfigEntry>().OwnsMany(p => p.Banners);
modelBuilder.Entity<Viewer>().OwnsMany(v => v.PackOpenCounts);
// GameConfiguration.Config: on Postgres use EF Core 8's OwnsOne+ToJson(jsonb column).
// On SQLite (tests) ToJson's WriteJson has a known NullReferenceException when owned
// collections are present — use a plain TEXT value converter instead so the same
// entity shape works for both providers without separate test models.
bool isSqlite = Database.ProviderName?.Contains("Sqlite", StringComparison.OrdinalIgnoreCase) == true;
if (isSqlite)
{
// Store as JSON text via a value converter; EF treats Config as a single column.
var configConverter = new ValueConverter<GameConfigRoot, string>(
model => JsonSerializer.Serialize(model, (JsonSerializerOptions?)null),
json => JsonSerializer.Deserialize<GameConfigRoot>(json, (JsonSerializerOptions?)null)
?? new GameConfigRoot());
// Deep-equality comparer: serialize both sides and compare strings so that
// mutations to nested properties (e.g. Config.Rotation.TsRotationId = "10015")
// are detected by EF's snapshot change tracker and written to the DB on SaveChanges.
var configComparer = new ValueComparer<GameConfigRoot>(
(a, b) => JsonSerializer.Serialize(a, (JsonSerializerOptions?)null)
== JsonSerializer.Serialize(b, (JsonSerializerOptions?)null),
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null).GetHashCode(),
v => JsonSerializer.Deserialize<GameConfigRoot>(
JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
(JsonSerializerOptions?)null) ?? new GameConfigRoot());
modelBuilder.Entity<GameConfiguration>()
.Property(c => c.Config)
.HasColumnName("Config")
.HasConversion(configConverter, configComparer);
}
else
{
// Production path: jsonb column with full OwnsOne tree so EF can filter/project
// into sub-properties at the DB level if needed.
modelBuilder.Entity<GameConfiguration>().OwnsOne(c => c.Config, b =>
{
b.ToJson("Config");
b.OwnsOne(r => r.DefaultGrants);
b.OwnsOne(r => r.Player);
b.OwnsOne(r => r.DefaultLoadout);
b.OwnsOne(r => r.Challenge);
b.OwnsOne(r => r.Rotation);
b.OwnsOne(r => r.PackRates, pr =>
{
pr.OwnsOne(p => p.Default);
pr.OwnsMany(p => p.PerSlot);
});
});
}
new BaseDataSeeder().Seed(modelBuilder);
new DefaultSettingsSeeder().Seed(modelBuilder);
@@ -135,4 +187,22 @@ public class SVSimDbContext : DbContext
Database.Migrate();
_logger.LogInformation("Migrations applied.");
}
/// <summary>
/// Idempotent runtime seed for entities that can't use HasData (notably GameConfiguration
/// because of EF Core 8's HasData+OwnsOne(ToJson) jsonb limitation).
/// </summary>
public async Task EnsureSeedDataAsync()
{
if (!await GameConfigurations.AnyAsync(c => c.Id == "default"))
{
GameConfigurations.Add(new Models.GameConfiguration
{
Id = "default",
Config = new Models.GameConfigRoot(),
});
await SaveChangesAsync();
_logger.LogInformation("Seeded default GameConfiguration row.");
}
}
}