feat(viewer): add MyPageBgSelectType + MyPageBgId scalars + MyPageBgRotation owned collection
Adds BGType persistence (0=Deck/1=CustomBG/2=RandomBG) to Viewer via two scalar columns and an owned collection keyed (ViewerId, Slot). Two persistence tests confirm round-trip and zero-defaults on fresh viewers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
16
SVSim.Database/Models/MyPageBgRotationEntry.cs
Normal file
16
SVSim.Database/Models/MyPageBgRotationEntry.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace SVSim.Database.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One row per (viewer, slot) in the viewer's saved MyPage BG rotation pool. The client posts
|
||||||
|
/// the full pool on every <c>/user_mypage/update</c> regardless of mode, so the server overwrites
|
||||||
|
/// it atomically each time. Slot is the 0-based position; order is preserved for the
|
||||||
|
/// <c>/mypage/index</c> echo.
|
||||||
|
/// </summary>
|
||||||
|
[Owned]
|
||||||
|
public class MyPageBgRotationEntry
|
||||||
|
{
|
||||||
|
public int Slot { get; set; }
|
||||||
|
public int BgId { get; set; }
|
||||||
|
}
|
||||||
@@ -33,6 +33,12 @@ public class Viewer : BaseEntity<long>
|
|||||||
|
|
||||||
public DateTime LastLogin { get; set; }
|
public DateTime LastLogin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>BGType enum: 0=Deck, 1=CustomBG, 2=RandomBG. Default 0 = follow equipped deck's leader skin.</summary>
|
||||||
|
public int MyPageBgSelectType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The single chosen MyPageBG cosmetic id, used when SelectType=CustomBG. 0 = none.</summary>
|
||||||
|
public int MyPageBgId { get; set; }
|
||||||
|
|
||||||
#region Owned
|
#region Owned
|
||||||
|
|
||||||
public ViewerInfo Info { get; set; } = new ViewerInfo();
|
public ViewerInfo Info { get; set; } = new ViewerInfo();
|
||||||
@@ -67,6 +73,8 @@ public class Viewer : BaseEntity<long>
|
|||||||
|
|
||||||
public List<ViewerFreePackClaim> FreePackClaims { get; set; } = new List<ViewerFreePackClaim>();
|
public List<ViewerFreePackClaim> FreePackClaims { get; set; } = new List<ViewerFreePackClaim>();
|
||||||
|
|
||||||
|
public List<MyPageBgRotationEntry> MyPageBgRotation { get; set; } = new List<MyPageBgRotationEntry>();
|
||||||
|
|
||||||
public List<ViewerGachaPointBalance> GachaPointBalances { get; set; } = new List<ViewerGachaPointBalance>();
|
public List<ViewerGachaPointBalance> GachaPointBalances { get; set; } = new List<ViewerGachaPointBalance>();
|
||||||
|
|
||||||
public List<ViewerGachaPointReceived> GachaPointReceived { get; set; } = new List<ViewerGachaPointReceived>();
|
public List<ViewerGachaPointReceived> GachaPointReceived { get; set; } = new List<ViewerGachaPointReceived>();
|
||||||
|
|||||||
@@ -172,6 +172,12 @@ public class SVSimDbContext : DbContext
|
|||||||
b.HasKey("ViewerId", nameof(ViewerFreePackClaim.FreeGachaCampaignId));
|
b.HasKey("ViewerId", nameof(ViewerFreePackClaim.FreeGachaCampaignId));
|
||||||
b.Property(x => x.FreeGachaCampaignId).ValueGeneratedNever();
|
b.Property(x => x.FreeGachaCampaignId).ValueGeneratedNever();
|
||||||
});
|
});
|
||||||
|
modelBuilder.Entity<Viewer>().OwnsMany(v => v.MyPageBgRotation, b =>
|
||||||
|
{
|
||||||
|
b.WithOwner().HasForeignKey("ViewerId");
|
||||||
|
b.HasKey("ViewerId", nameof(MyPageBgRotationEntry.Slot));
|
||||||
|
b.Property(x => x.Slot).ValueGeneratedNever();
|
||||||
|
});
|
||||||
|
|
||||||
// OwnedCardEntry and OwnedItemEntry use composite PK (ViewerId, Id) where Id is auto-
|
// OwnedCardEntry and OwnedItemEntry use composite PK (ViewerId, Id) where Id is auto-
|
||||||
// generated, which silently permits multiple rows per (Viewer, Card) or (Viewer, Item).
|
// generated, which silently permits multiple rows per (Viewer, Card) or (Viewer, Item).
|
||||||
|
|||||||
61
SVSim.UnitTests/Persistence/MyPageBgPersistenceTests.cs
Normal file
61
SVSim.UnitTests/Persistence/MyPageBgPersistenceTests.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using SVSim.Database;
|
||||||
|
using SVSim.Database.Models;
|
||||||
|
using SVSim.UnitTests.Infrastructure;
|
||||||
|
|
||||||
|
namespace SVSim.UnitTests.Persistence;
|
||||||
|
|
||||||
|
public class MyPageBgPersistenceTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task Viewer_round_trips_mypage_bg_scalars_and_rotation_pool()
|
||||||
|
{
|
||||||
|
using var factory = new SVSimTestFactory();
|
||||||
|
long viewerId = await factory.SeedViewerAsync();
|
||||||
|
|
||||||
|
using (var seedScope = factory.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var ctx = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||||
|
var viewer = await ctx.Viewers
|
||||||
|
.Include(v => v.MyPageBgRotation)
|
||||||
|
.FirstAsync(v => v.Id == viewerId);
|
||||||
|
viewer.MyPageBgSelectType = 2;
|
||||||
|
viewer.MyPageBgId = 1213410310;
|
||||||
|
viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry { Slot = 0, BgId = 1211410310 });
|
||||||
|
viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry { Slot = 1, BgId = 1212410310 });
|
||||||
|
viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry { Slot = 2, BgId = 1213410310 });
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var verifyScope = factory.Services.CreateScope();
|
||||||
|
var ctx2 = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||||
|
var roundtrip = await ctx2.Viewers
|
||||||
|
.Include(v => v.MyPageBgRotation)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstAsync(v => v.Id == viewerId);
|
||||||
|
|
||||||
|
Assert.That(roundtrip.MyPageBgSelectType, Is.EqualTo(2));
|
||||||
|
Assert.That(roundtrip.MyPageBgId, Is.EqualTo(1213410310));
|
||||||
|
Assert.That(roundtrip.MyPageBgRotation.OrderBy(r => r.Slot).Select(r => r.BgId),
|
||||||
|
Is.EqualTo(new[] { 1211410310, 1212410310, 1213410310 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Viewer_fresh_viewer_has_zero_defaults_and_empty_rotation()
|
||||||
|
{
|
||||||
|
using var factory = new SVSimTestFactory();
|
||||||
|
long viewerId = await factory.SeedViewerAsync();
|
||||||
|
|
||||||
|
using var scope = factory.Services.CreateScope();
|
||||||
|
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||||
|
var viewer = await ctx.Viewers
|
||||||
|
.Include(v => v.MyPageBgRotation)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstAsync(v => v.Id == viewerId);
|
||||||
|
|
||||||
|
Assert.That(viewer.MyPageBgSelectType, Is.EqualTo(0));
|
||||||
|
Assert.That(viewer.MyPageBgId, Is.EqualTo(0));
|
||||||
|
Assert.That(viewer.MyPageBgRotation, Is.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user