using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; using SPMS.Application.DTOs.Message; using SPMS.Application.Interfaces; using SPMS.Domain.Common; using SPMS.Domain.Exceptions; namespace SPMS.API.Controllers; [ApiController] [Route("v1/in/message")] [ApiExplorerSettings(GroupName = "message")] public class MessageController : ControllerBase { private readonly IMessageValidationService _validationService; private readonly IMessageService _messageService; public MessageController(IMessageValidationService validationService, IMessageService messageService) { _validationService = validationService; _messageService = messageService; } [HttpPost("save")] [SwaggerOperation(Summary = "메시지 저장", Description = "메시지 템플릿을 저장합니다. 메시지 코드가 자동 생성됩니다. 필드명은 snake_case(title, body, image_url, link_url, link_type, data)를 사용합니다. 저장 전 validate API로 사전 검증을 권장합니다.")] public async Task SaveAsync([FromBody] MessageSaveRequestDto request) { var serviceId = GetServiceId(); var adminId = GetAdminId(); var result = await _messageService.SaveAsync(serviceId, adminId, request); return Ok(ApiResponse.Success(result, "메시지 저장 성공")); } [HttpPost("list")] [SwaggerOperation(Summary = "메시지 목록 조회", Description = "메시지 목록을 페이지 단위로 조회합니다. X-Service-Code 헤더가 있으면 해당 서비스만, 없으면 전체 서비스 메시지를 반환합니다. send_status 필터(complete/pending/failed)를 지원합니다.")] public async Task GetListAsync([FromBody] MessageListRequestDto request) { var serviceId = GetServiceIdOrNull(); var result = await _messageService.GetListAsync(serviceId, request); return Ok(ApiResponse.Success(result)); } [HttpPost("info")] [SwaggerOperation(Summary = "메시지 상세 조회", Description = "메시지 코드로 상세 정보를 조회합니다. 템플릿 변수 목록을 포함합니다.")] public async Task GetInfoAsync([FromBody] MessageInfoRequestDto request) { var serviceId = GetServiceId(); var result = await _messageService.GetInfoAsync(serviceId, request); return Ok(ApiResponse.Success(result)); } [HttpPost("delete")] [SwaggerOperation(Summary = "메시지 삭제", Description = "메시지를 소프트 삭제합니다. 30일 후 스케줄러에 의해 완전 삭제됩니다.")] public async Task DeleteAsync([FromBody] MessageDeleteRequestDto request) { var serviceId = GetServiceId(); await _messageService.DeleteAsync(serviceId, request); return Ok(ApiResponse.Success(null, "메시지 삭제 성공")); } [HttpPost("validate")] [SwaggerOperation(Summary = "메시지 유효성 검사", Description = "메시지 내용의 유효성을 검사합니다. save API와 동일한 snake_case 필드명(title, body, image_url, link_url, link_type, data)을 사용합니다. 검증 실패 시 data.errors[]에 field + message 단위로 오류가 반환됩니다.")] public IActionResult ValidateAsync([FromBody] MessageValidateRequestDto request) { var result = _validationService.Validate(request); return Ok(ApiResponse.Success(result)); } [HttpPost("preview")] [SwaggerOperation(Summary = "메시지 미리보기", Description = "메시지 템플릿에 변수를 치환하여 미리보기를 생성합니다.")] public async Task PreviewAsync([FromBody] MessagePreviewRequestDto request) { var serviceId = GetServiceId(); var result = await _messageService.PreviewAsync(serviceId, request); return Ok(ApiResponse.Success(result)); } private long GetServiceId() { if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId) return serviceId; throw new SpmsException(ErrorCodes.BadRequest, "서비스 식별 정보가 없습니다.", 400); } private long? GetServiceIdOrNull() { if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId) return serviceId; return null; } private long GetAdminId() { var adminIdClaim = User.FindFirst("adminId")?.Value; if (string.IsNullOrEmpty(adminIdClaim) || !long.TryParse(adminIdClaim, out var adminId)) throw new SpmsException(ErrorCodes.Unauthorized, "인증 정보가 올바르지 않습니다.", 401); return adminId; } }