refactor(server): file_management_service: move all method without owner check into private, add folder_ports
This commit is contained in:
@@ -254,58 +254,34 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────
|
|
||||||
// Management port (delete, move)
|
|
||||||
// ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Primary port for file management operations
|
/// Primary port for file management operations
|
||||||
pub trait FileManagementUseCase: Send + Sync + 'static {
|
pub trait FileManagementUseCase: Send + Sync + 'static {
|
||||||
/// Moves a file to another folder (system/internal — no ownership check).
|
|
||||||
async fn move_file(
|
|
||||||
&self,
|
|
||||||
file_id: &str,
|
|
||||||
folder_id: Option<String>,
|
|
||||||
) -> Result<FileDto, DomainError>;
|
|
||||||
|
|
||||||
/// Moves a file, enforcing that `caller_id` is the owner.
|
/// Moves a file, enforcing that `caller_id` is the owner.
|
||||||
async fn move_file_owned(
|
async fn move_file_with_perms(
|
||||||
&self,
|
&self,
|
||||||
file_id: &str,
|
file_id: &str,
|
||||||
caller_id: Uuid,
|
caller_id: Uuid,
|
||||||
folder_id: Option<String>,
|
folder_id: Option<String>,
|
||||||
) -> Result<FileDto, DomainError>;
|
) -> Result<FileDto, DomainError>;
|
||||||
|
|
||||||
/// Copies a file to another folder (zero-copy with dedup).
|
|
||||||
async fn copy_file(
|
|
||||||
&self,
|
|
||||||
file_id: &str,
|
|
||||||
target_folder_id: Option<String>,
|
|
||||||
) -> Result<FileDto, DomainError>;
|
|
||||||
|
|
||||||
/// Copies a file, enforcing that `caller_id` is the owner.
|
/// Copies a file, enforcing that `caller_id` is the owner.
|
||||||
async fn copy_file_owned(
|
async fn copy_file_with_perms(
|
||||||
&self,
|
&self,
|
||||||
file_id: &str,
|
file_id: &str,
|
||||||
caller_id: Uuid,
|
caller_id: Uuid,
|
||||||
target_folder_id: Option<String>,
|
target_folder_id: Option<String>,
|
||||||
) -> Result<FileDto, DomainError>;
|
) -> Result<FileDto, DomainError>;
|
||||||
|
|
||||||
/// Renames a file (system/internal — no ownership check).
|
|
||||||
async fn rename_file(&self, file_id: &str, new_name: &str) -> Result<FileDto, DomainError>;
|
|
||||||
|
|
||||||
/// Renames a file, enforcing that `caller_id` is the owner.
|
/// Renames a file, enforcing that `caller_id` is the owner.
|
||||||
async fn rename_file_owned(
|
async fn rename_file_with_perms(
|
||||||
&self,
|
&self,
|
||||||
file_id: &str,
|
file_id: &str,
|
||||||
caller_id: Uuid,
|
caller_id: Uuid,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<FileDto, DomainError>;
|
) -> Result<FileDto, DomainError>;
|
||||||
|
|
||||||
/// Deletes a file (system/internal — no ownership check).
|
|
||||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Deletes a file, enforcing that `caller_id` is the owner.
|
/// Deletes a file, enforcing that `caller_id` is the owner.
|
||||||
async fn delete_file_owned(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError>;
|
async fn delete_file_with_perms(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError>;
|
||||||
|
|
||||||
/// Smart delete: trash-first with dedup reference cleanup.
|
/// Smart delete: trash-first with dedup reference cleanup.
|
||||||
///
|
///
|
||||||
@@ -314,30 +290,22 @@ pub trait FileManagementUseCase: Send + Sync + 'static {
|
|||||||
/// 3. Decrements the dedup reference count for the content hash.
|
/// 3. Decrements the dedup reference count for the content hash.
|
||||||
///
|
///
|
||||||
/// Returns `Ok(true)` when trashed, `Ok(false)` when permanently deleted.
|
/// Returns `Ok(true)` when trashed, `Ok(false)` when permanently deleted.
|
||||||
async fn delete_with_cleanup(&self, id: &str, user_id: Uuid) -> Result<bool, DomainError>;
|
async fn delete_and_cleanup_with_perms(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
user_id: Uuid,
|
||||||
|
) -> Result<bool, DomainError>;
|
||||||
|
|
||||||
/// Copies an entire folder subtree atomically (WebDAV COPY Depth: infinity).
|
/// Copies an entire folder subtree atomically (WebDAV COPY Depth: infinity).
|
||||||
|
/// enforcing that `caller_id` owns both the source folder
|
||||||
|
/// and the target parent folder.
|
||||||
///
|
///
|
||||||
/// Creates a copy of `source_folder_id` (with optional name override) under
|
/// Creates a copy of `source_folder_id` (with optional name override) under
|
||||||
/// `target_parent_id`, including ALL sub-folders and files. Files are
|
/// `target_parent_id`, including ALL sub-folders and files. Files are
|
||||||
/// zero-copy (blob ref_counts incremented in batch).
|
/// zero-copy (blob ref_counts incremented in batch).
|
||||||
///
|
///
|
||||||
/// Default: returns error (only available with PostgreSQL backend).
|
/// Default: returns error (only available with PostgreSQL backend).
|
||||||
async fn copy_folder_tree(
|
async fn copy_folder_tree_with_perms(
|
||||||
&self,
|
|
||||||
_source_folder_id: &str,
|
|
||||||
_target_parent_id: Option<String>,
|
|
||||||
_dest_name: Option<String>,
|
|
||||||
) -> Result<CopyFolderTreeResult, DomainError> {
|
|
||||||
Err(DomainError::internal_error(
|
|
||||||
"FileManagement",
|
|
||||||
"copy_folder_tree not implemented",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies a folder tree, enforcing that `caller_id` owns both the source folder
|
|
||||||
/// and the target parent folder.
|
|
||||||
async fn copy_folder_tree_owned(
|
|
||||||
&self,
|
&self,
|
||||||
source_folder_id: &str,
|
source_folder_id: &str,
|
||||||
caller_id: Uuid,
|
caller_id: Uuid,
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/// Primary port for folder operations
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::application::dtos::folder_dto::{
|
||||||
|
CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::errors::DomainError;
|
||||||
|
|
||||||
|
pub trait FolderUseCase: Send + Sync + 'static {
|
||||||
|
/// Creates a new folder
|
||||||
|
async fn create_folder_with_perms(
|
||||||
|
&self,
|
||||||
|
dto: CreateFolderDto,
|
||||||
|
caller_id: Uuid,
|
||||||
|
) -> Result<FolderDto, DomainError>;
|
||||||
|
|
||||||
|
/// Gets a folder by its ID
|
||||||
|
async fn get_folder(&self, id: &str) -> Result<FolderDto, DomainError>;
|
||||||
|
|
||||||
|
/// Gets a folder by its ID, enforcing that `caller_id` is the owner.
|
||||||
|
///
|
||||||
|
/// Returns `NotFound` if the folder does not exist **or** belongs to
|
||||||
|
/// another user. All user-facing handlers should use this method.
|
||||||
|
async fn get_folder_with_perms(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
caller_id: Uuid,
|
||||||
|
) -> Result<FolderDto, DomainError>;
|
||||||
|
|
||||||
|
/// Gets a folder by its path
|
||||||
|
async fn get_folder_by_path(&self, path: &str) -> Result<FolderDto, DomainError>;
|
||||||
|
|
||||||
|
/// Lists folders within a parent folder
|
||||||
|
async fn list_folders(&self, parent_id: Option<&str>) -> Result<Vec<FolderDto>, DomainError>;
|
||||||
|
|
||||||
|
/// Lists folders scoped to a specific owner (for user-facing endpoints).
|
||||||
|
/// At root level, only returns folders belonging to this user.
|
||||||
|
async fn list_folders_for_owner(
|
||||||
|
&self,
|
||||||
|
parent_id: Option<&str>,
|
||||||
|
owner_id: Uuid,
|
||||||
|
) -> Result<Vec<FolderDto>, DomainError>;
|
||||||
|
|
||||||
|
/// Lists folders with pagination
|
||||||
|
async fn list_folders_paginated(
|
||||||
|
&self,
|
||||||
|
parent_id: Option<&str>,
|
||||||
|
pagination: &crate::application::dtos::pagination::PaginationRequestDto,
|
||||||
|
) -> Result<crate::application::dtos::pagination::PaginatedResponseDto<FolderDto>, DomainError>;
|
||||||
|
|
||||||
|
/// Lists folders with pagination, scoped to a specific owner.
|
||||||
|
async fn list_folders_for_owner_paginated(
|
||||||
|
&self,
|
||||||
|
parent_id: Option<&str>,
|
||||||
|
owner_id: Uuid,
|
||||||
|
pagination: &crate::application::dtos::pagination::PaginationRequestDto,
|
||||||
|
) -> Result<crate::application::dtos::pagination::PaginatedResponseDto<FolderDto>, DomainError>;
|
||||||
|
|
||||||
|
/// Renames a folder (ownership verified against caller_id)
|
||||||
|
async fn rename_folder_with_perms(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
dto: RenameFolderDto,
|
||||||
|
caller_id: Uuid,
|
||||||
|
) -> Result<FolderDto, DomainError>;
|
||||||
|
|
||||||
|
/// Moves a folder to another parent (ownership verified against caller_id)
|
||||||
|
async fn move_folder_with_perms(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
dto: MoveFolderDto,
|
||||||
|
caller_id: Uuid,
|
||||||
|
) -> Result<FolderDto, DomainError>;
|
||||||
|
|
||||||
|
/// Deletes a folder (ownership verified against caller_id)
|
||||||
|
async fn delete_folder_with_perms(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
/// Creates a root-level home folder for a user during registration.
|
||||||
|
async fn create_home_folder(
|
||||||
|
&self,
|
||||||
|
user_id: Uuid,
|
||||||
|
name: String,
|
||||||
|
) -> Result<FolderDto, DomainError>;
|
||||||
|
|
||||||
|
/// Lists every folder in a subtree rooted at `folder_id` (inclusive),
|
||||||
|
/// ordered by path. Uses ltree `<@` — single GiST-indexed query.
|
||||||
|
///
|
||||||
|
/// Default: returns an empty vec (stubs / mocks).
|
||||||
|
async fn list_subtree_folders(&self, folder_id: &str) -> Result<Vec<FolderDto>, DomainError> {
|
||||||
|
let _ = folder_id;
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,97 +2,11 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::application::dtos::folder_dto::{
|
|
||||||
CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto,
|
|
||||||
};
|
|
||||||
use crate::application::dtos::search_dto::{
|
use crate::application::dtos::search_dto::{
|
||||||
SearchCriteriaDto, SearchResultsDto, SearchSuggestionsDto,
|
SearchCriteriaDto, SearchResultsDto, SearchSuggestionsDto,
|
||||||
};
|
};
|
||||||
use crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
|
|
||||||
/// Primary port for folder operations
|
|
||||||
pub trait FolderUseCase: Send + Sync + 'static {
|
|
||||||
/// Creates a new folder
|
|
||||||
async fn create_folder(
|
|
||||||
&self,
|
|
||||||
dto: CreateFolderDto,
|
|
||||||
caller_id: Uuid,
|
|
||||||
) -> Result<FolderDto, DomainError>;
|
|
||||||
|
|
||||||
/// Gets a folder by its ID
|
|
||||||
async fn get_folder(&self, id: &str) -> Result<FolderDto, DomainError>;
|
|
||||||
|
|
||||||
/// Gets a folder by its ID, enforcing that `caller_id` is the owner.
|
|
||||||
///
|
|
||||||
/// Returns `NotFound` if the folder does not exist **or** belongs to
|
|
||||||
/// another user. All user-facing handlers should use this method.
|
|
||||||
async fn get_folder_owned(&self, id: &str, caller_id: Uuid) -> Result<FolderDto, DomainError>;
|
|
||||||
|
|
||||||
/// Gets a folder by its path
|
|
||||||
async fn get_folder_by_path(&self, path: &str) -> Result<FolderDto, DomainError>;
|
|
||||||
|
|
||||||
/// Lists folders within a parent folder
|
|
||||||
async fn list_folders(&self, parent_id: Option<&str>) -> Result<Vec<FolderDto>, DomainError>;
|
|
||||||
|
|
||||||
/// Lists folders scoped to a specific owner (for user-facing endpoints).
|
|
||||||
/// At root level, only returns folders belonging to this user.
|
|
||||||
async fn list_folders_for_owner(
|
|
||||||
&self,
|
|
||||||
parent_id: Option<&str>,
|
|
||||||
owner_id: Uuid,
|
|
||||||
) -> Result<Vec<FolderDto>, DomainError>;
|
|
||||||
|
|
||||||
/// Lists folders with pagination
|
|
||||||
async fn list_folders_paginated(
|
|
||||||
&self,
|
|
||||||
parent_id: Option<&str>,
|
|
||||||
pagination: &crate::application::dtos::pagination::PaginationRequestDto,
|
|
||||||
) -> Result<crate::application::dtos::pagination::PaginatedResponseDto<FolderDto>, DomainError>;
|
|
||||||
|
|
||||||
/// Lists folders with pagination, scoped to a specific owner.
|
|
||||||
async fn list_folders_for_owner_paginated(
|
|
||||||
&self,
|
|
||||||
parent_id: Option<&str>,
|
|
||||||
owner_id: Uuid,
|
|
||||||
pagination: &crate::application::dtos::pagination::PaginationRequestDto,
|
|
||||||
) -> Result<crate::application::dtos::pagination::PaginatedResponseDto<FolderDto>, DomainError>;
|
|
||||||
|
|
||||||
/// Renames a folder (ownership verified against caller_id)
|
|
||||||
async fn rename_folder(
|
|
||||||
&self,
|
|
||||||
id: &str,
|
|
||||||
dto: RenameFolderDto,
|
|
||||||
caller_id: Uuid,
|
|
||||||
) -> Result<FolderDto, DomainError>;
|
|
||||||
|
|
||||||
/// Moves a folder to another parent (ownership verified against caller_id)
|
|
||||||
async fn move_folder(
|
|
||||||
&self,
|
|
||||||
id: &str,
|
|
||||||
dto: MoveFolderDto,
|
|
||||||
caller_id: Uuid,
|
|
||||||
) -> Result<FolderDto, DomainError>;
|
|
||||||
|
|
||||||
/// Deletes a folder (ownership verified against caller_id)
|
|
||||||
async fn delete_folder(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Creates a root-level home folder for a user during registration.
|
|
||||||
async fn create_home_folder(
|
|
||||||
&self,
|
|
||||||
user_id: Uuid,
|
|
||||||
name: String,
|
|
||||||
) -> Result<FolderDto, DomainError>;
|
|
||||||
|
|
||||||
/// Lists every folder in a subtree rooted at `folder_id` (inclusive),
|
|
||||||
/// ordered by path. Uses ltree `<@` — single GiST-indexed query.
|
|
||||||
///
|
|
||||||
/// Default: returns an empty vec (stubs / mocks).
|
|
||||||
async fn list_subtree_folders(&self, folder_id: &str) -> Result<Vec<FolderDto>, DomainError> {
|
|
||||||
let _ = folder_id;
|
|
||||||
Ok(Vec::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary port for file and folder search.
|
* Primary port for file and folder search.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub mod dedup_ports;
|
|||||||
pub mod favorites_ports;
|
pub mod favorites_ports;
|
||||||
pub mod file_lifecycle;
|
pub mod file_lifecycle;
|
||||||
pub mod file_ports;
|
pub mod file_ports;
|
||||||
|
pub mod folder_ports;
|
||||||
pub mod inbound;
|
pub mod inbound;
|
||||||
pub mod music_ports;
|
pub mod music_ports;
|
||||||
pub mod outbound;
|
pub mod outbound;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::application::ports::auth_ports::{
|
|||||||
OidcIdClaims, OidcServicePort, PasswordHasherPort, SessionStoragePort, TokenServicePort,
|
OidcIdClaims, OidcServicePort, PasswordHasherPort, SessionStoragePort, TokenServicePort,
|
||||||
UserStoragePort,
|
UserStoragePort,
|
||||||
};
|
};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
use crate::application::services::folder_service::FolderService;
|
use crate::application::services::folder_service::FolderService;
|
||||||
use crate::common::config::OidcConfig;
|
use crate::common::config::OidcConfig;
|
||||||
use crate::common::errors::{DomainError, ErrorKind};
|
use crate::common::errors::{DomainError, ErrorKind};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use tracing::info;
|
|||||||
use crate::application::dtos::file_dto::FileDto;
|
use crate::application::dtos::file_dto::FileDto;
|
||||||
use crate::application::dtos::folder_dto::{FolderDto, MoveFolderDto};
|
use crate::application::dtos::folder_dto::{FolderDto, MoveFolderDto};
|
||||||
use crate::application::ports::file_ports::{FileManagementUseCase, FileRetrievalUseCase};
|
use crate::application::ports::file_ports::{FileManagementUseCase, FileRetrievalUseCase};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
use crate::application::ports::storage_ports::CopyFolderTreeResult;
|
use crate::application::ports::storage_ports::CopyFolderTreeResult;
|
||||||
use crate::application::ports::trash_ports::TrashUseCase;
|
use crate::application::ports::trash_ports::TrashUseCase;
|
||||||
use crate::application::services::file_management_service::FileManagementService;
|
use crate::application::services::file_management_service::FileManagementService;
|
||||||
@@ -145,7 +145,7 @@ impl BatchOperationService {
|
|||||||
|
|
||||||
async move {
|
async move {
|
||||||
let copy_result = mgmt
|
let copy_result = mgmt
|
||||||
.copy_file_owned(&file_id, user_id, target_folder.map(|s| s.to_string()))
|
.copy_file_with_perms(&file_id, user_id, target_folder.map(|s| s.to_string()))
|
||||||
.await;
|
.await;
|
||||||
(file_id, copy_result)
|
(file_id, copy_result)
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ impl BatchOperationService {
|
|||||||
|
|
||||||
async move {
|
async move {
|
||||||
let move_result = mgmt
|
let move_result = mgmt
|
||||||
.move_file_owned(&file_id, user_id, target_folder.map(|s| s.to_string()))
|
.move_file_with_perms(&file_id, user_id, target_folder.map(|s| s.to_string()))
|
||||||
.await;
|
.await;
|
||||||
(file_id, move_result)
|
(file_id, move_result)
|
||||||
}
|
}
|
||||||
@@ -270,7 +270,7 @@ impl BatchOperationService {
|
|||||||
let mgmt = self.file_management.clone();
|
let mgmt = self.file_management.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let delete_result = mgmt.delete_file_owned(&file_id, user_id).await;
|
let delete_result = mgmt.delete_file_with_perms(&file_id, user_id).await;
|
||||||
let id_for_result = file_id.clone();
|
let id_for_result = file_id.clone();
|
||||||
(file_id, delete_result.map(|_| id_for_result))
|
(file_id, delete_result.map(|_| id_for_result))
|
||||||
}
|
}
|
||||||
@@ -390,7 +390,9 @@ impl BatchOperationService {
|
|||||||
let folder_service = self.folder_service.clone();
|
let folder_service = self.folder_service.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let delete_result = folder_service.delete_folder(&folder_id, user_id).await;
|
let delete_result = folder_service
|
||||||
|
.delete_folder_with_perms(&folder_id, user_id)
|
||||||
|
.await;
|
||||||
let id_for_result = folder_id.clone();
|
let id_for_result = folder_id.clone();
|
||||||
(folder_id, delete_result.map(|_| id_for_result))
|
(folder_id, delete_result.map(|_| id_for_result))
|
||||||
}
|
}
|
||||||
@@ -583,7 +585,9 @@ impl BatchOperationService {
|
|||||||
let dto = MoveFolderDto {
|
let dto = MoveFolderDto {
|
||||||
parent_id: target.map(|s| s.to_string()),
|
parent_id: target.map(|s| s.to_string()),
|
||||||
};
|
};
|
||||||
let move_result = folder_service.move_folder(&folder_id, dto, user_id).await;
|
let move_result = folder_service
|
||||||
|
.move_folder_with_perms(&folder_id, dto, user_id)
|
||||||
|
.await;
|
||||||
(folder_id, move_result)
|
(folder_id, move_result)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -644,7 +648,7 @@ impl BatchOperationService {
|
|||||||
|
|
||||||
async move {
|
async move {
|
||||||
let copy_result = file_management
|
let copy_result = file_management
|
||||||
.copy_folder_tree_owned(
|
.copy_folder_tree_with_perms(
|
||||||
&folder_id,
|
&folder_id,
|
||||||
user_id,
|
user_id,
|
||||||
target.map(|s| s.to_string()),
|
target.map(|s| s.to_string()),
|
||||||
@@ -732,7 +736,7 @@ impl BatchOperationService {
|
|||||||
for folder_id in &folder_ids {
|
for folder_id in &folder_ids {
|
||||||
match self
|
match self
|
||||||
.folder_service
|
.folder_service
|
||||||
.get_folder_owned(folder_id, user_id)
|
.get_folder_with_perms(folder_id, user_id)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(root_folder) => {
|
Ok(root_folder) => {
|
||||||
@@ -979,7 +983,7 @@ impl BatchOperationService {
|
|||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
parent_id: parent_id.clone(),
|
parent_id: parent_id.clone(),
|
||||||
};
|
};
|
||||||
let create_result = folder_service.create_folder(dto, user_id).await;
|
let create_result = folder_service.create_folder_with_perms(dto, user_id).await;
|
||||||
let id = format!("{}:{}", name, parent_id.unwrap_or_default());
|
let id = format!("{}:{}", name, parent_id.unwrap_or_default());
|
||||||
(id, create_result)
|
(id, create_result)
|
||||||
}
|
}
|
||||||
@@ -1039,7 +1043,9 @@ impl BatchOperationService {
|
|||||||
let folder_service = self.folder_service.clone();
|
let folder_service = self.folder_service.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let get_result = folder_service.get_folder_owned(&folder_id, user_id).await;
|
let get_result = folder_service
|
||||||
|
.get_folder_with_perms(&folder_id, user_id)
|
||||||
|
.await;
|
||||||
(folder_id, get_result)
|
(folder_id, get_result)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -103,9 +103,8 @@ impl FileManagementService {
|
|||||||
};
|
};
|
||||||
folder_repo.verify_owner(target, caller_id).await
|
folder_repo.verify_owner(target, caller_id).await
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl FileManagementUseCase for FileManagementService {
|
//impl FileManagementPrivateUseCase for FileManagementService {
|
||||||
async fn move_file(
|
async fn move_file(
|
||||||
&self,
|
&self,
|
||||||
file_id: &str,
|
file_id: &str,
|
||||||
@@ -135,20 +134,6 @@ impl FileManagementUseCase for FileManagementService {
|
|||||||
Ok(FileDto::from(moved_file))
|
Ok(FileDto::from(moved_file))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn move_file_owned(
|
|
||||||
&self,
|
|
||||||
file_id: &str,
|
|
||||||
caller_id: Uuid,
|
|
||||||
folder_id: Option<String>,
|
|
||||||
) -> Result<FileDto, DomainError> {
|
|
||||||
// Verify file ownership first
|
|
||||||
self.verify_owner(file_id, caller_id).await?;
|
|
||||||
// Verify target folder ownership (prevents file from "disappearing")
|
|
||||||
self.verify_target_folder_owner(folder_id.as_deref(), caller_id)
|
|
||||||
.await?;
|
|
||||||
self.move_file(file_id, folder_id).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn copy_file(
|
async fn copy_file(
|
||||||
&self,
|
&self,
|
||||||
file_id: &str,
|
file_id: &str,
|
||||||
@@ -178,18 +163,6 @@ impl FileManagementUseCase for FileManagementService {
|
|||||||
Ok(FileDto::from(copied_file))
|
Ok(FileDto::from(copied_file))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn copy_file_owned(
|
|
||||||
&self,
|
|
||||||
file_id: &str,
|
|
||||||
caller_id: Uuid,
|
|
||||||
target_folder_id: Option<String>,
|
|
||||||
) -> Result<FileDto, DomainError> {
|
|
||||||
self.verify_owner(file_id, caller_id).await?;
|
|
||||||
self.verify_target_folder_owner(target_folder_id.as_deref(), caller_id)
|
|
||||||
.await?;
|
|
||||||
self.copy_file(file_id, target_folder_id).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn rename_file(&self, file_id: &str, new_name: &str) -> Result<FileDto, DomainError> {
|
async fn rename_file(&self, file_id: &str, new_name: &str) -> Result<FileDto, DomainError> {
|
||||||
if let Err(reason) = validate_storage_name(new_name) {
|
if let Err(reason) = validate_storage_name(new_name) {
|
||||||
return Err(DomainError::validation_error(format!(
|
return Err(DomainError::validation_error(format!(
|
||||||
@@ -217,65 +190,7 @@ impl FileManagementUseCase for FileManagementService {
|
|||||||
Ok(FileDto::from(renamed_file))
|
Ok(FileDto::from(renamed_file))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rename_file_owned(
|
|
||||||
&self,
|
|
||||||
file_id: &str,
|
|
||||||
caller_id: Uuid,
|
|
||||||
new_name: &str,
|
|
||||||
) -> Result<FileDto, DomainError> {
|
|
||||||
self.verify_owner(file_id, caller_id).await?;
|
|
||||||
self.rename_file(file_id, new_name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError> {
|
async fn delete_file(&self, id: &str) -> Result<(), DomainError> {
|
||||||
self.file_repository.delete_file(id).await?;
|
|
||||||
if let Some(cc) = &self.content_cache {
|
|
||||||
cc.invalidate(id).await;
|
|
||||||
}
|
|
||||||
for hook in &self.file_deleted_hooks {
|
|
||||||
hook.on_file_deleted(id).await;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_file_owned(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError> {
|
|
||||||
self.verify_owner(id, caller_id).await?;
|
|
||||||
self.delete_file(id).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Smart delete: trash-first with dedup reference cleanup.
|
|
||||||
///
|
|
||||||
/// Blob ref_count bookkeeping is handled entirely by the PG trigger
|
|
||||||
/// `trg_files_decrement_blob_ref` which fires on DELETE FROM storage.files.
|
|
||||||
/// We do NOT decrement here — trashing is a soft-delete (UPDATE, not DELETE)
|
|
||||||
/// so the blob must remain referenced until the file is permanently deleted.
|
|
||||||
async fn delete_with_cleanup(&self, id: &str, user_id: Uuid) -> Result<bool, DomainError> {
|
|
||||||
// Step 1: Try trash (soft delete — file row stays, blob stays referenced)
|
|
||||||
if let Some(trash) = &self.trash_service {
|
|
||||||
info!("Moving file to trash: {}", id);
|
|
||||||
match trash.move_to_trash(id, "file", user_id).await {
|
|
||||||
Ok(_) => {
|
|
||||||
info!("File successfully moved to trash: {}", id);
|
|
||||||
// Invalidate content cache — trashed files must not be served.
|
|
||||||
if let Some(cc) = &self.content_cache {
|
|
||||||
cc.invalidate(id).await;
|
|
||||||
}
|
|
||||||
// Do NOT decrement blob ref here — the file row still exists
|
|
||||||
// (is_trashed = TRUE). The trigger will decrement when the
|
|
||||||
// row is actually DELETEd during trash emptying.
|
|
||||||
return Ok(true); // trashed
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("Could not move file to trash: {:?}", err);
|
|
||||||
warn!("Falling back to permanent delete");
|
|
||||||
// fall through
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("Trash service not available, using permanent delete");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Permanent delete — trigger handles blob ref_count
|
|
||||||
warn!("Permanently deleting file: {}", id);
|
warn!("Permanently deleting file: {}", id);
|
||||||
self.file_repository.delete_file(id).await?;
|
self.file_repository.delete_file(id).await?;
|
||||||
if let Some(cc) = &self.content_cache {
|
if let Some(cc) = &self.content_cache {
|
||||||
@@ -285,8 +200,7 @@ impl FileManagementUseCase for FileManagementService {
|
|||||||
hook.on_file_deleted(id).await;
|
hook.on_file_deleted(id).await;
|
||||||
}
|
}
|
||||||
info!("File permanently deleted: {}", id);
|
info!("File permanently deleted: {}", id);
|
||||||
|
Ok(())
|
||||||
Ok(false) // permanently deleted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn copy_folder_tree(
|
async fn copy_folder_tree(
|
||||||
@@ -319,8 +233,95 @@ impl FileManagementUseCase for FileManagementService {
|
|||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn copy_folder_tree_owned(
|
impl FileManagementUseCase for FileManagementService {
|
||||||
|
async fn move_file_with_perms(
|
||||||
|
&self,
|
||||||
|
file_id: &str,
|
||||||
|
caller_id: Uuid,
|
||||||
|
folder_id: Option<String>,
|
||||||
|
) -> Result<FileDto, DomainError> {
|
||||||
|
// Verify file ownership first
|
||||||
|
self.verify_owner(file_id, caller_id).await?;
|
||||||
|
// Verify target folder ownership (prevents file from "disappearing")
|
||||||
|
self.verify_target_folder_owner(folder_id.as_deref(), caller_id)
|
||||||
|
.await?;
|
||||||
|
self.move_file(file_id, folder_id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn copy_file_with_perms(
|
||||||
|
&self,
|
||||||
|
file_id: &str,
|
||||||
|
caller_id: Uuid,
|
||||||
|
target_folder_id: Option<String>,
|
||||||
|
) -> Result<FileDto, DomainError> {
|
||||||
|
self.verify_owner(file_id, caller_id).await?;
|
||||||
|
self.verify_target_folder_owner(target_folder_id.as_deref(), caller_id)
|
||||||
|
.await?;
|
||||||
|
self.copy_file(file_id, target_folder_id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn rename_file_with_perms(
|
||||||
|
&self,
|
||||||
|
file_id: &str,
|
||||||
|
caller_id: Uuid,
|
||||||
|
new_name: &str,
|
||||||
|
) -> Result<FileDto, DomainError> {
|
||||||
|
self.verify_owner(file_id, caller_id).await?;
|
||||||
|
self.rename_file(file_id, new_name).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_file_with_perms(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError> {
|
||||||
|
self.verify_owner(id, caller_id).await?;
|
||||||
|
self.delete_file(id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Smart delete: trash-first with dedup reference cleanup.
|
||||||
|
///
|
||||||
|
/// Blob ref_count bookkeeping is handled entirely by the PG trigger
|
||||||
|
/// `trg_files_decrement_blob_ref` which fires on DELETE FROM storage.files.
|
||||||
|
/// We do NOT decrement here — trashing is a soft-delete (UPDATE, not DELETE)
|
||||||
|
/// so the blob must remain referenced until the file is permanently deleted.
|
||||||
|
async fn delete_and_cleanup_with_perms(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
caller_id: Uuid,
|
||||||
|
) -> Result<bool, DomainError> {
|
||||||
|
self.verify_owner(id, caller_id).await?;
|
||||||
|
// Step 1: Try trash (soft delete — file row stays, blob stays referenced)
|
||||||
|
if let Some(trash) = &self.trash_service {
|
||||||
|
info!("Moving file to trash: {}", id);
|
||||||
|
match trash.move_to_trash(id, "file", caller_id).await {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("File successfully moved to trash: {}", id);
|
||||||
|
// Invalidate content cache — trashed files must not be served.
|
||||||
|
if let Some(cc) = &self.content_cache {
|
||||||
|
cc.invalidate(id).await;
|
||||||
|
}
|
||||||
|
// Do NOT decrement blob ref here — the file row still exists
|
||||||
|
// (is_trashed = TRUE). The trigger will decrement when the
|
||||||
|
// row is actually DELETEd during trash emptying.
|
||||||
|
return Ok(true); // trashed
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Could not move file to trash: {:?}", err);
|
||||||
|
warn!("Falling back to permanent delete");
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Trash service not available, using permanent delete");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Permanent delete — trigger handles blob ref_count
|
||||||
|
|
||||||
|
self.delete_file(id).await?;
|
||||||
|
|
||||||
|
Ok(false) // permanently deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn copy_folder_tree_with_perms(
|
||||||
&self,
|
&self,
|
||||||
source_folder_id: &str,
|
source_folder_id: &str,
|
||||||
caller_id: Uuid,
|
caller_id: Uuid,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::application::dtos::folder_dto::{
|
use crate::application::dtos::folder_dto::{
|
||||||
CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto,
|
CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto,
|
||||||
};
|
};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
use crate::common::errors::{DomainError, ErrorKind};
|
use crate::common::errors::{DomainError, ErrorKind};
|
||||||
use crate::domain::repositories::folder_repository::FolderRepository;
|
use crate::domain::repositories::folder_repository::FolderRepository;
|
||||||
use crate::domain::services::path_service::{StoragePath, validate_storage_name};
|
use crate::domain::services::path_service::{StoragePath, validate_storage_name};
|
||||||
@@ -25,7 +25,7 @@ impl FolderService {
|
|||||||
struct FolderServiceStub;
|
struct FolderServiceStub;
|
||||||
|
|
||||||
impl FolderUseCase for FolderServiceStub {
|
impl FolderUseCase for FolderServiceStub {
|
||||||
async fn create_folder(
|
async fn create_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_dto: CreateFolderDto,
|
_dto: CreateFolderDto,
|
||||||
_user_id: Uuid,
|
_user_id: Uuid,
|
||||||
@@ -37,7 +37,7 @@ impl FolderService {
|
|||||||
Ok(FolderDto::empty())
|
Ok(FolderDto::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_folder_owned(
|
async fn get_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_id: &str,
|
_id: &str,
|
||||||
_caller_id: Uuid,
|
_caller_id: Uuid,
|
||||||
@@ -101,7 +101,7 @@ impl FolderService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rename_folder(
|
async fn rename_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_id: &str,
|
_id: &str,
|
||||||
_dto: RenameFolderDto,
|
_dto: RenameFolderDto,
|
||||||
@@ -110,7 +110,7 @@ impl FolderService {
|
|||||||
Ok(FolderDto::empty())
|
Ok(FolderDto::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn move_folder(
|
async fn move_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_id: &str,
|
_id: &str,
|
||||||
_dto: MoveFolderDto,
|
_dto: MoveFolderDto,
|
||||||
@@ -119,7 +119,11 @@ impl FolderService {
|
|||||||
Ok(FolderDto::empty())
|
Ok(FolderDto::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_folder(&self, _id: &str, _caller_id: Uuid) -> Result<(), DomainError> {
|
async fn delete_folder_with_perms(
|
||||||
|
&self,
|
||||||
|
_id: &str,
|
||||||
|
_caller_id: Uuid,
|
||||||
|
) -> Result<(), DomainError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +142,7 @@ impl FolderService {
|
|||||||
|
|
||||||
impl FolderUseCase for FolderService {
|
impl FolderUseCase for FolderService {
|
||||||
/// Creates a new folder
|
/// Creates a new folder
|
||||||
async fn create_folder(
|
async fn create_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
dto: CreateFolderDto,
|
dto: CreateFolderDto,
|
||||||
caller_id: Uuid,
|
caller_id: Uuid,
|
||||||
@@ -204,7 +208,11 @@ impl FolderUseCase for FolderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a folder by its ID, enforcing that `caller_id` is the owner.
|
/// Gets a folder by its ID, enforcing that `caller_id` is the owner.
|
||||||
async fn get_folder_owned(&self, id: &str, caller_id: Uuid) -> Result<FolderDto, DomainError> {
|
async fn get_folder_with_perms(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
caller_id: Uuid,
|
||||||
|
) -> Result<FolderDto, DomainError> {
|
||||||
let folder_dto = self.get_folder(id).await?;
|
let folder_dto = self.get_folder(id).await?;
|
||||||
if folder_dto.owner_id.as_deref() != Some(&caller_id.to_string()) {
|
if folder_dto.owner_id.as_deref() != Some(&caller_id.to_string()) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -261,10 +269,6 @@ impl FolderUseCase for FolderService {
|
|||||||
parent_id: Option<&str>,
|
parent_id: Option<&str>,
|
||||||
owner_id: Uuid,
|
owner_id: Uuid,
|
||||||
) -> Result<Vec<FolderDto>, DomainError> {
|
) -> Result<Vec<FolderDto>, DomainError> {
|
||||||
let owner_id_short = {
|
|
||||||
let s = owner_id.to_string();
|
|
||||||
s[..8.min(s.len())].to_string()
|
|
||||||
};
|
|
||||||
let folders = self
|
let folders = self
|
||||||
.folder_storage
|
.folder_storage
|
||||||
.list_folders_by_owner(parent_id, owner_id)
|
.list_folders_by_owner(parent_id, owner_id)
|
||||||
@@ -286,6 +290,10 @@ impl FolderUseCase for FolderService {
|
|||||||
"No root folders found for user {}, creating home folder automatically",
|
"No root folders found for user {}, creating home folder automatically",
|
||||||
owner_id
|
owner_id
|
||||||
);
|
);
|
||||||
|
let owner_id_short = {
|
||||||
|
let s = owner_id.to_string();
|
||||||
|
s[..8.min(s.len())].to_string()
|
||||||
|
};
|
||||||
let folder_name = format!("My Folder - {}", owner_id_short);
|
let folder_name = format!("My Folder - {}", owner_id_short);
|
||||||
match self
|
match self
|
||||||
.folder_storage
|
.folder_storage
|
||||||
@@ -388,7 +396,7 @@ impl FolderUseCase for FolderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Renames a folder after verifying ownership.
|
/// Renames a folder after verifying ownership.
|
||||||
async fn rename_folder(
|
async fn rename_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
dto: RenameFolderDto,
|
dto: RenameFolderDto,
|
||||||
@@ -431,7 +439,7 @@ impl FolderUseCase for FolderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Moves a folder to a new parent after verifying ownership.
|
/// Moves a folder to a new parent after verifying ownership.
|
||||||
async fn move_folder(
|
async fn move_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
dto: MoveFolderDto,
|
dto: MoveFolderDto,
|
||||||
@@ -497,7 +505,7 @@ impl FolderUseCase for FolderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a folder after verifying ownership.
|
/// Deletes a folder after verifying ownership.
|
||||||
async fn delete_folder(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError> {
|
async fn delete_folder_with_perms(&self, id: &str, caller_id: Uuid) -> Result<(), DomainError> {
|
||||||
// Verify the folder exists and belongs to the caller
|
// Verify the folder exists and belongs to the caller
|
||||||
let folder = self.folder_storage.get_folder(id).await?;
|
let folder = self.folder_storage.get_folder(id).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ async fn stub_move_file_owned_returns_ok() {
|
|||||||
let user_id = Uuid::new_v4();
|
let user_id = Uuid::new_v4();
|
||||||
let stub = StubFileManagementUseCase;
|
let stub = StubFileManagementUseCase;
|
||||||
let result = stub
|
let result = stub
|
||||||
.move_file_owned("file-1", user_id, Some("folder-2".to_string()))
|
.move_file_with_perms("file-1", user_id, Some("folder-2".to_string()))
|
||||||
.await;
|
.await;
|
||||||
assert!(result.is_ok(), "stub should return Ok for move_file_owned");
|
assert!(result.is_ok(), "stub should return Ok for move_file_owned");
|
||||||
}
|
}
|
||||||
@@ -372,7 +372,7 @@ async fn stub_rename_file_owned_returns_ok() {
|
|||||||
let user_id = Uuid::new_v4();
|
let user_id = Uuid::new_v4();
|
||||||
let stub = StubFileManagementUseCase;
|
let stub = StubFileManagementUseCase;
|
||||||
let result = stub
|
let result = stub
|
||||||
.rename_file_owned("file-1", user_id, "new-name.txt")
|
.rename_file_with_perms("file-1", user_id, "new-name.txt")
|
||||||
.await;
|
.await;
|
||||||
assert!(
|
assert!(
|
||||||
result.is_ok(),
|
result.is_ok(),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
||||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
||||||
use crate::application::services::folder_service::FolderService;
|
use crate::application::services::folder_service::FolderService;
|
||||||
use crate::application::services::share_service::ShareService;
|
use crate::application::services::share_service::ShareService;
|
||||||
|
|||||||
+22
-36
@@ -25,7 +25,9 @@ use crate::application::dtos::search_dto::{
|
|||||||
use crate::application::ports::file_ports::{
|
use crate::application::ports::file_ports::{
|
||||||
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, OptimizedFileContent,
|
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, OptimizedFileContent,
|
||||||
};
|
};
|
||||||
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
|
|
||||||
|
use crate::application::ports::inbound::SearchUseCase;
|
||||||
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
||||||
use crate::application::ports::zip_ports::ZipPort;
|
use crate::application::ports::zip_ports::ZipPort;
|
||||||
use crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
@@ -353,7 +355,7 @@ impl I18nService for StubI18nService {
|
|||||||
pub struct StubFolderUseCase;
|
pub struct StubFolderUseCase;
|
||||||
|
|
||||||
impl FolderUseCase for StubFolderUseCase {
|
impl FolderUseCase for StubFolderUseCase {
|
||||||
async fn create_folder(
|
async fn create_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_dto: CreateFolderDto,
|
_dto: CreateFolderDto,
|
||||||
_user_id: Uuid,
|
_user_id: Uuid,
|
||||||
@@ -365,7 +367,7 @@ impl FolderUseCase for StubFolderUseCase {
|
|||||||
Ok(FolderDto::default())
|
Ok(FolderDto::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_folder_owned(
|
async fn get_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_id: &str,
|
_id: &str,
|
||||||
_caller_id: Uuid,
|
_caller_id: Uuid,
|
||||||
@@ -406,7 +408,7 @@ impl FolderUseCase for StubFolderUseCase {
|
|||||||
Ok(PaginatedResponseDto::new(Vec::new(), 0, 10, 0))
|
Ok(PaginatedResponseDto::new(Vec::new(), 0, 10, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rename_folder(
|
async fn rename_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_id: &str,
|
_id: &str,
|
||||||
_dto: RenameFolderDto,
|
_dto: RenameFolderDto,
|
||||||
@@ -415,7 +417,7 @@ impl FolderUseCase for StubFolderUseCase {
|
|||||||
Ok(FolderDto::default())
|
Ok(FolderDto::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn move_folder(
|
async fn move_folder_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_id: &str,
|
_id: &str,
|
||||||
_dto: MoveFolderDto,
|
_dto: MoveFolderDto,
|
||||||
@@ -424,7 +426,11 @@ impl FolderUseCase for StubFolderUseCase {
|
|||||||
Ok(FolderDto::default())
|
Ok(FolderDto::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_folder(&self, _id: &str, _caller_id: Uuid) -> Result<(), DomainError> {
|
async fn delete_folder_with_perms(
|
||||||
|
&self,
|
||||||
|
_id: &str,
|
||||||
|
_caller_id: Uuid,
|
||||||
|
) -> Result<(), DomainError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,23 +623,7 @@ impl FileRetrievalUseCase for StubFileRetrievalUseCase {
|
|||||||
pub struct StubFileManagementUseCase;
|
pub struct StubFileManagementUseCase;
|
||||||
|
|
||||||
impl FileManagementUseCase for StubFileManagementUseCase {
|
impl FileManagementUseCase for StubFileManagementUseCase {
|
||||||
async fn move_file(
|
async fn copy_file_with_perms(
|
||||||
&self,
|
|
||||||
_file_id: &str,
|
|
||||||
_folder_id: Option<String>,
|
|
||||||
) -> Result<FileDto, DomainError> {
|
|
||||||
Ok(FileDto::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn copy_file(
|
|
||||||
&self,
|
|
||||||
_file_id: &str,
|
|
||||||
_folder_id: Option<String>,
|
|
||||||
) -> Result<FileDto, DomainError> {
|
|
||||||
Ok(FileDto::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn copy_file_owned(
|
|
||||||
&self,
|
&self,
|
||||||
_file_id: &str,
|
_file_id: &str,
|
||||||
_caller_id: Uuid,
|
_caller_id: Uuid,
|
||||||
@@ -642,23 +632,19 @@ impl FileManagementUseCase for StubFileManagementUseCase {
|
|||||||
Ok(FileDto::default())
|
Ok(FileDto::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rename_file(&self, _file_id: &str, _new_name: &str) -> Result<FileDto, DomainError> {
|
async fn delete_file_with_perms(&self, _id: &str, _caller_id: Uuid) -> Result<(), DomainError> {
|
||||||
Ok(FileDto::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_file(&self, _id: &str) -> Result<(), DomainError> {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_file_owned(&self, _id: &str, _caller_id: Uuid) -> Result<(), DomainError> {
|
async fn delete_and_cleanup_with_perms(
|
||||||
Ok(())
|
&self,
|
||||||
}
|
_id: &str,
|
||||||
|
_user_id: Uuid,
|
||||||
async fn delete_with_cleanup(&self, _id: &str, _user_id: Uuid) -> Result<bool, DomainError> {
|
) -> Result<bool, DomainError> {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn move_file_owned(
|
async fn move_file_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_file_id: &str,
|
_file_id: &str,
|
||||||
_caller_id: Uuid,
|
_caller_id: Uuid,
|
||||||
@@ -667,7 +653,7 @@ impl FileManagementUseCase for StubFileManagementUseCase {
|
|||||||
Ok(FileDto::default())
|
Ok(FileDto::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rename_file_owned(
|
async fn rename_file_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_file_id: &str,
|
_file_id: &str,
|
||||||
_caller_id: Uuid,
|
_caller_id: Uuid,
|
||||||
@@ -676,7 +662,7 @@ impl FileManagementUseCase for StubFileManagementUseCase {
|
|||||||
Ok(FileDto::default())
|
Ok(FileDto::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn copy_folder_tree_owned(
|
async fn copy_folder_tree_with_perms(
|
||||||
&self,
|
&self,
|
||||||
_source_folder_id: &str,
|
_source_folder_id: &str,
|
||||||
_caller_id: Uuid,
|
_caller_id: Uuid,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::application::services::folder_service::FolderService;
|
|||||||
use crate::{
|
use crate::{
|
||||||
application::dtos::file_dto::FileDto,
|
application::dtos::file_dto::FileDto,
|
||||||
application::ports::file_ports::FileRetrievalUseCase,
|
application::ports::file_ports::FileRetrievalUseCase,
|
||||||
application::ports::inbound::FolderUseCase,
|
application::ports::folder_ports::FolderUseCase,
|
||||||
application::ports::zip_ports::ZipPort,
|
application::ports::zip_ports::ZipPort,
|
||||||
common::errors::{DomainError, ErrorKind, Result},
|
common::errors::{DomainError, ErrorKind, Result},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -117,10 +117,10 @@ impl FileHandler {
|
|||||||
|
|
||||||
// ── SECURITY: Verify folder ownership before upload (IDOR V-03 fix) ──
|
// ── SECURITY: Verify folder ownership before upload (IDOR V-03 fix) ──
|
||||||
if let Some(ref fid) = folder_id {
|
if let Some(ref fid) = folder_id {
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
let folder_service = &state.applications.folder_service;
|
let folder_service = &state.applications.folder_service;
|
||||||
if folder_service
|
if folder_service
|
||||||
.get_folder_owned(fid, auth_user.id)
|
.get_folder_with_perms(fid, auth_user.id)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
@@ -875,7 +875,7 @@ impl FileHandler {
|
|||||||
|
|
||||||
// Auth required: trash-first with dedup cleanup + ownership verification
|
// Auth required: trash-first with dedup cleanup + ownership verification
|
||||||
let result = mgmt
|
let result = mgmt
|
||||||
.delete_with_cleanup(&id, auth_user.id)
|
.delete_and_cleanup_with_perms(&id, auth_user.id)
|
||||||
.await
|
.await
|
||||||
.map(|was_trashed| {
|
.map(|was_trashed| {
|
||||||
if was_trashed {
|
if was_trashed {
|
||||||
@@ -917,7 +917,10 @@ impl FileHandler {
|
|||||||
|
|
||||||
tracing::info!("Renaming file {} to \"{}\"", id, new_name);
|
tracing::info!("Renaming file {} to \"{}\"", id, new_name);
|
||||||
let mgmt = &state.applications.file_management_service;
|
let mgmt = &state.applications.file_management_service;
|
||||||
match mgmt.rename_file_owned(&id, auth_user.id, &new_name).await {
|
match mgmt
|
||||||
|
.rename_file_with_perms(&id, auth_user.id, &new_name)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
|
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
|
||||||
Err(err) => AppError::from(err).into_response(),
|
Err(err) => AppError::from(err).into_response(),
|
||||||
}
|
}
|
||||||
@@ -935,7 +938,7 @@ impl FileHandler {
|
|||||||
let mgmt = &state.applications.file_management_service;
|
let mgmt = &state.applications.file_management_service;
|
||||||
|
|
||||||
match mgmt
|
match mgmt
|
||||||
.move_file_owned(&id, auth_user.id, payload.folder_id)
|
.move_file_with_perms(&id, auth_user.id, payload.folder_id)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(file) => (StatusCode::OK, Json(file)).into_response(),
|
Ok(file) => (StatusCode::OK, Json(file)).into_response(),
|
||||||
@@ -956,7 +959,10 @@ impl FileHandler {
|
|||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
|
|
||||||
let mgmt = &state.applications.file_management_service;
|
let mgmt = &state.applications.file_management_service;
|
||||||
match mgmt.move_file_owned(&id, auth_user.id, folder_id).await {
|
match mgmt
|
||||||
|
.move_file_with_perms(&id, auth_user.id, folder_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
|
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
|
||||||
Err(err) => AppError::from(err).into_response(),
|
Err(err) => AppError::from(err).into_response(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::application::dtos::folder_dto::{
|
|||||||
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
||||||
use crate::application::dtos::pagination::PaginationRequestDto;
|
use crate::application::dtos::pagination::PaginationRequestDto;
|
||||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
use crate::application::ports::trash_ports::TrashUseCase;
|
use crate::application::ports::trash_ports::TrashUseCase;
|
||||||
use crate::application::services::folder_service::FolderService;
|
use crate::application::services::folder_service::FolderService;
|
||||||
use crate::common::di::AppState as GlobalAppState;
|
use crate::common::di::AppState as GlobalAppState;
|
||||||
@@ -76,7 +76,7 @@ impl FolderHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match service.create_folder(dto, auth_user.id).await {
|
match service.create_folder_with_perms(dto, auth_user.id).await {
|
||||||
Ok(folder) => (StatusCode::CREATED, Json(folder)).into_response(),
|
Ok(folder) => (StatusCode::CREATED, Json(folder)).into_response(),
|
||||||
Err(err) => AppError::from(err).into_response(),
|
Err(err) => AppError::from(err).into_response(),
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,10 @@ impl FolderHandler {
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Json(dto): Json<RenameFolderDto>,
|
Json(dto): Json<RenameFolderDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match service.rename_folder(&id, dto, auth_user.id).await {
|
match service
|
||||||
|
.rename_folder_with_perms(&id, dto, auth_user.id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
||||||
Err(err) => AppError::from(err).into_response(),
|
Err(err) => AppError::from(err).into_response(),
|
||||||
}
|
}
|
||||||
@@ -257,7 +260,7 @@ impl FolderHandler {
|
|||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Json(dto): Json<MoveFolderDto>,
|
Json(dto): Json<MoveFolderDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match service.move_folder(&id, dto, auth_user.id).await {
|
match service.move_folder_with_perms(&id, dto, auth_user.id).await {
|
||||||
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
||||||
Err(err) => AppError::from(err).into_response(),
|
Err(err) => AppError::from(err).into_response(),
|
||||||
}
|
}
|
||||||
@@ -269,7 +272,7 @@ impl FolderHandler {
|
|||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match service.delete_folder(&id, auth_user.id).await {
|
match service.delete_folder_with_perms(&id, auth_user.id).await {
|
||||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||||
Err(err) => AppError::from(err).into_response(),
|
Err(err) => AppError::from(err).into_response(),
|
||||||
}
|
}
|
||||||
@@ -304,7 +307,7 @@ impl FolderHandler {
|
|||||||
|
|
||||||
// Fallback to permanent delete if trash is unavailable or failed
|
// Fallback to permanent delete if trash is unavailable or failed
|
||||||
let folder_service = &state.applications.folder_service;
|
let folder_service = &state.applications.folder_service;
|
||||||
match folder_service.delete_folder(&id, user_id).await {
|
match folder_service.delete_folder_with_perms(&id, user_id).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::info!("Folder permanently deleted: {}", id);
|
tracing::info!("Folder permanently deleted: {}", id);
|
||||||
StatusCode::NO_CONTENT.into_response()
|
StatusCode::NO_CONTENT.into_response()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use crate::application::dtos::file_dto::FileDto;
|
|||||||
use crate::application::dtos::folder_dto::FolderDto;
|
use crate::application::dtos::folder_dto::FolderDto;
|
||||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||||
use crate::application::ports::file_ports::{FileManagementUseCase, FileUploadUseCase};
|
use crate::application::ports::file_ports::{FileManagementUseCase, FileUploadUseCase};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
use crate::application::ports::storage_ports::StorageUsagePort;
|
use crate::application::ports::storage_ports::StorageUsagePort;
|
||||||
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
||||||
use crate::application::services::folder_service::FolderService;
|
use crate::application::services::folder_service::FolderService;
|
||||||
@@ -1046,7 +1046,7 @@ async fn handle_mkcol(
|
|||||||
// their proper HTTP status codes (was: blanket 500 swallowed
|
// their proper HTTP status codes (was: blanket 500 swallowed
|
||||||
// ownership-rejection NotFound from verify_owner).
|
// ownership-rejection NotFound from verify_owner).
|
||||||
let created = folder_service
|
let created = folder_service
|
||||||
.create_folder(create_dto, user.id)
|
.create_folder_with_perms(create_dto, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
parent_id = Some(created.id);
|
parent_id = Some(created.id);
|
||||||
@@ -1092,7 +1092,7 @@ async fn handle_delete(
|
|||||||
match resolver.resolve_path_for_user(&path, user.id).await {
|
match resolver.resolve_path_for_user(&path, user.id).await {
|
||||||
Ok(ResolvedResource::Folder(folder)) => {
|
Ok(ResolvedResource::Folder(folder)) => {
|
||||||
folder_service
|
folder_service
|
||||||
.delete_folder(&folder.id, user.id)
|
.delete_folder_with_perms(&folder.id, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
AppError::internal_error(format!("Failed to delete folder: {}", e))
|
AppError::internal_error(format!("Failed to delete folder: {}", e))
|
||||||
@@ -1100,7 +1100,7 @@ async fn handle_delete(
|
|||||||
}
|
}
|
||||||
Ok(ResolvedResource::File(file)) => {
|
Ok(ResolvedResource::File(file)) => {
|
||||||
file_management_service
|
file_management_service
|
||||||
.delete_file(&file.id)
|
.delete_file_with_perms(&file.id, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
AppError::internal_error(format!("Failed to delete file: {}", e))
|
AppError::internal_error(format!("Failed to delete file: {}", e))
|
||||||
@@ -1115,7 +1115,7 @@ async fn handle_delete(
|
|||||||
if let Ok(folder) = folder_result {
|
if let Ok(folder) = folder_result {
|
||||||
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||||
folder_service
|
folder_service
|
||||||
.delete_folder(&folder.id, user.id)
|
.delete_folder_with_perms(&folder.id, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
|
||||||
} else {
|
} else {
|
||||||
@@ -1126,7 +1126,7 @@ async fn handle_delete(
|
|||||||
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||||
|
|
||||||
file_management_service
|
file_management_service
|
||||||
.delete_file(&file.id)
|
.delete_file_with_perms(&file.id, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to delete file: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Failed to delete file: {}", e)))?;
|
||||||
}
|
}
|
||||||
@@ -1248,7 +1248,7 @@ async fn handle_move(
|
|||||||
};
|
};
|
||||||
|
|
||||||
folder_service
|
folder_service
|
||||||
.move_folder(&folder.id, move_dto, user.id)
|
.move_folder_with_perms(&folder.id, move_dto, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
|
|
||||||
@@ -1257,7 +1257,7 @@ async fn handle_move(
|
|||||||
name: dest_folder_name.to_string(),
|
name: dest_folder_name.to_string(),
|
||||||
};
|
};
|
||||||
folder_service
|
folder_service
|
||||||
.rename_folder(&folder.id, rename_dto, user.id)
|
.rename_folder_with_perms(&folder.id, rename_dto, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
}
|
}
|
||||||
@@ -1291,13 +1291,13 @@ async fn handle_move(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
file_management_service
|
file_management_service
|
||||||
.move_file(&file.id, Some(dest_parent_path.to_string()))
|
.move_file_with_perms(&file.id, user.id, Some(dest_parent_path.to_string()))
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
}
|
}
|
||||||
if file.name != dest_filename {
|
if file.name != dest_filename {
|
||||||
file_management_service
|
file_management_service
|
||||||
.rename_file(&file.id, dest_filename)
|
.rename_file_with_perms(&file.id, user.id, dest_filename)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
}
|
}
|
||||||
@@ -1349,7 +1349,7 @@ async fn handle_move(
|
|||||||
};
|
};
|
||||||
|
|
||||||
folder_service
|
folder_service
|
||||||
.move_folder(&folder.id, move_dto, user.id)
|
.move_folder_with_perms(&folder.id, move_dto, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to move folder: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Failed to move folder: {}", e)))?;
|
||||||
|
|
||||||
@@ -1358,7 +1358,7 @@ async fn handle_move(
|
|||||||
name: dest_folder_name.to_string(),
|
name: dest_folder_name.to_string(),
|
||||||
};
|
};
|
||||||
folder_service
|
folder_service
|
||||||
.rename_folder(&folder.id, rename_dto, user.id)
|
.rename_folder_with_perms(&folder.id, rename_dto, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
}
|
}
|
||||||
@@ -1398,13 +1398,13 @@ async fn handle_move(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
file_management_service
|
file_management_service
|
||||||
.move_file(&file.id, Some(dest_parent_path.to_string()))
|
.move_file_with_perms(&file.id, user.id, Some(dest_parent_path.to_string()))
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
}
|
}
|
||||||
if file.name != dest_filename {
|
if file.name != dest_filename {
|
||||||
file_management_service
|
file_management_service
|
||||||
.rename_file(&file.id, dest_filename)
|
.rename_file_with_perms(&file.id, user.id, dest_filename)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
}
|
}
|
||||||
@@ -1535,8 +1535,9 @@ async fn handle_copy(
|
|||||||
if recursive {
|
if recursive {
|
||||||
let file_management_service = &state.applications.file_management_service;
|
let file_management_service = &state.applications.file_management_service;
|
||||||
file_management_service
|
file_management_service
|
||||||
.copy_folder_tree(
|
.copy_folder_tree_with_perms(
|
||||||
&folder.id,
|
&folder.id,
|
||||||
|
user.id,
|
||||||
target_parent_id,
|
target_parent_id,
|
||||||
Some(dest_folder_name.to_string()),
|
Some(dest_folder_name.to_string()),
|
||||||
)
|
)
|
||||||
@@ -1550,7 +1551,7 @@ async fn handle_copy(
|
|||||||
parent_id: target_parent_id,
|
parent_id: target_parent_id,
|
||||||
};
|
};
|
||||||
folder_service
|
folder_service
|
||||||
.create_folder(create_dto, user.id)
|
.create_folder_with_perms(create_dto, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
AppError::internal_error(format!(
|
AppError::internal_error(format!(
|
||||||
@@ -1586,7 +1587,7 @@ async fn handle_copy(
|
|||||||
|
|
||||||
let file_management_service = &state.applications.file_management_service;
|
let file_management_service = &state.applications.file_management_service;
|
||||||
file_management_service
|
file_management_service
|
||||||
.copy_file(&file.id, target_folder_id)
|
.copy_file_with_perms(&file.id, user.id, target_folder_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to copy file: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Failed to copy file: {}", e)))?;
|
||||||
}
|
}
|
||||||
@@ -1639,8 +1640,9 @@ async fn handle_copy(
|
|||||||
if recursive {
|
if recursive {
|
||||||
let file_management_service = &state.applications.file_management_service;
|
let file_management_service = &state.applications.file_management_service;
|
||||||
file_management_service
|
file_management_service
|
||||||
.copy_folder_tree(
|
.copy_folder_tree_with_perms(
|
||||||
&folder.id,
|
&folder.id,
|
||||||
|
user.id,
|
||||||
target_parent_id,
|
target_parent_id,
|
||||||
Some(dest_folder_name.to_string()),
|
Some(dest_folder_name.to_string()),
|
||||||
)
|
)
|
||||||
@@ -1654,7 +1656,7 @@ async fn handle_copy(
|
|||||||
parent_id: target_parent_id,
|
parent_id: target_parent_id,
|
||||||
};
|
};
|
||||||
folder_service
|
folder_service
|
||||||
.create_folder(create_dto, user.id)
|
.create_folder_with_perms(create_dto, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
AppError::internal_error(format!(
|
AppError::internal_error(format!(
|
||||||
@@ -1697,7 +1699,7 @@ async fn handle_copy(
|
|||||||
|
|
||||||
let file_management_service = &state.applications.file_management_service;
|
let file_management_service = &state.applications.file_management_service;
|
||||||
file_management_service
|
file_management_service
|
||||||
.copy_file(&file.id, target_folder_id)
|
.copy_file_with_perms(&file.id, user.id, target_folder_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to copy file: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Failed to copy file: {}", e)))?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ use crate::application::dtos::folder_dto::FolderDto;
|
|||||||
use crate::application::dtos::search_dto::SearchCriteriaDto;
|
use crate::application::dtos::search_dto::SearchCriteriaDto;
|
||||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||||
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
|
use crate::application::ports::inbound::SearchUseCase;
|
||||||
use crate::common::di::AppState;
|
use crate::common::di::AppState;
|
||||||
use crate::interfaces::errors::AppError;
|
use crate::interfaces::errors::AppError;
|
||||||
use crate::interfaces::middleware::auth::CurrentUser;
|
use crate::interfaces::middleware::auth::CurrentUser;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::application::ports::favorites_ports::FavoritesUseCase;
|
|||||||
use crate::application::ports::file_ports::{
|
use crate::application::ports::file_ports::{
|
||||||
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase,
|
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase,
|
||||||
};
|
};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::folder_ports::FolderUseCase;
|
||||||
use crate::application::ports::trash_ports::TrashUseCase;
|
use crate::application::ports::trash_ports::TrashUseCase;
|
||||||
use crate::common::di::AppState;
|
use crate::common::di::AppState;
|
||||||
use crate::common::mime_detect::{filename_from_path, refine_content_type};
|
use crate::common::mime_detect::{filename_from_path, refine_content_type};
|
||||||
@@ -656,7 +656,7 @@ async fn handle_mkcol(
|
|||||||
name: segment.to_string(),
|
name: segment.to_string(),
|
||||||
parent_id: Some(parent_id.clone()),
|
parent_id: Some(parent_id.clone()),
|
||||||
};
|
};
|
||||||
match folder_service.create_folder(dto, user.id).await {
|
match folder_service.create_folder_with_perms(dto, user.id).await {
|
||||||
Ok(created) => {
|
Ok(created) => {
|
||||||
parent_id = created.id.clone();
|
parent_id = created.id.clone();
|
||||||
}
|
}
|
||||||
@@ -732,7 +732,7 @@ async fn handle_delete(
|
|||||||
|
|
||||||
if let Ok(folder) = folder_service.get_folder_by_path(&internal_path).await {
|
if let Ok(folder) = folder_service.get_folder_by_path(&internal_path).await {
|
||||||
folder_service
|
folder_service
|
||||||
.delete_folder(&folder.id, user.id)
|
.delete_folder_with_perms(&folder.id, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
|
||||||
|
|
||||||
@@ -744,7 +744,7 @@ async fn handle_delete(
|
|||||||
|
|
||||||
if let Ok(file) = file_service.get_file_by_path(&internal_path).await {
|
if let Ok(file) = file_service.get_file_by_path(&internal_path).await {
|
||||||
file_mgmt
|
file_mgmt
|
||||||
.delete_file(&file.id)
|
.delete_file_with_perms(&file.id, user.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to delete file: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Failed to delete file: {}", e)))?;
|
||||||
|
|
||||||
@@ -798,7 +798,7 @@ async fn handle_move(
|
|||||||
if src_parent_sub == dest_parent_sub {
|
if src_parent_sub == dest_parent_sub {
|
||||||
// Same parent → rename.
|
// Same parent → rename.
|
||||||
file_mgmt
|
file_mgmt
|
||||||
.rename_file(&file.id, dest_name)
|
.rename_file_with_perms(&file.id, user.id, dest_name)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;
|
||||||
} else {
|
} else {
|
||||||
@@ -809,14 +809,14 @@ async fn handle_move(
|
|||||||
.map_err(|_| AppError::not_found("Destination folder not found"))?;
|
.map_err(|_| AppError::not_found("Destination folder not found"))?;
|
||||||
|
|
||||||
file_mgmt
|
file_mgmt
|
||||||
.move_file(&file.id, Some(dest_parent.id.clone()))
|
.move_file_with_perms(&file.id, user.id, Some(dest_parent.id.clone()))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Move failed: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Move failed: {}", e)))?;
|
||||||
|
|
||||||
// If the filename changed too, rename after move.
|
// If the filename changed too, rename after move.
|
||||||
if file.name != dest_name {
|
if file.name != dest_name {
|
||||||
file_mgmt
|
file_mgmt
|
||||||
.rename_file(&file.id, dest_name)
|
.rename_file_with_perms(&file.id, user.id, dest_name)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;
|
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;
|
||||||
}
|
}
|
||||||
@@ -851,7 +851,7 @@ async fn handle_move(
|
|||||||
// Same parent → rename.
|
// Same parent → rename.
|
||||||
use crate::application::dtos::folder_dto::RenameFolderDto;
|
use crate::application::dtos::folder_dto::RenameFolderDto;
|
||||||
folder_service
|
folder_service
|
||||||
.rename_folder(
|
.rename_folder_with_perms(
|
||||||
&folder.id,
|
&folder.id,
|
||||||
RenameFolderDto {
|
RenameFolderDto {
|
||||||
name: dest_name.to_string(),
|
name: dest_name.to_string(),
|
||||||
@@ -869,7 +869,7 @@ async fn handle_move(
|
|||||||
|
|
||||||
use crate::application::dtos::folder_dto::MoveFolderDto;
|
use crate::application::dtos::folder_dto::MoveFolderDto;
|
||||||
folder_service
|
folder_service
|
||||||
.move_folder(
|
.move_folder_with_perms(
|
||||||
&folder.id,
|
&folder.id,
|
||||||
MoveFolderDto {
|
MoveFolderDto {
|
||||||
parent_id: Some(dest_parent.id.clone()),
|
parent_id: Some(dest_parent.id.clone()),
|
||||||
@@ -883,7 +883,7 @@ async fn handle_move(
|
|||||||
if folder.name != dest_name {
|
if folder.name != dest_name {
|
||||||
use crate::application::dtos::folder_dto::RenameFolderDto;
|
use crate::application::dtos::folder_dto::RenameFolderDto;
|
||||||
folder_service
|
folder_service
|
||||||
.rename_folder(
|
.rename_folder_with_perms(
|
||||||
&folder.id,
|
&folder.id,
|
||||||
RenameFolderDto {
|
RenameFolderDto {
|
||||||
name: dest_name.to_string(),
|
name: dest_name.to_string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user