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:
Dionisio
2026-02-15 23:45:11 +01:00
parent 6e1b77f244
commit 7737ed90c7
33 changed files with 3078 additions and 1958 deletions
+38
View File
@@ -21,6 +21,10 @@ pub struct Folder {
/// Parent folder ID (None if it's a root folder)
parent_id: Option<String>,
/// Owner user ID — scopes folder visibility per user.
/// `None` only for legacy/stub folders; real folders always have an owner.
owner_id: Option<String>,
/// Creation timestamp
created_at: u64,
@@ -38,6 +42,7 @@ impl Default for Folder {
storage_path: StoragePath::from_string("/"),
path_string: "/".to_string(),
parent_id: None,
owner_id: None,
created_at: 0,
modified_at: 0,
}
@@ -51,6 +56,17 @@ impl Folder {
name: String,
storage_path: StoragePath,
parent_id: Option<String>,
) -> FolderResult<Self> {
Self::new_with_owner(id, name, storage_path, parent_id, None)
}
/// Creates a new folder with validation and an explicit owner.
pub fn new_with_owner(
id: String,
name: String,
storage_path: StoragePath,
parent_id: Option<String>,
owner_id: Option<String>,
) -> FolderResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
@@ -71,6 +87,7 @@ impl Folder {
storage_path,
path_string,
parent_id,
owner_id,
created_at: now,
modified_at: now,
})
@@ -84,6 +101,19 @@ impl Folder {
parent_id: Option<String>,
created_at: u64,
modified_at: u64,
) -> FolderResult<Self> {
Self::with_timestamps_and_owner(id, name, storage_path, parent_id, None, created_at, modified_at)
}
/// Creates a folder with specific timestamps and owner (for DB reconstruction)
pub fn with_timestamps_and_owner(
id: String,
name: String,
storage_path: StoragePath,
parent_id: Option<String>,
owner_id: Option<String>,
created_at: u64,
modified_at: u64,
) -> FolderResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
@@ -99,6 +129,7 @@ impl Folder {
storage_path,
path_string,
parent_id,
owner_id,
created_at,
modified_at,
})
@@ -133,6 +164,10 @@ impl Folder {
self.modified_at
}
pub fn owner_id(&self) -> Option<&str> {
self.owner_id.as_deref()
}
/// Creates a new Folder instance from a DTO
/// This function is primarily for conversions in batch handlers
pub fn from_dto(
@@ -153,6 +188,7 @@ impl Folder {
storage_path,
path_string: path,
parent_id,
owner_id: None,
created_at,
modified_at,
}
@@ -188,6 +224,7 @@ impl Folder {
storage_path: new_storage_path,
path_string: new_path_string,
parent_id: self.parent_id.clone(),
owner_id: self.owner_id.clone(),
created_at: self.created_at,
modified_at: now,
})
@@ -219,6 +256,7 @@ impl Folder {
storage_path: new_storage_path,
path_string: new_path_string,
parent_id,
owner_id: self.owner_id.clone(),
created_at: self.created_at,
modified_at: now,
})