feat: 발송 상세 로그 조회 API 구현 (#136)
All checks were successful
SPMS_API/pipeline/head This commit looks good
All checks were successful
SPMS_API/pipeline/head This commit looks good
Reviewed-on: https://git.ipstein.myds.me/SPMS/SPMS_API/pulls/137
This commit is contained in:
commit
ce266956c7
|
|
@ -63,6 +63,15 @@ public class StatsController : ControllerBase
|
||||||
return Ok(ApiResponse<DeviceStatResponseDto>.Success(result, "조회 성공"));
|
return Ok(ApiResponse<DeviceStatResponseDto>.Success(result, "조회 성공"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("send-log")]
|
||||||
|
[SwaggerOperation(Summary = "발송 상세 로그 조회", Description = "특정 메시지의 개별 디바이스별 발송 상세 로그를 조회합니다.")]
|
||||||
|
public async Task<IActionResult> GetSendLogDetailAsync([FromBody] SendLogDetailRequestDto request)
|
||||||
|
{
|
||||||
|
var serviceId = GetServiceId();
|
||||||
|
var result = await _statsService.GetSendLogDetailAsync(serviceId, request);
|
||||||
|
return Ok(ApiResponse<SendLogDetailResponseDto>.Success(result, "조회 성공"));
|
||||||
|
}
|
||||||
|
|
||||||
private long GetServiceId()
|
private long GetServiceId()
|
||||||
{
|
{
|
||||||
if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId)
|
if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId)
|
||||||
|
|
|
||||||
23
SPMS.Application/DTOs/Stats/SendLogDetailRequestDto.cs
Normal file
23
SPMS.Application/DTOs/Stats/SendLogDetailRequestDto.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Stats;
|
||||||
|
|
||||||
|
public class SendLogDetailRequestDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "message_code는 필수입니다.")]
|
||||||
|
[JsonPropertyName("message_code")]
|
||||||
|
public string MessageCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("status")]
|
||||||
|
public string? Status { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("platform")]
|
||||||
|
public string? Platform { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("page")]
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
|
||||||
|
[JsonPropertyName("size")]
|
||||||
|
public int Size { get; set; } = 20;
|
||||||
|
}
|
||||||
34
SPMS.Application/DTOs/Stats/SendLogDetailResponseDto.cs
Normal file
34
SPMS.Application/DTOs/Stats/SendLogDetailResponseDto.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using SPMS.Application.DTOs.Notice;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Stats;
|
||||||
|
|
||||||
|
public class SendLogDetailResponseDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public List<SendLogDetailItemDto> Items { get; set; } = [];
|
||||||
|
|
||||||
|
[JsonPropertyName("pagination")]
|
||||||
|
public PaginationDto Pagination { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SendLogDetailItemDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("device_id")]
|
||||||
|
public long DeviceId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("device_token")]
|
||||||
|
public string DeviceToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("platform")]
|
||||||
|
public string Platform { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("status")]
|
||||||
|
public string Status { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("fail_reason")]
|
||||||
|
public string? FailReason { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("sent_at")]
|
||||||
|
public DateTime SentAt { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -9,4 +9,5 @@ public interface IStatsService
|
||||||
Task<MessageStatResponseDto> GetMessageStatAsync(long serviceId, MessageStatRequestDto request);
|
Task<MessageStatResponseDto> GetMessageStatAsync(long serviceId, MessageStatRequestDto request);
|
||||||
Task<HourlyStatResponseDto> GetHourlyAsync(long serviceId, HourlyStatRequestDto request);
|
Task<HourlyStatResponseDto> GetHourlyAsync(long serviceId, HourlyStatRequestDto request);
|
||||||
Task<DeviceStatResponseDto> GetDeviceStatAsync(long serviceId);
|
Task<DeviceStatResponseDto> GetDeviceStatAsync(long serviceId);
|
||||||
|
Task<SendLogDetailResponseDto> GetSendLogDetailAsync(long serviceId, SendLogDetailRequestDto request);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,61 @@ public class StatsService : IStatsService
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<SendLogDetailResponseDto> GetSendLogDetailAsync(long serviceId, SendLogDetailRequestDto request)
|
||||||
|
{
|
||||||
|
var message = await _messageRepository.GetByMessageCodeAndServiceAsync(request.MessageCode, serviceId);
|
||||||
|
if (message == null)
|
||||||
|
throw new SpmsException(ErrorCodes.MessageNotFound, "존재하지 않는 메시지 코드입니다.", 404);
|
||||||
|
|
||||||
|
PushResult? status = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Status))
|
||||||
|
{
|
||||||
|
status = request.Status.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"success" => PushResult.Success,
|
||||||
|
"failed" => PushResult.Failed,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform? platform = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Platform))
|
||||||
|
{
|
||||||
|
platform = request.Platform.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"ios" => Domain.Enums.Platform.iOS,
|
||||||
|
"android" => Domain.Enums.Platform.Android,
|
||||||
|
"web" => Domain.Enums.Platform.Web,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var (items, totalCount) = await _pushSendLogRepository.GetDetailLogPagedAsync(
|
||||||
|
serviceId, message.Id, request.Page, request.Size, status, platform);
|
||||||
|
|
||||||
|
var totalPages = (int)Math.Ceiling((double)totalCount / request.Size);
|
||||||
|
|
||||||
|
return new SendLogDetailResponseDto
|
||||||
|
{
|
||||||
|
Items = items.Select(l => new SendLogDetailItemDto
|
||||||
|
{
|
||||||
|
DeviceId = l.DeviceId,
|
||||||
|
DeviceToken = l.Device?.DeviceToken ?? string.Empty,
|
||||||
|
Platform = l.Device?.Platform.ToString().ToLowerInvariant() ?? string.Empty,
|
||||||
|
Status = l.Status.ToString().ToLowerInvariant(),
|
||||||
|
FailReason = l.FailReason,
|
||||||
|
SentAt = l.SentAt
|
||||||
|
}).ToList(),
|
||||||
|
Pagination = new DTOs.Notice.PaginationDto
|
||||||
|
{
|
||||||
|
Page = request.Page,
|
||||||
|
Size = request.Size,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
TotalPages = totalPages
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static double CalcCtr(int openCount, int successCount)
|
private static double CalcCtr(int openCount, int successCount)
|
||||||
{
|
{
|
||||||
return successCount > 0 ? Math.Round((double)openCount / successCount * 100, 2) : 0;
|
return successCount > 0 ? Math.Round((double)openCount / successCount * 100, 2) : 0;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ public interface IPushSendLogRepository : IRepository<PushSendLog>
|
||||||
Task<List<HourlyStatRaw>> GetHourlyStatsAsync(long serviceId, DateTime startDate, DateTime endDate);
|
Task<List<HourlyStatRaw>> GetHourlyStatsAsync(long serviceId, DateTime startDate, DateTime endDate);
|
||||||
Task<MessageStatRaw?> GetMessageStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate);
|
Task<MessageStatRaw?> GetMessageStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate);
|
||||||
Task<List<MessageDailyStatRaw>> GetMessageDailyStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate);
|
Task<List<MessageDailyStatRaw>> GetMessageDailyStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate);
|
||||||
|
Task<(IReadOnlyList<PushSendLog> Items, int TotalCount)> GetDetailLogPagedAsync(
|
||||||
|
long serviceId, long messageId, int page, int size,
|
||||||
|
PushResult? status = null, Platform? platform = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HourlyStatRaw
|
public class HourlyStatRaw
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,31 @@ public class PushSendLogRepository : Repository<PushSendLog>, IPushSendLogReposi
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<PushSendLog> Items, int TotalCount)> GetDetailLogPagedAsync(
|
||||||
|
long serviceId, long messageId, int page, int size,
|
||||||
|
PushResult? status = null, Platform? platform = null)
|
||||||
|
{
|
||||||
|
var query = _dbSet
|
||||||
|
.Include(l => l.Device)
|
||||||
|
.Where(l => l.ServiceId == serviceId && l.MessageId == messageId);
|
||||||
|
|
||||||
|
if (status.HasValue)
|
||||||
|
query = query.Where(l => l.Status == status.Value);
|
||||||
|
|
||||||
|
if (platform.HasValue)
|
||||||
|
query = query.Where(l => l.Device.Platform == platform.Value);
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
|
||||||
|
var items = await query
|
||||||
|
.OrderByDescending(l => l.SentAt)
|
||||||
|
.Skip((page - 1) * size)
|
||||||
|
.Take(size)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return (items, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<MessageDailyStatRaw>> GetMessageDailyStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate)
|
public async Task<List<MessageDailyStatRaw>> GetMessageDailyStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate)
|
||||||
{
|
{
|
||||||
var query = _dbSet.Where(l => l.ServiceId == serviceId && l.MessageId == messageId);
|
var query = _dbSet.Where(l => l.ServiceId == serviceId && l.MessageId == messageId);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user