Edit.cshtml.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. using Application.Abstractions.Messaging;
  2. using Microsoft.AspNetCore.Mvc;
  3. using Microsoft.AspNetCore.Mvc.RazorPages;
  4. using Microsoft.AspNetCore.WebUtilities;
  5. using SharedKernel.Attributes;
  6. using SharedKernel.Extensions;
  7. using System.ComponentModel;
  8. using System.ComponentModel.DataAnnotations;
  9. namespace Admin.Pages.Forum.Posts.List;
  10. public class EditModel(IMediator mediator) : PageModel
  11. {
  12. [BindProperty]
  13. public string? QueryString { get; set; }
  14. public string? ReturnUrl { get; set; }
  15. [BindProperty]
  16. public InputModel Input { get; set; } = new();
  17. public string? CurrentThumbnail { get; private set; }
  18. public bool IsQnA { get; set; }
  19. public List<(int ID, string FileName, string Url, string? Extension, long? Size, int Downloads)> ExistingFiles { get; set; } = [];
  20. public List<(int TagID, string Name)> ExistingTags { get; set; } = [];
  21. public sealed class InputModel
  22. {
  23. [DisplayName("ID")]
  24. [Required(ErrorMessage = "{0}은 필수입니다.")]
  25. public int ID { get; set; }
  26. public int BoardID { get; set; }
  27. [DisplayName("말머리")]
  28. public int? BoardPrefixID { get; set; }
  29. [DisplayName("제목")]
  30. [Required(ErrorMessage = "{0}은 필수입니다.")]
  31. [StringLength(255, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")]
  32. public string Subject { get; set; } = null!;
  33. [DisplayName("내용")]
  34. public string? Content { get; set; }
  35. [DisplayName("대표 이미지")]
  36. [AllowedExtensions("jpg,jpeg,png,gif,webp,bmp", ErrorMessage = "이미지 파일은 jpg, jpeg, png, gif, webp, bmp 형식이어야 합니다.")]
  37. public IFormFile? ThumbnailFile { get; set; }
  38. [DisplayName("첨부파일")]
  39. [AllowedExtensions("jpg,jpeg,png,gif,webp,bmp,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip,rar,7z,hwp,hwpx,csv", ErrorMessage = "첨부 파일은 허용된 형식이어야 합니다.")]
  40. public List<IFormFile>? Files { get; set; }
  41. [DisplayName("태그")]
  42. public string? TagsInput { get; set; }
  43. [DisplayName("공지")]
  44. public bool IsNotice { get; set; } = false;
  45. [DisplayName("비밀")]
  46. public bool IsSecret { get; set; } = false;
  47. [DisplayName("익명")]
  48. public bool IsAnonymous { get; set; } = false;
  49. public string? ReturnUrl { get; set; }
  50. }
  51. public async Task OnGetAsync(int id, CancellationToken ct)
  52. {
  53. var postResult = await mediator.Send(new GetPost.Query(id), ct);
  54. if (postResult.IsFailure)
  55. {
  56. TempData["ErrorMessages"] = postResult.Error.Description;
  57. QueryString = Request.QueryString.ToString();
  58. return;
  59. }
  60. var result = postResult.Value;
  61. CurrentThumbnail = result.Thumbnail;
  62. IsQnA = result.IsQnA;
  63. var referer = Request.Headers.Referer.ToString();
  64. ReturnUrl = string.IsNullOrWhiteSpace(referer) ? null : referer;
  65. Input = new InputModel
  66. {
  67. ID = result.ID,
  68. BoardID = result.BoardID,
  69. BoardPrefixID = result.BoardPrefixID,
  70. Subject = result.Subject,
  71. Content = result.Content,
  72. IsNotice = result.IsNotice,
  73. IsSecret = result.IsSecret,
  74. IsAnonymous = result.IsAnonymous,
  75. TagsInput = result.Tags.Count > 0 ? string.Join(", ", result.Tags.Select(t => t.Name)) : null,
  76. ReturnUrl = ReturnUrl
  77. };
  78. ExistingFiles = [..result.Files.Where(f => !f.IsDisabled).Select(f => (f.ID, f.FileName, f.Url, f.Extension, f.Size, f.Downloads))];
  79. ExistingTags = [..result.Tags.Select(t => (t.TagID, t.Name))];
  80. QueryString = Request.QueryString.ToString();
  81. }
  82. public async Task<IActionResult> OnGetPrefixesAsync(int id, int boardID, CancellationToken ct)
  83. {
  84. var result = await mediator.Send(new GetBoardPrefixes.Query(boardID), ct);
  85. var items = result.List.Where(c => c.IsActive).Select(c => new { id = c.ID, name = c.Name });
  86. return new JsonResult(items);
  87. }
  88. public async Task<IActionResult> OnPostAsync(CancellationToken ct)
  89. {
  90. if (!ModelState.IsValid)
  91. {
  92. TempData["ErrorMessages"] = ModelState.GetErrorMessages();
  93. return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}");
  94. }
  95. var tags = string.IsNullOrWhiteSpace(Input.TagsInput)
  96. ? null
  97. : Input.TagsInput.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
  98. var result = await mediator.Send(new UpdatePost.Command(
  99. Input.ID,
  100. Input.BoardPrefixID,
  101. Input.Subject,
  102. Input.Content,
  103. Input.ThumbnailFile,
  104. Input.Files,
  105. tags,
  106. Input.IsNotice,
  107. Input.IsSecret,
  108. Input.IsAnonymous
  109. ), ct);
  110. if (result.IsFailure)
  111. {
  112. TempData["ErrorMessages"] = result.Error.Description;
  113. }
  114. else
  115. {
  116. TempData["SuccessMessage"] = "게시글이 수정되었습니다.";
  117. }
  118. return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}");
  119. }
  120. public async Task<IActionResult> OnPostDeleteThumbnailAsync(CancellationToken ct)
  121. {
  122. var result = await mediator.Send(new DeletePostThumbnail.Command(Input.ID), ct);
  123. if (result.IsFailure)
  124. {
  125. TempData["ErrorMessages"] = result.Error.Description;
  126. }
  127. else
  128. {
  129. TempData["SuccessMessage"] = "대표 이미지가 삭제되었습니다.";
  130. }
  131. return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}");
  132. }
  133. public async Task<IActionResult> OnPostDeleteFileAsync(int fileID, CancellationToken ct)
  134. {
  135. try
  136. {
  137. await mediator.Send(new DeletePostFile.Command([fileID]), ct);
  138. TempData["SuccessMessage"] = "첨부파일이 삭제되었습니다.";
  139. }
  140. catch (Exception e)
  141. {
  142. TempData["ErrorMessages"] = e.Message;
  143. }
  144. return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}");
  145. }
  146. public async Task<IActionResult> OnPostDeleteAsync(CancellationToken ct)
  147. {
  148. try
  149. {
  150. await mediator.Send(new DeletePost.Command([Input.ID]), ct);
  151. TempData["SuccessMessage"] = "게시글이 삭제되었습니다.";
  152. return RedirectToPage("/Forum/Posts/List/Index");
  153. }
  154. catch (Exception e)
  155. {
  156. TempData["ErrorMessages"] = e.Message;
  157. return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}");
  158. }
  159. }
  160. private string GetCleanQueryString()
  161. {
  162. var query = QueryHelpers.ParseQuery(Request.QueryString.ToString());
  163. query.Remove("handler");
  164. query.Remove("fileID");
  165. if (query.Count == 0)
  166. {
  167. return string.Empty;
  168. }
  169. var pairs = query.SelectMany(kvp => kvp.Value, (kvp, v) => $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(v ?? "")}");
  170. return "?" + string.Join("&", pairs);
  171. }
  172. }