using Application.Abstractions.Messaging; using Application.Abstractions.Data; using Application.Abstractions.Cache; using Application.Abstractions.Crypto; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.Crypto.Ticker.GetAll; public sealed class Handler(IAppDbContext db, ICacheService cache) : IQueryHandler { public async Task Handle(Query request, CancellationToken ct) { var quote = string.IsNullOrWhiteSpace(request.Quote) ? "KRW" : request.Quote.ToUpper(); // Redis에서 Ticker 목록 가져오기 var tickers = await cache.GetAsync>(CacheKeys.CryptoTickers(quote), ct) ?? []; var tickerMap = tickers.ToDictionary(t => t.Market, t => t); // DB에서 CoinMarket + Coin 정보 가져오기 var prefix = $"{quote}-"; var query = db.CoinMarket.AsNoTracking().Include(cm => cm.Coin).Where(cm => cm.Market.StartsWith(prefix) && cm.Coin.IsActive && !cm.Coin.IsDelisted); if (request.FeaturedOnly) { query = query.Where(cm => cm.Coin.IsFeatured); } var coinMarkets = await query.OrderByDescending(cm => cm.Coin.IsFeatured).ThenBy(cm => cm.Coin.DisplayOrder).ThenBy(cm => cm.Coin.Symbol).Select(cm => new { cm.Market, cm.Coin.Symbol, cm.Coin.KorName, cm.Coin.EngName, cm.Coin.LogoImage, cm.Coin.IsFeatured, cm.Coin.DisplayOrder }).ToListAsync(ct); var rows = new List(); foreach (var cm in coinMarkets) { tickerMap.TryGetValue(cm.Market, out var ticker); rows.Add(new Response.Row( cm.Market, cm.Symbol, cm.KorName, cm.EngName, cm.LogoImage, ticker?.OpeningPrice ?? 0m, ticker?.HighPrice ?? 0m, ticker?.LowPrice ?? 0m, ticker?.TradePrice ?? 0m, ticker?.PrevClosingPrice ?? 0m, ticker?.Change ?? "", ticker?.ChangePrice ?? 0m, ticker?.SignedChangePrice ?? 0m, ticker?.ChangeRate ?? 0m, ticker?.SignedChangeRate ?? 0m, ticker?.TradeVolume ?? 0m, ticker?.AccTradeVolume ?? 0m, ticker?.AccTradeVolume24h ?? 0m, ticker?.AccTradePrice ?? 0m, ticker?.AccTradePrice24h ?? 0m, cm.IsFeatured, cm.DisplayOrder )); } return new Response(rows); } }