Commit Graph

727 Commits

Author SHA1 Message Date
gamer147
11215bd69f feat(profile): ProfileController + DTOs + integration tests
Add /profile/index endpoint that returns user_rank_match_total_win (stubbed 0)
and user_class_list built from viewer Classes + owned LeaderSkins. Six NUnit
integration tests cover zero wins, all classes present, level/exp/default skin,
leader_skin_id_list population, is_random_leader_skin round-trip, and 401 on
unauthenticated access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 17:37:35 -04:00
gamer147
f204656f4d refactor(user-class): require owned skin list, read IsRandomLeaderSkin from data 2026-06-09 17:31:06 -04:00
gamer147
da6b448598 feat(viewer): migration for ViewerClassData.IsRandomLeaderSkin
Adds AddViewerClassDataIsRandomLeaderSkin migration: boolean column on
the ViewerClassData shadow table, nullable: false, defaultValue: false.
Also updates the model snapshot to reflect the new bool property.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 17:27:16 -04:00
gamer147
2f4420bf15 feat(viewer): add ViewerClassData.IsRandomLeaderSkin column
Adds the bool column (defaults false) to the [Owned] ViewerClassData
entity and a two-test persistence suite that verifies round-trip and
default-value behaviour via SQLite/EnsureCreated.
2026-06-09 17:24:32 -04:00
gamer147
b50d69d3a5 test(user-mypage): assert /user_mypage/update returns 401 without auth
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 17:04:12 -04:00
gamer147
483cc1c1e0 feat(mypage): /mypage/index reflects persisted bg selection
Wires MyPageController.Index to read Viewer.MyPageBgId/SelectType/BgRotation
instead of emitting an empty MyPageBgSetting placeholder; adds Include for
MyPageBgRotation in ViewerRepository.GetViewerByShortUdid; adds fresh-viewer
zero-defaults test and write→read round-trip test (9/9 controller tests pass).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:53:34 -04:00
gamer147
6123a64b37 test(user-mypage): explain Clear() + cover unparseable rotation entries
Add a comment above MyPageBgRotation.Clear() explaining the EF OwnsMany
delete-on-clear semantics. Add a 7th test covering garbage/"" entries in
mypage_id_list falling back to BgId=0 while adjacent valid entries are
unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:50:38 -04:00
gamer147
9ff6c70faf feat(user-mypage): UserMyPageController + DTOs + persistence tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:46:49 -04:00
gamer147
b447f5032d fix(mypage): wire mypage_id/select_type/mypage_id_list as strings
Prod capture (traffic_prod_misc_clicking.ndjson) shows all three
MyPageBgSetting fields arrive as decimal strings, not ints.  Update the
DTO from int/int/List<int> to string/string/List<string> with "0"/empty
defaults, and add a literal wire-shape round-trip test pinning the
exact JSON against the capture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:40:38 -04:00
gamer147
51ef460d39 feat(viewer): migration for mypage bg selection
Adds AddViewerMyPageBgSelection migration: two int scalars on Viewers
(MyPageBgId, MyPageBgSelectType default 0) and ViewerMyPageBgRotation
owned table with composite PK (ViewerId, Slot), FK cascade to Viewers.
Also adds ToTable(ViewerMyPageBgRotation) to OwnsMany config so EF
uses the correct table name instead of defaulting to the entity class.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:33:12 -04:00
gamer147
ee808a60a2 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>
2026-06-09 16:26:48 -04:00
gamer147
8de78ba7ed test(inventory): lock Unknown-source fallthrough behaviour
Adds Commit_writes_history_row_with_Unknown_source_when_caller_omits_Source
to InventoryHistoryTests — asserts that BeginAsync without a configure callback
writes AcquireType=0 / Message="Unknown", per spec §10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:33:04 -04:00
gamer147
5334263793 feat(inventory): tag remaining BeginAsync call sites for acquire history
Add GrantSource.CardCraft (16) for card crafting via red ether, and tag
CardInventoryRepository.Create accordingly. LoadController backfill and
ArenaTwoPickService.Entry are debit/infrastructure-only — left as Unknown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:09:43 -04:00
gamer147
c2c3abc6f0 feat(gift): tag receive-gift tx as AdminGrant for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:08:27 -04:00
gamer147
6ec6a9c3fc feat(spot-card): tag exchange tx as GachaPointExchange for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:08:03 -04:00
gamer147
d759465cf2 feat(achievement): tag receive-reward tx as AchievementReward for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:07:42 -04:00
gamer147
349d7f32cd feat(leader-skin): tag buy tx as LeaderSkinBuy for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:07:19 -04:00
gamer147
30cb4727f6 feat(sleeve): tag buy tx as SleeveBuy for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:06:47 -04:00
gamer147
38a21e5e72 feat(build-deck): tag buy tx as BuildDeckBuy for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:06:23 -04:00
gamer147
5cf3cf70e8 feat(item-purchase): tag purchase tx as ItemPurchase for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:05:59 -04:00
gamer147
259e3ebe29 feat(2pick): tag finish-reward tx as ArenaTwoPickFinish for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:05:37 -04:00
gamer147
c72b692560 feat(battle-pass): tag claim tx as BattlePassClaim for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:05:09 -04:00
gamer147
d767944a83 feat(story): tag finish-reward tx as StoryFinish for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:04:11 -04:00
gamer147
21ee113d21 feat(puzzle): tag inventory tx as PuzzleReward for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:03:45 -04:00
gamer147
4aa1367b6f feat(pack): tag gacha-point exchange tx for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:03:21 -04:00
gamer147
f1d96ff554 feat(pack): tag inventory tx as PackOpen for acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:02:56 -04:00
gamer147
9130d6de11 test(inventory): pack-open shape produces acquire history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:59:39 -04:00
gamer147
d01294ebb4 test(item-acquire-history): literal wire-shape round-trip 2026-06-09 14:57:18 -04:00
gamer147
37d89aa602 refactor(inventory): share retention cap + invariant-culture date format
Introduce InventoryHistoryConfig.RetentionRowsPerViewer as the single
source of truth for the 300-row audit-log cap; InventoryTransaction
aliases it and ItemAcquireHistoryController.Take() references it
directly so the two sites cannot drift. Also adds CultureInfo.InvariantCulture
to the AcquireTime.ToString() call, matching every other WireDateFormat
site in the codebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:55:43 -04:00
gamer147
f9a971a546 feat(item-acquire-history): controller + DTOs
Add ItemAcquireHistoryController (POST /item_acquire_history/info) with its
three DTOs and two integration tests (ordering + empty-viewer). The endpoint
reads ViewerAcquireHistory rows written by InventoryTransaction.CommitAsync,
ordered newest-first, capped at 300. Tests access doc.RootElement.histories
directly (no envelope wrapper in the test path — middleware skips non-UnityPlayer UA).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:49:43 -04:00
gamer147
00fbf1a185 docs(inventory): explain two-phase prune query (SQLite constraint)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:44:57 -04:00
gamer147
77ad614258 feat(inventory): prune acquire history above 300-row cap
Adds PruneAcquireHistoryAsync to InventoryTransaction.CommitAsync; runs
inside the open DB transaction after history rows are flushed, keeping at
most 300 rows per viewer (oldest discarded). Adds a covering test that
verifies the cap and per-viewer isolation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:41:04 -04:00
gamer147
fb1e6829b7 refactor(inventory): consolidate IsCurrency, skip num=0 grants in history
- Drop IsWalletCurrency (duplicate of IsCurrency); use IsCurrency in WriteAcquireHistory.
- Add comment on first SaveChangesAsync in CommitAsync explaining the two-phase flush.
- Guard WriteAcquireHistory loop with grant.Num == 0 check so synthetic DebitItem post-state ops do not produce history rows.
- Add InventoryHistoryTests.Commit_writes_no_history_row_for_item_debit to lock in the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:37:50 -04:00
gamer147
bea5a1efd4 feat(inventory): write acquire history rows on commit
CommitAsync now calls WriteAcquireHistory() between the two SaveChanges
calls: iterates _ops, skips SpendOps, writes one ViewerAcquireHistoryEntry
per GrantOp. Cascade rows get GrantSource.CardCosmeticCascade; wallet
currencies zero RewardDetailId; all rows in a single commit share one
DateTime.UtcNow timestamp. Closes _source plumbing from Task 5.

5 new tests added (46 total inventory, 0 failures).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:32:51 -04:00
gamer147
015c7ce259 feat(inventory): thread GrantSource into InventoryTransaction
Add _source field + ctor parameter (between freeplay and log) to
InventoryTransaction; pass loadCfg.Source from InventoryService.BeginAsync.
Field is captured but not yet consumed — Task 6 wires it into history rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:28:05 -04:00
gamer147
f394529c8c feat(inventory): migration for viewer_acquire_history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:24:36 -04:00
gamer147
51595b0c7d refactor(inventory): drop redundant index name + IsRequired on ViewerAcquireHistory
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:22:53 -04:00
gamer147
0d036e1bff feat(inventory): add ViewerAcquireHistoryEntry entity + DbSet
Adds the ViewerAcquireHistoryEntry model (8 fields: Id, ViewerId,
RewardType, RewardDetailId, RewardCount, AcquireType, Message,
AcquireTime), registers DbSet<ViewerAcquireHistoryEntry> 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 <noreply@anthropic.com>
2026-06-09 14:19:00 -04:00
gamer147
82dc877639 feat(inventory): add Source to InventoryLoadConfig
Adds a `GrantSource Source { get; set; }` property (defaults to
`GrantSource.Unknown`) to `InventoryLoadConfig`. Plumbing-only — no
behavior change; callers that don't set `Source` get Unknown rows,
greppable via `acquire_type=0` in dev.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:13:56 -04:00
gamer147
645c32e11c docs(inventory): annotate GrantSource gap + For() exception
Add comment above AdminGrant = 99 explaining the intentional 16–98 gap,
and add <exception> XML doc on GrantSourceMessages.For() so IDE tooling
surfaces the ArgumentOutOfRangeException.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:11:45 -04:00
gamer147
9f65326449 feat(inventory): add GrantSource enum + message lookup
Introduces GrantSource (17 values, DB-persisted) and GrantSourceMessages.For()
for the item-acquire-history feature. Values 1/2 mirror prod captures;
coverage test verifies every enum value has a non-empty message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 14:08:01 -04:00
gamer147
998402ebc3 refactor(tutorial-presents): promote static catalogue to seed-driven TutorialPresentEntries table
The five tutorial gifts every fresh viewer is given at signup used to live as a
static C# array in SVSim.Database/SeedData/TutorialPresents.cs — outside the
seed-JSON pipeline used by all 40+ other globals tables. Editing a gift required
a code change + rebuild instead of a JSON edit + bootstrap re-run.

Now authored in SVSim.Bootstrap/Data/seeds/tutorial-presents.json and loaded into
a new TutorialPresentEntries table via TutorialPresentsImporter (clear-and-rewrite,
mirroring HomeDialogs). ViewerRepository.RegisterAnonymousViewer reads the table
at signup and projects each row into a ViewerPresent with Source="tutorial".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 09:53:10 -04:00
gamer147
7118b92522 refactor(pack): type PackChildGachaEntry.TypeDetail as CardPackType enum 2026-06-09 08:48:16 -04:00
gamer147
833bd85d36 data(seed): regenerate packs.json including free-pack children from event-crate capture 2026-06-08 21:51:05 -04:00
gamer147
57d231cd56 feat(pack): /pack/open supports type_detail=10 FREE_PACKS with per-campaign daily quota 2026-06-08 21:43:04 -04:00
gamer147
6c7e8ae8ad feat(pack): /pack/info filters spent free-pack children and emits campaign metadata 2026-06-08 21:38:26 -04:00
gamer147
b9c29b53d9 feat(pack): add free-pack metadata fields to PackChildGachaDto 2026-06-08 21:34:49 -04:00
gamer147
7d7cf699f8 feat(viewer): add FreePackClaims owned collection for daily-free quota tracking 2026-06-08 21:34:20 -04:00
gamer147
d762c5766f feat(pack): persist daily_free_gacha_count on PackChildGachaEntry 2026-06-08 21:31:02 -04:00
gamer147
7e4a9654b2 feat(seed): add DailyFreeGachaCount to PackChildGachaSeed 2026-06-08 21:23:14 -04:00