using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Domain.Entities.Donations.ValueObject; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; using SharedKernel.Storage; namespace Application.Features.Api.ChannelTitle.UploadIcon; internal sealed class Handler(IAppDbContext db, IFileStorage fileStorage) : ICommandHandler> { private static readonly string[] AllowedExtensions = [".gif", ".jpg", ".jpeg", ".png", ".webp"]; private static readonly string[] AllowedContentTypes = ["image/gif", "image/jpeg", "image/png", "image/webp"]; public async Task> Handle(Command r, CancellationToken ct) { if (r.File is null || r.File.Length == 0) { return Result.Failure(Error.Problem("ChannelTitle.UploadIcon.Empty", "파일이 비어 있습니다.")); } var maxSizeBytes = (long)DonationConstants.Title.MaxIconFileSizeMB * 1024 * 1024; if (r.File.Length > maxSizeBytes) { return Result.Failure(Error.Problem("ChannelTitle.UploadIcon.FileTooLarge", $"파일 크기는 {DonationConstants.Title.MaxIconFileSizeMB}MB 이하여야 합니다.")); } if (!string.IsNullOrEmpty(r.File.ContentType) && !AllowedContentTypes.Contains(r.File.ContentType.ToLowerInvariant())) { return Result.Failure(Error.Problem("ChannelTitle.UploadIcon.InvalidType", "이미지 파일(gif, jpeg, png, webp)만 업로드할 수 있습니다.")); } var channel = await db.Channel.AsNoTracking().FirstOrDefaultAsync(c => c.ID == r.ChannelID, ct); if (channel is null) { return Result.Failure(Error.NotFound("ChannelTitle.UploadIcon.ChannelNotFound", "채널을 찾을 수 없습니다.")); } if (channel.MemberID != r.MemberID) { return Result.Failure(Error.Forbidden("ChannelTitle.UploadIcon.Forbidden", "본인 채널만 업로드할 수 있습니다.")); } var path = new FileStoragePath(UploadTarget.Upload, UploadFolder.ChannelTitle, r.ChannelID); var result = await fileStorage.SaveFileAsync(r.File, path, AllowedExtensions, ct); if (result is null) { return Result.Failure(Error.Problem("ChannelTitle.UploadIcon.Failed", "파일 업로드에 실패했습니다. 허용된 확장자(gif, jpeg, png, webp)인지 확인하세요.")); } return Result.Success(result.Url); } }