using Application.Abstractions.Interfaces; using Application.DTOs.FileDTOs; using Entities.Interfaces; using FileEntity = Entities.Models.File; namespace Application.Services { public class FileService : IFileService { private readonly IFileRepository _fileRepository; private readonly IUserRepository _userRepository; private readonly IOrderRepository _orderRepository; private readonly string[] _allowedExtensions = { ".jpg", ".jpeg", ".png", ".gif" }; private const long MaxFileSize = 5 * 1024 * 1024; // 5MB public FileService( IFileRepository fileRepository, IUserRepository userRepository, IOrderRepository orderRepository) { _fileRepository = fileRepository; _userRepository = userRepository; _orderRepository = orderRepository; } public async Task UploadProfilePhotoAsync(int userId, FileUploadDTO file, string contentRootPath) { // Validate user exists var user = await _userRepository.GetByIdAsync(userId); if (user == null) { throw new ArgumentException("User not found"); } // Validate file ValidateFile(file); // Delete old profile photo if exists if (user.ProfilePhotoId.HasValue) { await DeleteProfilePhotoAsync(userId, contentRootPath); } // Create upload directory var uploadsPath = Path.Combine(contentRootPath, "Uploads", "Profiles"); Directory.CreateDirectory(uploadsPath); // Generate unique filename var extension = Path.GetExtension(file.FileName).ToLowerInvariant(); var uniqueFileName = $"{userId}_{Guid.NewGuid()}{extension}"; var filePath = Path.Combine(uploadsPath, uniqueFileName); // Save file to disk using (var fileStream = new FileStream(filePath, FileMode.Create)) { await file.Content.CopyToAsync(fileStream); } // Create file entity var fileEntity = new FileEntity { FileName = file.FileName, FilePath = $"/Uploads/Profiles/{uniqueFileName}", ContentType = file.ContentType, FileSize = file.Length, UserId = userId, UploadedAt = DateTime.UtcNow }; await _fileRepository.AddAsync(fileEntity); await _fileRepository.SaveChangesAsync(); // Update user's profile photo reference user.ProfilePhotoId = fileEntity.Id; await _userRepository.UpdateAsync(user); await _userRepository.SaveChangesAsync(); return MapToResponseDTO(fileEntity); } public async Task> UploadOrderImagesAsync(int orderId, List files, string contentRootPath) { // Validate order exists var order = await _orderRepository.GetByIdAsync(orderId); if (order == null) { throw new ArgumentException("Order not found"); } if (files == null || files.Count == 0) { throw new ArgumentException("No files provided"); } var uploadedFiles = new List(); // Create upload directory var uploadsPath = Path.Combine(contentRootPath, "Uploads", "Orders", orderId.ToString()); Directory.CreateDirectory(uploadsPath); foreach (var file in files) { // Validate file ValidateFile(file); // Generate unique filename var extension = Path.GetExtension(file.FileName).ToLowerInvariant(); var uniqueFileName = $"{Guid.NewGuid()}{extension}"; var filePath = Path.Combine(uploadsPath, uniqueFileName); // Save file to disk using (var fileStream = new FileStream(filePath, FileMode.Create)) { await file.Content.CopyToAsync(fileStream); } // Create file entity var fileEntity = new FileEntity { FileName = file.FileName, FilePath = $"/Uploads/Orders/{orderId}/{uniqueFileName}", ContentType = file.ContentType, FileSize = file.Length, OrderId = orderId, UploadedAt = DateTime.UtcNow }; await _fileRepository.AddAsync(fileEntity); uploadedFiles.Add(MapToResponseDTO(fileEntity)); } await _fileRepository.SaveChangesAsync(); return uploadedFiles; } public async Task GetFileByIdAsync(int fileId) { var file = await _fileRepository.GetByIdAsync(fileId); return file == null ? null : MapToResponseDTO(file); } public async Task> GetOrderFilesAsync(int orderId) { var files = await _fileRepository.GetByOrderIdAsync(orderId); return files.Select(MapToResponseDTO).ToList(); } public async Task DeleteFileAsync(int fileId, string contentRootPath) { var file = await _fileRepository.GetByIdAsync(fileId); if (file == null) { return false; } // Delete file from disk var diskPath = Path.Combine(contentRootPath, file.FilePath.TrimStart('/')); if (File.Exists(diskPath)) { File.Delete(diskPath); } // Delete from database await _fileRepository.DeleteAsync(fileId); await _fileRepository.SaveChangesAsync(); return true; } public async Task DeleteProfilePhotoAsync(int userId, string contentRootPath) { var user = await _userRepository.GetByIdAsync(userId); if (user == null || !user.ProfilePhotoId.HasValue) { return false; } var photo = await _fileRepository.GetByIdAsync(user.ProfilePhotoId.Value); if (photo != null) { // Delete file from disk var oldFilePath = Path.Combine(contentRootPath, photo.FilePath.TrimStart('/')); if (File.Exists(oldFilePath)) { File.Delete(oldFilePath); } await _fileRepository.DeleteAsync(photo.Id); await _fileRepository.SaveChangesAsync(); } return true; } public async Task GetFileContentAsync(int fileId, string contentRootPath) { var file = await _fileRepository.GetByIdAsync(fileId); if (file == null) { return null; } var filePath = Path.Combine(contentRootPath, file.FilePath.TrimStart('/')); if (!File.Exists(filePath)) { return null; } return await File.ReadAllBytesAsync(filePath); } public async Task IsUserAuthorizedForFileAsync(int fileId, int userId, bool isAdmin) { var file = await _fileRepository.GetByIdAsync(fileId); if (file == null) { return false; } // Admin has access to all files if (isAdmin) { return true; } // Check if user owns this file (either through order or profile photo) if (file.OrderId.HasValue) { var order = await _orderRepository.GetByIdAsync(file.OrderId.Value); if (order != null && order.SenderId == userId) { return true; } } else if (file.UserId.HasValue && file.UserId == userId) { return true; } return false; } private void ValidateFile(FileUploadDTO file) { var extension = Path.GetExtension(file.FileName).ToLowerInvariant(); if (!_allowedExtensions.Contains(extension)) { throw new ArgumentException($"Invalid file extension. Allowed: {string.Join(", ", _allowedExtensions)}"); } if (file.Length > MaxFileSize) { throw new ArgumentException("File exceeds maximum size of 5MB"); } } private static FileResponseDTO MapToResponseDTO(FileEntity file) { return new FileResponseDTO { Id = file.Id, FileName = file.FileName, FilePath = file.FilePath, ContentType = file.ContentType, FileSize = file.FileSize, UploadedAt = file.UploadedAt }; } } }