using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Domain.Entities.Developers.ValueObject; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.V1.Purchases.List; internal sealed class Handler(IAppDbContext db) : IQueryHandler> { public async Task> Handle(Query request, CancellationToken ct) { var page = Math.Max(1, request.Page); var size = Math.Clamp(request.Size, 1, 100); var query = db.ApiPurchase.AsNoTracking().AsQueryable(); // 호출 주체 범위 제한 — OAuth2: 해당 앱 / PAT: 본인 소유 앱 전체 if (request.ApplicationID.HasValue) { query = query.Where(c => c.ApplicationID == request.ApplicationID.Value); } else if (request.OwnerMemberID.HasValue) { query = query.Where(c => c.Application!.OwnerMemberID == request.OwnerMemberID.Value); } else { return Result.Failure(Error.Unauthorized("Purchase.CallerUnknown", "호출 주체를 식별할 수 없습니다.")); } // 상태 필터 (pending / confirmed / canceled) if (!string.IsNullOrWhiteSpace(request.Status)) { if (!Enum.TryParse(request.Status.Trim(), ignoreCase: true, out var status)) { return Result.Failure(Error.Problem("Purchase.StatusInvalid", "status 는 pending / confirmed / canceled 중 하나여야 합니다.")); } query = query.Where(c => c.Status == status); } if (request.From.HasValue) { query = query.Where(c => c.CreatedAt >= request.From.Value); } if (request.To.HasValue) { query = query.Where(c => c.CreatedAt <= request.To.Value); } var total = await query.CountAsync(ct); var items = await query .OrderByDescending(c => c.ID) .Skip((page - 1) * size) .Take(size) .Select(c => new Response.Row( c.ID, c.OrderID, (int)c.Marketplace, c.Channel!.DonationCode, c.Game!.Code ?? string.Empty, c.OrderPrice, c.CommissionRate, c.CommissionAmount, c.Status.ToString(), c.ConfirmDueAt, c.ConfirmedAt, c.CanceledAt, c.CreatedAt )) .ToListAsync(ct); return Result.Success(new Response(total, page, size, items)); } }