diff --git a/src/TaxBaik.Application/DTOs/CreateBlogPostDto.cs b/src/TaxBaik.Application/DTOs/CreateBlogPostDto.cs index 2a5923a..6d172d2 100644 --- a/src/TaxBaik.Application/DTOs/CreateBlogPostDto.cs +++ b/src/TaxBaik.Application/DTOs/CreateBlogPostDto.cs @@ -4,7 +4,7 @@ public class CreateBlogPostDto { public required string Title { get; set; } public required string Content { get; set; } - public int? CategoryId { get; set; } + public int CategoryId { get; set; } public string? Tags { get; set; } public string? SeoTitle { get; set; } public string? SeoDescription { get; set; } @@ -18,7 +18,7 @@ public class BlogPostResponseDto public int Id { get; set; } public string Title { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; - public int? CategoryId { get; set; } + public int CategoryId { get; set; } public string? Tags { get; set; } public string? SeoTitle { get; set; } public string? SeoDescription { get; set; } diff --git a/src/TaxBaik.Application/DTOs/Validators.cs b/src/TaxBaik.Application/DTOs/Validators.cs index d393c1a..64345c5 100644 --- a/src/TaxBaik.Application/DTOs/Validators.cs +++ b/src/TaxBaik.Application/DTOs/Validators.cs @@ -8,6 +8,7 @@ public sealed class CreateBlogPostDtoValidator : AbstractValidator x.Title).NotEmpty().MaximumLength(ValidationRules.TitleMaxLength); RuleFor(x => x.Content).NotEmpty().MinimumLength(ValidationRules.MessageMinLength).MaximumLength(ValidationRules.ContentMaxLength); + RuleFor(x => x.CategoryId).GreaterThan(0); RuleFor(x => x.Tags).MaximumLength(ValidationRules.TagsMaxLength); RuleFor(x => x.SeoTitle).MaximumLength(ValidationRules.SeoTitleMaxLength); RuleFor(x => x.SeoDescription).MaximumLength(ValidationRules.SeoDescriptionMaxLength); diff --git a/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml b/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml index fd5ee15..8f0e206 100644 --- a/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml +++ b/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml @@ -2,6 +2,7 @@ @model TaxBaik.Web.Pages.Admin.Blog.CreateModel @{ ViewData["Title"] = "블로그 등록"; + ViewData["BlogCategories"] = Model.Categories; } @await Html.PartialAsync("_BlogEditor", new TaxBaik.Web.Pages.Shared.BlogEditorModel( diff --git a/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml.cs b/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml.cs index 6fa00bf..6a8b653 100644 --- a/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml.cs +++ b/src/TaxBaik.Web/Pages/Admin/Blog/Create.cshtml.cs @@ -3,12 +3,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using TaxBaik.Application.DTOs; using TaxBaik.Application.Services; +using TaxBaik.Domain.Entities; using TaxBaik.Web.Services; namespace TaxBaik.Web.Pages.Admin.Blog; [Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)] -public class CreateModel(BlogService blogService) : PageModel +public class CreateModel(BlogService blogService, CategoryService categoryService) : PageModel { [BindProperty] public CreateBlogPostDto Input { get; set; } = new() @@ -17,14 +18,47 @@ public class CreateModel(BlogService blogService) : PageModel Title = string.Empty }; - public IActionResult OnGet() => Page(); + public IReadOnlyList Categories { get; private set; } = []; + + public async Task OnGetAsync(CancellationToken ct) + { + Categories = (await EnsureCategoriesAsync(ct)).ToList(); + if (Categories.Count > 0) + Input.CategoryId = Categories[0].Id; + + return Page(); + } public async Task OnPostAsync(CancellationToken ct) { + Categories = (await EnsureCategoriesAsync(ct)).ToList(); if (!ModelState.IsValid) return Page(); await blogService.CreateAsync(Input, ct); return RedirectToPage("/Admin/Blog/Index"); } + + private async Task> EnsureCategoriesAsync(CancellationToken ct) + { + var categories = (await categoryService.GetAllAsync(ct)).ToList(); + if (categories.Count > 0) + return categories; + + var defaults = new[] + { + ("사업자 세무", "business-tax", 1), + ("부동산 세금", "real-estate-tax", 2), + ("종합소득세", "income-tax", 3), + ("부가가치세", "vat", 4), + ("가족자산·증여", "family-asset", 5) + }; + + foreach (var (name, slug, sortOrder) in defaults) + { + await categoryService.CreateAsync(name, null, ct); + } + + return await categoryService.GetAllAsync(ct); + } } diff --git a/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml b/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml index 80d710d..5a16e5d 100644 --- a/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml +++ b/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml @@ -3,6 +3,7 @@ @{ ViewData["Title"] = "블로그 수정"; ViewData["BlogEditorId"] = Model.Id; + ViewData["BlogCategories"] = Model.Categories; } @await Html.PartialAsync("_BlogEditor", new TaxBaik.Web.Pages.Shared.BlogEditorModel( diff --git a/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml.cs b/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml.cs index a5f9907..77219c0 100644 --- a/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml.cs +++ b/src/TaxBaik.Web/Pages/Admin/Blog/Edit.cshtml.cs @@ -3,12 +3,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using TaxBaik.Application.DTOs; using TaxBaik.Application.Services; +using TaxBaik.Domain.Entities; using TaxBaik.Web.Services; namespace TaxBaik.Web.Pages.Admin.Blog; [Authorize(AuthenticationSchemes = AdminAuthDefaults.Scheme)] -public class EditModel(BlogService blogService) : PageModel +public class EditModel(BlogService blogService, CategoryService categoryService) : PageModel { [BindProperty(SupportsGet = true)] public int Id { get; set; } @@ -20,17 +21,20 @@ public class EditModel(BlogService blogService) : PageModel Title = string.Empty }; + public IReadOnlyList Categories { get; private set; } = []; + public async Task OnGetAsync(CancellationToken ct) { var post = await blogService.GetByIdAsync(Id, ct); if (post is null) return NotFound(); + Categories = (await EnsureCategoriesAsync(ct)).ToList(); Input = new CreateBlogPostDto { Title = post.Title, Content = post.Content, - CategoryId = post.CategoryId, + CategoryId = post.CategoryId ?? 0, Tags = post.Tags, SeoTitle = post.SeoTitle, SeoDescription = post.SeoDescription, @@ -44,6 +48,7 @@ public class EditModel(BlogService blogService) : PageModel public async Task OnPostAsync(CancellationToken ct) { + Categories = (await EnsureCategoriesAsync(ct)).ToList(); if (!ModelState.IsValid) return Page(); @@ -59,4 +64,27 @@ public class EditModel(BlogService blogService) : PageModel await blogService.DeleteAsync(Id, ct); return RedirectToPage("/Admin/Blog/Index"); } + + private async Task> EnsureCategoriesAsync(CancellationToken ct) + { + var categories = (await categoryService.GetAllAsync(ct)).ToList(); + if (categories.Count > 0) + return categories; + + var defaults = new[] + { + ("사업자 세무", "business-tax", 1), + ("부동산 세금", "real-estate-tax", 2), + ("종합소득세", "income-tax", 3), + ("부가가치세", "vat", 4), + ("가족자산·증여", "family-asset", 5) + }; + + foreach (var (name, slug, sortOrder) in defaults) + { + await categoryService.CreateAsync(name, null, ct); + } + + return await categoryService.GetAllAsync(ct); + } } diff --git a/src/TaxBaik.Web/Pages/Shared/BlogEditorModel.cs b/src/TaxBaik.Web/Pages/Shared/BlogEditorModel.cs index c45deba..ef80142 100644 --- a/src/TaxBaik.Web/Pages/Shared/BlogEditorModel.cs +++ b/src/TaxBaik.Web/Pages/Shared/BlogEditorModel.cs @@ -3,7 +3,7 @@ namespace TaxBaik.Web.Pages.Shared; public sealed record BlogEditorModel( string Title, string Content, - int? CategoryId, + int CategoryId, bool IsPublished, string? ThumbnailUrl, string? SeoTitle, diff --git a/src/TaxBaik.Web/Pages/Shared/_BlogEditor.cshtml b/src/TaxBaik.Web/Pages/Shared/_BlogEditor.cshtml index ac80f37..f86a885 100644 --- a/src/TaxBaik.Web/Pages/Shared/_BlogEditor.cshtml +++ b/src/TaxBaik.Web/Pages/Shared/_BlogEditor.cshtml @@ -43,7 +43,20 @@
- + +
@@ -85,7 +98,7 @@
불러온 원본
@Model.Title
-
카테고리: @(Model.CategoryId?.ToString() ?? "미지정")
+
카테고리: @Model.CategoryId
발행: @(Model.IsPublished ? "예" : "아니오")
@Html.Raw(Model.Content)
diff --git a/src/TaxBaik.Web/Pages/Shared/_EmptyState.cshtml b/src/TaxBaik.Web/Pages/Shared/_EmptyState.cshtml index f7dc03d..d33854b 100644 --- a/src/TaxBaik.Web/Pages/Shared/_EmptyState.cshtml +++ b/src/TaxBaik.Web/Pages/Shared/_EmptyState.cshtml @@ -1,4 +1,7 @@ @model string +@{ + var emptyMessage = string.IsNullOrWhiteSpace(Model) ? "데이터가 없습니다." : Model; +}
@@ -8,5 +11,5 @@
-

@string.IsNullOrWhiteSpace(Model) ? "데이터가 없습니다." : Model

+

@emptyMessage