Handler.cs 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Microsoft.EntityFrameworkCore;
  4. using SharedKernel.Results;
  5. namespace Application.Features.Api.Member.Items.Equip;
  6. /// <summary>
  7. /// 지위 아이템 장착. 슬롯당 1개만 장착 가능하므로, 같은 슬롯에 이미 장착된 아이템은 자동 해제한 뒤 장착한다.
  8. /// (필터 유니크 인덱스 [MemberID, EquipSlot] WHERE IsEquipped=1 로 DB 레벨에서도 강제)
  9. /// </summary>
  10. internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result<Response>>
  11. {
  12. public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
  13. {
  14. var now = DateTime.UtcNow;
  15. var item = await db.MemberItem.FirstOrDefaultAsync(i => i.ID == request.MemberItemID, ct);
  16. if (item is null || item.MemberID != request.MemberID)
  17. {
  18. return Result.Failure<Response>(Error.NotFound("MemberItem.NotFound", "보유 아이템을 찾을 수 없습니다."));
  19. }
  20. if (item.EquipSlot is null)
  21. {
  22. return Result.Failure<Response>(Error.Problem("MemberItem.NotEquippable", "장착할 수 없는 아이템입니다."));
  23. }
  24. if (item.RevokedAt is not null)
  25. {
  26. return Result.Failure<Response>(Error.Conflict("MemberItem.Revoked", "회수된 아이템입니다."));
  27. }
  28. if (item.IsExpiredAt(now))
  29. {
  30. return Result.Failure<Response>(Error.Conflict("MemberItem.Expired", "만료된 아이템입니다."));
  31. }
  32. if (item.IsEquipped)
  33. {
  34. return Result.Success(new Response(item.ID, item.EquipSlot.Value, true));
  35. }
  36. var slot = item.EquipSlot.Value;
  37. // 같은 슬롯의 기존 장착 아이템 해제 (슬롯당 1개)
  38. var current = await db.MemberItem
  39. .Where(i => i.MemberID == request.MemberID && i.EquipSlot == slot && i.IsEquipped && i.ID != item.ID)
  40. .ToListAsync(ct);
  41. foreach (var equipped in current)
  42. {
  43. equipped.Unequip();
  44. }
  45. item.Equip(now);
  46. await db.SaveChangesAsync(ct);
  47. return Result.Success(new Response(item.ID, slot, true));
  48. }
  49. }