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> 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. }; } /// /// 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. /// private async Task> BuildArenaInfosAsync() { var season = await _globalsRepository.GetCurrentArenaSeason(); if (season is null) { return new List { new ArenaInfo { Mode = 0, Enable = 0, Cost = 0, RupeeCost = 0, TicketCost = 0, IsJoin = false, }, }; } return new List { 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. } }; } }