Handler.cs 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Donations.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Features.Api.Studio.Dashboard.GetDashboard;
  6. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  7. {
  8. private static readonly Response Empty = new(null, null, new FinancialSummary(0, 0, 0, 0), []);
  9. public async Task<Response> Handle(Query request, CancellationToken ct)
  10. {
  11. // ── 채널 정보 ──
  12. var channel = await db.Channel.AsNoTracking()
  13. .Where(c => c.MemberID == request.MemberID && c.IsActive)
  14. .Select(c => new { c.ID, c.SID, c.Name, c.ThumbnailUrl, c.IsVerified, c.SubscriberCount, c.WidgetToken })
  15. .FirstOrDefaultAsync(ct);
  16. if (channel is null)
  17. {
  18. return Empty;
  19. }
  20. // ── 위젯 URL ──
  21. var widgets = new WidgetUrls(
  22. channel.WidgetToken,
  23. $"/widget/alert/{channel.WidgetToken}",
  24. $"/widget/goal/{channel.WidgetToken}",
  25. $"/widget/rank/{channel.WidgetToken}",
  26. $"/widget/crew/{channel.WidgetToken}",
  27. $"/remote/{channel.WidgetToken}"
  28. );
  29. var channelInfo = new ChannelInfo(
  30. channel.ID, channel.SID, channel.Name,
  31. channel.ThumbnailUrl, channel.IsVerified, channel.SubscriberCount
  32. );
  33. // ── 재무 요약 ──
  34. var wallet = await db.Wallet.AsNoTracking()
  35. .Include(w => w.Balances)
  36. .FirstOrDefaultAsync(w => w.MemberID == request.MemberID, ct);
  37. var availableBalance = 0;
  38. if (wallet is not null)
  39. {
  40. var donationBal = wallet.Balances.FirstOrDefault(b => b.Type == Domain.Entities.Wallets.ValueObject.WalletBalanceType.Donation);
  41. if (donationBal is not null)
  42. {
  43. availableBalance = (int)donationBal.Amount.Value;
  44. }
  45. }
  46. var todayStart = DateTime.UtcNow.Date;
  47. var monthStart = new DateTime(todayStart.Year, todayStart.Month, 1, 0, 0, 0, DateTimeKind.Utc);
  48. var todayDonations = await db.Donation.AsNoTracking()
  49. .Where(d => d.ReceiverMemberID == request.MemberID && !d.IsTest && d.CreatedAt >= todayStart)
  50. .SumAsync(d => d.NetAmount, ct);
  51. var monthDonations = await db.Donation.AsNoTracking()
  52. .Where(d => d.ReceiverMemberID == request.MemberID && !d.IsTest && d.CreatedAt >= monthStart)
  53. .SumAsync(d => d.NetAmount, ct);
  54. var pendingWithdrawal = await db.WithdrawalRequest.AsNoTracking()
  55. .Where(w => w.MemberID == request.MemberID && (w.Status == WithdrawalStatus.Pending || w.Status == WithdrawalStatus.Processing))
  56. .SumAsync(w => w.RequestedAmount, ct);
  57. var financial = new FinancialSummary(availableBalance, todayDonations, monthDonations, pendingWithdrawal);
  58. // ── 최근 후원 5건 ──
  59. var recentDonations = await db.Donation.AsNoTracking()
  60. .Where(d => d.ReceiverMemberID == request.MemberID && !d.IsTest)
  61. .OrderByDescending(d => d.CreatedAt)
  62. .Take(5)
  63. .Select(d => new RecentDonation(d.ID, d.SendName, d.Amount, d.Message, d.CreatedAt))
  64. .ToListAsync(ct);
  65. return new Response(channelInfo, widgets, financial, recentDonations);
  66. }
  67. }