feat: migrate InquiryController to FastEndpoints Endpoints (Phase 3)
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m28s

IMPLEMENTATION:
- Create 7 FastEndpoints Endpoint classes for Inquiry API:
  - SubmitEndpoint: POST /api/inquiry (public)
  - GetPagedEndpoint: GET /api/inquiry (auth, paginated)
  - GetByIdEndpoint: GET /api/inquiry/{id} (auth)
  - UpdateStatusEndpoint: PUT /api/inquiry/{id}/status (auth)
  - UpdateAdminMemoEndpoint: PUT /api/inquiry/{id}/memo (auth)
  - UpdateEndpoint: PUT /api/inquiry/{id} (auth)
  - ConvertToClientEndpoint: POST /api/inquiry/{id}/convert-to-client (auth)

- Create InquiryDtos.cs with shared response types:
  - InquiryQuery (query parameters)
  - InquiryPagedResponse (paginated response)
  - UpdateStatusRequest, UpdateAdminMemoRequest, ConvertToClientRequest
  - ConvertToClientResponse, MessageResponse

- Backup InquiryController.cs (no longer active)

VERIFICATION:
 dotnet build: 0 errors, 0 warnings
 dotnet test: 26/26 passed
 Local service publish successful
 FastEndpoints auto-discovery working
 All 21 endpoints verified (Auth 4 + Blog 10 + Inquiry 7)

MIGRATION PROGRESS:
 Phase 1: Auth (4 endpoints) - DEPLOYED
 Phase 2: Blog (10 endpoints) - DEPLOYED
 Phase 3: Inquiry (7 endpoints) - READY FOR DEPLOYMENT

Next: Deploy Phase 3, then continue with remaining 13 Controllers

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 17:21:36 +09:00
parent c92118ab32
commit 474a7cc72f
18 changed files with 336 additions and 0 deletions
@@ -0,0 +1,59 @@
using System.Security.Claims;
using FastEndpoints;
using TaxBaik.Application.Services;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class ConvertToClientEndpoint : Endpoint<ConvertToClientRequest, ConvertToClientResponse>
{
private readonly InquiryService _inquiryService;
private readonly ClientService _clientService;
public ConvertToClientEndpoint(InquiryService inquiryService, ClientService clientService)
{
_inquiryService = inquiryService;
_clientService = clientService;
}
public override void Configure()
{
Post("/api/inquiry/{id}/convert-to-client");
Policies("Bearer");
}
public override async Task HandleAsync(ConvertToClientRequest request, CancellationToken ct)
{
var id = Route<int>("id");
var inquiry = await _inquiryService.GetByIdAsync(id);
if (inquiry == null)
{
ThrowError("문의를 찾을 수 없습니다.", statusCode: 404);
}
if (inquiry.ClientId != null)
{
ThrowError("이미 고객 카드가 연결되어 있습니다.", statusCode: 400);
}
try
{
var clientId = await _clientService.CreateFromInquiryAsync(
request.Name ?? inquiry.Name,
request.Phone ?? inquiry.Phone,
request.ServiceType ?? inquiry.ServiceType);
await _inquiryService.LinkClientAsync(inquiry.Id, clientId);
await _inquiryService.UpdateStatusAsync(inquiry.Id, "consulting", User.FindFirstValue(ClaimTypes.Name) ?? "system");
await SendAsync(new ConvertToClientResponse
{
ClientId = clientId,
Message = "고객 카드가 생성되었습니다."
}, 200, cancellation: ct);
}
catch (Exception ex)
{
ThrowError(ex.Message);
}
}
}
@@ -0,0 +1,32 @@
using FastEndpoints;
using TaxBaik.Application.Services;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class GetByIdEndpoint : Endpoint<EmptyRequest, object>
{
private readonly InquiryService _inquiryService;
public GetByIdEndpoint(InquiryService inquiryService)
{
_inquiryService = inquiryService;
}
public override void Configure()
{
Get("/api/inquiry/{id}");
Policies("Bearer");
}
public override async Task HandleAsync(EmptyRequest _, CancellationToken ct)
{
var id = Route<int>("id");
var inquiry = await _inquiryService.GetByIdAsync(id);
if (inquiry == null)
{
ThrowError("문의를 찾을 수 없습니다.", statusCode: 404);
}
await SendAsync(inquiry, 200, cancellation: ct);
}
}
@@ -0,0 +1,32 @@
using FastEndpoints;
using TaxBaik.Application.Services;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class GetPagedEndpoint : Endpoint<InquiryQuery, InquiryPagedResponse>
{
private readonly InquiryService _inquiryService;
public GetPagedEndpoint(InquiryService inquiryService)
{
_inquiryService = inquiryService;
}
public override void Configure()
{
Get("/api/inquiry");
Policies("Bearer");
}
public override async Task HandleAsync(InquiryQuery request, CancellationToken ct)
{
var (inquiries, total) = await _inquiryService.GetPagedAsync(request.Page, request.PageSize);
await SendAsync(new InquiryPagedResponse
{
Data = inquiries.Cast<object>().ToList(),
Total = total,
Page = request.Page,
PageSize = request.PageSize
}, 200, cancellation: ct);
}
}
@@ -0,0 +1,45 @@
using TaxBaik.Application.DTOs;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class InquiryPagedResponse
{
public List<object> Data { get; set; } = [];
public int Total { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
}
public class InquiryQuery
{
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
public class UpdateStatusRequest
{
public string Status { get; set; } = string.Empty;
}
public class UpdateAdminMemoRequest
{
public string? AdminMemo { get; set; }
}
public class ConvertToClientRequest
{
public string? Name { get; set; }
public string? Phone { get; set; }
public string? ServiceType { get; set; }
}
public class ConvertToClientResponse
{
public int ClientId { get; set; }
public string Message { get; set; } = string.Empty;
}
public class MessageResponse
{
public string Message { get; set; } = string.Empty;
}
@@ -0,0 +1,46 @@
using FastEndpoints;
using TaxBaik.Application.DTOs;
using TaxBaik.Application.Services;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class SubmitEndpoint : Endpoint<SubmitInquiryDto, MessageResponse>
{
private readonly InquiryService _inquiryService;
public SubmitEndpoint(InquiryService inquiryService)
{
_inquiryService = inquiryService;
}
public override void Configure()
{
Post("/api/inquiry");
AllowAnonymous();
}
public override async Task HandleAsync(SubmitInquiryDto request, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Phone))
{
ThrowError("이름과 전화번호를 입력하세요.");
}
try
{
await _inquiryService.SubmitAsync(
request.Name,
request.Phone,
request.ServiceType,
request.Message,
request.Email,
HttpContext.Connection.RemoteIpAddress?.ToString(),
request.SuppressNotification);
await SendAsync(new MessageResponse { Message = "상담 신청이 접수되었습니다." }, 200, cancellation: ct);
}
catch (ValidationException ex)
{
ThrowError(ex.Message);
}
}
}
@@ -0,0 +1,40 @@
using FastEndpoints;
using TaxBaik.Application.Services;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class UpdateAdminMemoEndpoint : Endpoint<UpdateAdminMemoRequest, MessageResponse>
{
private readonly InquiryService _inquiryService;
public UpdateAdminMemoEndpoint(InquiryService inquiryService)
{
_inquiryService = inquiryService;
}
public override void Configure()
{
Put("/api/inquiry/{id}/memo");
Policies("Bearer");
}
public override async Task HandleAsync(UpdateAdminMemoRequest request, CancellationToken ct)
{
var id = Route<int>("id");
var inquiry = await _inquiryService.GetByIdAsync(id);
if (inquiry == null)
{
ThrowError("문의를 찾을 수 없습니다.", statusCode: 404);
}
try
{
await _inquiryService.UpdateAdminMemoAsync(id, request.AdminMemo);
await SendAsync(new MessageResponse { Message = "메모가 저장되었습니다." }, 200, cancellation: ct);
}
catch (Exception ex)
{
ThrowError(ex.Message);
}
}
}
@@ -0,0 +1,40 @@
using FastEndpoints;
using TaxBaik.Application.DTOs;
using TaxBaik.Application.Services;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class UpdateEndpoint : Endpoint<UpdateInquiryDto, object>
{
private readonly InquiryService _inquiryService;
public UpdateEndpoint(InquiryService inquiryService)
{
_inquiryService = inquiryService;
}
public override void Configure()
{
Put("/api/inquiry/{id}");
Policies("Bearer");
}
public override async Task HandleAsync(UpdateInquiryDto request, CancellationToken ct)
{
var id = Route<int>("id");
try
{
var result = await _inquiryService.UpdateAsync(id, request);
if (result == null)
{
ThrowError("문의를 찾을 수 없습니다.", statusCode: 404);
}
await SendAsync(result, 200, cancellation: ct);
}
catch (ValidationException ex)
{
ThrowError(ex.Message);
}
}
}
@@ -0,0 +1,42 @@
using System.Security.Claims;
using FastEndpoints;
using TaxBaik.Application.Services;
namespace TaxBaik.Web.Endpoints.Inquiry;
public class UpdateStatusEndpoint : Endpoint<UpdateStatusRequest, MessageResponse>
{
private readonly InquiryService _inquiryService;
public UpdateStatusEndpoint(InquiryService inquiryService)
{
_inquiryService = inquiryService;
}
public override void Configure()
{
Put("/api/inquiry/{id}/status");
Policies("Bearer");
}
public override async Task HandleAsync(UpdateStatusRequest request, CancellationToken ct)
{
var id = Route<int>("id");
var inquiry = await _inquiryService.GetByIdAsync(id);
if (inquiry == null)
{
ThrowError("문의를 찾을 수 없습니다.", statusCode: 404);
}
try
{
var changedBy = User.FindFirstValue(ClaimTypes.Name) ?? User.Identity?.Name;
await _inquiryService.UpdateStatusAsync(id, request.Status, changedBy);
await SendAsync(new MessageResponse { Message = "상태가 변경되었습니다." }, 200, cancellation: ct);
}
catch (ValidationException ex)
{
ThrowError(ex.Message);
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.