Edit.cshtml.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 EditModel(IMediator mediator) : PageModel
  11. {
  12. [BindProperty]
  13. public string? QueryString { get; set; }
  14. public string? CurrentThumbnail { get; private set; }
  15. public string? CurrentBigImage { get; private set; }
  16. public IReadOnlyList<GameOptions.OptionItem> GenreOptions { get; private set; } = [];
  17. public IReadOnlyList<GameOptions.OptionItem> CategoryOptions { get; private set; } = [];
  18. public IReadOnlyList<GetGame.Response.ImageRow> CurrentImages { get; private set; } = [];
  19. public IReadOnlyList<GameOptions.OptionItem> LanguageOptions { get; private set; } = [];
  20. public IReadOnlyList<GetGame.Response.LanguageSupportRow> CurrentLanguages { get; private set; } = [];
  21. [BindProperty]
  22. public InputModel Input { get; set; } = new();
  23. public sealed class InputModel
  24. {
  25. [DisplayName("ID")]
  26. [Required(ErrorMessage = "{0}은(는) 필수입니다.")]
  27. public int ID { get; set; }
  28. [DisplayName("게임 코드")]
  29. [StringLength(6, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  30. public string? Code { get; set; }
  31. [DisplayName("한글명")]
  32. [Required(ErrorMessage = "{0}은(는) 필수입니다.")]
  33. [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  34. public string KorName { get; set; } = null!;
  35. [DisplayName("영문명")]
  36. [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  37. public string? EngName { get; set; }
  38. [DisplayName("게임사")]
  39. [Required(ErrorMessage = "{0}은(는) 필수입니다.")]
  40. [StringLength(100, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  41. public string Publisher { get; set; } = null!;
  42. [DisplayName("새 작은 이미지")]
  43. [AllowedExtensions("jpg,jpeg,png,gif,webp", ErrorMessage = "이미지 형식은 jpg, jpeg, png, gif, webp 만 허용합니다.")]
  44. public IFormFile? ThumbnailFile { get; set; }
  45. [DisplayName("작은 이미지 삭제")]
  46. public bool RemoveThumbnail { get; set; }
  47. [DisplayName("새 큰 이미지")]
  48. [AllowedExtensions("jpg,jpeg,png,gif,webp", ErrorMessage = "이미지 형식은 jpg, jpeg, png, gif, webp 만 허용합니다.")]
  49. public IFormFile? BigImageFile { get; set; }
  50. [DisplayName("큰 이미지 삭제")]
  51. public bool RemoveBigImage { get; set; }
  52. [DisplayName("설명")]
  53. public string? Description { get; set; }
  54. [DisplayName("게임사 수익률(%)")]
  55. [Range(0, 100, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")]
  56. public decimal CommissionRate { get; set; }
  57. [DisplayName("API 수수료율(%)")]
  58. [Range(0, 100, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")]
  59. public decimal ApiCommissionRate { get; set; }
  60. [DisplayName("게임 사이트 주소")]
  61. [StringLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  62. public string? Link { get; set; }
  63. [DisplayName("YouTube 예고편 URL")]
  64. [StringLength(500, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
  65. public string? TrailerUrl { get; set; }
  66. [DisplayName("최소 사양")]
  67. public string? MinSpec { get; set; }
  68. [DisplayName("권장 사양")]
  69. public string? RecommendedSpec { get; set; }
  70. [DisplayName("장르")]
  71. public List<int> GenreIDs { get; set; } = [];
  72. [DisplayName("카테고리")]
  73. public List<int> CategoryIDs { get; set; } = [];
  74. [DisplayName("새 상세 이미지")]
  75. public List<IFormFile>? DetailImages { get; set; }
  76. [DisplayName("삭제할 상세 이미지")]
  77. public List<int> RemoveImageIDs { get; set; } = [];
  78. [DisplayName("상세 이미지 순서")]
  79. public List<int> ImageOrder { get; set; } = [];
  80. [DisplayName("출시일")]
  81. public DateOnly? ReleaseDate { get; set; }
  82. [DisplayName("언어 지원")]
  83. public List<LanguageRow> Languages { get; set; } = [];
  84. [DisplayName("순서")]
  85. [Range(0, 9999, ErrorMessage = "{0} 값의 범위는 {1} ~ {2} 입니다.")]
  86. public int Order { get; set; }
  87. [DisplayName("사용 여부")]
  88. public bool IsActive { get; set; }
  89. public sealed class LanguageRow
  90. {
  91. public int GameLanguageID { get; set; }
  92. public bool Interface { get; set; }
  93. public bool FullAudio { get; set; }
  94. public bool Subtitles { get; set; }
  95. }
  96. }
  97. public async Task OnGetAsync(int id, CancellationToken ct)
  98. {
  99. await LoadOptionsAsync(ct);
  100. var result = await mediator.Send(new GetGame.Query(id), ct);
  101. if (result.IsFailure)
  102. {
  103. TempData["ErrorMessages"] = result.Error.Description;
  104. QueryString = Request.QueryString.ToString();
  105. return;
  106. }
  107. var data = result.Value;
  108. CurrentThumbnail = data.Thumbnail;
  109. CurrentBigImage = data.BigImage;
  110. CurrentImages = data.Images;
  111. CurrentLanguages = data.Languages;
  112. Input = new InputModel
  113. {
  114. ID = data.ID,
  115. Code = data.Code,
  116. KorName = data.KorName,
  117. EngName = data.EngName,
  118. Publisher = data.Publisher,
  119. Description = data.Description,
  120. CommissionRate = data.CommissionRate,
  121. ApiCommissionRate = data.ApiCommissionRate,
  122. Link = data.Link,
  123. Order = data.Order,
  124. IsActive = data.IsActive,
  125. TrailerUrl = data.TrailerUrl,
  126. MinSpec = data.MinSpec,
  127. RecommendedSpec = data.RecommendedSpec,
  128. GenreIDs = new List<int>(data.GenreIDs),
  129. CategoryIDs = new List<int>(data.CategoryIDs),
  130. ReleaseDate = data.ReleaseDate
  131. };
  132. QueryString = Request.QueryString.ToString();
  133. }
  134. private async Task LoadOptionsAsync(CancellationToken ct)
  135. {
  136. var options = await mediator.Send(new GameOptions.Query(), ct);
  137. if (options.IsSuccess)
  138. {
  139. GenreOptions = options.Value.Genres;
  140. CategoryOptions = options.Value.Categories;
  141. LanguageOptions = options.Value.Languages;
  142. }
  143. }
  144. public async Task<IActionResult> OnPostAsync(CancellationToken ct)
  145. {
  146. if (!ModelState.IsValid)
  147. {
  148. TempData["ErrorMessages"] = ModelState.GetErrorMessages();
  149. // 입력값 유지를 위해 redirect 대신 Page() 반환 — 표시용 읽기 데이터(옵션/기존 이미지·언어)는 다시 로드
  150. await LoadOptionsAsync(ct);
  151. var current = await mediator.Send(new GetGame.Query(Input.ID), ct);
  152. if (current.IsSuccess)
  153. {
  154. CurrentThumbnail = current.Value.Thumbnail;
  155. CurrentBigImage = current.Value.BigImage;
  156. CurrentImages = current.Value.Images;
  157. CurrentLanguages = current.Value.Languages;
  158. }
  159. QueryString = Request.QueryString.ToString();
  160. return Page();
  161. }
  162. var result = await mediator.Send(new UpdateGame.Command(
  163. Input.ID,
  164. Input.Code,
  165. Input.KorName,
  166. Input.EngName,
  167. Input.Publisher,
  168. Input.ThumbnailFile,
  169. Input.RemoveThumbnail,
  170. Input.BigImageFile,
  171. Input.RemoveBigImage,
  172. Input.Description,
  173. Input.CommissionRate,
  174. Input.ApiCommissionRate,
  175. Input.Link,
  176. Input.Order,
  177. Input.IsActive,
  178. Input.TrailerUrl,
  179. Input.MinSpec,
  180. Input.RecommendedSpec,
  181. Input.GenreIDs,
  182. Input.CategoryIDs,
  183. Input.DetailImages,
  184. Input.RemoveImageIDs,
  185. Input.ReleaseDate,
  186. Input.ImageOrder,
  187. Input.Languages.Select(l => new UpdateGame.Command.LanguageRow(l.GameLanguageID, l.Interface, l.FullAudio, l.Subtitles)).ToList()
  188. ), ct);
  189. if (result.IsFailure)
  190. {
  191. TempData["ErrorMessages"] = result.Error.Description;
  192. }
  193. else
  194. {
  195. TempData["SuccessMessage"] = $"{Input.KorName} 게임이 수정되었습니다.";
  196. }
  197. return Redirect($"/Store/Game/Edit/{Input.ID}{Request.QueryString}");
  198. }
  199. }