879 lines
31 KiB
C#
879 lines
31 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using DysonNetwork.Shared.Http;
|
|
using DysonNetwork.Shared.Models;
|
|
using DysonNetwork.Shared.Proto;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace DysonNetwork.Drive.Index;
|
|
|
|
[ApiController]
|
|
[Route("/api/index")]
|
|
[Authorize]
|
|
public class FileIndexController(
|
|
FileIndexService fileIndexService,
|
|
FolderService folderService,
|
|
AppDatabase db,
|
|
ILogger<FileIndexController> logger
|
|
) : ControllerBase
|
|
{
|
|
/// <summary>
|
|
/// Gets files in a specific path for the current user
|
|
/// </summary>
|
|
/// <param name="path">The path to browse (defaults to root "/")</param>
|
|
/// <returns>List of files in the specified path</returns>
|
|
[HttpGet("browse")]
|
|
public async Task<IActionResult> BrowseFiles([FromQuery] string path = "/")
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
var fileIndexes = await fileIndexService.GetByPathAsync(accountId, path);
|
|
|
|
// Get child folders using the folder system
|
|
var childFolders = await GetChildFoldersAsync(accountId, path);
|
|
|
|
return Ok(new
|
|
{
|
|
Path = path,
|
|
Files = fileIndexes,
|
|
Folders = childFolders,
|
|
TotalCount = fileIndexes.Count
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to browse files for account {AccountId} at path {Path}", accountId, path);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "BROWSE_FAILED",
|
|
Message = "Failed to browse files",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets child folders for a given parent path using the folder system
|
|
/// </summary>
|
|
/// <param name="accountId">The account ID</param>
|
|
/// <param name="parentPath">The parent path to find children for</param>
|
|
/// <returns>List of child folder objects</returns>
|
|
private async Task<List<SnCloudFolder>> GetChildFoldersAsync(Guid accountId, string parentPath)
|
|
{
|
|
var normalizedParentPath = FileIndexService.NormalizePath(parentPath);
|
|
|
|
// Try to find a folder that corresponds to this path
|
|
var parentFolder = await FindFolderByPathAsync(accountId, normalizedParentPath);
|
|
|
|
if (parentFolder != null)
|
|
{
|
|
// Use folder-based approach
|
|
return await folderService.GetChildFoldersAsync(parentFolder.Id, accountId);
|
|
}
|
|
else
|
|
{
|
|
// Fall back to path-based approach - find folders that start with this path
|
|
var allFolders = await folderService.GetByAccountIdAsync(accountId);
|
|
var childFolders = new List<SnCloudFolder>();
|
|
|
|
foreach (var folder in allFolders)
|
|
{
|
|
// For path-based folders, we need to check if they belong under this path
|
|
// This is a simplified approach - in a full implementation, folders would have path information
|
|
if (folder.ParentFolderId == null && normalizedParentPath == "/")
|
|
{
|
|
// Root level folders
|
|
childFolders.Add(folder);
|
|
}
|
|
// For nested folders, we'd need path information in the folder model
|
|
}
|
|
|
|
return childFolders.OrderBy(f => f.Name).ToList();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to find a folder by its path
|
|
/// </summary>
|
|
/// <param name="accountId">The account ID</param>
|
|
/// <param name="path">The path to search for</param>
|
|
/// <returns>The folder if found, null otherwise</returns>
|
|
private async Task<SnCloudFolder?> FindFolderByPathAsync(Guid accountId, string path)
|
|
{
|
|
// This is a simplified implementation
|
|
// In a full implementation, folders would have path information stored
|
|
var allFolders = await folderService.GetByAccountIdAsync(accountId);
|
|
|
|
// For now, just return null to use path-based approach
|
|
// TODO: Implement proper path-to-folder mapping
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or creates a folder hierarchy based on a file path
|
|
/// </summary>
|
|
/// <param name="filePath">The file path (e.g., "/folder/sub/file.txt")</param>
|
|
/// <param name="accountId">The account ID</param>
|
|
/// <returns>The folder where the file should be placed</returns>
|
|
private async Task<SnCloudFolder> GetOrCreateFolderByPathAsync(string filePath, Guid accountId)
|
|
{
|
|
// Extract folder path from file path (remove filename)
|
|
var lastSlashIndex = filePath.LastIndexOf('/');
|
|
var folderPath = lastSlashIndex == 0 ? "/" : filePath[..(lastSlashIndex + 1)];
|
|
|
|
// Ensure root folder exists
|
|
var rootFolder = await folderService.EnsureRootFolderAsync(accountId);
|
|
|
|
// If it's the root folder, return it
|
|
if (folderPath == "/")
|
|
return rootFolder;
|
|
|
|
// Split the folder path into segments
|
|
var pathSegments = folderPath.Trim('/').Split('/', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
var currentParent = rootFolder;
|
|
var currentPath = "/";
|
|
|
|
// Create folder hierarchy
|
|
foreach (var segment in pathSegments)
|
|
{
|
|
currentPath += segment + "/";
|
|
|
|
// Check if folder already exists
|
|
var existingFolder = await db.Folders
|
|
.FirstOrDefaultAsync(f => f.AccountId == accountId && f.Path == currentPath);
|
|
|
|
if (existingFolder != null)
|
|
{
|
|
currentParent = existingFolder;
|
|
continue;
|
|
}
|
|
|
|
// Create new folder
|
|
var newFolder = await folderService.CreateAsync(segment, accountId, currentParent.Id);
|
|
currentParent = newFolder;
|
|
}
|
|
|
|
return currentParent;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all files for the current user (across all paths)
|
|
/// </summary>
|
|
/// <returns>List of all files for the user</returns>
|
|
[HttpGet("all")]
|
|
public async Task<IActionResult> GetAllFiles()
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
var fileIndexes = await fileIndexService.GetByAccountIdAsync(accountId);
|
|
|
|
return Ok(new
|
|
{
|
|
Files = fileIndexes,
|
|
TotalCount = fileIndexes.Count
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to get all files for account {AccountId}", accountId);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "GET_ALL_FAILED",
|
|
Message = "Failed to get files",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves a file to a new path
|
|
/// </summary>
|
|
/// <param name="indexId">The file index ID</param>
|
|
/// <param name="newPath">The new path</param>
|
|
/// <returns>The updated file index</returns>
|
|
[HttpPost("move/{indexId}")]
|
|
public async Task<IActionResult> MoveFile(Guid indexId, [FromBody] MoveFileRequest request)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
// Verify ownership
|
|
var existingIndex = await db.FileIndexes
|
|
.Include(fi => fi.File)
|
|
.FirstOrDefaultAsync(fi => fi.Id == indexId && fi.AccountId == accountId);
|
|
|
|
if (existingIndex == null)
|
|
return new ObjectResult(ApiError.NotFound("File index")) { StatusCode = 404 };
|
|
|
|
var updatedIndex = await fileIndexService.UpdateAsync(indexId, request.NewPath);
|
|
|
|
if (updatedIndex == null)
|
|
return new ObjectResult(ApiError.NotFound("File index")) { StatusCode = 404 };
|
|
|
|
return Ok(new
|
|
{
|
|
updatedIndex.FileId,
|
|
IndexId = updatedIndex.Id,
|
|
OldPath = existingIndex.Path,
|
|
NewPath = updatedIndex.Path,
|
|
Message = "File moved successfully"
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to move file index {IndexId} for account {AccountId}", indexId, accountId);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "MOVE_FAILED",
|
|
Message = "Failed to move file",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a file index (does not delete the actual file by default)
|
|
/// </summary>
|
|
/// <param name="indexId">The file index ID</param>
|
|
/// <param name="deleteFile">Whether to also delete the actual file data</param>
|
|
/// <returns>Success message</returns>
|
|
[HttpDelete("remove/{indexId}")]
|
|
public async Task<IActionResult> RemoveFileIndex(Guid indexId, [FromQuery] bool deleteFile = false)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
// Verify ownership
|
|
var existingIndex = await db.FileIndexes
|
|
.Include(fi => fi.File)
|
|
.FirstOrDefaultAsync(fi => fi.Id == indexId && fi.AccountId == accountId);
|
|
|
|
if (existingIndex == null)
|
|
return new ObjectResult(ApiError.NotFound("File index")) { StatusCode = 404 };
|
|
|
|
var fileId = existingIndex.FileId;
|
|
var fileName = existingIndex.File.Name;
|
|
var filePath = existingIndex.Path;
|
|
|
|
// Remove the index
|
|
var removed = await fileIndexService.RemoveAsync(indexId);
|
|
|
|
if (!removed)
|
|
return new ObjectResult(ApiError.NotFound("File index")) { StatusCode = 404 };
|
|
|
|
// Optionally delete the actual file
|
|
if (!deleteFile)
|
|
return Ok(new
|
|
{
|
|
Message = deleteFile
|
|
? "File index and file data removed successfully"
|
|
: "File index removed successfully",
|
|
FileId = fileId,
|
|
FileName = fileName,
|
|
Path = filePath,
|
|
FileDataDeleted = deleteFile
|
|
});
|
|
try
|
|
{
|
|
// Check if there are any other indexes for this file
|
|
var remainingIndexes = await fileIndexService.GetByFileIdAsync(fileId);
|
|
if (remainingIndexes.Count == 0)
|
|
{
|
|
// No other indexes exist, safe to delete the file
|
|
var file = await db.Files.FirstOrDefaultAsync(f => f.Id == fileId.ToString());
|
|
if (file != null)
|
|
{
|
|
db.Files.Remove(file);
|
|
await db.SaveChangesAsync();
|
|
logger.LogInformation("Deleted file {FileId} ({FileName}) as requested", fileId, fileName);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogWarning(ex, "Failed to delete file {FileId} while removing index", fileId);
|
|
// Continue even if file deletion fails
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
Message = deleteFile ? "File index and file data removed successfully" : "File index removed successfully",
|
|
FileId = fileId,
|
|
FileName = fileName,
|
|
Path = filePath,
|
|
FileDataDeleted = deleteFile
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to remove file index {IndexId} for account {AccountId}", indexId, accountId);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "REMOVE_FAILED",
|
|
Message = "Failed to remove file",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all file indexes in a specific path
|
|
/// </summary>
|
|
/// <param name="path">The path to clear</param>
|
|
/// <param name="deleteFiles">Whether to also delete the actual file data</param>
|
|
/// <returns>Success message with count of removed items</returns>
|
|
[HttpDelete("clear-path")]
|
|
public async Task<IActionResult> ClearPath([FromQuery] string path = "/", [FromQuery] bool deleteFiles = false)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
var removedCount = await fileIndexService.RemoveByPathAsync(accountId, path);
|
|
|
|
if (!deleteFiles || removedCount <= 0)
|
|
return Ok(new
|
|
{
|
|
Message = deleteFiles
|
|
? $"Cleared {removedCount} file indexes from path and deleted orphaned files"
|
|
: $"Cleared {removedCount} file indexes from path",
|
|
Path = path,
|
|
RemovedCount = removedCount,
|
|
FilesDeleted = deleteFiles
|
|
});
|
|
// Get the files that were in this path and check if they have other indexes
|
|
var filesInPath = await fileIndexService.GetByPathAsync(accountId, path);
|
|
var fileIdsToCheck = filesInPath.Select(fi => fi.FileId).Distinct().ToList();
|
|
|
|
foreach (var fileId in fileIdsToCheck)
|
|
{
|
|
var remainingIndexes = await fileIndexService.GetByFileIdAsync(fileId);
|
|
if (remainingIndexes.Count != 0) continue;
|
|
// No other indexes exist, safe to delete the file
|
|
var file = await db.Files.FirstOrDefaultAsync(f => f.Id == fileId.ToString());
|
|
if (file == null) continue;
|
|
db.Files.Remove(file);
|
|
logger.LogInformation("Deleted orphaned file {FileId} after clearing path {Path}", fileId, path);
|
|
}
|
|
await db.SaveChangesAsync();
|
|
|
|
return Ok(new
|
|
{
|
|
Message = deleteFiles ?
|
|
$"Cleared {removedCount} file indexes from path and deleted orphaned files" :
|
|
$"Cleared {removedCount} file indexes from path",
|
|
Path = path,
|
|
RemovedCount = removedCount,
|
|
FilesDeleted = deleteFiles
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to clear path {Path} for account {AccountId}", path, accountId);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "CLEAR_PATH_FAILED",
|
|
Message = "Failed to clear path",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new file index (useful for adding existing files to a path)
|
|
/// </summary>
|
|
/// <param name="request">The create index request</param>
|
|
/// <returns>The created file index</returns>
|
|
[HttpPost("create")]
|
|
public async Task<IActionResult> CreateFileIndex([FromBody] CreateFileIndexRequest request)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
// Verify the file exists and belongs to the user
|
|
var file = await db.Files.FirstOrDefaultAsync(f => f.Id == request.FileId);
|
|
if (file == null)
|
|
return new ObjectResult(ApiError.NotFound("File")) { StatusCode = 404 };
|
|
|
|
if (file.AccountId != accountId)
|
|
return new ObjectResult(ApiError.Unauthorized(forbidden: true)) { StatusCode = 403 };
|
|
|
|
// Check if index already exists for this file and path - since Path is now optional, we need to check by folder
|
|
// For now, we'll check if any index exists for this file in the same folder that would result from the path
|
|
var targetFolder = await GetOrCreateFolderByPathAsync(FileIndexService.NormalizePath(request.Path), accountId);
|
|
var existingIndex = await db.FileIndexes
|
|
.FirstOrDefaultAsync(fi => fi.FileId == request.FileId && fi.FolderId == targetFolder.Id && fi.AccountId == accountId);
|
|
|
|
if (existingIndex != null)
|
|
return new ObjectResult(ApiError.Validation(new Dictionary<string, string[]>
|
|
{
|
|
{ "fileId", ["File index already exists for this path"] }
|
|
})) { StatusCode = 400 };
|
|
|
|
var fileIndex = await fileIndexService.CreateAsync(request.Path, request.FileId, accountId);
|
|
|
|
return Ok(new
|
|
{
|
|
IndexId = fileIndex.Id,
|
|
fileIndex.FileId,
|
|
Path = fileIndex.Path,
|
|
Message = "File index created successfully"
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to create file index for file {FileId} at path {Path} for account {AccountId}",
|
|
request.FileId, request.Path, accountId);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "CREATE_INDEX_FAILED",
|
|
Message = "Failed to create file index",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets unindexed files for the current user (files that exist but don't have file indexes)
|
|
/// </summary>
|
|
/// <param name="offset">Pagination offset</param>
|
|
/// <param name="take">Number of files to take</param>
|
|
/// <returns>List of unindexed files</returns>
|
|
[HttpGet("unindexed")]
|
|
public async Task<IActionResult> GetUnindexedFiles([FromQuery] int offset = 0, [FromQuery] int take = 20)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
// Get files that belong to the user but don't have any file indexes
|
|
var unindexedFiles = await db.Files
|
|
.Where(f => f.AccountId == accountId)
|
|
.Where(f => !db.FileIndexes.Any(fi => fi.FileId == f.Id && fi.AccountId == accountId))
|
|
.OrderByDescending(f => f.CreatedAt)
|
|
.Skip(offset)
|
|
.Take(take)
|
|
.ToListAsync();
|
|
|
|
var totalCount = await db.Files
|
|
.Where(f => f.AccountId == accountId)
|
|
.Where(f => !db.FileIndexes.Any(fi => fi.FileId == f.Id && fi.AccountId == accountId))
|
|
.CountAsync();
|
|
|
|
return Ok(new
|
|
{
|
|
Files = unindexedFiles,
|
|
TotalCount = totalCount,
|
|
Offset = offset,
|
|
Take = take
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to get unindexed files for account {AccountId}", accountId);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "GET_UNINDEXED_FAILED",
|
|
Message = "Failed to get unindexed files",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches for files by name or metadata
|
|
/// </summary>
|
|
/// <param name="query">The search query</param>
|
|
/// <param name="path">Optional path to limit search to</param>
|
|
/// <returns>Matching files</returns>
|
|
[HttpGet("search")]
|
|
public async Task<IActionResult> SearchFiles([FromQuery] string query, [FromQuery] string? path = null)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
// Build the query with all conditions at once
|
|
var searchTerm = query.ToLower();
|
|
var baseQuery = db.FileIndexes
|
|
.Where(fi => fi.AccountId == accountId)
|
|
.Include(fi => fi.File);
|
|
|
|
IQueryable<SnCloudFileIndex> queryable;
|
|
|
|
// If a path is specified, find the folder and filter by folder ID
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
var normalizedPath = FileIndexService.NormalizePath(path);
|
|
var targetFolder = await GetOrCreateFolderByPathAsync(normalizedPath, accountId);
|
|
queryable = baseQuery.Where(fi => fi.FolderId == targetFolder.Id);
|
|
}
|
|
else
|
|
{
|
|
queryable = baseQuery;
|
|
}
|
|
|
|
var fileIndexes = await queryable
|
|
.Where(fi =>
|
|
fi.File.Name.ToLower().Contains(searchTerm) ||
|
|
(fi.File.Description != null && fi.File.Description.ToLower().Contains(searchTerm)) ||
|
|
(fi.File.MimeType != null && fi.File.MimeType.ToLower().Contains(searchTerm)))
|
|
.ToListAsync();
|
|
|
|
return Ok(new
|
|
{
|
|
Query = query,
|
|
Path = path,
|
|
Results = fileIndexes,
|
|
TotalCount = fileIndexes.Count()
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to search files for account {AccountId} with query {Query}", accountId, query);
|
|
return new ObjectResult(new ApiError
|
|
{
|
|
Code = "SEARCH_FAILED",
|
|
Message = "Failed to search files",
|
|
Status = 500
|
|
}) { StatusCode = 500 };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new folder
|
|
/// </summary>
|
|
/// <param name="request">The folder creation request</param>
|
|
/// <returns>The created folder</returns>
|
|
[HttpPost("folders")]
|
|
public async Task<IActionResult> CreateFolder([FromBody] CreateFolderRequest request)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
var folder = await folderService.CreateAsync(request.Name, accountId, request.ParentFolderId);
|
|
return Ok(folder);
|
|
}
|
|
catch (ArgumentException ex)
|
|
{
|
|
return BadRequest(ex.Message);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Conflict(ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a folder by ID with its contents
|
|
/// </summary>
|
|
/// <param name="folderId">The folder ID</param>
|
|
/// <returns>The folder with child folders and files</returns>
|
|
[HttpGet("folders/{folderId:guid}")]
|
|
public async Task<IActionResult> GetFolderById(Guid folderId)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
var folder = await folderService.GetByIdAsync(folderId, accountId);
|
|
|
|
if (folder == null)
|
|
{
|
|
return NotFound($"Folder with ID '{folderId}' not found");
|
|
}
|
|
|
|
return Ok(folder);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all folders for the current account
|
|
/// </summary>
|
|
/// <returns>List of folders</returns>
|
|
[HttpGet("folders")]
|
|
public async Task<IActionResult> GetAllFolders()
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
var folders = await folderService.GetByAccountIdAsync(accountId);
|
|
return Ok(folders);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets child folders of a parent folder
|
|
/// </summary>
|
|
/// <param name="parentFolderId">The parent folder ID</param>
|
|
/// <returns>List of child folders</returns>
|
|
[HttpGet("folders/children/{parentFolderId:guid}")]
|
|
public async Task<IActionResult> GetChildFolders(Guid parentFolderId)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
var folders = await folderService.GetChildFoldersAsync(parentFolderId, accountId);
|
|
return Ok(folders);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates a folder's name
|
|
/// </summary>
|
|
/// <param name="folderId">The folder ID</param>
|
|
/// <param name="request">The update request</param>
|
|
/// <returns>The updated folder</returns>
|
|
[HttpPut("folders/{folderId:guid}")]
|
|
public async Task<IActionResult> UpdateFolder(Guid folderId, [FromBody] UpdateFolderRequest request)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
var folder = await folderService.UpdateAsync(folderId, request.Name, accountId);
|
|
|
|
if (folder == null)
|
|
{
|
|
return NotFound($"Folder with ID '{folderId}' not found");
|
|
}
|
|
|
|
return Ok(folder);
|
|
}
|
|
catch (ArgumentException ex)
|
|
{
|
|
return BadRequest(ex.Message);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Conflict(ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a folder and all its contents
|
|
/// </summary>
|
|
/// <param name="folderId">The folder ID</param>
|
|
/// <returns>Success status</returns>
|
|
[HttpDelete("folders/{folderId:guid}")]
|
|
public async Task<IActionResult> DeleteFolder(Guid folderId)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
var deleted = await folderService.DeleteAsync(folderId, accountId);
|
|
|
|
if (!deleted)
|
|
{
|
|
return NotFound($"Folder with ID '{folderId}' not found");
|
|
}
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves a folder to a new parent folder
|
|
/// </summary>
|
|
/// <param name="folderId">The folder ID</param>
|
|
/// <param name="request">The move request</param>
|
|
/// <returns>The moved folder</returns>
|
|
[HttpPost("folders/{folderId:guid}/move")]
|
|
public async Task<IActionResult> MoveFolder(Guid folderId, [FromBody] MoveFolderRequest request)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
var folder = await folderService.MoveAsync(folderId, request.NewParentFolderId, accountId);
|
|
|
|
if (folder == null)
|
|
{
|
|
return NotFound($"Folder with ID '{folderId}' not found");
|
|
}
|
|
|
|
return Ok(folder);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Conflict(ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches for folders by name
|
|
/// </summary>
|
|
/// <param name="searchTerm">The search term</param>
|
|
/// <returns>List of matching folders</returns>
|
|
[HttpGet("folders/search")]
|
|
public async Task<IActionResult> SearchFolders([FromQuery] string searchTerm)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
if (string.IsNullOrWhiteSpace(searchTerm))
|
|
{
|
|
return BadRequest("Search term cannot be empty");
|
|
}
|
|
|
|
var folders = await folderService.SearchAsync(accountId, searchTerm);
|
|
return Ok(folders);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets files in a specific folder
|
|
/// </summary>
|
|
/// <param name="folderId">The folder ID</param>
|
|
/// <returns>List of files in the folder</returns>
|
|
[HttpGet("folders/{folderId:guid}/files")]
|
|
public async Task<IActionResult> GetFilesInFolder(Guid folderId)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
var files = await fileIndexService.GetByFolderAsync(accountId, folderId);
|
|
return Ok(files);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves a file to a different folder
|
|
/// </summary>
|
|
/// <param name="fileIndexId">The file index ID</param>
|
|
/// <param name="request">The move request</param>
|
|
/// <returns>The updated file index</returns>
|
|
[HttpPost("files/{fileIndexId:guid}/move-to-folder")]
|
|
public async Task<IActionResult> MoveFileToFolder(Guid fileIndexId, [FromBody] MoveFileToFolderRequest request)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
return new ObjectResult(ApiError.Unauthorized()) { StatusCode = 401 };
|
|
|
|
var accountId = Guid.Parse(currentUser.Id);
|
|
|
|
try
|
|
{
|
|
var fileIndex = await fileIndexService.MoveAsync(fileIndexId, request.NewFolderId, accountId);
|
|
|
|
if (fileIndex == null)
|
|
{
|
|
return NotFound($"File index with ID '{fileIndexId}' not found");
|
|
}
|
|
|
|
return Ok(fileIndex);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Conflict(ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class MoveFileRequest
|
|
{
|
|
public string NewPath { get; set; } = null!;
|
|
}
|
|
|
|
public class CreateFileIndexRequest
|
|
{
|
|
[MaxLength(32)] public string FileId { get; set; } = null!;
|
|
public string Path { get; set; } = null!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for creating a folder
|
|
/// </summary>
|
|
public class CreateFolderRequest
|
|
{
|
|
/// <summary>
|
|
/// The name of the folder
|
|
/// </summary>
|
|
public string Name { get; set; } = null!;
|
|
|
|
/// <summary>
|
|
/// Optional parent folder ID (null for root folder)
|
|
/// </summary>
|
|
public Guid? ParentFolderId { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for updating a folder
|
|
/// </summary>
|
|
public class UpdateFolderRequest
|
|
{
|
|
/// <summary>
|
|
/// The new name for the folder
|
|
/// </summary>
|
|
public string Name { get; set; } = null!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for moving a folder
|
|
/// </summary>
|
|
public class MoveFolderRequest
|
|
{
|
|
/// <summary>
|
|
/// The new parent folder ID (null for root)
|
|
/// </summary>
|
|
public Guid? NewParentFolderId { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request model for moving a file to a folder
|
|
/// </summary>
|
|
public class MoveFileToFolderRequest
|
|
{
|
|
/// <summary>
|
|
/// The new folder ID
|
|
/// </summary>
|
|
public Guid NewFolderId { get; set; }
|
|
}
|