Files
SVSimServer/SVSim.EmulatedEntrypoint/Controllers/StoryController.cs
gamer147 652011ea74 refactor(controllers): migrate practice/story/item_purchase to MissionEventKeys
PracticeController.Finish now uses MissionEventKeys.Practice.WinAll — the
counter keys emitted match the catalog's named form (practice_win:elite:arisa
etc.), closing the numeric-vs-named mismatch that had all practice-elite
achievements silently at zero. Also removes the "bridging is a follow-up" TODO.

StoryController.Finish uses MissionEventKeys.Story.ChapterFinishAll for the
same shape. ItemPurchaseController.CounterKey routes through
MissionEventKeys.ItemPurchase so the "item_purchase:" prefix has one owner.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 12:59:34 -04:00

112 lines
4.1 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SVSim.Database.Entities.Story;
using SVSim.Database.Services;
using SVSim.EmulatedEntrypoint.Models.Dtos.Story;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Controllers;
[ApiController]
[Authorize]
public class StoryController : SVSimController
{
private readonly IStoryService _service;
private readonly IMissionProgressService _missionProgress;
public StoryController(IStoryService service, IMissionProgressService missionProgress)
{
_service = service;
_missionProgress = missionProgress;
}
[HttpPost("/story/section")]
[HttpPost("/main_story/section")]
[HttpPost("/limited_story/section")]
[HttpPost("/event_story/section")]
public async Task<ActionResult<SectionResponse>> Section(SectionRequest req)
{
if (!TryGetViewerId(out long vid)) return Unauthorized();
return await _service.GetSectionsAsync(ResolveApiType(), vid);
}
[HttpPost("/main_story/leader_select")]
[HttpPost("/limited_story/leader_select")]
[HttpPost("/event_story/leader_select")]
public async Task<ActionResult<LeaderSelectResponse>> LeaderSelect(LeaderSelectRequest req)
{
if (!TryGetViewerId(out long vid)) return Unauthorized();
return await _service.GetLeaderSelectAsync(ResolveApiType(), req.SectionId, vid);
}
[HttpPost("/main_story/info")]
[HttpPost("/limited_story/info")]
[HttpPost("/event_story/info")]
public async Task<ActionResult<InfoResponse>> Info(InfoRequest req)
{
if (!TryGetViewerId(out long vid)) return Unauthorized();
int? chara = req.CharaId == 0 ? null : req.CharaId;
return await _service.GetInfoAsync(ResolveApiType(), req.SectionId, chara, vid);
}
[HttpPost("/main_story/get_deck_list")]
[HttpPost("/event_story/get_deck_list")]
public async Task<ActionResult<GetDeckListResponse>> GetDeckList(GetDeckListRequest req)
{
if (!TryGetViewerId(out long vid)) return Unauthorized();
return await _service.GetDeckListAsync(ResolveApiType(), req.StoryId, vid);
}
[HttpPost("/main_story/start")]
[HttpPost("/limited_story/start")]
[HttpPost("/event_story/start")]
public async Task<ActionResult<StartResponse>> Start(StartRequest req)
{
if (!TryGetViewerId(out long vid)) return Unauthorized();
return await _service.StartAsync(ResolveApiType(), req.StoryIds, vid);
}
[HttpPost("/main_story/finish")]
[HttpPost("/limited_story/finish")]
[HttpPost("/event_story/finish")]
public async Task<ActionResult<FinishResponse>> Finish(FinishRequest req)
{
if (!TryGetViewerId(out long vid)) return Unauthorized();
var result = await _service.FinishAsync(ResolveApiType(), req, vid);
// Emit story-chapter-finish events for mission/achievement progress.
var apiType = ResolveApiType();
var prefix = apiType switch
{
StoryApiType.Main => "main",
StoryApiType.Limited => "limited",
StoryApiType.Event => "event",
_ => null,
};
if (prefix is not null && req.StoryId != 0)
{
await _missionProgress.RecordEventAsync(
vid, MissionEventKeys.Story.ChapterFinishAll(prefix, req.StoryId));
}
return result;
}
[HttpPost("/main_story/all_finish")]
[HttpPost("/limited_story/all_finish")]
[HttpPost("/event_story/all_finish")]
public async Task<ActionResult<FinishResponse>> AllFinish(AllFinishRequest req)
{
if (!TryGetViewerId(out long vid)) return Unauthorized();
return await _service.AllFinishAsync(ResolveApiType(), req.StoryIds, req.IsFinish == 1, vid);
}
private StoryApiType ResolveApiType()
{
var path = HttpContext.Request.Path.Value ?? "";
if (path.StartsWith("/main_story")) return StoryApiType.Main;
if (path.StartsWith("/limited_story")) return StoryApiType.Limited;
if (path.StartsWith("/event_story")) return StoryApiType.Event;
return StoryApiType.AllStory; // /story/section
}
}