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 WriteModel(IMediator mediator) : PageModel { [BindProperty] public InputModel Input { get; set; } = new(); public IReadOnlyList GenreOptions { get; private set; } = []; public IReadOnlyList CategoryOptions { get; private set; } = []; public sealed class InputModel { [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("큰 이미지")] [AllowedExtensions("jpg,jpeg,png,gif,webp", ErrorMessage = "이미지 형식은 jpg, jpeg, png, gif, webp 만 허용합니다.")] public IFormFile? BigImageFile { get; set; } [DisplayName("설명")] public string? Description { get; set; } [DisplayName("게임사 수익률(%)")] [Required(ErrorMessage = "{0}은(는) 필수입니다.")] [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 DateOnly? ReleaseDate { get; set; } [DisplayName("순서")] [Range(0, 9999, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")] public int Order { get; set; } = 0; [DisplayName("사용 여부")] public bool IsActive { get; set; } = true; } public async Task OnGetAsync(CancellationToken ct) { await LoadOptionsAsync(ct); // 게임 코드 자동 생성 (사용자는 그대로 사용하거나 재생성 버튼으로 갱신) Input.Code = await mediator.Send(new GenerateGameCode.Query(), ct); } 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; } } public async Task OnGetGenerateCodeAsync(CancellationToken ct) { var code = await mediator.Send(new GenerateGameCode.Query(), ct); return new JsonResult(new { code }); } public async Task OnPostAsync(CancellationToken ct) { await LoadOptionsAsync(ct); if (!ModelState.IsValid) { TempData["ErrorMessages"] = ModelState.GetErrorMessages(); return Page(); } var result = await mediator.Send(new CreateGame.Command( Input.Code, Input.KorName, Input.EngName, Input.Publisher, Input.ThumbnailFile, Input.BigImageFile, Input.Description, Input.CommissionRate, Input.Link, Input.Order, Input.IsActive, Input.TrailerUrl, Input.MinSpec, Input.RecommendedSpec, Input.GenreIDs, Input.CategoryIDs, Input.DetailImages, Input.ReleaseDate ), ct); if (result.IsFailure) { TempData["ErrorMessages"] = result.Error.Description; return Page(); } TempData["SuccessMessage"] = $"{Input.KorName} 게임이 등록되었습니다."; return RedirectToPage("/Store/Game/Index"); } }