| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Microsoft.EntityFrameworkCore;
- using SharedKernel.Results;
- namespace Application.Features.Api.Member.Items.Equip;
- /// <summary>
- /// 지위 아이템 장착. 슬롯당 1개만 장착 가능하므로, 같은 슬롯에 이미 장착된 아이템은 자동 해제한 뒤 장착한다.
- /// (필터 유니크 인덱스 [MemberID, EquipSlot] WHERE IsEquipped=1 로 DB 레벨에서도 강제)
- /// </summary>
- internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result<Response>>
- {
- public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
- {
- var now = DateTime.UtcNow;
- var item = await db.MemberItem.FirstOrDefaultAsync(i => i.ID == request.MemberItemID, ct);
- if (item is null || item.MemberID != request.MemberID)
- {
- return Result.Failure<Response>(Error.NotFound("MemberItem.NotFound", "보유 아이템을 찾을 수 없습니다."));
- }
- if (item.EquipSlot is null)
- {
- return Result.Failure<Response>(Error.Problem("MemberItem.NotEquippable", "장착할 수 없는 아이템입니다."));
- }
- if (item.RevokedAt is not null)
- {
- return Result.Failure<Response>(Error.Conflict("MemberItem.Revoked", "회수된 아이템입니다."));
- }
- if (item.IsExpiredAt(now))
- {
- return Result.Failure<Response>(Error.Conflict("MemberItem.Expired", "만료된 아이템입니다."));
- }
- if (item.IsEquipped)
- {
- return Result.Success(new Response(item.ID, item.EquipSlot.Value, true));
- }
- var slot = item.EquipSlot.Value;
- // 같은 슬롯의 기존 장착 아이템 해제 (슬롯당 1개)
- var current = await db.MemberItem
- .Where(i => i.MemberID == request.MemberID && i.EquipSlot == slot && i.IsEquipped && i.ID != item.ID)
- .ToListAsync(ct);
- foreach (var equipped in current)
- {
- equipped.Unequip();
- }
- item.Equip(now);
- await db.SaveChangesAsync(ct);
- return Result.Success(new Response(item.ID, slot, true));
- }
- }
|