refactor(achievement): route receive_reward through InventoryService

Replace RewardGrantService with IInventoryService tx. EnsureCurrentAsync
still runs before BeginAsync to avoid EF concurrent-context conflicts;
tx.Viewer replaces the manually loaded viewer graph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-31 16:39:07 -04:00
parent 369edd4537
commit 4ba7d8f6d0

View File

@@ -2,9 +2,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SVSim.Database; using SVSim.Database;
using SVSim.Database.Enums; using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.Database.Repositories.Mission; using SVSim.Database.Repositories.Mission;
using SVSim.Database.Services; using SVSim.Database.Services.Inventory;
using SVSim.EmulatedEntrypoint.Models.Dtos.Achievement; using SVSim.EmulatedEntrypoint.Models.Dtos.Achievement;
using SVSim.EmulatedEntrypoint.Services; using SVSim.EmulatedEntrypoint.Services;
@@ -22,20 +21,20 @@ public class AchievementController : SVSimController
private readonly IMissionCatalogRepository _catalog; private readonly IMissionCatalogRepository _catalog;
private readonly IViewerMissionStateService _state; private readonly IViewerMissionStateService _state;
private readonly IMissionAssembler _assembler; private readonly IMissionAssembler _assembler;
private readonly RewardGrantService _grantService; private readonly IInventoryService _inv;
public AchievementController( public AchievementController(
SVSimDbContext db, SVSimDbContext db,
IMissionCatalogRepository catalog, IMissionCatalogRepository catalog,
IViewerMissionStateService state, IViewerMissionStateService state,
IMissionAssembler assembler, IMissionAssembler assembler,
RewardGrantService grantService) IInventoryService inv)
{ {
_db = db; _db = db;
_catalog = catalog; _catalog = catalog;
_state = state; _state = state;
_assembler = assembler; _assembler = assembler;
_grantService = grantService; _inv = inv;
} }
[HttpPost("receive_reward")] [HttpPost("receive_reward")]
@@ -44,21 +43,15 @@ public class AchievementController : SVSimController
{ {
if (!TryGetViewerId(out long viewerId)) return Unauthorized(); if (!TryGetViewerId(out long viewerId)) return Unauthorized();
// Load viewer with all the collections RewardGrantService may need to mutate. // EnsureCurrentAsync needs a viewer id — use a lightweight pre-check load then
var viewer = await _db.Viewers // materialize state before opening the inventory tx.
.Include(v => v.MissionData) var viewerIdCheck = await _db.Viewers
.Include(v => v.Currency) .Where(v => v.Id == viewerId)
.Include(v => v.Cards) .Select(v => v.Id)
.Include(v => v.Items) .FirstOrDefaultAsync(ct);
.Include(v => v.Sleeves) if (viewerIdCheck == 0) return Unauthorized();
.Include(v => v.Emblems)
.Include(v => v.Degrees)
.Include(v => v.LeaderSkins)
.Include(v => v.MyPageBackgrounds)
.AsSplitQuery()
.FirstAsync(v => v.Id == viewerId, ct);
await _state.EnsureCurrentAsync(viewer.Id, ct); await _state.EnsureCurrentAsync(viewerId, ct);
await _db.SaveChangesAsync(ct); await _db.SaveChangesAsync(ct);
// Re-read viewer's achievement for this type after state-service materialization. // Re-read viewer's achievement for this type after state-service materialization.
@@ -75,9 +68,10 @@ public class AchievementController : SVSimController
return Ok(new { result_code = FailureResultCode }); return Ok(new { result_code = FailureResultCode });
} }
// Grant via the canonical RewardGrantService primitive. // Open inventory tx and grant via InventoryService.
var granted = await _grantService.ApplyAsync( await using var tx = await _inv.BeginAsync(viewerId, ct);
viewer,
var granted = await tx.GrantAsync(
(UserGoodsType)catalogRow.RewardType, (UserGoodsType)catalogRow.RewardType,
catalogRow.RewardDetailId, catalogRow.RewardDetailId,
catalogRow.RewardNumber, catalogRow.RewardNumber,
@@ -99,9 +93,9 @@ public class AchievementController : SVSimController
} }
ach.NowAchievedLevel = request.Level; ach.NowAchievedLevel = request.Level;
await _db.SaveChangesAsync(ct); await tx.CommitAsync(ct);
var dto = await _assembler.BuildAsync(viewer, ct); var dto = await _assembler.BuildAsync(tx.Viewer, ct);
var resp = new AchievementReceiveRewardResponse var resp = new AchievementReceiveRewardResponse
{ {
UserMissionList = dto.UserMissionList, UserMissionList = dto.UserMissionList,