684 lines
24 KiB
C#
684 lines
24 KiB
C#
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
|
|
|
|
/// <summary>
|
|
/// Upload a single file
|
|
/// </summary>
|
|
/// <param name="file">The uploaded file</param>
|
|
/// <param name="featureName">Feature name (e.g., "AIdocumentprocessing")</param>
|
|
/// <param name="entityType">Entity type (e.g., "ShippingDocuments")</param>
|
|
/// <param name="entityId">Entity ID</param>
|
|
/// <param name="rawText">Optional raw text for searchable documents</param>
|
|
[HttpPost]
|
|
public async Task<IActionResult> 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}"
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upload multiple files at once
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<IActionResult> UploadMultipleFiles(
|
|
List<IFormFile> 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<object>();
|
|
var errors = new List<string>();
|
|
|
|
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
|
|
|
|
/// <summary>
|
|
/// Download a file by ID
|
|
/// </summary>
|
|
[HttpGet]
|
|
public async Task<IActionResult> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Preview a file inline (for PDFs, images)
|
|
/// </summary>
|
|
[HttpGet]
|
|
public async Task<IActionResult> 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
|
|
|
|
/// <summary>
|
|
/// Get all files
|
|
/// </summary>
|
|
[HttpGet]
|
|
public async Task<IActionResult> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search files by filename or content
|
|
/// </summary>
|
|
[HttpGet]
|
|
public async Task<IActionResult> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get files for a specific entity
|
|
/// </summary>
|
|
[HttpGet]
|
|
public async Task<IActionResult> 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
|
|
|
|
/// <summary>
|
|
/// Delete a file by ID
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<IActionResult> 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}"
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete multiple files at once
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<IActionResult> DeleteMultipleFiles(
|
|
List<int> 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<string>();
|
|
|
|
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
|
|
|
|
/// <summary>
|
|
/// Get file information by ID
|
|
/// </summary>
|
|
[HttpGet]
|
|
public async Task<IActionResult> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update file metadata (RawText)
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<IActionResult> 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
|
|
|
|
/// <summary>
|
|
/// Get content type based on file extension
|
|
/// </summary>
|
|
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"
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a snippet of text around the search term
|
|
/// </summary>
|
|
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
|
|
}
|
|
} |