Commit Graph

746 Commits

Author SHA1 Message Date
gamer147
0bb0f46abc feat(replay): add ViewerBattleHistory entity + migration
New table backs /replay/info; composite PK (ViewerId, BattleId), index on
(ViewerId, CreateTime) for the newest-first list query. 50-row per-viewer
retention enforced by BattleHistoryWriter (next commit).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 07:32:05 -04:00
gamer147
2d65fcd91c test(friend): end-to-end multi-viewer apply→approve→friendship flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:14:45 -04:00
gamer147
d848f4a03f test(friend): literal wire-shape round-trip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:13:16 -04:00
gamer147
2d13f0b72d feat(friend): FriendController with 12 endpoints + integration tests 2026-06-09 22:12:00 -04:00
gamer147
a6e5c9f0bc feat(friend): wire DTOs for /friend/* endpoints
12 files: 3 entry types (FriendEntryDto, FriendApplyEntryDto,
PlayedTogetherEntryDto), 5 response wrappers, 4 request DTOs.
All carry [MessagePackObject] + [Key] + [JsonPropertyName] per convention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:06:36 -04:00
gamer147
b5b4781693 feat(friend): bulk apply ops + IPlayedTogetherWriter with retention cap
Implements RejectAllAppliesAsync, CancelAllAppliesAsync (ExecuteDelete bulk
deletes on incoming/outgoing applies respectively) and RecordAsync (upsert
played-together row with 50-row per-viewer retention eviction). 4 new tests
added; all 1186 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:04:04 -04:00
gamer147
17591a6ebd feat(friend): implement single-target write methods on FriendService
SendApplyAsync, ApproveApplyAsync, RejectApplyAsync, CancelApplyAsync,
RejectFriendAsync all implemented. 11 new tests appended; all 23 friend
tests pass, full suite 1182/1182 green.
2026-06-09 21:59:32 -04:00
gamer147
d078f275f8 feat(friend): implement 5 read methods on FriendService + register DI + read test suite
GetFriendsAsync, GetReceiveAppliesAsync, GetSendAppliesAsync, GetPlayedTogetherAsync,
SearchAsync all implemented. LoadViewerProjectionAsync materialises the full Viewer
entity (with Include/ThenInclude for SelectedEmblem/Degree) then projects in-memory —
avoids the EF Core limitation where Include is silently ignored under Select.
FriendService + IPlayedTogetherWriter registered as Scoped in Program.cs.
12 read tests, all green; full suite 1171/1171 still passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:53:10 -04:00
gamer147
300eee36e9 feat(friend): IFriendService + IPlayedTogetherWriter + DTO records
Task 3: service contract (interface + DTOs) and FriendService skeleton.
All methods throw NotImplementedException; Tasks 4-6 fill in the logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:44:51 -04:00
gamer147
f40ecb8ca7 feat(friend): migration for friend system tables
Generates AddFriendSystemTables migration: ViewerFriends (composite PK,
2 FK cascades), ViewerFriendApplies (int identity PK, unique on
From/To, 2 FK cascades), ViewerPlayedTogethers (composite PK, Owner FK
only). Includes composite/unique indexes. 4/4 FriendPersistenceTests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:42:41 -04:00
gamer147
1813217c16 feat(friend): add ViewerFriend + ViewerFriendApply + ViewerPlayedTogether entities
Lays the persistence foundation for the /friend/* API surface. Three new
model classes with composite PKs / unique constraints / FK cascades registered
on SVSimDbContext; 4/4 persistence tests pass on SQLite in-memory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:40:08 -04:00
gamer147
c1eec9057a test(gift): claim Card/Sleeve presents; reject unsupported types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 20:58:53 -04:00
gamer147
742058403c refactor(campaign): delegate gift-reward-type check to GiftRewardTypes
Delete local IsSupportedGiftRewardType and replace its single call site with
GiftRewardTypes.IsSupported — Card (5) and Sleeve (6) are now accepted.
Update unsupported-type test sentinel from 5 (Card) to 11 (SpotCard).
Add Card and Sleeve success-path tests; full suite 1152/1152.
2026-06-09 20:52:47 -04:00
gamer147
b2a5b69423 fix(gift): wire reward_type is UserGoodsType integer, not legacy 1/4/9 encoding
Replace WireRewardTypeToUserGoodsType switch with a validating identity cast backed
by GiftRewardTypes.IsSupported. Wire type 1 is RedEther (UserGoodsType.RedEther),
not Crystal (UserGoodsType.Crystal=2); the old switch silently granted the wrong
wallet for every tutorial-completion claim. Update all 5 GiftControllerTests assertions
and 1 TutorialFlowEndToEndTests assertion to expect RedEther instead of Crystals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 20:45:49 -04:00
gamer147
366a71688d feat(gift): add GiftRewardTypes supported-set helper
Centralizes which UserGoodsType values IInventoryTransaction.GrantAsync
can handle in the gift-inbox flow; both GiftController (post-fix) and
CampaignController will consult this instead of duplicating the set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 20:39:10 -04:00
gamer147
0d32d2167b feat(campaign): CampaignController.RegisterSerialCode + DTOs + tests
Adds POST /campaign/regist_serial_code with 9 integration tests covering
success path, all disqualifier conditions (unknown/disabled/expired/future/
already-redeemed/unsupported-reward-type), 401 on missing auth, and case-
sensitivity. IsSupportedGiftRewardType uses gift-wire literals (1/4/9) not
UserGoodsType enum values, matching GiftController.WireRewardTypeToUserGoodsType.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 18:51:03 -04:00
gamer147
9f7e78691b feat(serial-code): migration for SerialCode tables
Adds AddSerialCodeTables migration: SerialCodes, SerialCodeRewards,
ViewerSerialCodeRedemptions with FKs, cascade deletes, unique index
on Code, and composite index on (SerialCodeId, Slot).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:45:18 -04:00
gamer147
206be77a86 feat(serial-code): add SerialCode + SerialCodeReward + ViewerSerialCodeRedemption entities
Three new EF entities for /campaign/regist_serial_code: SerialCodeEntry (code, message,
window, enabled flag), SerialCodeRewardEntry (FK child, per-slot reward), and
ViewerSerialCodeRedemption (composite-PK redemption record). Registered in SVSimDbContext
with unique index on Code and cascade FK constraints. 3/3 persistence tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:42:10 -04:00
gamer147
b117fe825c test(profile): literal wire-shape round-trip for UserClass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 17:41:11 -04:00
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