diff --git a/src/TaxBaik.Web/Controllers/AdminDashboardController.cs b/src/TaxBaik.Web/Controllers/AdminDashboardController.cs deleted file mode 100644 index 5d5d089..0000000 --- a/src/TaxBaik.Web/Controllers/AdminDashboardController.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TaxBaik.Application.Services; - -namespace TaxBaik.Web.Controllers; - -/// -/// 관리자 대시보드 API -/// SOLID: Single Responsibility - 대시보드 데이터만 담당 -/// -[ApiController] -[Route("api/admin-dashboard")] -[Authorize] -public class AdminDashboardController : ControllerBase -{ - private readonly AdminDashboardService _dashboardService; - private readonly TaxFilingService _taxFilingService; - - public AdminDashboardController( - AdminDashboardService dashboardService, - TaxFilingService taxFilingService) - { - _dashboardService = dashboardService; - _taxFilingService = taxFilingService; - } - - /// - /// 대시보드 요약 정보 조회 - /// GET /api/admin-dashboard/summary - /// - [HttpGet("summary")] - public async Task 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 - }); - } - } - - /// - /// 30일 이내 마감 임박 신고 조회 - /// GET /api/admin-dashboard/upcoming-filings?days=30 - /// - [HttpGet("upcoming-filings")] - public async Task 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 - }); - } - } - - /// - /// 최근 문의 조회 - /// GET /api/admin-dashboard/recent-inquiries?limit=10 - /// - [HttpGet("recent-inquiries")] - public async Task 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 - }); - } - } - - /// - /// 월별 통계 - /// GET /api/admin-dashboard/monthly-stats?month=2026-06 - /// - [HttpGet("monthly-stats")] - public async Task 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 - }); - } - } -} diff --git a/src/TaxBaik.Web/Controllers/CommonCodeController.cs b/src/TaxBaik.Web/Controllers/CommonCodeController.cs deleted file mode 100644 index 38ab6c6..0000000 --- a/src/TaxBaik.Web/Controllers/CommonCodeController.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TaxBaik.Application.Services; -using TaxBaik.Domain.Entities; - -namespace TaxBaik.Web.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class CommonCodeController(CommonCodeService commonCodeService) : ControllerBase -{ - [HttpGet] - public async Task GetAllActive() - { - try - { - var codes = await commonCodeService.GetAllActiveAsync(); - return Ok(codes); - } - catch (Exception ex) - { - return StatusCode(500, new { error = "공통코드 조회 실패", message = ex.Message }); - } - } - - [HttpGet("group/{group}")] - public async Task GetByGroup(string group) - { - try - { - var codes = await commonCodeService.GetByGroupAsync(group); - return Ok(codes); - } - catch (Exception ex) - { - return StatusCode(500, new { error = "그룹별 공통코드 조회 실패", message = ex.Message }); - } - } - - [HttpGet("groups")] - public async Task GetGroups() - { - try - { - var groups = await commonCodeService.GetAllGroupsAsync(); - return Ok(groups); - } - catch (Exception ex) - { - return StatusCode(500, new { error = "공통코드 그룹 조회 실패", message = ex.Message }); - } - } - - [HttpGet("{group}/{value}")] - public async Task Get(string group, string value) - { - var code = await commonCodeService.GetAsync(group, value); - return code is null ? NotFound() : Ok(code); - } - - [HttpPost] - public async Task Upsert([FromBody] CommonCode code) - { - if (string.IsNullOrWhiteSpace(code.CodeGroup) || string.IsNullOrWhiteSpace(code.CodeValue) || string.IsNullOrWhiteSpace(code.CodeName)) - return BadRequest(new { error = "코드 그룹, 값, 이름은 필수입니다." }); - if (code.CodeGroup.Any(char.IsWhiteSpace)) - return BadRequest(new { error = "code_group에는 공백을 사용할 수 없습니다." }); - if (code.CodeValue.Contains(' ')) - return BadRequest(new { error = "code_value에는 공백을 사용할 수 없습니다." }); - - await commonCodeService.UpsertAsync(code); - return Ok(code); - } - - [HttpDelete("{group}/{value}")] - public async Task Delete(string group, string value) - { - await commonCodeService.DeleteAsync(group, value); - return NoContent(); - } -} diff --git a/src/TaxBaik.Web/Controllers/CompanyController.cs b/src/TaxBaik.Web/Controllers/CompanyController.cs deleted file mode 100644 index 373c352..0000000 --- a/src/TaxBaik.Web/Controllers/CompanyController.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TaxBaik.Application.Services; - -namespace TaxBaik.Web.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class CompanyController(CompanyService companyService) : ControllerBase -{ - [HttpGet("{id:int}")] - public async Task GetById(int id) - { - try - { - var company = await companyService.GetByIdAsync(id); - if (company == null) - return NotFound(new ProblemDetails { Title = "회사를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound }); - return Ok(company); - } - catch (Exception ex) - { - return StatusCode(500, new ProblemDetails { Title = "회사 조회 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError }); - } - } - - [HttpGet("code/{code}")] - public async Task GetByCode(string code) - { - try - { - var company = await companyService.GetByCodeAsync(code); - if (company == null) - return NotFound(new ProblemDetails { Title = "회사를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound }); - return Ok(company); - } - catch (Exception ex) - { - return StatusCode(500, new ProblemDetails { Title = "회사 조회 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError }); - } - } - - [HttpGet] - public async Task GetPaged([FromQuery] int page = 1, [FromQuery] int pageSize = 20) - { - try - { - var (companies, total) = await companyService.GetPagedAsync(page, pageSize); - return Ok(new { data = companies, total, page, pageSize }); - } - catch (Exception ex) - { - return StatusCode(500, new ProblemDetails { Title = "회사 목록 조회 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError }); - } - } - - [HttpPost] - public async Task Create([FromBody] CreateCompanyRequest request) - { - try - { - var id = await companyService.CreateAsync( - request.CompanyCode, request.CompanyName, request.ContactPerson, - request.Phone, request.Email, request.Memo); - return CreatedAtAction(nameof(GetById), new { id }, new { message = "회사가 등록되었습니다.", id }); - } - catch (ValidationException ex) - { - return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); - } - catch (Exception ex) - { - return StatusCode(500, new ProblemDetails { Title = "회사 등록 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError }); - } - } - - [HttpPut("{id:int}")] - public async Task Update(int id, [FromBody] UpdateCompanyRequest request) - { - try - { - await companyService.UpdateAsync(id, request.CompanyCode, request.CompanyName, - request.ContactPerson, request.Phone, request.Email, request.Memo, request.IsActive); - return Ok(new { message = "회사가 수정되었습니다." }); - } - catch (ValidationException ex) - { - return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); - } - catch (Exception ex) - { - return StatusCode(500, new ProblemDetails { Title = "회사 수정 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError }); - } - } - - [HttpDelete("{id:int}")] - public async Task Delete(int id) - { - try - { - await companyService.DeleteAsync(id); - return Ok(new { message = "회사가 삭제되었습니다." }); - } - catch (ValidationException ex) - { - return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); - } - catch (Exception ex) - { - return StatusCode(500, new ProblemDetails { Title = "회사 삭제 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError }); - } - } - - public record CreateCompanyRequest(string CompanyCode, string CompanyName, string? ContactPerson, string? Phone, string? Email, string? Memo); - public record UpdateCompanyRequest(string CompanyCode, string CompanyName, string? ContactPerson, string? Phone, string? Email, string? Memo, bool IsActive); -} diff --git a/src/TaxBaik.Web/Controllers/SiteSettingsController.cs b/src/TaxBaik.Web/Controllers/SiteSettingsController.cs deleted file mode 100644 index 0b3f5e0..0000000 --- a/src/TaxBaik.Web/Controllers/SiteSettingsController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TaxBaik.Application.Services; - -namespace TaxBaik.Web.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class SiteSettingsController : ControllerBase -{ - private readonly SiteSettingService _siteSettingService; - - public SiteSettingsController(SiteSettingService siteSettingService) - { - _siteSettingService = siteSettingService; - } - - [HttpGet] - public async Task Get() - { - var settings = await _siteSettingService.GetAllAsync(); - return Ok(settings); - } - - [HttpPut] - public async Task Save([FromBody] SaveSiteSettingsRequest request) - { - if (request is null) - return BadRequest(new { message = "요청 본문이 비어 있습니다." }); - - await _siteSettingService.SaveAsync(request.Phone, request.Email, request.KakaoUrl, request.InstagramUrl); - return Ok(new { message = "사이트 설정이 저장되었습니다." }); - } -} - -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; -} diff --git a/src/TaxBaik.Web/Controllers/TaxFilingController.cs b/src/TaxBaik.Web/Controllers/TaxFilingController.cs deleted file mode 100644 index 1727b0c..0000000 --- a/src/TaxBaik.Web/Controllers/TaxFilingController.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using TaxBaik.Application.Services; -using TaxBaik.Domain.Entities; - -namespace TaxBaik.Web.Controllers; - -[ApiController] -[Route("api/[controller]")] -[Authorize] -public class TaxFilingController : ControllerBase -{ - private readonly TaxFilingService _taxFilingService; - - public TaxFilingController(TaxFilingService taxFilingService) - { - _taxFilingService = taxFilingService; - } - - [HttpGet("upcoming")] - public async Task GetUpcoming([FromQuery] int daysAhead = 30) - { - var filings = await _taxFilingService.GetUpcomingAsync(daysAhead); - return Ok(new { data = filings }); - } - - [HttpGet("client/{clientId}")] - public async Task GetByClientId(int clientId) - { - var filings = await _taxFilingService.GetByClientIdAsync(clientId); - return Ok(new { data = filings }); - } - - [HttpGet("{id}")] - public async Task GetById(int id) - { - var filing = await _taxFilingService.GetByIdAsync(id); - if (filing == null) - return NotFound(new ProblemDetails { Title = "신고 일정을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound }); - - return Ok(filing); - } - - [HttpPost] - public async Task Create([FromBody] TaxFiling filing) - { - try - { - var filingId = await _taxFilingService.CreateAsync(filing); - var result = await _taxFilingService.GetByIdAsync(filingId); - return CreatedAtAction(nameof(GetById), new { id = filingId }, result); - } - catch (Exception ex) - { - return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); - } - } - - [HttpPut("{id}")] - public async Task Update(int id, [FromBody] TaxFiling filing) - { - filing.Id = id; - try - { - await _taxFilingService.UpdateAsync(filing); - var result = await _taxFilingService.GetByIdAsync(id); - if (result == null) - return NotFound(new ProblemDetails { Title = "신고 일정을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound }); - - return Ok(result); - } - catch (Exception ex) - { - return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest }); - } - } - - [HttpDelete("{id}")] - public async Task Delete(int id) - { - await _taxFilingService.DeleteAsync(id); - return NoContent(); - } -} diff --git a/src/TaxBaik.Web/Endpoints/AdminDashboard/AdminDashboardDtos.cs b/src/TaxBaik.Web/Endpoints/AdminDashboard/AdminDashboardDtos.cs new file mode 100644 index 0000000..a318d4d --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/AdminDashboard/AdminDashboardDtos.cs @@ -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 RecentInquiries { get; set; } = []; +} + +public class UpcomingFilingsResponse +{ + public List Data { get; set; } = []; + public int Days { get; set; } +} + +public class UpcomingFilingsQuery +{ + public int Days { get; set; } = 30; +} + +public class RecentInquiriesResponse +{ + public List 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; } +} diff --git a/src/TaxBaik.Web/Endpoints/AdminDashboard/GetMonthlyStatsEndpoint.cs b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetMonthlyStatsEndpoint.cs new file mode 100644 index 0000000..d40bf20 --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetMonthlyStatsEndpoint.cs @@ -0,0 +1,44 @@ +using FastEndpoints; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.AdminDashboard; + +public class GetMonthlyStatsEndpoint : Endpoint +{ + 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); + } + } +} diff --git a/src/TaxBaik.Web/Endpoints/AdminDashboard/GetRecentInquiriesEndpoint.cs b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetRecentInquiriesEndpoint.cs new file mode 100644 index 0000000..e939847 --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetRecentInquiriesEndpoint.cs @@ -0,0 +1,40 @@ +using FastEndpoints; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.AdminDashboard; + +public class GetRecentInquiriesEndpoint : Endpoint +{ + 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); + } + } +} diff --git a/src/TaxBaik.Web/Endpoints/AdminDashboard/GetSummaryEndpoint.cs b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetSummaryEndpoint.cs new file mode 100644 index 0000000..6b77e8e --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetSummaryEndpoint.cs @@ -0,0 +1,40 @@ +using FastEndpoints; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.AdminDashboard; + +public class GetSummaryEndpoint : EndpointWithoutRequest +{ + 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); + } + } +} diff --git a/src/TaxBaik.Web/Endpoints/AdminDashboard/GetUpcomingFilingsEndpoint.cs b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetUpcomingFilingsEndpoint.cs new file mode 100644 index 0000000..38f4009 --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/AdminDashboard/GetUpcomingFilingsEndpoint.cs @@ -0,0 +1,38 @@ +using FastEndpoints; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.AdminDashboard; + +public class GetUpcomingFilingsEndpoint : Endpoint +{ + 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().ToList(), + Days = days + }, 200, cancellation: ct); + } + catch (Exception ex) + { + await SendErrorsAsync(500, ct); + } + } +} diff --git a/src/TaxBaik.Web/Controllers/ClientLogsController.cs b/src/TaxBaik.Web/Endpoints/ClientLogs/PostLogEndpoint.cs similarity index 70% rename from src/TaxBaik.Web/Controllers/ClientLogsController.cs rename to src/TaxBaik.Web/Endpoints/ClientLogs/PostLogEndpoint.cs index a13e85c..37abca8 100644 --- a/src/TaxBaik.Web/Controllers/ClientLogsController.cs +++ b/src/TaxBaik.Web/Endpoints/ClientLogs/PostLogEndpoint.cs @@ -1,21 +1,47 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.RateLimiting; +using FastEndpoints; -namespace TaxBaik.Web.Controllers; +namespace TaxBaik.Web.Endpoints.ClientLogs; -[ApiController] -[Route("api/client-logs")] -[AllowAnonymous] -[EnableRateLimiting("client-logs")] -public class ClientLogsController(ILogger logger) : ControllerBase +public class ClientLogEntry { - [HttpPost] - public IActionResult Post([FromBody] ClientLogEntry entry) + 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 +{ + private readonly ILogger _logger; + + public PostLogEndpoint(ILogger 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)) { - return BadRequest(); + 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}"; @@ -42,32 +68,13 @@ public class ClientLogsController(ILogger logger) : Contro // Client warnings (level: warning/info) → Log file only if (entry.Level?.Equals("error", StringComparison.OrdinalIgnoreCase) ?? true) { - logger.LogError(logMessage, args); + _logger.LogError(logMessage, args); } else { - logger.LogWarning(logMessage, args); + _logger.LogWarning(logMessage, args); } - return Ok(); + await SendAsync(new EmptyResponse(), 200, cancellation: ct); } } - -public sealed 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; } -} diff --git a/src/TaxBaik.Web/Endpoints/SiteSettings/GetEndpoint.cs b/src/TaxBaik.Web/Endpoints/SiteSettings/GetEndpoint.cs new file mode 100644 index 0000000..b55a222 --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/SiteSettings/GetEndpoint.cs @@ -0,0 +1,26 @@ +using FastEndpoints; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.SiteSettings; + +public class GetEndpoint : Endpoint> +{ + 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); + } +} diff --git a/src/TaxBaik.Web/Endpoints/SiteSettings/SaveEndpoint.cs b/src/TaxBaik.Web/Endpoints/SiteSettings/SaveEndpoint.cs new file mode 100644 index 0000000..edf0864 --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/SiteSettings/SaveEndpoint.cs @@ -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 +{ + 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); + } +}