using SharedKernel.Attributes; using SharedKernel.Extensions; using GameOptions = Application.Features.Admin.Store.Game.Options; using Application.Abstractions.Messaging; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Admin.Pages.Store.Game; public class EditModel(IMediator mediator) : PageModel { [BindProperty] public string? QueryString { get; set; } public string? CurrentThumbnail { get; private set; } public string? CurrentBigImage { get; private set; } public IReadOnlyList GenreOptions { get; private set; } = []; public IReadOnlyList CategoryOptions { get; private set; } = []; public IReadOnlyList CurrentImages { get; private set; } = []; public IReadOnlyList LanguageOptions { get; private set; } = []; public IReadOnlyList CurrentLanguages { get; private set; } = []; [BindProperty] public InputModel Input { get; set; } = new(); public sealed class InputModel { [DisplayName("ID")] [Required(ErrorMessage = "{0}은(는) 필수입니다.")] public int ID { get; set; } [DisplayName("게임 코드")] [StringLength(6, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")] public string? Code { get; set; } [DisplayName("한글명")] [Required(ErrorMessage = "{0}은(는) 필수입니다.")] [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")] public string KorName { get; set; } = null!; [DisplayName("영문명")] [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")] public string? EngName { get; set; } [DisplayName("게임사")] [Required(ErrorMessage = "{0}은(는) 필수입니다.")] [StringLength(100, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")] public string Publisher { get; set; } = null!; [DisplayName("새 작은 이미지")] [AllowedExtensions("jpg,jpeg,png,gif,webp", ErrorMessage = "이미지 형식은 jpg, jpeg, png, gif, webp 만 허용합니다.")] public IFormFile? ThumbnailFile { get; set; } [DisplayName("작은 이미지 삭제")] public bool RemoveThumbnail { get; set; } [DisplayName("새 큰 이미지")] [AllowedExtensions("jpg,jpeg,png,gif,webp", ErrorMessage = "이미지 형식은 jpg, jpeg, png, gif, webp 만 허용합니다.")] public IFormFile? BigImageFile { get; set; } [DisplayName("큰 이미지 삭제")] public bool RemoveBigImage { get; set; } [DisplayName("설명")] public string? Description { get; set; } [DisplayName("게임사 수익률(%)")] [Range(0, 100, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")] public decimal CommissionRate { get; set; } [DisplayName("게임 사이트 주소")] [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")] public string? Link { get; set; } [DisplayName("YouTube 예고편 URL")] [StringLength(500, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")] public string? TrailerUrl { get; set; } [DisplayName("최소 사양")] public string? MinSpec { get; set; } [DisplayName("권장 사양")] public string? RecommendedSpec { get; set; } [DisplayName("장르")] public List GenreIDs { get; set; } = []; [DisplayName("카테고리")] public List CategoryIDs { get; set; } = []; [DisplayName("새 상세 이미지")] public List? DetailImages { get; set; } [DisplayName("삭제할 상세 이미지")] public List RemoveImageIDs { get; set; } = []; [DisplayName("상세 이미지 순서")] public List ImageOrder { get; set; } = []; [DisplayName("출시일")] public DateOnly? ReleaseDate { get; set; } [DisplayName("언어 지원")] public List Languages { get; set; } = []; [DisplayName("순서")] [Range(0, 9999, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")] public int Order { get; set; } [DisplayName("사용 여부")] public bool IsActive { get; set; } public sealed class LanguageRow { public int GameLanguageID { get; set; } public bool Interface { get; set; } public bool FullAudio { get; set; } public bool Subtitles { get; set; } } } public async Task OnGetAsync(int id, CancellationToken ct) { await LoadOptionsAsync(ct); var result = await mediator.Send(new GetGame.Query(id), ct); if (result.IsFailure) { TempData["ErrorMessages"] = result.Error.Description; QueryString = Request.QueryString.ToString(); return; } var data = result.Value; CurrentThumbnail = data.Thumbnail; CurrentBigImage = data.BigImage; CurrentImages = data.Images; CurrentLanguages = data.Languages; Input = new InputModel { ID = data.ID, Code = data.Code, KorName = data.KorName, EngName = data.EngName, Publisher = data.Publisher, Description = data.Description, CommissionRate = data.CommissionRate, Link = data.Link, Order = data.Order, IsActive = data.IsActive, TrailerUrl = data.TrailerUrl, MinSpec = data.MinSpec, RecommendedSpec = data.RecommendedSpec, GenreIDs = new List(data.GenreIDs), CategoryIDs = new List(data.CategoryIDs), ReleaseDate = data.ReleaseDate }; QueryString = Request.QueryString.ToString(); } private async Task LoadOptionsAsync(CancellationToken ct) { var options = await mediator.Send(new GameOptions.Query(), ct); if (options.IsSuccess) { GenreOptions = options.Value.Genres; CategoryOptions = options.Value.Categories; LanguageOptions = options.Value.Languages; } } public async Task OnPostAsync(CancellationToken ct) { if (!ModelState.IsValid) { TempData["ErrorMessages"] = ModelState.GetErrorMessages(); // 입력값 유지를 위해 redirect 대신 Page() 반환 — 표시용 읽기 데이터(옵션/기존 이미지·언어)는 다시 로드 await LoadOptionsAsync(ct); var current = await mediator.Send(new GetGame.Query(Input.ID), ct); if (current.IsSuccess) { CurrentThumbnail = current.Value.Thumbnail; CurrentBigImage = current.Value.BigImage; CurrentImages = current.Value.Images; CurrentLanguages = current.Value.Languages; } QueryString = Request.QueryString.ToString(); return Page(); } var result = await mediator.Send(new UpdateGame.Command( Input.ID, Input.Code, Input.KorName, Input.EngName, Input.Publisher, Input.ThumbnailFile, Input.RemoveThumbnail, Input.BigImageFile, Input.RemoveBigImage, Input.Description, Input.CommissionRate, Input.Link, Input.Order, Input.IsActive, Input.TrailerUrl, Input.MinSpec, Input.RecommendedSpec, Input.GenreIDs, Input.CategoryIDs, Input.DetailImages, Input.RemoveImageIDs, Input.ReleaseDate, Input.ImageOrder, Input.Languages.Select(l => new UpdateGame.Command.LanguageRow(l.GameLanguageID, l.Interface, l.FullAudio, l.Subtitles)).ToList() ), ct); if (result.IsFailure) { TempData["ErrorMessages"] = result.Error.Description; } else { TempData["SuccessMessage"] = $"{Input.KorName} 게임이 수정되었습니다."; } return Redirect($"/Store/Game/Edit/{Input.ID}{Request.QueryString}"); } }