Migrate SiteSettings controller to FastEndpoints
Refactored SiteSettingsController to FastEndpoints pattern: - Created GetEndpoint.cs: GET /api/sitesettings (authorized) - Created SaveEndpoint.cs: PUT /api/sitesettings (authorized) - Removed legacy SiteSettingsController.cs Both endpoints use Bearer token authentication and are auto-discovered by FastEndpoints configuration in Program.cs. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using InquiryEntity = TaxBaik.Domain.Entities.Inquiry;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.AdminDashboard;
|
||||
|
||||
public class AdminDashboardSummaryResponse
|
||||
{
|
||||
public int ThisMonthInquiries { get; set; }
|
||||
public int NewInquiries { get; set; }
|
||||
public int TotalPosts { get; set; }
|
||||
public int PublishedPosts { get; set; }
|
||||
public List<InquiryEntity> RecentInquiries { get; set; } = [];
|
||||
}
|
||||
|
||||
public class UpcomingFilingsResponse
|
||||
{
|
||||
public List<object> Data { get; set; } = [];
|
||||
public int Days { get; set; }
|
||||
}
|
||||
|
||||
public class UpcomingFilingsQuery
|
||||
{
|
||||
public int Days { get; set; } = 30;
|
||||
}
|
||||
|
||||
public class RecentInquiriesResponse
|
||||
{
|
||||
public List<InquiryEntity> Data { get; set; } = [];
|
||||
public int Limit { get; set; }
|
||||
}
|
||||
|
||||
public class RecentInquiriesQuery
|
||||
{
|
||||
public int Limit { get; set; } = 10;
|
||||
}
|
||||
|
||||
public class MonthlyStatsQuery
|
||||
{
|
||||
public string? Month { get; set; }
|
||||
}
|
||||
|
||||
public class MonthlyStatsResponse
|
||||
{
|
||||
public string Month { get; set; } = string.Empty;
|
||||
public int TotalInquiries { get; set; }
|
||||
public int ConsultingCount { get; set; }
|
||||
public int CompletedCount { get; set; }
|
||||
public int NewCount { get; set; }
|
||||
public double CompletionRate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.AdminDashboard;
|
||||
|
||||
public class GetMonthlyStatsEndpoint : Endpoint<MonthlyStatsQuery, MonthlyStatsResponse>
|
||||
{
|
||||
private readonly AdminDashboardService _dashboardService;
|
||||
|
||||
public GetMonthlyStatsEndpoint(AdminDashboardService dashboardService)
|
||||
{
|
||||
_dashboardService = dashboardService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/admin-dashboard/monthly-stats");
|
||||
Policies("Bearer");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(MonthlyStatsQuery request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await _dashboardService.GetMonthlyStatsAsync(request.Month, ct);
|
||||
|
||||
// Convert dynamic result to typed response
|
||||
var statsDict = (dynamic)stats;
|
||||
await SendAsync(new MonthlyStatsResponse
|
||||
{
|
||||
Month = statsDict.month,
|
||||
TotalInquiries = statsDict.totalInquiries,
|
||||
ConsultingCount = statsDict.consultingCount,
|
||||
CompletedCount = statsDict.completedCount,
|
||||
NewCount = statsDict.newCount,
|
||||
CompletionRate = statsDict.completionRate
|
||||
}, 200, cancellation: ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.AdminDashboard;
|
||||
|
||||
public class GetRecentInquiriesEndpoint : Endpoint<RecentInquiriesQuery, RecentInquiriesResponse>
|
||||
{
|
||||
private readonly AdminDashboardService _dashboardService;
|
||||
|
||||
public GetRecentInquiriesEndpoint(AdminDashboardService dashboardService)
|
||||
{
|
||||
_dashboardService = dashboardService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/admin-dashboard/recent-inquiries");
|
||||
Policies("Bearer");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(RecentInquiriesQuery request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var limit = request.Limit <= 0 ? 10 : request.Limit;
|
||||
if (limit > 100) limit = 100; // Security: max 100
|
||||
|
||||
var inquiries = await _dashboardService.GetRecentInquiriesAsync(limit, ct);
|
||||
await SendAsync(new RecentInquiriesResponse
|
||||
{
|
||||
Data = inquiries.ToList(),
|
||||
Limit = limit
|
||||
}, 200, cancellation: ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.AdminDashboard;
|
||||
|
||||
public class GetSummaryEndpoint : EndpointWithoutRequest<AdminDashboardSummaryResponse>
|
||||
{
|
||||
private readonly AdminDashboardService _dashboardService;
|
||||
|
||||
public GetSummaryEndpoint(AdminDashboardService dashboardService)
|
||||
{
|
||||
_dashboardService = dashboardService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/admin-dashboard/summary");
|
||||
Policies("Bearer");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var summary = await _dashboardService.GetSummaryAsync(ct);
|
||||
await SendAsync(new AdminDashboardSummaryResponse
|
||||
{
|
||||
ThisMonthInquiries = summary.ThisMonthInquiries,
|
||||
NewInquiries = summary.NewInquiries,
|
||||
TotalPosts = summary.TotalPosts,
|
||||
PublishedPosts = summary.PublishedPosts,
|
||||
RecentInquiries = summary.RecentInquiries.ToList()
|
||||
}, 200, cancellation: ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.AdminDashboard;
|
||||
|
||||
public class GetUpcomingFilingsEndpoint : Endpoint<UpcomingFilingsQuery, UpcomingFilingsResponse>
|
||||
{
|
||||
private readonly TaxFilingService _taxFilingService;
|
||||
|
||||
public GetUpcomingFilingsEndpoint(TaxFilingService taxFilingService)
|
||||
{
|
||||
_taxFilingService = taxFilingService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/admin-dashboard/upcoming-filings");
|
||||
Policies("Bearer");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(UpcomingFilingsQuery request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var days = request.Days <= 0 ? 30 : request.Days;
|
||||
var filings = await _taxFilingService.GetUpcomingAsync(days);
|
||||
await SendAsync(new UpcomingFilingsResponse
|
||||
{
|
||||
Data = filings.Cast<object>().ToList(),
|
||||
Days = days
|
||||
}, 200, cancellation: ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await SendErrorsAsync(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using FastEndpoints;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.ClientLogs;
|
||||
|
||||
public class ClientLogEntry
|
||||
{
|
||||
public string? Level { get; set; }
|
||||
public string? Source { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public string? Url { get; set; }
|
||||
public string? Route { get; set; }
|
||||
public string? Screen { get; set; }
|
||||
public string? Feature { get; set; }
|
||||
public string? Action { get; set; }
|
||||
public string? Step { get; set; }
|
||||
public string? Entity { get; set; }
|
||||
public string? EntityId { get; set; }
|
||||
public string? DataKey { get; set; }
|
||||
public string? BuildVersion { get; set; }
|
||||
public string? UserAgent { get; set; }
|
||||
public string? Stack { get; set; }
|
||||
}
|
||||
|
||||
public class PostLogEndpoint : Endpoint<ClientLogEntry, EmptyResponse>
|
||||
{
|
||||
private readonly ILogger<PostLogEndpoint> _logger;
|
||||
|
||||
public PostLogEndpoint(ILogger<PostLogEndpoint> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/client-logs");
|
||||
AllowAnonymous();
|
||||
RateLimit(limit: 10, window: 60); // 10 requests per 60 seconds
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(ClientLogEntry entry, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entry.Message))
|
||||
{
|
||||
ThrowError("Message is required");
|
||||
}
|
||||
|
||||
var logMessage = "ClientLog {Level} {Source} {Message} Url={Url} Route={Route} Screen={Screen} Feature={Feature} Action={Action} Step={Step} Entity={Entity} EntityId={EntityId} DataKey={DataKey} BuildVersion={BuildVersion} UserAgent={UserAgent} Stack={Stack}";
|
||||
var args = new object?[]
|
||||
{
|
||||
entry.Level ?? "error",
|
||||
entry.Source ?? "unknown",
|
||||
entry.Message,
|
||||
entry.Url ?? string.Empty,
|
||||
entry.Route ?? string.Empty,
|
||||
entry.Screen ?? string.Empty,
|
||||
entry.Feature ?? string.Empty,
|
||||
entry.Action ?? string.Empty,
|
||||
entry.Step ?? string.Empty,
|
||||
entry.Entity ?? string.Empty,
|
||||
entry.EntityId ?? string.Empty,
|
||||
entry.DataKey ?? string.Empty,
|
||||
entry.BuildVersion ?? string.Empty,
|
||||
entry.UserAgent ?? string.Empty,
|
||||
entry.Stack ?? string.Empty
|
||||
};
|
||||
|
||||
// Client errors (level: error) → Telegram alert
|
||||
// Client warnings (level: warning/info) → Log file only
|
||||
if (entry.Level?.Equals("error", StringComparison.OrdinalIgnoreCase) ?? true)
|
||||
{
|
||||
_logger.LogError(logMessage, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(logMessage, args);
|
||||
}
|
||||
|
||||
await SendAsync(new EmptyResponse(), 200, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.SiteSettings;
|
||||
|
||||
public class GetEndpoint : Endpoint<EmptyRequest, IReadOnlyDictionary<string, string>>
|
||||
{
|
||||
private readonly SiteSettingService _siteSettingService;
|
||||
|
||||
public GetEndpoint(SiteSettingService siteSettingService)
|
||||
{
|
||||
_siteSettingService = siteSettingService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/sitesettings");
|
||||
Policies("Bearer");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(EmptyRequest _, CancellationToken ct)
|
||||
{
|
||||
var settings = await _siteSettingService.GetAllAsync(ct);
|
||||
await SendAsync(settings, 200, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using FastEndpoints;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Endpoints.SiteSettings;
|
||||
|
||||
public class SaveSiteSettingsRequest
|
||||
{
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string KakaoUrl { get; set; } = string.Empty;
|
||||
public string InstagramUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class SaveResponse
|
||||
{
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class SaveEndpoint : Endpoint<SaveSiteSettingsRequest, SaveResponse>
|
||||
{
|
||||
private readonly SiteSettingService _siteSettingService;
|
||||
|
||||
public SaveEndpoint(SiteSettingService siteSettingService)
|
||||
{
|
||||
_siteSettingService = siteSettingService;
|
||||
}
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/api/sitesettings");
|
||||
Policies("Bearer");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(SaveSiteSettingsRequest request, CancellationToken ct)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
ThrowError("요청 본문이 비어 있습니다.");
|
||||
}
|
||||
|
||||
await _siteSettingService.SaveAsync(request.Phone, request.Email, request.KakaoUrl, request.InstagramUrl, ct);
|
||||
|
||||
await SendAsync(new SaveResponse
|
||||
{
|
||||
Message = "사이트 설정이 저장되었습니다."
|
||||
}, 200, cancellation: ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user