using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Domain.Entities.Donations.ValueObject; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.Studio.Dashboard.GetDashboard; internal sealed class Handler(IAppDbContext db) : IQueryHandler { private static readonly Response Empty = new(null, null, new FinancialSummary(0, 0, 0, 0), []); public async Task Handle(Query request, CancellationToken ct) { // ── 채널 정보 ── var channel = await db.Channel.AsNoTracking() .Where(c => c.MemberID == request.MemberID && c.IsActive) .Select(c => new { c.ID, c.SID, c.Name, c.ThumbnailUrl, c.IsVerified, c.SubscriberCount, c.WidgetToken }) .FirstOrDefaultAsync(ct); if (channel is null) { return Empty; } // ── 위젯 URL ── var widgets = new WidgetUrls( channel.WidgetToken, $"/widget/alert/{channel.WidgetToken}", $"/widget/goal/{channel.WidgetToken}", $"/widget/rank/{channel.WidgetToken}", $"/widget/crew/{channel.WidgetToken}", $"/remote/{channel.WidgetToken}" ); var channelInfo = new ChannelInfo( channel.ID, channel.SID, channel.Name, channel.ThumbnailUrl, channel.IsVerified, channel.SubscriberCount ); // ── 재무 요약 ── var wallet = await db.Wallet.AsNoTracking() .Include(w => w.Balances) .FirstOrDefaultAsync(w => w.MemberID == request.MemberID, ct); var availableBalance = 0; if (wallet is not null) { var donationBal = wallet.Balances.FirstOrDefault(b => b.Type == Domain.Entities.Wallets.ValueObject.WalletBalanceType.Donation); if (donationBal is not null) { availableBalance = (int)donationBal.Amount.Value; } } var todayStart = DateTime.UtcNow.Date; var monthStart = new DateTime(todayStart.Year, todayStart.Month, 1, 0, 0, 0, DateTimeKind.Utc); var todayDonations = await db.Donation.AsNoTracking() .Where(d => d.ReceiverMemberID == request.MemberID && !d.IsTest && d.CreatedAt >= todayStart) .SumAsync(d => d.NetAmount, ct); var monthDonations = await db.Donation.AsNoTracking() .Where(d => d.ReceiverMemberID == request.MemberID && !d.IsTest && d.CreatedAt >= monthStart) .SumAsync(d => d.NetAmount, ct); var pendingWithdrawal = await db.WithdrawalRequest.AsNoTracking() .Where(w => w.MemberID == request.MemberID && (w.Status == WithdrawalStatus.Pending || w.Status == WithdrawalStatus.Processing)) .SumAsync(w => w.RequestedAmount, ct); var financial = new FinancialSummary(availableBalance, todayDonations, monthDonations, pendingWithdrawal); // ── 최근 후원 5건 ── var recentDonations = await db.Donation.AsNoTracking() .Where(d => d.ReceiverMemberID == request.MemberID && !d.IsTest) .OrderByDescending(d => d.CreatedAt) .Take(5) .Select(d => new RecentDonation(d.ID, d.SendName, d.Amount, d.Message, d.CreatedAt)) .ToListAsync(ct); return new Response(channelInfo, widgets, financial, recentDonations); } }