Handler.cs 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Microsoft.EntityFrameworkCore;
  4. using SharedKernel.Results;
  5. namespace Application.Features.Api.Member.GetLevel;
  6. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Result<Response>>
  7. {
  8. public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
  9. {
  10. var member = await db.Member.AsNoTracking()
  11. .Where(c => c.ID == request.MemberID)
  12. .Select(c => new { c.ID, c.MemberGradeID })
  13. .FirstOrDefaultAsync(ct);
  14. if (member is null)
  15. {
  16. return Result.Failure<Response>(Error.NotFound("Member.NotFound", "회원 정보를 찾을 수 없습니다."));
  17. }
  18. var exp = await db.MemberStats.AsNoTracking()
  19. .Where(c => c.MemberID == request.MemberID)
  20. .Select(c => (long?)c.Exp)
  21. .FirstOrDefaultAsync(ct) ?? 0;
  22. // 활성 등급을 RequiredExp 오름차순으로 로드 (소량)
  23. var grades = await db.MemberGrade.AsNoTracking()
  24. .Where(c => c.IsActive)
  25. .OrderBy(c => c.RequiredExp)
  26. .Select(c => new { c.ID, c.KorName, c.RequiredExp, c.Image, c.TextColor })
  27. .ToListAsync(ct);
  28. // 현재 등급 = 회원 등급 스냅샷 (없으면 충족하는 최고 등급으로 추정)
  29. var current = member.MemberGradeID is int gid
  30. ? grades.FirstOrDefault(c => c.ID == gid)
  31. : grades.LastOrDefault(c => c.RequiredExp <= exp);
  32. // 다음 등급 = 현재 RequiredExp 초과 첫 등급
  33. var currentRequired = current?.RequiredExp ?? -1;
  34. var next = grades.FirstOrDefault(c => c.RequiredExp > currentRequired);
  35. long? expToNext = next is not null ? Math.Max(0, next.RequiredExp - exp) : null;
  36. return Result.Success(new Response(
  37. exp,
  38. current?.ID,
  39. current?.KorName,
  40. current?.Image,
  41. current?.TextColor,
  42. next?.ID,
  43. next?.KorName,
  44. next?.RequiredExp,
  45. expToNext
  46. ));
  47. }
  48. }