Handler.cs 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace Application.Features.Api.Attendance.GetList;
  5. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  6. {
  7. public async Task<Response> Handle(Query request, CancellationToken ct)
  8. {
  9. // 로컬(서버 KST) 기준 날짜를 UTC 범위로 환산
  10. var targetDateLocal = DateTime.SpecifyKind(request.Date?.Date ?? DateTime.Now.Date, DateTimeKind.Local);
  11. var startUtc = targetDateLocal.ToUniversalTime();
  12. var endUtc = startUtc.AddDays(1);
  13. var query = db.Attendance.AsNoTracking()
  14. .Where(x => x.CreatedAt >= startUtc && x.CreatedAt < endUtc);
  15. var total = await query.CountAsync(ct);
  16. // 시간순 Rank 사전 계산 (정렬 옵션과 무관하게 항상 시간 기준)
  17. var rankList = await query
  18. .OrderBy(x => x.CreatedAt)
  19. .Select(x => new { x.MemberID, x.CreatedAt })
  20. .ToListAsync(ct);
  21. var rankMap = new Dictionary<int, int>();
  22. for (var i = 0; i < rankList.Count; i++)
  23. {
  24. rankMap[rankList[i].MemberID] = i + 1;
  25. }
  26. // 정렬 분기
  27. var sortBy = request.SortBy ?? "time";
  28. IQueryable<Domain.Entities.Members.Attendance> sortedQuery = sortBy switch
  29. {
  30. "count" => from a in query
  31. join stats in db.MemberStats.AsNoTracking() on a.MemberID equals stats.MemberID
  32. orderby stats.AttendanceCount descending, a.CreatedAt
  33. select a,
  34. "streak" => query.OrderByDescending(x => x.ConsecutiveDays).ThenBy(x => x.CreatedAt),
  35. _ => query.OrderBy(x => x.CreatedAt)
  36. };
  37. var list = await sortedQuery
  38. .Skip((request.Page - 1) * request.PerPage)
  39. .Take(request.PerPage)
  40. .Select(x => new AttendanceItem(
  41. 0,
  42. x.MemberID,
  43. x.Member.SID,
  44. x.Member.Name ?? "",
  45. x.Member.Thumb,
  46. x.Greeting,
  47. x.ConsecutiveDays,
  48. x.ExpRewarded,
  49. x.PointRewarded,
  50. x.CreatedAt
  51. ))
  52. .ToListAsync(ct);
  53. // Rank 주입 + UTC → 로컬 변환
  54. var ranked = list.Select(item => item with {
  55. Rank = rankMap.GetValueOrDefault(item.MemberID),
  56. CreatedAt = DateTime.SpecifyKind(item.CreatedAt, DateTimeKind.Utc).ToLocalTime()
  57. }).ToList();
  58. return new Response(total, ranked);
  59. }
  60. }