From 0d036e1bfff9a01444df4f72bd6a794bf5e976f3 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 9 Jun 2026 14:19:00 -0400 Subject: [PATCH] feat(inventory): add ViewerAcquireHistoryEntry entity + DbSet Adds the ViewerAcquireHistoryEntry model (8 fields: Id, ViewerId, RewardType, RewardDetailId, RewardCount, AcquireType, Message, AcquireTime), registers DbSet on SVSimDbContext, configures model (PK, FK cascade to Viewer, MaxLength 64 on Message, composite index on ViewerId/AcquireTime/Id), and adds a DbSet round-trip integration test. Co-Authored-By: Claude Sonnet 4.6 --- .../Models/ViewerAcquireHistoryEntry.cs | 30 +++++++++++++++++++ SVSim.Database/SVSimDbContext.cs | 14 +++++++++ .../Inventory/InventoryHistoryTests.cs | 29 ++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 SVSim.Database/Models/ViewerAcquireHistoryEntry.cs diff --git a/SVSim.Database/Models/ViewerAcquireHistoryEntry.cs b/SVSim.Database/Models/ViewerAcquireHistoryEntry.cs new file mode 100644 index 0000000..7837f68 --- /dev/null +++ b/SVSim.Database/Models/ViewerAcquireHistoryEntry.cs @@ -0,0 +1,30 @@ +namespace SVSim.Database.Models; + +/// +/// One row per grant emitted by InventoryTransaction.CommitAsync. Rendered as the +/// histories[] array on POST /item_acquire_history/info. Capped at 300 rows +/// per viewer; oldest pruned on commit. +/// +public sealed class ViewerAcquireHistoryEntry +{ + public long Id { get; set; } + public long ViewerId { get; set; } + + /// UserGoodsType cast to int; matches the wire reward_type. + public int RewardType { get; set; } + + /// Detail id for the goods; 0 for wallet currencies. + public long RewardDetailId { get; set; } + + /// Delta granted in this row — NOT a post-state total. + public int RewardCount { get; set; } + + /// GrantSource cast to int; matches the wire acquire_type. + public int AcquireType { get; set; } + + /// Pre-localized text the client renders verbatim. Capped at 64 chars. + public string Message { get; set; } = string.Empty; + + /// Server UTC at commit time. Stamped once per CommitAsync, identical across all rows in that commit. + public DateTime AcquireTime { get; set; } +} diff --git a/SVSim.Database/SVSimDbContext.cs b/SVSim.Database/SVSimDbContext.cs index 5fd2e3f..4297621 100644 --- a/SVSim.Database/SVSimDbContext.cs +++ b/SVSim.Database/SVSimDbContext.cs @@ -103,6 +103,7 @@ public class SVSimDbContext : DbContext public DbSet ViewerPresents => Set(); public DbSet TutorialPresentEntries => Set(); + public DbSet ViewerAcquireHistory => Set(); public DbSet ArenaTwoPickRewards { get; set; } = null!; public DbSet ViewerArenaTwoPickRuns { get; set; } = null!; @@ -398,6 +399,19 @@ public class SVSimDbContext : DbContext b.Property(p => p.PresentId).HasMaxLength(64); }); + modelBuilder.Entity(b => + { + b.HasKey(e => e.Id); + b.Property(e => e.Id).ValueGeneratedOnAdd(); + b.Property(e => e.Message).HasMaxLength(64).IsRequired(); + b.HasOne() + .WithMany() + .HasForeignKey(e => e.ViewerId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(e => new { e.ViewerId, e.AcquireTime, e.Id }) + .HasDatabaseName("IX_ViewerAcquireHistory_ViewerId_AcquireTime_Id"); + }); + base.OnModelCreating(modelBuilder); } diff --git a/SVSim.UnitTests/Services/Inventory/InventoryHistoryTests.cs b/SVSim.UnitTests/Services/Inventory/InventoryHistoryTests.cs index 8e98bbf..7de50e4 100644 --- a/SVSim.UnitTests/Services/Inventory/InventoryHistoryTests.cs +++ b/SVSim.UnitTests/Services/Inventory/InventoryHistoryTests.cs @@ -1,3 +1,5 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using SVSim.Database.Services.Inventory; namespace SVSim.UnitTests.Services.Inventory; @@ -37,4 +39,31 @@ public class InventoryHistoryTests var cfg = new InventoryLoadConfig { Source = GrantSource.PackOpen }; Assert.That(cfg.Source, Is.EqualTo(GrantSource.PackOpen)); } + + [Test] + public async Task ViewerAcquireHistory_DbSet_round_trips_a_row() + { + using var factory = new SVSim.UnitTests.Infrastructure.SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + using var scope = factory.Services.CreateScope(); + var ctx = scope.ServiceProvider.GetRequiredService(); + + ctx.ViewerAcquireHistory.Add(new SVSim.Database.Models.ViewerAcquireHistoryEntry + { + ViewerId = viewerId, + RewardType = (int)SVSim.Database.Enums.UserGoodsType.Rupy, + RewardDetailId = 0, + RewardCount = 50, + AcquireType = (int)GrantSource.DailyBonus, + Message = "Daily Bonus", + AcquireTime = new DateTime(2026, 6, 9, 12, 0, 0, DateTimeKind.Utc), + }); + await ctx.SaveChangesAsync(); + + var roundtrip = await ctx.ViewerAcquireHistory.AsNoTracking() + .Where(h => h.ViewerId == viewerId).ToListAsync(); + Assert.That(roundtrip, Has.Count.EqualTo(1)); + Assert.That(roundtrip[0].RewardCount, Is.EqualTo(50)); + Assert.That(roundtrip[0].AcquireType, Is.EqualTo((int)GrantSource.DailyBonus)); + } }