Write.cshtml.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. using SharedKernel.Attributes;
  2. using SharedKernel.Extensions;
  3. using GameOptions = Application.Features.Admin.Store.Game.Options;
  4. using MediatR;
  5. using Microsoft.AspNetCore.Mvc;
  6. using Microsoft.AspNetCore.Mvc.RazorPages;
  7. using System.ComponentModel;
  8. using System.ComponentModel.DataAnnotations;
  9. namespace Admin.Pages.Store.Game;
  10. public class WriteModel(IMediator mediator) : PageModel
  11. {
  12. [BindProperty]
  13. public InputModel Input { get; set; } = new();
  14. public IReadOnlyList<GameOptions.OptionItem> GenreOptions { get; private set; } = [];
  15. public IReadOnlyList<GameOptions.OptionItem> CategoryOptions { get; private set; } = [];
  16. public sealed class InputModel
  17. {
  18. [DisplayName("게임 코드")]
  19. [StringLength(6, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  20. public string? Code { get; set; }
  21. [DisplayName("한글명")]
  22. [Required(ErrorMessage = "{0}은(는) 필수입니다.")]
  23. [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  24. public string KorName { get; set; } = null!;
  25. [DisplayName("영문명")]
  26. [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  27. public string? EngName { get; set; }
  28. [DisplayName("게임사")]
  29. [Required(ErrorMessage = "{0}은(는) 필수입니다.")]
  30. [StringLength(100, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  31. public string Publisher { get; set; } = null!;
  32. [DisplayName("작은 이미지")]
  33. [AllowedExtensions("jpg,jpeg,png,gif,webp", ErrorMessage = "이미지 형식은 jpg, jpeg, png, gif, webp 만 허용합니다.")]
  34. public IFormFile? ThumbnailFile { get; set; }
  35. [DisplayName("큰 이미지")]
  36. [AllowedExtensions("jpg,jpeg,png,gif,webp", ErrorMessage = "이미지 형식은 jpg, jpeg, png, gif, webp 만 허용합니다.")]
  37. public IFormFile? BigImageFile { get; set; }
  38. [DisplayName("설명")]
  39. public string? Description { get; set; }
  40. [DisplayName("게임사 수익률(%)")]
  41. [Required(ErrorMessage = "{0}은(는) 필수입니다.")]
  42. [Range(0, 100, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")]
  43. public decimal CommissionRate { get; set; }
  44. [DisplayName("API 수수료율(%)")]
  45. [Range(0, 100, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")]
  46. public decimal ApiCommissionRate { get; set; }
  47. [DisplayName("게임 사이트 주소")]
  48. [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  49. public string? Link { get; set; }
  50. [DisplayName("YouTube 예고편 URL")]
  51. [StringLength(500, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  52. public string? TrailerUrl { get; set; }
  53. [DisplayName("최소 사양")]
  54. public string? MinSpec { get; set; }
  55. [DisplayName("권장 사양")]
  56. public string? RecommendedSpec { get; set; }
  57. [DisplayName("장르")]
  58. public List<int> GenreIDs { get; set; } = [];
  59. [DisplayName("카테고리")]
  60. public List<int> CategoryIDs { get; set; } = [];
  61. [DisplayName("상세 이미지")]
  62. public List<IFormFile>? DetailImages { get; set; }
  63. [DisplayName("출시일")]
  64. public DateOnly? ReleaseDate { get; set; }
  65. [DisplayName("순서")]
  66. [Range(0, 9999, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")]
  67. public int Order { get; set; } = 0;
  68. [DisplayName("사용 여부")]
  69. public bool IsActive { get; set; } = true;
  70. }
  71. public async Task OnGetAsync(CancellationToken ct)
  72. {
  73. await LoadOptionsAsync(ct);
  74. // 게임 코드 자동 생성 (사용자는 그대로 사용하거나 재생성 버튼으로 갱신)
  75. Input.Code = await mediator.Send(new GenerateGameCode.Query(), ct);
  76. }
  77. private async Task LoadOptionsAsync(CancellationToken ct)
  78. {
  79. var options = await mediator.Send(new GameOptions.Query(), ct);
  80. if (options.IsSuccess)
  81. {
  82. GenreOptions = options.Value.Genres;
  83. CategoryOptions = options.Value.Categories;
  84. }
  85. }
  86. public async Task<IActionResult> OnGetGenerateCodeAsync(CancellationToken ct)
  87. {
  88. var code = await mediator.Send(new GenerateGameCode.Query(), ct);
  89. return new JsonResult(new { code });
  90. }
  91. public async Task<IActionResult> OnPostAsync(CancellationToken ct)
  92. {
  93. await LoadOptionsAsync(ct);
  94. if (!ModelState.IsValid)
  95. {
  96. TempData["ErrorMessages"] = ModelState.GetErrorMessages();
  97. return Page();
  98. }
  99. var result = await mediator.Send(new CreateGame.Command(
  100. Input.Code,
  101. Input.KorName,
  102. Input.EngName,
  103. Input.Publisher,
  104. Input.ThumbnailFile,
  105. Input.BigImageFile,
  106. Input.Description,
  107. Input.CommissionRate,
  108. Input.ApiCommissionRate,
  109. Input.Link,
  110. Input.Order,
  111. Input.IsActive,
  112. Input.TrailerUrl,
  113. Input.MinSpec,
  114. Input.RecommendedSpec,
  115. Input.GenreIDs,
  116. Input.CategoryIDs,
  117. Input.DetailImages,
  118. Input.ReleaseDate
  119. ), ct);
  120. if (result.IsFailure)
  121. {
  122. TempData["ErrorMessages"] = result.Error.Description;
  123. return Page();
  124. }
  125. TempData["SuccessMessage"] = $"{Input.KorName} 게임이 등록되었습니다.";
  126. return RedirectToPage("/Store/Game/Index");
  127. }
  128. }