From e24e0c2398a003c4ed4717a4c8f651da21846634 Mon Sep 17 00:00:00 2001 From: SEAN Date: Tue, 10 Feb 2026 13:57:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20FAQ=20=EB=AA=A9=EB=A1=9D=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SPMS.API/Controllers/FaqController.cs | 32 +++++++++++++++ .../DTOs/Faq/FaqListRequestDto.cs | 6 +++ .../DTOs/Faq/FaqListResponseDto.cs | 30 ++++++++++++++ SPMS.Application/DependencyInjection.cs | 1 + SPMS.Application/Interfaces/IFaqService.cs | 8 ++++ SPMS.Application/Services/FaqService.cs | 41 +++++++++++++++++++ SPMS.Domain/Interfaces/IFaqRepository.cs | 8 ++++ SPMS.Infrastructure/DependencyInjection.cs | 1 + .../Persistence/Repositories/FaqRepository.cs | 22 ++++++++++ 9 files changed, 149 insertions(+) create mode 100644 SPMS.API/Controllers/FaqController.cs create mode 100644 SPMS.Application/DTOs/Faq/FaqListRequestDto.cs create mode 100644 SPMS.Application/DTOs/Faq/FaqListResponseDto.cs create mode 100644 SPMS.Application/Interfaces/IFaqService.cs create mode 100644 SPMS.Application/Services/FaqService.cs create mode 100644 SPMS.Domain/Interfaces/IFaqRepository.cs create mode 100644 SPMS.Infrastructure/Persistence/Repositories/FaqRepository.cs diff --git a/SPMS.API/Controllers/FaqController.cs b/SPMS.API/Controllers/FaqController.cs new file mode 100644 index 0000000..e582b53 --- /dev/null +++ b/SPMS.API/Controllers/FaqController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using SPMS.Application.DTOs.Faq; +using SPMS.Application.Interfaces; +using SPMS.Domain.Common; + +namespace SPMS.API.Controllers; + +[ApiController] +[Route("v1/in/public/faq")] +[ApiExplorerSettings(GroupName = "public")] +public class FaqController : ControllerBase +{ + private readonly IFaqService _faqService; + + public FaqController(IFaqService faqService) + { + _faqService = faqService; + } + + [HttpPost("list")] + [SwaggerOperation(Summary = "FAQ 목록", Description = "활성화된 FAQ 목록을 조회합니다. category로 필터링 가능.")] + public async Task GetListAsync([FromBody] FaqListRequestDto request) + { + var serviceCode = Request.Headers["X-Service-Code"].FirstOrDefault(); + if (string.IsNullOrWhiteSpace(serviceCode)) + return BadRequest(ApiResponse.Fail(ErrorCodes.BadRequest, "X-Service-Code 헤더가 필요합니다.")); + + var result = await _faqService.GetListAsync(serviceCode, request); + return Ok(ApiResponse.Success(result)); + } +} diff --git a/SPMS.Application/DTOs/Faq/FaqListRequestDto.cs b/SPMS.Application/DTOs/Faq/FaqListRequestDto.cs new file mode 100644 index 0000000..2f5eaf8 --- /dev/null +++ b/SPMS.Application/DTOs/Faq/FaqListRequestDto.cs @@ -0,0 +1,6 @@ +namespace SPMS.Application.DTOs.Faq; + +public class FaqListRequestDto +{ + public string? Category { get; set; } +} diff --git a/SPMS.Application/DTOs/Faq/FaqListResponseDto.cs b/SPMS.Application/DTOs/Faq/FaqListResponseDto.cs new file mode 100644 index 0000000..e7dabd9 --- /dev/null +++ b/SPMS.Application/DTOs/Faq/FaqListResponseDto.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; + +namespace SPMS.Application.DTOs.Faq; + +public class FaqListResponseDto +{ + [JsonPropertyName("items")] + public List Items { get; set; } = new(); + + [JsonPropertyName("total_count")] + public int TotalCount { get; set; } +} + +public class FaqItemDto +{ + [JsonPropertyName("faq_id")] + public long FaqId { get; set; } + + [JsonPropertyName("category")] + public string? Category { get; set; } + + [JsonPropertyName("question")] + public string Question { get; set; } = string.Empty; + + [JsonPropertyName("answer")] + public string Answer { get; set; } = string.Empty; + + [JsonPropertyName("sort_order")] + public int SortOrder { get; set; } +} diff --git a/SPMS.Application/DependencyInjection.cs b/SPMS.Application/DependencyInjection.cs index 7b8790c..79e0283 100644 --- a/SPMS.Application/DependencyInjection.cs +++ b/SPMS.Application/DependencyInjection.cs @@ -14,6 +14,7 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/SPMS.Application/Interfaces/IFaqService.cs b/SPMS.Application/Interfaces/IFaqService.cs new file mode 100644 index 0000000..fa263a8 --- /dev/null +++ b/SPMS.Application/Interfaces/IFaqService.cs @@ -0,0 +1,8 @@ +using SPMS.Application.DTOs.Faq; + +namespace SPMS.Application.Interfaces; + +public interface IFaqService +{ + Task GetListAsync(string serviceCode, FaqListRequestDto request); +} diff --git a/SPMS.Application/Services/FaqService.cs b/SPMS.Application/Services/FaqService.cs new file mode 100644 index 0000000..e7435f3 --- /dev/null +++ b/SPMS.Application/Services/FaqService.cs @@ -0,0 +1,41 @@ +using SPMS.Application.DTOs.Faq; +using SPMS.Application.Interfaces; +using SPMS.Domain.Common; +using SPMS.Domain.Exceptions; +using SPMS.Domain.Interfaces; + +namespace SPMS.Application.Services; + +public class FaqService : IFaqService +{ + private readonly IFaqRepository _faqRepository; + private readonly IServiceRepository _serviceRepository; + + public FaqService(IFaqRepository faqRepository, IServiceRepository serviceRepository) + { + _faqRepository = faqRepository; + _serviceRepository = serviceRepository; + } + + public async Task GetListAsync(string serviceCode, FaqListRequestDto request) + { + var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode); + if (service == null) + throw new SpmsException(ErrorCodes.NotFound, "존재하지 않는 서비스입니다.", 404); + + var items = await _faqRepository.GetActiveListAsync(service.Id, request.Category); + + return new FaqListResponseDto + { + Items = items.Select(f => new FaqItemDto + { + FaqId = f.Id, + Category = f.Category, + Question = f.Question, + Answer = f.Answer, + SortOrder = f.SortOrder + }).ToList(), + TotalCount = items.Count + }; + } +} diff --git a/SPMS.Domain/Interfaces/IFaqRepository.cs b/SPMS.Domain/Interfaces/IFaqRepository.cs new file mode 100644 index 0000000..7e7459b --- /dev/null +++ b/SPMS.Domain/Interfaces/IFaqRepository.cs @@ -0,0 +1,8 @@ +using SPMS.Domain.Entities; + +namespace SPMS.Domain.Interfaces; + +public interface IFaqRepository : IRepository +{ + Task> GetActiveListAsync(long serviceId, string? category = null); +} diff --git a/SPMS.Infrastructure/DependencyInjection.cs b/SPMS.Infrastructure/DependencyInjection.cs index 5481114..ce3a7e0 100644 --- a/SPMS.Infrastructure/DependencyInjection.cs +++ b/SPMS.Infrastructure/DependencyInjection.cs @@ -29,6 +29,7 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // External Services services.AddScoped(); diff --git a/SPMS.Infrastructure/Persistence/Repositories/FaqRepository.cs b/SPMS.Infrastructure/Persistence/Repositories/FaqRepository.cs new file mode 100644 index 0000000..f39a776 --- /dev/null +++ b/SPMS.Infrastructure/Persistence/Repositories/FaqRepository.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using SPMS.Domain.Entities; +using SPMS.Domain.Interfaces; + +namespace SPMS.Infrastructure.Persistence.Repositories; + +public class FaqRepository : Repository, IFaqRepository +{ + public FaqRepository(AppDbContext context) : base(context) { } + + public async Task> GetActiveListAsync(long serviceId, string? category = null) + { + var query = _dbSet.Where(f => f.ServiceId == serviceId && f.IsActive); + + if (!string.IsNullOrWhiteSpace(category)) + query = query.Where(f => f.Category == category); + + return await query + .OrderBy(f => f.SortOrder) + .ToListAsync(); + } +} -- 2.45.1