improvement: 메시지 상세/프리뷰 응답 강화 (#226) #227
|
|
@ -41,7 +41,7 @@ public class MessageController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("info")]
|
[HttpPost("info")]
|
||||||
[SwaggerOperation(Summary = "메시지 상세 조회", Description = "메시지 코드로 상세 정보를 조회합니다. 템플릿 변수 목록을 포함합니다.")]
|
[SwaggerOperation(Summary = "메시지 상세 조회", Description = "메시지 코드로 상세 정보를 조회합니다. 서비스 정보(service_name, service_code), 작성자(created_by_name), 발송 상태(latest_send_status), 템플릿 변수 목록을 포함합니다.")]
|
||||||
public async Task<IActionResult> GetInfoAsync([FromBody] MessageInfoRequestDto request)
|
public async Task<IActionResult> GetInfoAsync([FromBody] MessageInfoRequestDto request)
|
||||||
{
|
{
|
||||||
var serviceId = GetServiceId();
|
var serviceId = GetServiceId();
|
||||||
|
|
@ -67,7 +67,7 @@ public class MessageController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("preview")]
|
[HttpPost("preview")]
|
||||||
[SwaggerOperation(Summary = "메시지 미리보기", Description = "메시지 템플릿에 변수를 치환하여 미리보기를 생성합니다.")]
|
[SwaggerOperation(Summary = "메시지 미리보기", Description = "메시지 템플릿에 변수를 치환하여 미리보기를 생성합니다. 응답에 link_type을 포함합니다.")]
|
||||||
public async Task<IActionResult> PreviewAsync([FromBody] MessagePreviewRequestDto request)
|
public async Task<IActionResult> PreviewAsync([FromBody] MessagePreviewRequestDto request)
|
||||||
{
|
{
|
||||||
var serviceId = GetServiceId();
|
var serviceId = GetServiceId();
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,18 @@ public class MessageInfoResponseDto
|
||||||
[JsonPropertyName("is_active")]
|
[JsonPropertyName("is_active")]
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("service_name")]
|
||||||
|
public string ServiceName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("service_code")]
|
||||||
|
public string ServiceCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("created_by_name")]
|
||||||
|
public string CreatedByName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("latest_send_status")]
|
||||||
|
public string LatestSendStatus { get; set; } = "pending";
|
||||||
|
|
||||||
[JsonPropertyName("created_at")]
|
[JsonPropertyName("created_at")]
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace SPMS.Application.DTOs.Message;
|
namespace SPMS.Application.DTOs.Message;
|
||||||
|
|
||||||
public class MessagePreviewRequestDto
|
public class MessagePreviewRequestDto
|
||||||
{
|
{
|
||||||
[Required]
|
[JsonPropertyName("message_code")]
|
||||||
public string MessageCode { get; set; } = string.Empty;
|
public string MessageCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("variables")]
|
||||||
public Dictionary<string, string>? Variables { get; set; }
|
public Dictionary<string, string>? Variables { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,21 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace SPMS.Application.DTOs.Message;
|
namespace SPMS.Application.DTOs.Message;
|
||||||
|
|
||||||
public class MessagePreviewResponseDto
|
public class MessagePreviewResponseDto
|
||||||
{
|
{
|
||||||
|
[JsonPropertyName("title")]
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("body")]
|
||||||
public string Body { get; set; } = string.Empty;
|
public string Body { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("image_url")]
|
||||||
public string? ImageUrl { get; set; }
|
public string? ImageUrl { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("link_url")]
|
||||||
public string? LinkUrl { get; set; }
|
public string? LinkUrl { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("link_type")]
|
||||||
|
public string? LinkType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ public class MessageService : IMessageService
|
||||||
if (string.IsNullOrWhiteSpace(request.MessageCode))
|
if (string.IsNullOrWhiteSpace(request.MessageCode))
|
||||||
throw new SpmsException(ErrorCodes.BadRequest, "메시지 코드는 필수입니다.", 400);
|
throw new SpmsException(ErrorCodes.BadRequest, "메시지 코드는 필수입니다.", 400);
|
||||||
|
|
||||||
var message = await _messageRepository.GetByMessageCodeAndServiceAsync(request.MessageCode, serviceId);
|
var message = await _messageRepository.GetByMessageCodeWithDetailsAsync(request.MessageCode, serviceId);
|
||||||
if (message == null)
|
if (message == null)
|
||||||
throw new SpmsException(ErrorCodes.MessageNotFound, "존재하지 않는 메시지 코드입니다.", 404);
|
throw new SpmsException(ErrorCodes.MessageNotFound, "존재하지 않는 메시지 코드입니다.", 404);
|
||||||
|
|
||||||
|
|
@ -119,6 +119,8 @@ public class MessageService : IMessageService
|
||||||
data = JsonSerializer.Deserialize<JsonElement>(message.CustomData);
|
data = JsonSerializer.Deserialize<JsonElement>(message.CustomData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (totalSend, successCount) = await _messageRepository.GetSendStatsAsync(message.Id);
|
||||||
|
|
||||||
return new MessageInfoResponseDto
|
return new MessageInfoResponseDto
|
||||||
{
|
{
|
||||||
MessageCode = message.MessageCode,
|
MessageCode = message.MessageCode,
|
||||||
|
|
@ -130,6 +132,10 @@ public class MessageService : IMessageService
|
||||||
Data = data,
|
Data = data,
|
||||||
Variables = variables,
|
Variables = variables,
|
||||||
IsActive = !message.IsDeleted,
|
IsActive = !message.IsDeleted,
|
||||||
|
ServiceName = message.Service.ServiceName,
|
||||||
|
ServiceCode = message.Service.ServiceCode,
|
||||||
|
CreatedByName = message.CreatedByAdmin.Name,
|
||||||
|
LatestSendStatus = DetermineSendStatus(totalSend, successCount),
|
||||||
CreatedAt = message.CreatedAt
|
CreatedAt = message.CreatedAt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +169,8 @@ public class MessageService : IMessageService
|
||||||
Title = title,
|
Title = title,
|
||||||
Body = body,
|
Body = body,
|
||||||
ImageUrl = message.ImageUrl,
|
ImageUrl = message.ImageUrl,
|
||||||
LinkUrl = message.LinkUrl
|
LinkUrl = message.LinkUrl,
|
||||||
|
LinkType = message.LinkType
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ public interface IMessageRepository : IRepository<Message>
|
||||||
Task<(IReadOnlyList<MessageListProjection> Items, int TotalCount)> GetPagedForListAsync(
|
Task<(IReadOnlyList<MessageListProjection> Items, int TotalCount)> GetPagedForListAsync(
|
||||||
long? serviceId, int page, int size,
|
long? serviceId, int page, int size,
|
||||||
string? keyword = null, bool? isActive = null, string? sendStatus = null);
|
string? keyword = null, bool? isActive = null, string? sendStatus = null);
|
||||||
|
Task<Message?> GetByMessageCodeWithDetailsAsync(string messageCode, long serviceId);
|
||||||
|
Task<(int TotalSendCount, int SuccessCount)> GetSendStatsAsync(long messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MessageListProjection
|
public class MessageListProjection
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,29 @@ public class MessageRepository : Repository<Message>, IMessageRepository
|
||||||
return (items, totalCount);
|
return (items, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Message?> GetByMessageCodeWithDetailsAsync(string messageCode, long serviceId)
|
||||||
|
{
|
||||||
|
return await _dbSet
|
||||||
|
.Include(m => m.Service)
|
||||||
|
.Include(m => m.CreatedByAdmin)
|
||||||
|
.FirstOrDefaultAsync(m => m.MessageCode == messageCode && m.ServiceId == serviceId && !m.IsDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(int TotalSendCount, int SuccessCount)> GetSendStatsAsync(long messageId)
|
||||||
|
{
|
||||||
|
var stats = await _context.PushSendLogs
|
||||||
|
.Where(l => l.MessageId == messageId)
|
||||||
|
.GroupBy(_ => 1)
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
Total = g.Count(),
|
||||||
|
Success = g.Count(l => l.Status == PushResult.Success)
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return stats != null ? (stats.Total, stats.Success) : (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<(IReadOnlyList<MessageListProjection> Items, int TotalCount)> GetPagedForListAsync(
|
public async Task<(IReadOnlyList<MessageListProjection> Items, int TotalCount)> GetPagedForListAsync(
|
||||||
long? serviceId, int page, int size,
|
long? serviceId, int page, int size,
|
||||||
string? keyword = null, bool? isActive = null, string? sendStatus = null)
|
string? keyword = null, bool? isActive = null, string? sendStatus = null)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user