5053245575
**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>
123 lines
3.6 KiB
C#
123 lines
3.6 KiB
C#
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
|
|
});
|
|
}
|
|
}
|
|
}
|