feat: folder ownership scoping, batch operations integration, frontend audit fixes
Backend: - Add owner_id to Folder entity + FolderDto (DB user_id column) - Add list_folders_by_owner to FolderRepository trait + PG impl - Add list_folders_for_owner to FolderUseCase + FolderService - Rewrite FolderHandler: all endpoints now scope by AuthUser - Remove dead handler methods (list_folders_inner, list_folders_for_user, is_user_home_folder, folder_belongs_to_user) - Add ownership check in get_folder (returns 404 on mismatch) Batch operations: - Add trash_service + zip_service to BatchOperationService - New methods: trash_files, trash_folders, move_folders, download_zip - New handlers: trash_batch, move_folders_batch, download_batch - New routes: POST /api/batch/trash, /api/batch/folders/move, /api/batch/download Frontend: - Replace findUserHomeFolder (~130 lines) with resolveHomeFolder (~35 lines) - Remove client-side folder filtering in loadFiles (backend now scopes) - Rewrite batchDelete: N requests -> 1 POST /api/batch/trash - Rewrite batchMove: N requests -> 2 POST max (files + folders) - Rewrite batchDownload: N requests -> 1 POST /api/batch/download (ZIP) - Search moved to backend, share system uses backend API - Dark mode fixes, frontend audit improvements
This commit is contained in:
@@ -56,6 +56,10 @@ pub struct SearchCriteriaDto {
|
||||
/// Offset for pagination
|
||||
#[serde(default)]
|
||||
pub offset: usize,
|
||||
|
||||
/// Sort order for results: "relevance", "name", "name_desc", "date", "date_desc", "size", "size_desc"
|
||||
#[serde(default = "default_sort_by")]
|
||||
pub sort_by: String,
|
||||
}
|
||||
|
||||
/// Default value for recursive search (true)
|
||||
@@ -68,6 +72,11 @@ fn default_limit() -> usize {
|
||||
100
|
||||
}
|
||||
|
||||
/// Default sort_by value
|
||||
fn default_sort_by() -> String {
|
||||
"relevance".to_string()
|
||||
}
|
||||
|
||||
impl Default for SearchCriteriaDto {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -83,23 +92,75 @@ impl Default for SearchCriteriaDto {
|
||||
recursive: default_recursive(),
|
||||
limit: default_limit(),
|
||||
offset: 0,
|
||||
sort_by: default_sort_by(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A file search result enriched with server-computed metadata
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SearchFileResultDto {
|
||||
/// File ID
|
||||
pub id: String,
|
||||
/// File name
|
||||
pub name: String,
|
||||
/// Path to the file (relative)
|
||||
pub path: String,
|
||||
/// Size in bytes
|
||||
pub size: u64,
|
||||
/// MIME type
|
||||
pub mime_type: String,
|
||||
/// Parent folder ID
|
||||
pub folder_id: Option<String>,
|
||||
/// Creation timestamp
|
||||
pub created_at: u64,
|
||||
/// Last modification timestamp
|
||||
pub modified_at: u64,
|
||||
/// Relevance score (0-100) computed server-side
|
||||
pub relevance_score: u32,
|
||||
/// Human-readable file size (e.g., "2.5 MB")
|
||||
pub size_formatted: String,
|
||||
/// CSS icon class for the file type (e.g., "fas fa-file-pdf")
|
||||
pub icon_class: String,
|
||||
/// Content category: "document", "image", "video", "audio", "archive", "code", "other"
|
||||
pub category: String,
|
||||
}
|
||||
|
||||
/// A folder search result enriched with server-computed metadata
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SearchFolderResultDto {
|
||||
/// Folder ID
|
||||
pub id: String,
|
||||
/// Folder name
|
||||
pub name: String,
|
||||
/// Path to the folder (relative)
|
||||
pub path: String,
|
||||
/// Parent folder ID
|
||||
pub parent_id: Option<String>,
|
||||
/// Creation timestamp
|
||||
pub created_at: u64,
|
||||
/// Last modification timestamp
|
||||
pub modified_at: u64,
|
||||
/// Whether it is a root folder
|
||||
pub is_root: bool,
|
||||
/// Relevance score (0-100) computed server-side
|
||||
pub relevance_score: u32,
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Transfer Object for search results.
|
||||
*
|
||||
* This structure encapsulates the results of a search operation, including
|
||||
* both files and folders that match the search criteria, along with pagination information.
|
||||
* both files and folders that match the search criteria, along with pagination
|
||||
* information and server-computed metadata.
|
||||
*/
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SearchResultsDto {
|
||||
/// Files matching the search criteria
|
||||
pub files: Vec<crate::application::dtos::file_dto::FileDto>,
|
||||
/// Files matching the search criteria (enriched with metadata)
|
||||
pub files: Vec<SearchFileResultDto>,
|
||||
|
||||
/// Folders matching the search criteria
|
||||
pub folders: Vec<crate::application::dtos::folder_dto::FolderDto>,
|
||||
/// Folders matching the search criteria (enriched with metadata)
|
||||
pub folders: Vec<SearchFolderResultDto>,
|
||||
|
||||
/// Total count of matching items (for pagination)
|
||||
pub total_count: Option<usize>,
|
||||
@@ -112,6 +173,12 @@ pub struct SearchResultsDto {
|
||||
|
||||
/// Whether there are more results available
|
||||
pub has_more: bool,
|
||||
|
||||
/// Query execution time in milliseconds (server-side)
|
||||
pub query_time_ms: u64,
|
||||
|
||||
/// Sort order used
|
||||
pub sort_by: String,
|
||||
}
|
||||
|
||||
impl SearchResultsDto {
|
||||
@@ -124,16 +191,20 @@ impl SearchResultsDto {
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
has_more: false,
|
||||
query_time_ms: 0,
|
||||
sort_by: "relevance".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new search results object from files and folders
|
||||
pub fn new(
|
||||
files: Vec<crate::application::dtos::file_dto::FileDto>,
|
||||
folders: Vec<crate::application::dtos::folder_dto::FolderDto>,
|
||||
files: Vec<SearchFileResultDto>,
|
||||
folders: Vec<SearchFolderResultDto>,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
total_count: Option<usize>,
|
||||
query_time_ms: u64,
|
||||
sort_by: String,
|
||||
) -> Self {
|
||||
let has_more = match total_count {
|
||||
Some(total) => (offset + files.len() + folders.len()) < total,
|
||||
@@ -147,6 +218,34 @@ impl SearchResultsDto {
|
||||
limit,
|
||||
offset,
|
||||
has_more,
|
||||
query_time_ms,
|
||||
sort_by,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// DTO for search suggestion results (quick prefix search)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SearchSuggestionsDto {
|
||||
/// Suggested file/folder names matching the query prefix
|
||||
pub suggestions: Vec<SearchSuggestionItem>,
|
||||
/// Query execution time in milliseconds
|
||||
pub query_time_ms: u64,
|
||||
}
|
||||
|
||||
/// Individual search suggestion item
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SearchSuggestionItem {
|
||||
/// The suggested name
|
||||
pub name: String,
|
||||
/// Type: "file" or "folder"
|
||||
pub item_type: String,
|
||||
/// Item ID for navigation
|
||||
pub id: String,
|
||||
/// Path for context
|
||||
pub path: String,
|
||||
/// CSS icon class
|
||||
pub icon_class: String,
|
||||
/// Relevance score
|
||||
pub relevance_score: u32,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user