feat: FAQ 목록 API 구현 (#82)

This commit is contained in:
SEAN 2026-02-10 13:57:17 +09:00
parent 8e52802bbf
commit e24e0c2398
9 changed files with 149 additions and 0 deletions

View File

@ -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<IActionResult> 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<FaqListResponseDto>.Success(result));
}
}

View File

@ -0,0 +1,6 @@
namespace SPMS.Application.DTOs.Faq;
public class FaqListRequestDto
{
public string? Category { get; set; }
}

View File

@ -0,0 +1,30 @@
using System.Text.Json.Serialization;
namespace SPMS.Application.DTOs.Faq;
public class FaqListResponseDto
{
[JsonPropertyName("items")]
public List<FaqItemDto> 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; }
}

View File

@ -14,6 +14,7 @@ public static class DependencyInjection
services.AddScoped<IServiceManagementService, ServiceManagementService>(); services.AddScoped<IServiceManagementService, ServiceManagementService>();
services.AddScoped<INoticeService, NoticeService>(); services.AddScoped<INoticeService, NoticeService>();
services.AddScoped<IBannerService, BannerService>(); services.AddScoped<IBannerService, BannerService>();
services.AddScoped<IFaqService, FaqService>();
return services; return services;
} }

View File

@ -0,0 +1,8 @@
using SPMS.Application.DTOs.Faq;
namespace SPMS.Application.Interfaces;
public interface IFaqService
{
Task<FaqListResponseDto> GetListAsync(string serviceCode, FaqListRequestDto request);
}

View File

@ -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<FaqListResponseDto> 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
};
}
}

View File

@ -0,0 +1,8 @@
using SPMS.Domain.Entities;
namespace SPMS.Domain.Interfaces;
public interface IFaqRepository : IRepository<Faq>
{
Task<IReadOnlyList<Faq>> GetActiveListAsync(long serviceId, string? category = null);
}

View File

@ -29,6 +29,7 @@ public static class DependencyInjection
services.AddScoped<IAdminRepository, AdminRepository>(); services.AddScoped<IAdminRepository, AdminRepository>();
services.AddScoped<INoticeRepository, NoticeRepository>(); services.AddScoped<INoticeRepository, NoticeRepository>();
services.AddScoped<IBannerRepository, BannerRepository>(); services.AddScoped<IBannerRepository, BannerRepository>();
services.AddScoped<IFaqRepository, FaqRepository>();
// External Services // External Services
services.AddScoped<IJwtService, JwtService>(); services.AddScoped<IJwtService, JwtService>();

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore;
using SPMS.Domain.Entities;
using SPMS.Domain.Interfaces;
namespace SPMS.Infrastructure.Persistence.Repositories;
public class FaqRepository : Repository<Faq>, IFaqRepository
{
public FaqRepository(AppDbContext context) : base(context) { }
public async Task<IReadOnlyList<Faq>> 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();
}
}