Handler.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Store.ValueObject;
  4. using Microsoft.EntityFrameworkCore;
  5. using SharedKernel.Results;
  6. namespace Application.Features.Api.Store.Games.Detail;
  7. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
  8. {
  9. public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
  10. {
  11. var now = DateTime.UtcNow;
  12. var game = await db.Game.AsNoTracking()
  13. .Where(g => g.ID == request.ID && g.IsActive)
  14. .Select(g => new
  15. {
  16. g.ID,
  17. g.KorName,
  18. g.EngName,
  19. g.Publisher,
  20. g.Thumbnail,
  21. g.BigImage,
  22. g.Description,
  23. g.Link,
  24. g.TrailerUrl,
  25. g.MinSpec,
  26. g.RecommendedSpec,
  27. g.CreatedAt,
  28. g.ReleaseDate
  29. })
  30. .FirstOrDefaultAsync(ct);
  31. if (game is null)
  32. {
  33. return Result.Failure<Response>(Error.NotFound("Game.NotFound", "게임을 찾을 수 없습니다."));
  34. }
  35. var genres = await db.GameGenreMap.AsNoTracking()
  36. .Where(m => m.GameID == game.ID && m.GameGenre.IsActive)
  37. .OrderBy(m => m.GameGenre.Order)
  38. .Select(m => m.GameGenre.KorName)
  39. .ToListAsync(ct);
  40. var categories = await db.GameCategoryMap.AsNoTracking()
  41. .Where(m => m.GameID == game.ID && m.GameCategory.IsActive)
  42. .OrderBy(m => m.GameCategory.Order)
  43. .Select(m => m.GameCategory.KorName)
  44. .ToListAsync(ct);
  45. var images = await db.GameImage.AsNoTracking()
  46. .Where(i => i.GameID == game.ID && !i.IsDisabled)
  47. .OrderBy(i => i.Order).ThenBy(i => i.ID)
  48. .Select(i => i.Url)
  49. .ToListAsync(ct);
  50. var languages = await db.GameLanguageSupport.AsNoTracking()
  51. .Where(s => s.GameID == game.ID && s.GameLanguage.IsActive)
  52. .OrderBy(s => s.GameLanguage.Order).ThenBy(s => s.GameLanguage.KorName)
  53. .Select(s => new Response.LanguageRow(s.GameLanguage.KorName, s.GameLanguage.EngName, s.Interface, s.FullAudio, s.Subtitles))
  54. .ToListAsync(ct);
  55. // 같은 게임 활성 상품 중 무작위 8개 (NEWID 셔플)
  56. var productsRaw = await db.Product.AsNoTracking()
  57. .Where(p => p.GameID == game.ID
  58. && p.IsActive
  59. && (p.SaleStartAt == null || p.SaleStartAt <= now)
  60. && (p.SaleEndAt == null || p.SaleEndAt > now))
  61. .OrderBy(p => Guid.NewGuid())
  62. .Take(8)
  63. .Select(p => new
  64. {
  65. p.ID,
  66. p.Name,
  67. p.Thumbnail,
  68. p.Price,
  69. p.DiscountType,
  70. p.DiscountValue
  71. })
  72. .ToListAsync(ct);
  73. var products = productsRaw.Select(p => new Response.ProductRow(
  74. p.ID,
  75. p.Name,
  76. p.Thumbnail,
  77. p.Price,
  78. CalcSalePrice(p.Price, p.DiscountType, p.DiscountValue)
  79. )).ToList();
  80. return Result.Success(new Response(
  81. game.ID,
  82. game.KorName,
  83. game.EngName,
  84. game.Publisher,
  85. game.Thumbnail,
  86. game.BigImage,
  87. game.Description,
  88. game.Link,
  89. game.TrailerUrl,
  90. game.MinSpec,
  91. game.RecommendedSpec,
  92. game.CreatedAt,
  93. game.ReleaseDate,
  94. genres,
  95. categories,
  96. images,
  97. languages,
  98. products
  99. ));
  100. }
  101. private static int CalcSalePrice(int price, DiscountType type, int value)
  102. {
  103. return type switch
  104. {
  105. DiscountType.Percent => Math.Max(0, price - (price * value / 100)),
  106. DiscountType.Amount => Math.Max(0, price - value),
  107. _ => price
  108. };
  109. }
  110. }