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 SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.Database.Repositories.Mission;
using SVSim.Database.Services;
using SVSim.Database.Services.Inventory;
using SVSim.EmulatedEntrypoint.Models.Dtos.Achievement;
using SVSim.EmulatedEntrypoint.Services;
@@ -22,20 +21,20 @@ public class AchievementController : SVSimController
private readonly IMissionCatalogRepository _catalog;
private readonly IViewerMissionStateService _state;
private readonly IMissionAssembler _assembler;
private readonly RewardGrantService _grantService;
private readonly IInventoryService _inv;
public AchievementController(
SVSimDbContext db,
IMissionCatalogRepository catalog,
IViewerMissionStateService state,
IMissionAssembler assembler,
RewardGrantService grantService)
IInventoryService inv)
{
_db = db;
_catalog = catalog;
_state = state;
_assembler = assembler;
_grantService = grantService;
_inv = inv;
}
[HttpPost("receive_reward")]
@@ -44,21 +43,15 @@ public class AchievementController : SVSimController
{
if (!TryGetViewerId(out long viewerId)) return Unauthorized();
// Load viewer with all the collections RewardGrantService may need to mutate.
var viewer = await _db.Viewers
.Include(v => v.MissionData)
.Include(v => v.Currency)
.Include(v => v.Cards)
.Include(v => v.Items)
.Include(v => v.Sleeves)
.Include(v => v.Emblems)
.Include(v => v.Degrees)
.Include(v => v.LeaderSkins)
.Include(v => v.MyPageBackgrounds)
.AsSplitQuery()
.FirstAsync(v => v.Id == viewerId, ct);
// EnsureCurrentAsync needs a viewer id — use a lightweight pre-check load then
// materialize state before opening the inventory tx.
var viewerIdCheck = await _db.Viewers
.Where(v => v.Id == viewerId)
.Select(v => v.Id)
.FirstOrDefaultAsync(ct);
if (viewerIdCheck == 0) return Unauthorized();
await _state.EnsureCurrentAsync(viewer.Id, ct);
await _state.EnsureCurrentAsync(viewerId, ct);
await _db.SaveChangesAsync(ct);
// 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 });
}
// Grant via the canonical RewardGrantService primitive.
var granted = await _grantService.ApplyAsync(
viewer,
// Open inventory tx and grant via InventoryService.
await using var tx = await _inv.BeginAsync(viewerId, ct);
var granted = await tx.GrantAsync(
(UserGoodsType)catalogRow.RewardType,
catalogRow.RewardDetailId,
catalogRow.RewardNumber,
@@ -99,9 +93,9 @@ public class AchievementController : SVSimController
}
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
{
UserMissionList = dto.UserMissionList,