feat: implement API-first architecture Phase 1 - Dashboard API
**Architecture Refactor (SOLID Principles):** - Implement AdminDashboardController (REST API) - Add dashboard summary endpoint - Add upcoming filings endpoint - Add recent inquiries endpoint - Add monthly statistics endpoint **Database Layer (Repository Pattern):** - Extend IInquiryRepository with date range queries - Implement CountByDateRangeAsync - Implement CountByStatusAndDateAsync - Extend InquiryRepository with new methods **Service Layer (Single Responsibility):** - Extend AdminDashboardService with API methods - Add GetRecentInquiriesAsync - Add GetMonthlyStatsAsync with caching **Test Coverage:** - Update FakeInquiryRepository mock with new methods **SOLID Application:** ✓ Single Responsibility: Each class has one reason to change ✓ Open/Closed: Dashboard API can be extended without modifying existing code ✓ Dependency Inversion: Service depends on Repository abstraction ✓ Interface Segregation: API endpoints are focused and specific Status: ✓ Compiles successfully (0 errors, 0 warnings) Next phases: - Add remaining API controllers (Announcement, Client, FAQ, TaxFiling) - Refactor Blazor components to use API instead of services - Implement JWT token refresh mechanism - Add SignalR for change notifications Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 관리자 대시보드 API
|
||||
/// SOLID: Single Responsibility - 대시보드 데이터만 담당
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class AdminDashboardController : ControllerBase
|
||||
{
|
||||
private readonly AdminDashboardService _dashboardService;
|
||||
private readonly TaxFilingService _taxFilingService;
|
||||
|
||||
public AdminDashboardController(
|
||||
AdminDashboardService dashboardService,
|
||||
TaxFilingService taxFilingService)
|
||||
{
|
||||
_dashboardService = dashboardService;
|
||||
_taxFilingService = taxFilingService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 대시보드 요약 정보 조회
|
||||
/// GET /api/admin-dashboard/summary
|
||||
/// </summary>
|
||||
[HttpGet("summary")]
|
||||
public async Task<IActionResult> GetSummary()
|
||||
{
|
||||
try
|
||||
{
|
||||
var summary = await _dashboardService.GetSummaryAsync();
|
||||
return Ok(summary);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "대시보드 요약 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 30일 이내 마감 임박 신고 조회
|
||||
/// GET /api/admin-dashboard/upcoming-filings?days=30
|
||||
/// </summary>
|
||||
[HttpGet("upcoming-filings")]
|
||||
public async Task<IActionResult> GetUpcomingFilings([FromQuery] int days = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (days <= 0) days = 30;
|
||||
var filings = await _taxFilingService.GetUpcomingAsync(days);
|
||||
return Ok(new { data = filings, days });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "마감 임박 신고 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 최근 문의 조회
|
||||
/// GET /api/admin-dashboard/recent-inquiries?limit=10
|
||||
/// </summary>
|
||||
[HttpGet("recent-inquiries")]
|
||||
public async Task<IActionResult> GetRecentInquiries([FromQuery] int limit = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (limit <= 0) limit = 10;
|
||||
if (limit > 100) limit = 100; // 보안: 최대 100개
|
||||
|
||||
var inquiries = await _dashboardService.GetRecentInquiriesAsync(limit);
|
||||
return Ok(new { data = inquiries, limit });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "최근 문의 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 월별 통계
|
||||
/// GET /api/admin-dashboard/monthly-stats?month=2026-06
|
||||
/// </summary>
|
||||
[HttpGet("monthly-stats")]
|
||||
public async Task<IActionResult> GetMonthlyStats([FromQuery] string? month = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await _dashboardService.GetMonthlyStatsAsync(month);
|
||||
return Ok(stats);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "월별 통계 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user