using MediatR; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using SharedKernel.Attributes; using SharedKernel.Extensions; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Admin.Pages.Forum.Posts.List { public class EditModel(IMediator mediator) : PageModel { [BindProperty] public string? QueryString { get; set; } public string? ReturnUrl { get; set; } [BindProperty] public InputModel Input { get; set; } = new(); public string? CurrentThumbnail { get; private set; } public bool IsQnA { get; set; } public List<(int ID, string FileName, string Url, string? Extension, long? Size, int Downloads)> ExistingFiles { get; set; } = []; public List<(int TagID, string Name)> ExistingTags { get; set; } = []; public sealed class InputModel { [DisplayName("ID")] [Required(ErrorMessage = "{0}은 필수입니다.")] public int ID { get; set; } public int BoardID { get; set; } [DisplayName("말머리")] public int? BoardPrefixID { get; set; } [DisplayName("제목")] [Required(ErrorMessage = "{0}은 필수입니다.")] [StringLength(255, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")] public string Subject { get; set; } = null!; [DisplayName("내용")] public string? Content { get; set; } [DisplayName("대표 이미지")] [AllowedExtensions("jpg,jpeg,png,gif,webp,bmp", ErrorMessage = "이미지 파일은 jpg, jpeg, png, gif, webp, bmp 형식이어야 합니다.")] public IFormFile? ThumbnailFile { get; set; } [DisplayName("첨부파일")] [AllowedExtensions("jpg,jpeg,png,gif,webp,bmp,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip,rar,7z,hwp,hwpx,csv", ErrorMessage = "첨부 파일은 허용된 형식이어야 합니다.")] public List? Files { get; set; } [DisplayName("태그")] public string? TagsInput { get; set; } [DisplayName("공지")] public bool IsNotice { get; set; } = false; [DisplayName("비밀")] public bool IsSecret { get; set; } = false; [DisplayName("익명")] public bool IsAnonymous { get; set; } = false; public string? ReturnUrl { get; set; } } public async Task OnGetAsync(int id, CancellationToken ct) { var result = await mediator.Send(new GetPost.Query(id), ct); CurrentThumbnail = result.Thumbnail; IsQnA = result.IsQnA; var referer = Request.Headers.Referer.ToString(); ReturnUrl = string.IsNullOrWhiteSpace(referer) ? null : referer; Input = new InputModel { ID = result.ID, BoardID = result.BoardID, BoardPrefixID = result.BoardPrefixID, Subject = result.Subject, Content = result.Content, IsNotice = result.IsNotice, IsSecret = result.IsSecret, IsAnonymous = result.IsAnonymous, TagsInput = result.Tags.Count > 0 ? string.Join(", ", result.Tags.Select(t => t.Name)) : null, ReturnUrl = ReturnUrl }; ExistingFiles = [..result.Files.Where(f => !f.IsDisabled).Select(f => (f.ID, f.FileName, f.Url, f.Extension, f.Size, f.Downloads))]; ExistingTags = [..result.Tags.Select(t => (t.TagID, t.Name))]; QueryString = Request.QueryString.ToString(); } public async Task OnGetPrefixesAsync(int id, int boardID, CancellationToken ct) { var result = await mediator.Send(new GetBoardPrefixes.Query(boardID), ct); var items = result.List.Where(c => c.IsActive).Select(c => new { id = c.ID, name = c.Name }); return new JsonResult(items); } public async Task OnPostAsync(CancellationToken ct) { try { if (!ModelState.IsValid) { throw new Exception(ModelState.GetErrorMessages()); } var tags = string.IsNullOrWhiteSpace(Input.TagsInput) ? null : Input.TagsInput.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); await mediator.Send(new UpdatePost.Command( Input.ID, Input.BoardPrefixID, Input.Subject, Input.Content, Input.ThumbnailFile, Input.Files, tags, Input.IsNotice, Input.IsSecret, Input.IsAnonymous ), ct); TempData["SuccessMessage"] = "게시글이 수정되었습니다."; } catch (Exception e) { TempData["ErrorMessages"] = e.Message; } return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}"); } public async Task OnPostDeleteThumbnailAsync(CancellationToken ct) { try { await mediator.Send(new DeletePostThumbnail.Command(Input.ID), ct); TempData["SuccessMessage"] = "대표 이미지가 삭제되었습니다."; } catch (Exception e) { TempData["ErrorMessages"] = e.Message; } return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}"); } public async Task OnPostDeleteFileAsync(int fileID, CancellationToken ct) { try { await mediator.Send(new DeletePostFile.Command([fileID]), ct); TempData["SuccessMessage"] = "첨부파일이 삭제되었습니다."; } catch (Exception e) { TempData["ErrorMessages"] = e.Message; } return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}"); } public async Task OnPostDeleteAsync(CancellationToken ct) { try { await mediator.Send(new DeletePost.Command([Input.ID]), ct); TempData["SuccessMessage"] = "게시글이 삭제되었습니다."; return RedirectToPage("/Forum/Posts/List/Index"); } catch (Exception e) { TempData["ErrorMessages"] = e.Message; return Redirect($"/Forum/Posts/List/Edit/{Input.ID}{GetCleanQueryString()}"); } } private string GetCleanQueryString() { var query = QueryHelpers.ParseQuery(Request.QueryString.ToString()); query.Remove("handler"); query.Remove("fileID"); if (query.Count == 0) { return string.Empty; } var pairs = query.SelectMany(kvp => kvp.Value, (kvp, v) => $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(v ?? "")}"); return "?" + string.Join("&", pairs); } } }