using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Domain.Entities.Store.ValueObject; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; using SharedKernel.Storage; namespace Application.Features.Admin.Store.Product.Update; public sealed class Handler(IAppDbContext db, IFileStorage fileStorage) : ICommandHandler { private static readonly string[] AllowedImageExt = [".jpg", ".jpeg", ".png", ".gif", ".webp"]; public async Task Handle(Command request, CancellationToken ct) { if (!await db.Game.AnyAsync(c => c.ID == request.GameID, ct)) { return Result.Failure(Error.NotFound("Product.GameNotFound", "선택한 게임이 없습니다.")); } if (request.Price < 0) { return Result.Failure(Error.Problem("Product.InvalidPrice", "가격은 0 이상이어야 합니다.")); } if (request.DiscountType == DiscountType.Percent && (request.DiscountValue < 0 || request.DiscountValue > 99)) { return Result.Failure(Error.Problem("Product.InvalidDiscountPercent", "할인율은 0 ~ 99% 범위로 입력하세요.")); } if (request.DiscountType == DiscountType.Amount && (request.DiscountValue < 0 || request.DiscountValue > request.Price)) { return Result.Failure(Error.Problem("Product.InvalidDiscountAmount", "할인 금액은 0 이상이며 정가 이하로 입력하세요.")); } if (request.Stock < -1) { return Result.Failure(Error.Problem("Product.InvalidStock", "재고는 -1(무제한) 또는 0 이상이어야 합니다.")); } if (request.MinPurchase < 1 || request.MaxPurchase < request.MinPurchase || request.MaxPurchase > 999) { return Result.Failure(Error.Problem("Product.InvalidPurchaseRange", "최소/최대 구매 수량 범위가 올바르지 않습니다 (1 ≤ 최소 ≤ 최대 ≤ 999).")); } if (request.SaleStartAt.HasValue && request.SaleEndAt.HasValue && request.SaleStartAt.Value >= request.SaleEndAt.Value) { return Result.Failure(Error.Problem("Product.InvalidSalePeriod", "판매 시작일은 종료일보다 이전이어야 합니다.")); } var product = await db.Product.FirstOrDefaultAsync(c => c.ID == request.ID, ct); if (product is null) { return Result.Failure(Error.NotFound("Product.NotFound", "상품을 찾을 수 없습니다.")); } string? newThumbnail = product.Thumbnail; if (request.RemoveThumbnail) { newThumbnail = null; } if (request.ThumbnailFile is not null) { var uploadPath = new FileStoragePath(UploadTarget.Upload, UploadFolder.StoreProduct, product.ID); var saved = await fileStorage.SaveFileAsync(request.ThumbnailFile, uploadPath, AllowedImageExt, ct); if (saved is not null) { newThumbnail = saved.Url; } } product.Update( gameID: request.GameID, name: request.Name, type: request.Type, price: request.Price, discountType: request.DiscountType, discountValue: request.DiscountValue, stock: request.Stock, description: request.Description, thumbnail: newThumbnail, order: request.Order, minPurchase: request.MinPurchase, maxPurchase: request.MaxPurchase, saleStartAt: request.SaleStartAt, saleEndAt: request.SaleEndAt, isActive: request.IsActive, requireDonationChannel: request.RequireDonationChannel ); await db.SaveChangesAsync(ct); return Result.Success(); } }