Files
SVSimServer/SVSim.EmulatedEntrypoint/Controllers/MyPageController.cs
2026-05-23 18:14:42 -04:00

132 lines
6.6 KiB
C#

using Microsoft.AspNetCore.Mvc;
using SVSim.Database.Models;
using SVSim.Database.Repositories.Globals;
using SVSim.Database.Repositories.Viewer;
using SVSim.EmulatedEntrypoint.Constants;
using SVSim.EmulatedEntrypoint.Models.Dtos;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
namespace SVSim.EmulatedEntrypoint.Controllers;
public class MyPageController : SVSimController
{
private readonly IViewerRepository _viewerRepository;
private readonly IGlobalsRepository _globalsRepository;
public MyPageController(IViewerRepository viewerRepository, IGlobalsRepository globalsRepository)
{
_viewerRepository = viewerRepository;
_globalsRepository = globalsRepository;
}
[HttpPost("index")]
public async Task<ActionResult<MyPageIndexResponse>> Index(MyPageIndexRequest request)
{
var shortUdidClaim = User.Claims.FirstOrDefault(c => c.Type == ShadowverseClaimTypes.ShortUdidClaim)?.Value;
if (shortUdidClaim is null || !long.TryParse(shortUdidClaim, out long shortUdid))
{
return Unauthorized();
}
Viewer? viewer = await _viewerRepository.GetViewerByShortUdid(shortUdid);
if (viewer is null)
{
return NotFound();
}
var deviceHeader = Request.Headers["DEVICE"].FirstOrDefault();
int deviceType = int.TryParse(deviceHeader, out int parsed) ? parsed : 0;
// Stubs below are tagged TODO(mypage-stub). See the "Current server implementation"
// section of docs/api-spec/endpoints/post-login/mypage-index.md for the table of what
// each one would source from. Grep for "mypage-stub" to enumerate them.
return new MyPageIndexResponse
{
UserInfo = new UserInfo(deviceType, viewer),
UnreceivedMissionRewardCount = 0, // TODO(mypage-stub): viewer mission progress
ReceiveFriendApplyCount = 0, // TODO(mypage-stub): viewer friend-request inbox
UnreadPresentCount = 0, // TODO(mypage-stub): viewer presents/mail
FriendBattleInviteCount = 0, // TODO(mypage-stub): viewer room-invite count
GuildNotification = new GuildNotification(), // TODO(mypage-stub): viewer guild state
LastAnnounceId = 0, // TODO(mypage-stub): globals announcement metadata
LastAnnounceUpdateTime = string.Empty, // TODO(mypage-stub): globals announcement metadata
FeatureMaintenanceList = new(), // TODO(mypage-stub): FeatureMaintenanceEntry rows
ArenaInfo = await BuildArenaInfosAsync(),
IsArenaChallengePeriod = false, // TODO(mypage-stub): globals/ArenaSeason flag
IsAvailableColosseumFreeEntry = false, // TODO(mypage-stub): viewer + globals free-entry quota
Convention = new Convention // TODO(mypage-stub): viewer offline-event participation
{
IsJoinTournament = false,
IsAdminWatchUser = false,
},
UserConfig = new UserConfig(), // TODO(mypage-stub): persist viewer UserConfig
Quest = new Quest(), // TODO(mypage-stub): active Quest event + viewer flags
MasterPointRankingPeriod = new MasterPointRankingPeriod
{
// TODO(mypage-stub): source begin_time/end_time/period_num/necessary_score from the
// current Master Points season row in globals. Far-future fallback so the client's
// DateTime.Parse(end_time) succeeds and _masterResetNextTime gets seeded.
EndTime = "2030-01-01 00:00:00",
},
PreReleaseStatus = 0, // TODO(mypage-stub): derive from PreReleaseInfo
UserMyPageInfo = new UserMyPageInfo // TODO(mypage-stub): viewer mypage BG selection
{
UserMyPageSetting = new MyPageBgSetting(),
},
BasicPuzzle = new BasicPuzzle { IsDisplayBadge = false }, // TODO(mypage-stub): viewer practice-puzzle progress
IsBattlePassPeriod = (await _globalsRepository.GetGameConfiguration("default")).IsBattlePassPeriod,
SpecialCrystalInfo = new(), // TODO(mypage-stub): same shape/source as /load/index
// ColosseumInfo, ShopNotification, StoryNotification, IsHiddenBossAppeared all
// default-constructed by MyPageIndexResponse's field initializers.
// TODO(mypage-stub): wire colosseum_info from current Colosseum cup row.
// TODO(mypage-stub): wire shop_notification from per-product shop-appeal state.
// TODO(mypage-stub): wire story_notification from viewer story progress.
// TODO(mypage-stub): wire is_hidden_boss_appeared from globals event flag.
};
}
/// <summary>
/// Same shape as LoadController.BuildArenaInfosAsync, but /mypage/index has no
/// Keys.Contains("arena_info") guard on the client (ArenaData(jsonData["arena_info"])
/// at MyPageTask.cs:55 indexes [0] unconditionally). When no current Take Two season is
/// seeded we fall back to a minimal one-entry list so the client's ArenaData ctor doesn't
/// crash with IndexOutOfRange.
/// </summary>
private async Task<List<ArenaInfo>> BuildArenaInfosAsync()
{
var season = await _globalsRepository.GetCurrentArenaSeason();
if (season is null)
{
return new List<ArenaInfo>
{
new ArenaInfo
{
Mode = 0,
Enable = 0,
Cost = 0,
RupeeCost = 0,
TicketCost = 0,
IsJoin = false,
},
};
}
return new List<ArenaInfo>
{
new ArenaInfo
{
Mode = season.Mode,
Enable = season.Enable,
Cost = season.Cost,
RupeeCost = season.RupyCost,
TicketCost = season.TicketCost,
IsJoin = season.IsJoin,
// format_info is intentionally omitted here — /mypage/index's ArenaData
// ctor only needs the top-level fields. /load/index round-trips it via
// JsonbReadOptions; pull it in if a downstream check ever needs it.
}
};
}
}