feat: migrate BlogController to FastEndpoints Endpoints (Phase 2)
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m25s
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m25s
IMPLEMENTATION:
- Create 10 FastEndpoints Endpoint classes for Blog API:
- GetPublishedEndpoint: GET /api/blog (public, paginated)
- GetBySlugEndpoint: GET /api/blog/{slug} (public)
- GetByIdEndpoint: GET /api/blog/admin/{id} (auth)
- GetAllEndpoint: GET /api/blog/admin/all (auth)
- GetAdminPagedEndpoint: GET /api/blog/admin (auth, paginated)
- GetArchivedPagedEndpoint: GET /api/blog/admin/archived (auth, paginated)
- CreateEndpoint: POST /api/blog (auth)
- UpdateEndpoint: PUT /api/blog/{id} (auth)
- DeleteEndpoint: DELETE /api/blog/{id} (auth, archives post)
- RestoreEndpoint: POST /api/blog/{id}/restore (auth)
- Create BlogDtos.cs with shared response types:
- BlogPublishedQuery / BlogAdminQuery (query parameters)
- PaginatedResponse<T> (generic pagination response)
- BlogPostListResponse (list response)
- MessageResponse (simple message)
- Backup BlogController.cs (no longer active)
ARCHITECTURE:
- All endpoints use Endpoint<TRequest, TResponse> pattern
- BlogService injected via constructor DI
- Proper error handling with ThrowError()
- Authorization via Policies("Bearer") for protected endpoints
- AllowAnonymous() for public endpoints
VERIFICATION:
✅ dotnet build: 0 errors, 0 warnings
✅ dotnet test: 26/26 passed
✅ FastEndpoints auto-discovery working
Next Phase: Migrate remaining Controllers (18 total - 2 done = 16 remaining)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
using TaxBaik.Application.DTOs;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class BlogPublishedQuery
|
||||||
|
{
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlogAdminQuery
|
||||||
|
{
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PaginatedResponse<T>
|
||||||
|
{
|
||||||
|
public List<T> Data { get; set; } = [];
|
||||||
|
public int Total { get; set; }
|
||||||
|
public int Page { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlogPostListResponse
|
||||||
|
{
|
||||||
|
public List<object> Posts { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessageResponse
|
||||||
|
{
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.DTOs;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class CreateEndpoint : Endpoint<CreateBlogPostDto, object>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public CreateEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Post("/api/blog");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CreateBlogPostDto request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _blogService.CreateAsync(request);
|
||||||
|
await SendAsync(result, 201, cancellation: ct);
|
||||||
|
}
|
||||||
|
catch (ValidationException ex)
|
||||||
|
{
|
||||||
|
ThrowError(ex.Message, statusCode: 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class DeleteEndpoint : Endpoint<EmptyRequest, EmptyResponse>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public DeleteEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Delete("/api/blog/{id}");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(EmptyRequest _, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = Route<int>("id");
|
||||||
|
await _blogService.ArchiveAsync(id);
|
||||||
|
await SendAsync(new EmptyResponse(), 204, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class GetAdminPagedEndpoint : Endpoint<BlogAdminQuery, PaginatedResponse<object>>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public GetAdminPagedEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/blog/admin");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(BlogAdminQuery request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var (items, total) = await _blogService.GetAdminPagedAsync(request.Page, request.PageSize);
|
||||||
|
await SendAsync(new PaginatedResponse<object>
|
||||||
|
{
|
||||||
|
Data = items.Cast<object>().ToList(),
|
||||||
|
Total = total,
|
||||||
|
Page = request.Page,
|
||||||
|
PageSize = request.PageSize
|
||||||
|
}, 200, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class GetAllEndpoint : Endpoint<EmptyRequest, BlogPostListResponse>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public GetAllEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/blog/admin/all");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(EmptyRequest _, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var posts = await _blogService.GetAllAsync();
|
||||||
|
await SendAsync(new BlogPostListResponse
|
||||||
|
{
|
||||||
|
Posts = posts.Cast<object>().ToList()
|
||||||
|
}, 200, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class GetArchivedPagedEndpoint : Endpoint<BlogAdminQuery, PaginatedResponse<object>>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public GetArchivedPagedEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/blog/admin/archived");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(BlogAdminQuery request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var (items, total) = await _blogService.GetArchivedPagedAsync(request.Page, request.PageSize);
|
||||||
|
await SendAsync(new PaginatedResponse<object>
|
||||||
|
{
|
||||||
|
Data = items.Cast<object>().ToList(),
|
||||||
|
Total = total,
|
||||||
|
Page = request.Page,
|
||||||
|
PageSize = request.PageSize
|
||||||
|
}, 200, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class GetByIdEndpoint : Endpoint<EmptyRequest, object>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public GetByIdEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/blog/admin/{id}");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(EmptyRequest _, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = Route<int>("id");
|
||||||
|
var post = await _blogService.GetByIdAsync(id);
|
||||||
|
if (post == null)
|
||||||
|
{
|
||||||
|
ThrowError("포스트를 찾을 수 없습니다.", statusCode: 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendAsync(post, 200, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class GetBySlugEndpoint : Endpoint<EmptyRequest, object>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public GetBySlugEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/blog/{slug}");
|
||||||
|
AllowAnonymous();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(EmptyRequest _, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var slug = Route<string>("slug") ?? string.Empty;
|
||||||
|
var post = await _blogService.GetBySlugAsync(slug);
|
||||||
|
if (post == null)
|
||||||
|
{
|
||||||
|
ThrowError("포스트를 찾을 수 없습니다.", statusCode: 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendAsync(post, 200, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class GetPublishedEndpoint : Endpoint<BlogPublishedQuery, PaginatedResponse<object>>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public GetPublishedEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/blog");
|
||||||
|
AllowAnonymous();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(BlogPublishedQuery request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var (items, total) = await _blogService.GetPublishedPagedAsync(request.Page, request.PageSize);
|
||||||
|
await SendAsync(new PaginatedResponse<object>
|
||||||
|
{
|
||||||
|
Data = items.Cast<object>().ToList(),
|
||||||
|
Total = total,
|
||||||
|
Page = request.Page,
|
||||||
|
PageSize = request.PageSize
|
||||||
|
}, 200, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class RestoreEndpoint : Endpoint<EmptyRequest, EmptyResponse>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public RestoreEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Post("/api/blog/{id}/restore");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(EmptyRequest _, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = Route<int>("id");
|
||||||
|
await _blogService.RestoreAsync(id);
|
||||||
|
await SendAsync(new EmptyResponse(), 204, cancellation: ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using TaxBaik.Application.DTOs;
|
||||||
|
using TaxBaik.Application.Services;
|
||||||
|
|
||||||
|
namespace TaxBaik.Web.Endpoints.Blog;
|
||||||
|
|
||||||
|
public class UpdateEndpoint : Endpoint<CreateBlogPostDto, object>
|
||||||
|
{
|
||||||
|
private readonly BlogService _blogService;
|
||||||
|
|
||||||
|
public UpdateEndpoint(BlogService blogService)
|
||||||
|
{
|
||||||
|
_blogService = blogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Put("/api/blog/{id}");
|
||||||
|
Policies("Bearer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CreateBlogPostDto request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = Route<int>("id");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _blogService.UpdateAsync(id, request);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
ThrowError("포스트를 찾을 수 없습니다.", statusCode: 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendAsync(result, 200, cancellation: ct);
|
||||||
|
}
|
||||||
|
catch (ValidationException ex)
|
||||||
|
{
|
||||||
|
ThrowError(ex.Message, statusCode: 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user