using FruitBank.Common.Entities; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Nop.Core; using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer; using Nop.Plugin.Misc.FruitBankPlugin.Services.FileStorage; using Nop.Services.Security; using Nop.Web.Framework; using Nop.Web.Framework.Controllers; using Nop.Web.Framework.Mvc.Filters; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace Nop.Plugin.Misc.FruitBank.Controllers { [AuthorizeAdmin] [Area(AreaNames.ADMIN)] [AutoValidateAntiforgeryToken] public class FileStorageController : BasePluginController { private readonly FileStorageService _fileStorageService; private readonly FruitBankDbContext _dbContext; private readonly IPermissionService _permissionService; private readonly IWorkContext _workContext; public FileStorageController( FileStorageService fileStorageService, FruitBankDbContext dbContext, IPermissionService permissionService, IWorkContext workContext) { _fileStorageService = fileStorageService; _dbContext = dbContext; _permissionService = permissionService; _workContext = workContext; } #region Upload Files /// /// Upload a single file /// /// The uploaded file /// Feature name (e.g., "AIdocumentprocessing") /// Entity type (e.g., "ShippingDocuments") /// Entity ID /// Optional raw text for searchable documents [HttpPost] public async Task UploadFile( IFormFile file, string featureName, string entityType, int entityId, string rawText = null) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); if (file == null || file.Length == 0) return Json(new { success = false, message = "No file uploaded" }); if (string.IsNullOrWhiteSpace(featureName)) return Json(new { success = false, message = "Feature name is required" }); if (string.IsNullOrWhiteSpace(entityType)) return Json(new { success = false, message = "Entity type is required" }); if (entityId <= 0) return Json(new { success = false, message = "Valid entity ID is required" }); try { var currentUser = await _workContext.GetCurrentCustomerAsync(); var userId = currentUser.Id; using (var stream = file.OpenReadStream()) { var fileEntity = await _fileStorageService.SaveFileAsync( fileStream: stream, fileName: file.FileName, userId: userId, featureName: featureName, entityType: entityType, entityId: entityId, rawText: rawText ); return Json(new { success = true, message = "File uploaded successfully", file = new { id = fileEntity.Id, fileName = fileEntity.FileName, fileExtension = fileEntity.FileExtension, created = fileEntity.Created, hasRawText = !string.IsNullOrEmpty(fileEntity.RawText) } }); } } catch (Exception ex) { Console.Error.WriteLine($"Error uploading file: {ex}"); return Json(new { success = false, message = $"Error uploading file: {ex.Message}" }); } } /// /// Upload multiple files at once /// [HttpPost] public async Task UploadMultipleFiles( List files, string featureName, string entityType, int entityId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); if (files == null || files.Count == 0) return Json(new { success = false, message = "No files uploaded" }); try { var currentUser = await _workContext.GetCurrentCustomerAsync(); var userId = currentUser.Id; var uploadedFiles = new List(); var errors = new List(); foreach (var file in files) { try { if (file.Length > 0) { using (var stream = file.OpenReadStream()) { var fileEntity = await _fileStorageService.SaveFileAsync( fileStream: stream, fileName: file.FileName, userId: userId, featureName: featureName, entityType: entityType, entityId: entityId ); uploadedFiles.Add(new { id = fileEntity.Id, fileName = fileEntity.FileName + fileEntity.FileExtension }); } } } catch (Exception ex) { errors.Add($"{file.FileName}: {ex.Message}"); } } return Json(new { success = uploadedFiles.Count > 0, message = $"Uploaded {uploadedFiles.Count} of {files.Count} files", files = uploadedFiles, errors = errors }); } catch (Exception ex) { Console.Error.WriteLine($"Error uploading multiple files: {ex}"); return Json(new { success = false, message = $"Error: {ex.Message}" }); } } #endregion #region Download Files /// /// Download a file by ID /// [HttpGet] public async Task DownloadFile( int fileId, string featureName, string entityType, int entityId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Unauthorized(); try { var currentUser = await _workContext.GetCurrentCustomerAsync(); var userId = currentUser.Id; var (fileStream, fileInfo) = await _fileStorageService.GetFileByIdAsync( fileId: fileId, userId: userId, featureName: featureName, entityType: entityType, entityId: entityId ); var fileName = $"{fileInfo.FileName}{fileInfo.FileExtension}"; var contentType = GetContentType(fileInfo.FileExtension); return File(fileStream, contentType, fileName); } catch (FileNotFoundException ex) { return NotFound(new { success = false, message = ex.Message }); } catch (Exception ex) { Console.Error.WriteLine($"Error downloading file: {ex}"); return BadRequest(new { success = false, message = ex.Message }); } } /// /// Preview a file inline (for PDFs, images) /// [HttpGet] public async Task PreviewFile( int fileId, string featureName, string entityType, int entityId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Unauthorized(); try { var currentUser = await _workContext.GetCurrentCustomerAsync(); var userId = currentUser.Id; var (fileStream, fileInfo) = await _fileStorageService.GetFileByIdAsync( fileId: fileId, userId: userId, featureName: featureName, entityType: entityType, entityId: entityId ); var contentType = GetContentType(fileInfo.FileExtension); // Return inline for preview return File(fileStream, contentType); } catch (FileNotFoundException ex) { return NotFound(new { success = false, message = ex.Message }); } catch (Exception ex) { Console.Error.WriteLine($"Error previewing file: {ex}"); return BadRequest(new { success = false, message = ex.Message }); } } #endregion #region List & Search Files /// /// Get all files /// [HttpGet] public async Task GetAllFiles() { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); try { var files = await _fileStorageService.GetAllFilesAsync(); return Json(new { success = true, count = files.Count, files = files.Select(f => new { id = f.Id, fileName = f.FileName, fileExtension = f.FileExtension, fullName = $"{f.FileName}{f.FileExtension}", created = f.Created, modified = f.Modified, hasRawText = !string.IsNullOrEmpty(f.RawText) }) }); } catch (Exception ex) { Console.Error.WriteLine($"Error getting files: {ex}"); return Json(new { success = false, message = ex.Message }); } } /// /// Search files by filename or content /// [HttpGet] public async Task SearchFiles(string searchTerm) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); if (string.IsNullOrWhiteSpace(searchTerm)) return Json(new { success = false, message = "Search term is required" }); try { var files = await _fileStorageService.SearchFilesAsync(searchTerm); return Json(new { success = true, searchTerm = searchTerm, count = files.Count, files = files.Select(f => new { id = f.Id, fileName = f.FileName, fileExtension = f.FileExtension, fullName = $"{f.FileName}{f.FileExtension}", created = f.Created, hasRawText = !string.IsNullOrEmpty(f.RawText), // Include snippet of matched text if available textSnippet = GetTextSnippet(f.RawText, searchTerm) }) }); } catch (Exception ex) { Console.Error.WriteLine($"Error searching files: {ex}"); return Json(new { success = false, message = ex.Message }); } } /// /// Get files for a specific entity /// [HttpGet] public async Task GetEntityFiles(string entityType, int entityId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); try { // Example for ShippingDocuments - adapt for other entity types if (entityType == "ShippingDocuments") { var mappings = await _dbContext.ShippingDocumentToFiles .GetAll() .Where(m => m.ShippingDocumentId == entityId) .ToListAsync(); var fileIds = mappings.Select(m => m.FilesId).ToList(); var files = await _dbContext.Files .GetAll() .Where(f => fileIds.Contains(f.Id)) .ToListAsync(); return Json(new { success = true, entityType = entityType, entityId = entityId, count = files.Count, files = files.Select(f => new { id = f.Id, fileName = $"{f.FileName}{f.FileExtension}", created = f.Created, documentType = mappings.FirstOrDefault(m => m.FilesId == f.Id)?.DocumentType.ToString() }) }); } return Json(new { success = false, message = $"Entity type '{entityType}' not supported yet" }); } catch (Exception ex) { Console.Error.WriteLine($"Error getting entity files: {ex}"); return Json(new { success = false, message = ex.Message }); } } #endregion #region Delete Files /// /// Delete a file by ID /// [HttpPost] public async Task DeleteFile( int fileId, string featureName, string entityType, int entityId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); try { var currentUser = await _workContext.GetCurrentCustomerAsync(); var userId = currentUser.Id; var deleted = await _fileStorageService.DeleteFileAsync( fileId: fileId, userId: userId, featureName: featureName, entityType: entityType, entityId: entityId ); if (deleted) { // Also delete mappings (example for ShippingDocuments) if (entityType == "ShippingDocuments") { var mappings = await _dbContext.ShippingDocumentToFiles .GetAll() .Where(m => m.FilesId == fileId && m.ShippingDocumentId == entityId) .ToListAsync(); foreach (var mapping in mappings) { await _dbContext.ShippingDocumentToFiles.DeleteAsync(mapping); } } } return Json(new { success = deleted, message = deleted ? "File deleted successfully" : "File not found" }); } catch (Exception ex) { Console.Error.WriteLine($"Error deleting file: {ex}"); return Json(new { success = false, message = $"Error deleting file: {ex.Message}" }); } } /// /// Delete multiple files at once /// [HttpPost] public async Task DeleteMultipleFiles( List fileIds, string featureName, string entityType, int entityId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); if (fileIds == null || fileIds.Count == 0) return Json(new { success = false, message = "No file IDs provided" }); try { var currentUser = await _workContext.GetCurrentCustomerAsync(); var userId = currentUser.Id; var deletedCount = 0; var errors = new List(); foreach (var fileId in fileIds) { try { var deleted = await _fileStorageService.DeleteFileAsync( fileId: fileId, userId: userId, featureName: featureName, entityType: entityType, entityId: entityId ); if (deleted) { deletedCount++; } } catch (Exception ex) { errors.Add($"File {fileId}: {ex.Message}"); } } return Json(new { success = deletedCount > 0, message = $"Deleted {deletedCount} of {fileIds.Count} files", deletedCount = deletedCount, errors = errors }); } catch (Exception ex) { Console.Error.WriteLine($"Error deleting multiple files: {ex}"); return Json(new { success = false, message = $"Error: {ex.Message}" }); } } #endregion #region File Information /// /// Get file information by ID /// [HttpGet] public async Task GetFileInfo(int fileId) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); try { var fileEntity = await _dbContext.Files.GetByIdAsync(fileId); if (fileEntity == null) return NotFound(new { success = false, message = "File not found" }); return Json(new { success = true, file = new { id = fileEntity.Id, fileName = fileEntity.FileName, fileExtension = fileEntity.FileExtension, fullName = $"{fileEntity.FileName}{fileEntity.FileExtension}", created = fileEntity.Created, modified = fileEntity.Modified, hasRawText = !string.IsNullOrEmpty(fileEntity.RawText), rawTextLength = fileEntity.RawText?.Length ?? 0 } }); } catch (Exception ex) { Console.Error.WriteLine($"Error getting file info: {ex}"); return Json(new { success = false, message = ex.Message }); } } /// /// Update file metadata (RawText) /// [HttpPost] public async Task UpdateFileMetadata(int fileId, string rawText) { if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL)) return Json(new { success = false, message = "Access denied" }); try { var fileEntity = await _dbContext.Files.GetByIdAsync(fileId); if (fileEntity == null) return NotFound(new { success = false, message = "File not found" }); fileEntity.RawText = rawText; await _fileStorageService.AddOrUpdateFileAsync(fileEntity); return Json(new { success = true, message = "File metadata updated successfully" }); } catch (Exception ex) { Console.Error.WriteLine($"Error updating file metadata: {ex}"); return Json(new { success = false, message = $"Error updating metadata: {ex.Message}" }); } } #endregion #region Helper Methods /// /// Get content type based on file extension /// private string GetContentType(string extension) { return extension.ToLowerInvariant() switch { ".pdf" => "application/pdf", ".jpg" or ".jpeg" => "image/jpeg", ".png" => "image/png", ".gif" => "image/gif", ".webp" => "image/webp", ".bmp" => "image/bmp", ".txt" => "text/plain", ".csv" => "text/csv", ".json" => "application/json", ".xml" => "application/xml", ".html" => "text/html", ".doc" => "application/msword", ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".xls" => "application/vnd.ms-excel", ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".zip" => "application/zip", ".rar" => "application/x-rar-compressed", _ => "application/octet-stream" }; } /// /// Get a snippet of text around the search term /// private string GetTextSnippet(string text, string searchTerm, int contextLength = 100) { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(searchTerm)) return null; var index = text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase); if (index == -1) return null; var start = Math.Max(0, index - contextLength); var length = Math.Min(text.Length - start, contextLength * 2 + searchTerm.Length); var snippet = text.Substring(start, length); if (start > 0) snippet = "..." + snippet; if (start + length < text.Length) snippet = snippet + "..."; return snippet; } #endregion } }