SPMS_API/SPMS.API/Controllers/PushController.cs
SEAN 3a973f56ce feat: 상세 로그 다운로드 API 구현 (#140)
- POST /v1/in/push/log/export (EXP-02, API_SPMS_07_PUSH_09)
- 발송 로그 CSV 파일 다운로드 (페이징 없이 전체 반환)
- 최대 조회 기간 30일, 최대 100,000건 제한
- message_code, device_id, status 필터 지원

Closes #140
2026-02-11 09:43:47 +09:00

119 lines
5.3 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using SPMS.Application.DTOs.Push;
using SPMS.Application.Interfaces;
using SPMS.Domain.Common;
namespace SPMS.API.Controllers;
[ApiController]
[Route("v1/in/push")]
[ApiExplorerSettings(GroupName = "push")]
public class PushController : ControllerBase
{
private readonly IPushService _pushService;
public PushController(IPushService pushService)
{
_pushService = pushService;
}
[HttpPost("send")]
[SwaggerOperation(Summary = "단건 발송", Description = "특정 디바이스에 푸시 메시지를 즉시 발송합니다.")]
public async Task<IActionResult> SendAsync([FromBody] PushSendRequestDto request)
{
var serviceId = GetServiceId();
var result = await _pushService.SendAsync(serviceId, request);
return Ok(ApiResponse<PushSendResponseDto>.Success(result));
}
[HttpPost("send/tag")]
[SwaggerOperation(Summary = "태그 발송", Description = "태그 조건에 해당하는 디바이스에 푸시 메시지를 발송합니다.")]
public async Task<IActionResult> SendByTagAsync([FromBody] PushSendTagRequestDto request)
{
var serviceId = GetServiceId();
var result = await _pushService.SendByTagAsync(serviceId, request);
return Ok(ApiResponse<PushSendResponseDto>.Success(result));
}
[HttpPost("schedule")]
[SwaggerOperation(Summary = "예약 발송", Description = "지정 시간에 푸시 메시지 발송을 예약합니다.")]
public async Task<IActionResult> ScheduleAsync([FromBody] PushScheduleRequestDto request)
{
var serviceId = GetServiceId();
var result = await _pushService.ScheduleAsync(serviceId, request);
return Ok(ApiResponse<PushScheduleResponseDto>.Success(result));
}
[HttpPost("schedule/cancel")]
[SwaggerOperation(Summary = "예약 취소", Description = "예약된 발송을 취소합니다.")]
public async Task<IActionResult> CancelScheduleAsync([FromBody] PushScheduleCancelRequestDto request)
{
var serviceId = GetServiceId();
await _pushService.CancelScheduleAsync(request);
return Ok(ApiResponse.Success());
}
[HttpPost("log")]
[SwaggerOperation(Summary = "발송 로그 조회", Description = "푸시 발송 이력을 페이지 단위로 조회합니다.")]
public async Task<IActionResult> GetLogAsync([FromBody] PushLogRequestDto request)
{
var serviceId = GetServiceId();
var result = await _pushService.GetLogAsync(serviceId, request);
return Ok(ApiResponse<PushLogResponseDto>.Success(result));
}
[HttpPost("log/export")]
[SwaggerOperation(Summary = "발송 로그 내보내기", Description = "발송 로그를 CSV 파일로 다운로드합니다. 최대 30일, 100,000건.")]
public async Task<IActionResult> ExportLogAsync([FromBody] PushLogExportRequestDto request)
{
var serviceId = GetServiceId();
var fileBytes = await _pushService.ExportLogAsync(serviceId, request);
var fileName = $"push_log_{request.StartDate}_{request.EndDate}.csv";
return File(fileBytes, "text/csv", fileName);
}
[HttpPost("send/bulk")]
[SwaggerOperation(Summary = "대용량 발송", Description = "CSV 파일로 대량 푸시 발송을 요청합니다.")]
public async Task<IActionResult> SendBulkAsync(IFormFile file, [FromForm(Name = "message_code")] string messageCode)
{
var serviceId = GetServiceId();
if (file == null || file.Length == 0)
throw new Domain.Exceptions.SpmsException(ErrorCodes.BadRequest, "CSV 파일은 필수입니다.", 400);
if (string.IsNullOrWhiteSpace(messageCode))
throw new Domain.Exceptions.SpmsException(ErrorCodes.BadRequest, "message_code는 필수입니다.", 400);
await using var stream = file.OpenReadStream();
var result = await _pushService.SendBulkAsync(serviceId, stream, messageCode);
return Ok(ApiResponse<BulkSendResponseDto>.Success(result, "발송 요청이 접수되었습니다."));
}
[HttpPost("job/status")]
[SwaggerOperation(Summary = "발송 상태 조회", Description = "대용량/태그 발송 작업의 상태를 조회합니다.")]
public async Task<IActionResult> GetJobStatusAsync([FromBody] JobStatusRequestDto request)
{
var serviceId = GetServiceId();
var result = await _pushService.GetJobStatusAsync(serviceId, request);
return Ok(ApiResponse<JobStatusResponseDto>.Success(result, "조회 성공"));
}
[HttpPost("job/cancel")]
[SwaggerOperation(Summary = "발송 취소", Description = "대기 중이거나 처리 중인 작업을 취소합니다.")]
public async Task<IActionResult> CancelJobAsync([FromBody] JobCancelRequestDto request)
{
var serviceId = GetServiceId();
var result = await _pushService.CancelJobAsync(serviceId, request);
return Ok(ApiResponse<JobCancelResponseDto>.Success(result, "발송이 취소되었습니다."));
}
private long GetServiceId()
{
if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId)
return serviceId;
throw new Domain.Exceptions.SpmsException(ErrorCodes.BadRequest, "서비스 식별 정보가 없습니다.", 400);
}
}