Implement dual DB pools (primary + maintenance) and wire services

This commit is contained in:
Dionisio
2026-02-24 19:28:00 +01:00
parent 3a7aedf2d6
commit 9f692f03c3
13 changed files with 465 additions and 86 deletions
+20
View File
@@ -6,6 +6,7 @@ use std::pin::Pin;
use std::sync::Arc;
use crate::application::dtos::file_dto::FileDto;
use crate::application::ports::storage_ports::CopyFolderTreeResult;
use crate::common::errors::DomainError;
// ─────────────────────────────────────────────────────
@@ -221,6 +222,25 @@ pub trait FileManagementUseCase: Send + Sync + 'static {
///
/// Returns `Ok(true)` when trashed, `Ok(false)` when permanently deleted.
async fn delete_with_cleanup(&self, id: &str, user_id: &str) -> Result<bool, DomainError>;
/// Copies an entire folder subtree atomically (WebDAV COPY Depth: infinity).
///
/// Creates a copy of `source_folder_id` (with optional name override) under
/// `target_parent_id`, including ALL sub-folders and files. Files are
/// zero-copy (blob ref_counts incremented in batch).
///
/// Default: returns error (only available with PostgreSQL backend).
async fn copy_folder_tree(
&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",
))
}
}
/// Factory for creating file use case implementations
+33
View File
@@ -183,6 +183,17 @@ pub trait FileReadPort: Send + Sync + 'static {
// FileWritePort — all write / mutate operations
// ─────────────────────────────────────────────────────
/// Result of an atomic recursive folder tree copy.
#[derive(Debug, Clone)]
pub struct CopyFolderTreeResult {
/// UUID of the newly created root folder
pub new_root_folder_id: String,
/// Total folders created (including root)
pub folders_copied: i64,
/// Total files copied (zero-copy via dedup)
pub files_copied: i64,
}
/// Secondary port for file **writing**.
///
/// Covers: upload (buffered + streaming), move, delete, update,
@@ -265,6 +276,28 @@ pub trait FileWritePort: Send + Sync + 'static {
target_folder_id: Option<String>,
) -> Result<File, DomainError>;
/// Copies an entire folder subtree atomically using ltree.
///
/// Creates a copy of `source_folder_id` (with optional `dest_name`)
/// under `target_parent_id`, including ALL sub-folders and files.
/// Files are zero-copy (blob ref_counts are incremented in batch).
///
/// Uses a PL/pgSQL function: O(depth) folder INSERTs + 1 file batch
/// + 1 ref_count batch. Replaces the N+1 sequential copy pattern.
///
/// Default: returns error (only PostgreSQL backend implements this).
async fn copy_folder_tree(
&self,
_source_folder_id: &str,
_target_parent_id: Option<String>,
_dest_name: Option<String>,
) -> Result<CopyFolderTreeResult, DomainError> {
Err(DomainError::internal_error(
"FileWritePort",
"copy_folder_tree not implemented for this storage backend",
))
}
// ── Trash operations ──
/// Moves a file to the trash
@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::application::dtos::file_dto::FileDto;
use crate::application::ports::file_ports::FileManagementUseCase;
use crate::application::ports::storage_ports::FileWritePort;
use crate::application::ports::storage_ports::{CopyFolderTreeResult, FileWritePort};
use crate::application::ports::trash_ports::TrashUseCase;
use crate::common::errors::DomainError;
use tracing::{error, info, warn};
@@ -160,4 +160,32 @@ impl FileManagementUseCase for FileManagementService {
Ok(false) // permanently deleted
}
async fn copy_folder_tree(
&self,
source_folder_id: &str,
target_parent_id: Option<String>,
dest_name: Option<String>,
) -> Result<CopyFolderTreeResult, DomainError> {
info!(
"Copying folder tree: source={}, target_parent={:?}, dest_name={:?}",
source_folder_id, target_parent_id, dest_name
);
let result = self
.file_repository
.copy_folder_tree(source_folder_id, target_parent_id, dest_name)
.await
.map_err(|e| {
error!("Error copying folder tree (source: {}): {}", source_folder_id, e);
e
})?;
info!(
"Folder tree copied: {} folders, {} files (new root: {})",
result.folders_copied, result.files_copied, result.new_root_folder_id
);
Ok(result)
}
}
@@ -247,6 +247,17 @@ impl FileWritePort for MockFileRepository {
Ok(())
}
async fn update_file_content_from_temp(
&self,
_file_id: &str,
_temp_path: &std::path::Path,
_size: u64,
_content_type: Option<String>,
_pre_computed_hash: Option<String>,
) -> std::result::Result<(), DomainError> {
Ok(())
}
async fn register_file_deferred(
&self,
_name: String,