Handler.cs 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Common.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Features.Admin.Dashboard.MailHealth;
  6. public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  7. {
  8. public async Task<Response> Handle(Query request, CancellationToken ct)
  9. {
  10. var now = DateTime.UtcNow;
  11. var processingStuckCutoff = now.AddMinutes(-5);
  12. var stalePendingCutoff = now.AddHours(-24);
  13. var recent24hCutoff = now.AddHours(-24);
  14. // 한 번의 쿼리로 모든 집계 산출 (테이블 1회 스캔)
  15. var stats = await db.EmailLog.AsNoTracking().GroupBy(x => 1).Select(g => new
  16. {
  17. PendingCount = g.Count(x => x.Status == MailStatus.Pending),
  18. ProcessingStuckCount = g.Count(x => x.Status == MailStatus.Processing && x.CreatedAt < processingStuckCutoff),
  19. StalePendingCount = g.Count(x => x.Status == MailStatus.Pending && x.CreatedAt < stalePendingCutoff),
  20. Sent24hCount = g.Count(x => x.Status == MailStatus.Sent && x.ProcessedAt != null && x.ProcessedAt >= recent24hCutoff),
  21. Failed24hCount = g.Count(x => x.Status == MailStatus.Failed && x.FailedAt != null && x.FailedAt >= recent24hCutoff),
  22. TotalRowCount = g.Count(),
  23. OldestPendingAt = g.Where(x => x.Status == MailStatus.Pending).Min(x => (DateTime?)x.CreatedAt)
  24. }).FirstOrDefaultAsync(ct);
  25. if (stats is null)
  26. {
  27. return new Response();
  28. }
  29. return new Response
  30. {
  31. PendingCount = stats.PendingCount,
  32. ProcessingStuckCount = stats.ProcessingStuckCount,
  33. StalePendingCount = stats.StalePendingCount,
  34. Sent24hCount = stats.Sent24hCount,
  35. Failed24hCount = stats.Failed24hCount,
  36. TotalRowCount = stats.TotalRowCount,
  37. OldestPendingAt = stats.OldestPendingAt
  38. };
  39. }
  40. }