feat(missions): /load/index materializes viewer mission/achievement state
EnsureCurrentAsync now takes viewerId (was Viewer), so it works with LoadController's AsNoTracking-loaded detached viewers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,7 +58,7 @@ public class AchievementController : SVSimController
|
||||
.AsSplitQuery()
|
||||
.FirstAsync(v => v.Id == viewerId, ct);
|
||||
|
||||
await _state.EnsureCurrentAsync(viewer, ct);
|
||||
await _state.EnsureCurrentAsync(viewer.Id, ct);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
|
||||
// Re-read viewer's achievement for this type after state-service materialization.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.Database.Models.Config;
|
||||
@@ -47,11 +48,14 @@ public class LoadController : SVSimController
|
||||
private readonly ICardAcquisitionService _acquisition;
|
||||
private readonly IGameConfigService _config;
|
||||
private readonly IBattlePassService _battlePass;
|
||||
private readonly IViewerMissionStateService _missionState;
|
||||
private readonly SVSimDbContext _db;
|
||||
|
||||
public LoadController(IViewerRepository viewerRepository, ICardRepository cardRepository,
|
||||
ICollectionRepository collectionRepository, IGlobalsRepository globalsRepository,
|
||||
ICardAcquisitionService acquisition, IGameConfigService config,
|
||||
IBattlePassService battlePass)
|
||||
IBattlePassService battlePass, IViewerMissionStateService missionState,
|
||||
SVSimDbContext db)
|
||||
{
|
||||
_viewerRepository = viewerRepository;
|
||||
_cardRepository = cardRepository;
|
||||
@@ -60,6 +64,8 @@ public class LoadController : SVSimController
|
||||
_acquisition = acquisition;
|
||||
_config = config;
|
||||
_battlePass = battlePass;
|
||||
_missionState = missionState;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[HttpPost("index")]
|
||||
@@ -83,6 +89,11 @@ public class LoadController : SVSimController
|
||||
// (on a separate tracked instance) won't appear on this snapshot. Without the re-fetch,
|
||||
// the response payload would be one /load/index behind on newly-granted cosmetics.
|
||||
await _acquisition.BackfillCosmeticsAsync(viewer.Id);
|
||||
|
||||
// Lazy-materialize mission/achievement state. Idempotent — safe to call every /load/index.
|
||||
await _missionState.EnsureCurrentAsync(viewer.Id);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
viewer = await _viewerRepository.GetViewerByShortUdid(shortUdid);
|
||||
if (viewer is null)
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ public class MissionController : SVSimController
|
||||
if (!TryGetViewerId(out long viewerId)) return Unauthorized();
|
||||
var viewer = await LoadViewer(viewerId, ct);
|
||||
|
||||
await _state.EnsureCurrentAsync(viewer, ct);
|
||||
await _state.EnsureCurrentAsync(viewer.Id, ct);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
|
||||
var dto = await _assembler.BuildAsync(viewer, ct);
|
||||
|
||||
Reference in New Issue
Block a user