fix: critical bugs from deep audit
- Fix copy_files() data loss: implement real copy_file across full stack (FileWritePort, FileManagementUseCase, stubs, service, repository with atomic CTE + dedup ref_count increment, batch_operations caller) - Fix plaintext password in replace_default_admin: hash password via PasswordHasherPort before User::new() - Fix CalendarService hardcoded user_id: unify CalendarUseCase trait with explicit user_id parameter on all methods, remove zombie _for_user duplicates and hardcoded 'current_user_id', update 20 CalDAV handler call sites - Previous session: migrate DedupService to PostgreSQL (storage.blobs), atomic CTEs with compensation for file/folder repository operations
This commit is contained in:
+304
-212
@@ -1,54 +1,58 @@
|
||||
use sqlx::PgPool;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::application::services::auth_application_service::AuthApplicationService;
|
||||
use crate::application::services::admin_settings_service::AdminSettingsService;
|
||||
use crate::application::services::auth_application_service::AuthApplicationService;
|
||||
|
||||
use crate::infrastructure::services::path_service::PathService;
|
||||
use crate::infrastructure::repositories::share_fs_repository::ShareFsRepository;
|
||||
use crate::infrastructure::repositories::pg::{
|
||||
FolderDbRepository, FileBlobReadRepository, FileBlobWriteRepository, TrashDbRepository,
|
||||
};
|
||||
use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService;
|
||||
use crate::infrastructure::services::file_content_cache::{FileContentCache, FileContentCacheConfig};
|
||||
use crate::infrastructure::services::trash_cleanup_service::TrashCleanupService;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::application::services::i18n_application_service::I18nApplicationService;
|
||||
use crate::application::services::trash_service::TrashService;
|
||||
use crate::application::services::search_service::SearchService;
|
||||
use crate::application::services::share_service::ShareService;
|
||||
use crate::application::services::favorites_service::FavoritesService;
|
||||
use crate::application::services::recent_service::RecentService;
|
||||
use crate::application::ports::trash_ports::TrashUseCase;
|
||||
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
|
||||
use crate::application::ports::outbound::FolderStoragePort;
|
||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||
use crate::application::ports::recent_ports::RecentItemsUseCase;
|
||||
use crate::application::ports::file_ports::{FileUploadUseCase, FileRetrievalUseCase, FileManagementUseCase, FileUseCaseFactory};
|
||||
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
||||
use crate::application::services::{FileUploadService, FileRetrievalService, FileManagementService, AppFileUseCaseFactory};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::services::i18n_service::I18nService;
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::application::ports::cache_ports::ContentCachePort;
|
||||
use crate::application::ports::thumbnail_ports::ThumbnailPort;
|
||||
use crate::application::ports::transcode_ports::ImageTranscodePort;
|
||||
use crate::application::ports::dedup_ports::DedupPort;
|
||||
use crate::application::ports::chunked_upload_ports::ChunkedUploadPort;
|
||||
use crate::application::ports::compression_ports::CompressionPort;
|
||||
use crate::application::ports::dedup_ports::DedupPort;
|
||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||
use crate::application::ports::file_ports::{
|
||||
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, FileUseCaseFactory,
|
||||
};
|
||||
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
|
||||
use crate::application::ports::outbound::FolderStoragePort;
|
||||
use crate::application::ports::recent_ports::RecentItemsUseCase;
|
||||
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
||||
use crate::application::ports::thumbnail_ports::ThumbnailPort;
|
||||
use crate::application::ports::transcode_ports::ImageTranscodePort;
|
||||
use crate::application::ports::trash_ports::TrashUseCase;
|
||||
use crate::application::ports::zip_ports::ZipPort;
|
||||
use crate::application::services::favorites_service::FavoritesService;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::application::services::i18n_application_service::I18nApplicationService;
|
||||
use crate::application::services::recent_service::RecentService;
|
||||
use crate::application::services::search_service::SearchService;
|
||||
use crate::application::services::share_service::ShareService;
|
||||
use crate::application::services::trash_service::TrashService;
|
||||
use crate::application::services::{
|
||||
AppFileUseCaseFactory, FileManagementService, FileRetrievalService, FileUploadService,
|
||||
};
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::services::i18n_service::I18nService;
|
||||
use crate::infrastructure::repositories::pg::{
|
||||
FileBlobReadRepository, FileBlobWriteRepository, FolderDbRepository, TrashDbRepository,
|
||||
};
|
||||
use crate::infrastructure::repositories::share_fs_repository::ShareFsRepository;
|
||||
use crate::infrastructure::services::file_content_cache::{
|
||||
FileContentCache, FileContentCacheConfig,
|
||||
};
|
||||
use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService;
|
||||
use crate::infrastructure::services::path_service::PathService;
|
||||
use crate::infrastructure::services::trash_cleanup_service::TrashCleanupService;
|
||||
|
||||
use crate::common::stubs::{
|
||||
StubZipPort, StubCompressionPort,
|
||||
StubFileReadPort, StubFileWritePort, StubFolderStoragePort,
|
||||
StubI18nService, StubFolderUseCase, StubFileUploadUseCase,
|
||||
StubFileRetrievalUseCase, StubFileManagementUseCase, StubFileUseCaseFactory,
|
||||
StubSearchUseCase, StubDedupPort,
|
||||
StubCompressionPort, StubDedupPort, StubFileManagementUseCase, StubFileReadPort,
|
||||
StubFileRetrievalUseCase, StubFileUploadUseCase, StubFileUseCaseFactory, StubFileWritePort,
|
||||
StubFolderStoragePort, StubFolderUseCase, StubI18nService, StubSearchUseCase, StubZipPort,
|
||||
};
|
||||
|
||||
/// Factory for the different application components
|
||||
///
|
||||
///
|
||||
/// This factory centralizes the creation of all application services,
|
||||
/// ensuring the correct initialization order and resolving circular dependencies.
|
||||
pub struct AppServiceFactory {
|
||||
@@ -66,7 +70,7 @@ impl AppServiceFactory {
|
||||
config: AppConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Creates a new service factory with custom configuration
|
||||
pub fn with_config(storage_path: PathBuf, locales_path: PathBuf, config: AppConfig) -> Self {
|
||||
Self {
|
||||
@@ -75,72 +79,82 @@ impl AppServiceFactory {
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Gets the configuration
|
||||
pub fn config(&self) -> &AppConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
|
||||
/// Gets the storage path
|
||||
pub fn storage_path(&self) -> &PathBuf {
|
||||
&self.storage_path
|
||||
}
|
||||
|
||||
|
||||
/// Initializes the core system services.
|
||||
///
|
||||
/// Requires a `PgPool` because `DedupService` stores its index in PostgreSQL.
|
||||
pub async fn create_core_services(&self, db_pool: &Arc<PgPool>) -> Result<CoreServices, DomainError> {
|
||||
pub async fn create_core_services(
|
||||
&self,
|
||||
db_pool: &Arc<PgPool>,
|
||||
) -> Result<CoreServices, DomainError> {
|
||||
// Path service (still needed for blob storage root + thumbnails)
|
||||
let path_service = Arc::new(PathService::new(self.storage_path.clone()));
|
||||
|
||||
|
||||
// File content cache for ultra-fast file serving (hot files in RAM)
|
||||
let file_content_cache = Arc::new(FileContentCache::new(FileContentCacheConfig {
|
||||
max_file_size: 10 * 1024 * 1024, // 10MB max per file
|
||||
max_total_size: 512 * 1024 * 1024, // 512MB total cache
|
||||
max_entries: 10000, // Up to 10k files
|
||||
max_file_size: 10 * 1024 * 1024, // 10MB max per file
|
||||
max_total_size: 512 * 1024 * 1024, // 512MB total cache
|
||||
max_entries: 10000, // Up to 10k files
|
||||
}));
|
||||
tracing::info!("FileContentCache initialized: max 10MB/file, 512MB total, 10k entries");
|
||||
|
||||
|
||||
// Thumbnail service for thumbnail generation
|
||||
let thumbnail_service = Arc::new(
|
||||
crate::infrastructure::services::thumbnail_service::ThumbnailService::new(
|
||||
&self.storage_path,
|
||||
5000, // max 5000 thumbnails in cache
|
||||
100 * 1024 * 1024, // max 100MB cache
|
||||
)
|
||||
5000, // max 5000 thumbnails in cache
|
||||
100 * 1024 * 1024, // max 100MB cache
|
||||
),
|
||||
);
|
||||
// Initialize thumbnail directories
|
||||
thumbnail_service.initialize().await?;
|
||||
|
||||
|
||||
// Chunked upload service for large files (>10MB)
|
||||
let chunked_temp_dir = std::path::PathBuf::from(&self.storage_path).join(".uploads");
|
||||
let chunked_upload_service = Arc::new(
|
||||
crate::infrastructure::services::chunked_upload_service::ChunkedUploadService::new(chunked_temp_dir)
|
||||
crate::infrastructure::services::chunked_upload_service::ChunkedUploadService::new(
|
||||
chunked_temp_dir,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// Image transcoding service for automatic WebP conversion
|
||||
let image_transcode_service = Arc::new(
|
||||
crate::infrastructure::services::image_transcode_service::ImageTranscodeService::new(
|
||||
&self.storage_path,
|
||||
2000, // max 2000 transcoded images in cache
|
||||
50 * 1024 * 1024, // max 50MB in-memory cache
|
||||
)
|
||||
2000, // max 2000 transcoded images in cache
|
||||
50 * 1024 * 1024, // max 50MB in-memory cache
|
||||
),
|
||||
);
|
||||
image_transcode_service.initialize().await?;
|
||||
|
||||
|
||||
// Deduplication service — PRIMARY blob storage engine (PostgreSQL-backed index)
|
||||
let dedup_service = Arc::new(
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(&self.storage_path, db_pool.clone())
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(
|
||||
&self.storage_path,
|
||||
db_pool.clone(),
|
||||
),
|
||||
);
|
||||
dedup_service.initialize().await?;
|
||||
|
||||
|
||||
// Compression service (gzip)
|
||||
let compression_service: Arc<dyn CompressionPort> = Arc::new(
|
||||
crate::infrastructure::services::compression_service::GzipCompressionService::new()
|
||||
crate::infrastructure::services::compression_service::GzipCompressionService::new(),
|
||||
);
|
||||
|
||||
tracing::info!("Core services initialized: path service, file content cache, thumbnails, chunked upload, image transcode, dedup (PRIMARY blob storage), compression");
|
||||
|
||||
|
||||
tracing::info!(
|
||||
"Core services initialized: path service, file content cache, thumbnails, chunked upload, image transcode, dedup (PRIMARY blob storage), compression"
|
||||
);
|
||||
|
||||
Ok(CoreServices {
|
||||
path_service,
|
||||
file_content_cache,
|
||||
@@ -149,49 +163,56 @@ impl AppServiceFactory {
|
||||
image_transcode_service,
|
||||
dedup_service,
|
||||
compression_service,
|
||||
zip_service: Arc::new(StubZipPort), // Placeholder - replaced after app services init
|
||||
zip_service: Arc::new(StubZipPort), // Placeholder - replaced after app services init
|
||||
config: self.config.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Initializes the repository services (blob-storage model).
|
||||
///
|
||||
/// Requires a PgPool since all metadata lives in PostgreSQL.
|
||||
pub fn create_repository_services(&self, core: &CoreServices, db_pool: &Arc<PgPool>) -> RepositoryServices {
|
||||
pub fn create_repository_services(
|
||||
&self,
|
||||
core: &CoreServices,
|
||||
db_pool: &Arc<PgPool>,
|
||||
) -> RepositoryServices {
|
||||
// Folder repository — PostgreSQL-backed virtual folders
|
||||
let folder_repo_concrete = Arc::new(FolderDbRepository::new(db_pool.clone()));
|
||||
let folder_repository: Arc<dyn FolderStoragePort> = folder_repo_concrete.clone();
|
||||
|
||||
|
||||
// File repositories — PostgreSQL metadata + blob content via DedupService
|
||||
let file_read_repository: Arc<dyn FileReadPort> = Arc::new(FileBlobReadRepository::new(
|
||||
db_pool.clone(),
|
||||
core.dedup_service.clone(),
|
||||
folder_repo_concrete.clone(),
|
||||
));
|
||||
|
||||
|
||||
let file_write_repository: Arc<dyn FileWritePort> = Arc::new(FileBlobWriteRepository::new(
|
||||
db_pool.clone(),
|
||||
core.dedup_service.clone(),
|
||||
folder_repo_concrete.clone(),
|
||||
));
|
||||
|
||||
|
||||
// I18n repository
|
||||
let i18n_repository = Arc::new(FileSystemI18nService::new(
|
||||
self.locales_path.clone()
|
||||
));
|
||||
|
||||
let i18n_repository = Arc::new(FileSystemI18nService::new(self.locales_path.clone()));
|
||||
|
||||
// Trash repository — reads soft-delete flags from storage.files/folders
|
||||
let trash_repository = if core.config.features.enable_trash {
|
||||
Some(Arc::new(TrashDbRepository::new(
|
||||
db_pool.clone(),
|
||||
core.config.storage.trash_retention_days,
|
||||
)) as Arc<dyn crate::domain::repositories::trash_repository::TrashRepository>)
|
||||
))
|
||||
as Arc<
|
||||
dyn crate::domain::repositories::trash_repository::TrashRepository,
|
||||
>)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
tracing::info!("Repository services initialized with 100% blob storage model (PG metadata + DedupService blobs)");
|
||||
|
||||
|
||||
tracing::info!(
|
||||
"Repository services initialized with 100% blob storage model (PG metadata + DedupService blobs)"
|
||||
);
|
||||
|
||||
RepositoryServices {
|
||||
folder_repository,
|
||||
folder_repo_concrete,
|
||||
@@ -201,7 +222,7 @@ impl AppServiceFactory {
|
||||
trash_repository,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Initializes the application services
|
||||
pub fn create_application_services(
|
||||
&self,
|
||||
@@ -210,23 +231,21 @@ impl AppServiceFactory {
|
||||
trash_service: Option<Arc<dyn TrashUseCase>>,
|
||||
) -> ApplicationServices {
|
||||
// Main services
|
||||
let folder_service = Arc::new(FolderService::new(
|
||||
repos.folder_repository.clone()
|
||||
));
|
||||
|
||||
let folder_service = Arc::new(FolderService::new(repos.folder_repository.clone()));
|
||||
|
||||
// Refactored services with all infrastructure ports
|
||||
// In blob model, dedup is handled by the repository — no separate write-behind needed
|
||||
let file_upload_service = Arc::new(FileUploadService::new_with_read(
|
||||
repos.file_write_repository.clone(),
|
||||
repos.file_read_repository.clone(),
|
||||
));
|
||||
|
||||
|
||||
let file_retrieval_service = Arc::new(FileRetrievalService::new_with_cache(
|
||||
repos.file_read_repository.clone(),
|
||||
core.file_content_cache.clone(),
|
||||
core.image_transcode_service.clone(),
|
||||
));
|
||||
|
||||
|
||||
// FileManagementService with dedup and trash
|
||||
let file_management_service = Arc::new(FileManagementService::new_full(
|
||||
repos.file_write_repository.clone(),
|
||||
@@ -234,26 +253,24 @@ impl AppServiceFactory {
|
||||
trash_service.clone(),
|
||||
core.dedup_service.clone(),
|
||||
));
|
||||
|
||||
|
||||
let file_use_case_factory = Arc::new(AppFileUseCaseFactory::new(
|
||||
repos.file_read_repository.clone(),
|
||||
repos.file_write_repository.clone()
|
||||
repos.file_write_repository.clone(),
|
||||
));
|
||||
|
||||
let i18n_service = Arc::new(I18nApplicationService::new(
|
||||
repos.i18n_repository.clone()
|
||||
));
|
||||
|
||||
|
||||
let i18n_service = Arc::new(I18nApplicationService::new(repos.i18n_repository.clone()));
|
||||
|
||||
// Search service with cache
|
||||
let search_service: Option<Arc<dyn SearchUseCase>> = Some(Arc::new(SearchService::new(
|
||||
repos.file_read_repository.clone(),
|
||||
repos.folder_repository.clone(),
|
||||
300, // Cache TTL in seconds (5 minutes)
|
||||
300, // Cache TTL in seconds (5 minutes)
|
||||
1000, // Maximum cache entries
|
||||
)));
|
||||
|
||||
|
||||
tracing::info!("Application services initialized");
|
||||
|
||||
|
||||
ApplicationServices {
|
||||
// Concrete types for handlers that need them
|
||||
folder_service_concrete: folder_service.clone(),
|
||||
@@ -266,12 +283,12 @@ impl AppServiceFactory {
|
||||
i18n_service,
|
||||
trash_service, // Already set via parameter
|
||||
search_service,
|
||||
share_service: None, // Configured later with create_share_service
|
||||
share_service: None, // Configured later with create_share_service
|
||||
favorites_service: None, // Configured later with create_favorites_service
|
||||
recent_service: None, // Configured later with create_recent_service
|
||||
recent_service: None, // Configured later with create_recent_service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Creates the trash service
|
||||
pub async fn create_trash_service(
|
||||
&self,
|
||||
@@ -281,9 +298,9 @@ impl AppServiceFactory {
|
||||
tracing::info!("Trash service is disabled in configuration");
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
let trash_repo = repos.trash_repository.as_ref()?;
|
||||
|
||||
|
||||
// Wire ports directly to TrashService — no adapter layer needed
|
||||
let service = Arc::new(TrashService::new(
|
||||
trash_repo.clone(),
|
||||
@@ -292,20 +309,20 @@ impl AppServiceFactory {
|
||||
repos.folder_repository.clone(),
|
||||
self.config.storage.trash_retention_days,
|
||||
));
|
||||
|
||||
|
||||
// Initialize cleanup service
|
||||
let cleanup_service = TrashCleanupService::new(
|
||||
service.clone(),
|
||||
trash_repo.clone(),
|
||||
24, // Run cleanup every 24 hours
|
||||
);
|
||||
|
||||
|
||||
cleanup_service.start_cleanup_job().await;
|
||||
tracing::info!("Trash service initialized with daily cleanup schedule");
|
||||
|
||||
|
||||
Some(service as Arc<dyn TrashUseCase>)
|
||||
}
|
||||
|
||||
|
||||
/// Creates the sharing service
|
||||
pub fn create_share_service(
|
||||
&self,
|
||||
@@ -315,11 +332,9 @@ impl AppServiceFactory {
|
||||
tracing::info!("File sharing service is disabled in configuration");
|
||||
return None;
|
||||
}
|
||||
|
||||
let share_repository = Arc::new(ShareFsRepository::new(
|
||||
Arc::new(self.config.clone())
|
||||
));
|
||||
|
||||
|
||||
let share_repository = Arc::new(ShareFsRepository::new(Arc::new(self.config.clone())));
|
||||
|
||||
// Build a password hasher for share password verification
|
||||
let password_hasher: Arc<dyn crate::application::ports::auth_ports::PasswordHasherPort> =
|
||||
Arc::new(crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new());
|
||||
@@ -331,44 +346,37 @@ impl AppServiceFactory {
|
||||
repos.folder_repository.clone(),
|
||||
password_hasher,
|
||||
));
|
||||
|
||||
|
||||
tracing::info!("File sharing service initialized");
|
||||
Some(service)
|
||||
}
|
||||
|
||||
|
||||
/// Creates the favorites service (requires database)
|
||||
pub fn create_favorites_service(
|
||||
&self,
|
||||
db_pool: &Arc<PgPool>,
|
||||
) -> Arc<dyn FavoritesUseCase> {
|
||||
pub fn create_favorites_service(&self, db_pool: &Arc<PgPool>) -> Arc<dyn FavoritesUseCase> {
|
||||
let repo = Arc::new(
|
||||
crate::infrastructure::repositories::pg::FavoritesPgRepository::new(db_pool.clone())
|
||||
crate::infrastructure::repositories::pg::FavoritesPgRepository::new(db_pool.clone()),
|
||||
);
|
||||
let service = Arc::new(FavoritesService::new(repo));
|
||||
tracing::info!("Favorites service initialized");
|
||||
service
|
||||
}
|
||||
|
||||
|
||||
/// Creates the recent items service (requires database)
|
||||
pub fn create_recent_service(
|
||||
&self,
|
||||
db_pool: &Arc<PgPool>,
|
||||
) -> Arc<dyn RecentItemsUseCase> {
|
||||
pub fn create_recent_service(&self, db_pool: &Arc<PgPool>) -> Arc<dyn RecentItemsUseCase> {
|
||||
let repo = Arc::new(
|
||||
crate::infrastructure::repositories::pg::RecentItemsPgRepository::new(db_pool.clone())
|
||||
crate::infrastructure::repositories::pg::RecentItemsPgRepository::new(db_pool.clone()),
|
||||
);
|
||||
let service = Arc::new(RecentService::new(
|
||||
repo,
|
||||
50 // Maximum recent items per user
|
||||
repo, 50, // Maximum recent items per user
|
||||
));
|
||||
tracing::info!("Recent items service initialized");
|
||||
service
|
||||
}
|
||||
|
||||
|
||||
/// Preloads translations
|
||||
pub async fn preload_translations(&self, i18n_service: &I18nApplicationService) {
|
||||
use crate::domain::services::i18n_service::Locale;
|
||||
|
||||
|
||||
if let Err(e) = i18n_service.load_translations(Locale::English).await {
|
||||
tracing::warn!("Failed to load English translations: {}", e);
|
||||
}
|
||||
@@ -394,13 +402,13 @@ impl AppServiceFactory {
|
||||
db_pool: &Arc<PgPool>,
|
||||
) -> Arc<dyn crate::application::ports::storage_ports::StorageUsagePort> {
|
||||
let user_repository = Arc::new(
|
||||
crate::infrastructure::repositories::pg::UserPgRepository::new(db_pool.clone())
|
||||
crate::infrastructure::repositories::pg::UserPgRepository::new(db_pool.clone()),
|
||||
);
|
||||
let service = Arc::new(
|
||||
crate::application::services::storage_usage_service::StorageUsageService::new(
|
||||
repos.file_read_repository.clone(),
|
||||
user_repository,
|
||||
)
|
||||
),
|
||||
);
|
||||
tracing::info!("Storage usage service initialized");
|
||||
service
|
||||
@@ -415,7 +423,10 @@ impl AppServiceFactory {
|
||||
) -> Result<AppState, DomainError> {
|
||||
// Database is REQUIRED in 100% blob storage model
|
||||
let pool = db_pool.clone().ok_or_else(|| {
|
||||
DomainError::internal_error("Database", "PostgreSQL database is required for blob storage model")
|
||||
DomainError::internal_error(
|
||||
"Database",
|
||||
"PostgreSQL database is required for blob storage model",
|
||||
)
|
||||
})?;
|
||||
|
||||
// 1. Core services (PgPool needed for DedupService index)
|
||||
@@ -437,7 +448,9 @@ impl AppServiceFactory {
|
||||
// 6. Database-dependent services (PgPool always available in blob model)
|
||||
let favorites_service: Option<Arc<dyn FavoritesUseCase>>;
|
||||
let recent_service: Option<Arc<dyn RecentItemsUseCase>>;
|
||||
let storage_usage_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>;
|
||||
let storage_usage_service: Option<
|
||||
Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>,
|
||||
>;
|
||||
let mut auth_services: Option<crate::common::di::AuthServices> = None;
|
||||
|
||||
{
|
||||
@@ -457,7 +470,9 @@ impl AppServiceFactory {
|
||||
&self.config,
|
||||
pool.clone(),
|
||||
Some(apps.folder_service_concrete.clone()),
|
||||
).await {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(services) => {
|
||||
tracing::info!("Authentication services initialized successfully");
|
||||
auth_services = Some(services);
|
||||
@@ -477,7 +492,7 @@ impl AppServiceFactory {
|
||||
crate::infrastructure::services::zip_service::ZipService::new(
|
||||
apps.file_retrieval_service.clone(),
|
||||
apps.folder_service.clone(),
|
||||
)
|
||||
),
|
||||
);
|
||||
let mut core = core;
|
||||
core.zip_service = zip_service;
|
||||
@@ -501,11 +516,11 @@ impl AppServiceFactory {
|
||||
addressbook_use_case: None,
|
||||
contact_use_case: None,
|
||||
};
|
||||
|
||||
|
||||
// 9b. Wire admin settings service when auth is available
|
||||
if let Some(auth_svc) = &app_state.auth_service {
|
||||
let settings_repo = Arc::new(
|
||||
crate::infrastructure::repositories::pg::SettingsPgRepository::new(pool.clone())
|
||||
crate::infrastructure::repositories::pg::SettingsPgRepository::new(pool.clone()),
|
||||
);
|
||||
let server_base_url = self.config.base_url();
|
||||
|
||||
@@ -521,34 +536,50 @@ impl AppServiceFactory {
|
||||
|
||||
// Hot-reload OIDC from DB settings if configured
|
||||
match admin_svc.load_effective_oidc_config().await {
|
||||
Ok(eff) if eff.enabled && !eff.issuer_url.is_empty()
|
||||
&& !eff.client_id.is_empty() && !eff.client_secret.is_empty() =>
|
||||
Ok(eff)
|
||||
if eff.enabled
|
||||
&& !eff.issuer_url.is_empty()
|
||||
&& !eff.client_id.is_empty()
|
||||
&& !eff.client_secret.is_empty() =>
|
||||
{
|
||||
let oidc_svc = Arc::new(
|
||||
crate::infrastructure::services::oidc_service::OidcService::new(eff.clone())
|
||||
crate::infrastructure::services::oidc_service::OidcService::new(
|
||||
eff.clone(),
|
||||
),
|
||||
);
|
||||
auth_svc.auth_application_service.reload_oidc(oidc_svc, eff);
|
||||
tracing::info!("OIDC config loaded from admin settings (database)");
|
||||
}
|
||||
Ok(_) => {
|
||||
tracing::info!("No active OIDC config in admin settings — using env vars or defaults");
|
||||
tracing::info!(
|
||||
"No active OIDC config in admin settings — using env vars or defaults"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to load OIDC settings from database (table may not exist yet): {}", e);
|
||||
tracing::warn!(
|
||||
"Failed to load OIDC settings from database (table may not exist yet): {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app_state.admin_settings_service = Some(admin_svc);
|
||||
}
|
||||
|
||||
|
||||
// 10. Wire CalDAV/CardDAV services
|
||||
{
|
||||
// CalDAV
|
||||
let calendar_repo: Arc<dyn crate::domain::repositories::calendar_repository::CalendarRepository> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::CalendarPgRepository::new(pool.clone())
|
||||
let calendar_repo: Arc<
|
||||
dyn crate::domain::repositories::calendar_repository::CalendarRepository,
|
||||
> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::CalendarPgRepository::new(pool.clone()),
|
||||
);
|
||||
let event_repo: Arc<dyn crate::domain::repositories::calendar_event_repository::CalendarEventRepository> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::CalendarEventPgRepository::new(pool.clone())
|
||||
let event_repo: Arc<
|
||||
dyn crate::domain::repositories::calendar_event_repository::CalendarEventRepository,
|
||||
> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::CalendarEventPgRepository::new(
|
||||
pool.clone(),
|
||||
),
|
||||
);
|
||||
let calendar_storage = Arc::new(
|
||||
crate::infrastructure::adapters::calendar_storage_adapter::CalendarStorageAdapter::new(
|
||||
@@ -557,19 +588,32 @@ impl AppServiceFactory {
|
||||
)
|
||||
);
|
||||
let calendar_service = Arc::new(
|
||||
crate::application::services::calendar_service::CalendarService::new(calendar_storage)
|
||||
crate::application::services::calendar_service::CalendarService::new(
|
||||
calendar_storage,
|
||||
),
|
||||
);
|
||||
app_state.calendar_use_case = Some(calendar_service as Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>);
|
||||
|
||||
app_state.calendar_use_case = Some(
|
||||
calendar_service
|
||||
as Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>,
|
||||
);
|
||||
|
||||
// CardDAV
|
||||
let address_book_repo: Arc<dyn crate::domain::repositories::address_book_repository::AddressBookRepository> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::AddressBookPgRepository::new(pool.clone())
|
||||
let address_book_repo: Arc<
|
||||
dyn crate::domain::repositories::address_book_repository::AddressBookRepository,
|
||||
> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::AddressBookPgRepository::new(pool.clone()),
|
||||
);
|
||||
let contact_repo: Arc<dyn crate::domain::repositories::contact_repository::ContactRepository> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::ContactPgRepository::new(pool.clone())
|
||||
let contact_repo: Arc<
|
||||
dyn crate::domain::repositories::contact_repository::ContactRepository,
|
||||
> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::ContactPgRepository::new(pool.clone()),
|
||||
);
|
||||
let group_repo: Arc<dyn crate::domain::repositories::contact_repository::ContactGroupRepository> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::ContactGroupPgRepository::new(pool.clone())
|
||||
let group_repo: Arc<
|
||||
dyn crate::domain::repositories::contact_repository::ContactGroupRepository,
|
||||
> = Arc::new(
|
||||
crate::infrastructure::repositories::pg::ContactGroupPgRepository::new(
|
||||
pool.clone(),
|
||||
),
|
||||
);
|
||||
let contact_storage = Arc::new(
|
||||
crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter::new(
|
||||
@@ -578,9 +622,13 @@ impl AppServiceFactory {
|
||||
group_repo,
|
||||
)
|
||||
);
|
||||
app_state.addressbook_use_case = Some(contact_storage.clone() as Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>);
|
||||
app_state.contact_use_case = Some(contact_storage as Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>);
|
||||
|
||||
app_state.addressbook_use_case = Some(contact_storage.clone()
|
||||
as Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>);
|
||||
app_state.contact_use_case = Some(
|
||||
contact_storage
|
||||
as Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>,
|
||||
);
|
||||
|
||||
tracing::info!("CalDAV and CardDAV services initialized with PostgreSQL repositories");
|
||||
}
|
||||
|
||||
@@ -610,7 +658,8 @@ pub struct RepositoryServices {
|
||||
pub file_read_repository: Arc<dyn FileReadPort>,
|
||||
pub file_write_repository: Arc<dyn FileWritePort>,
|
||||
pub i18n_repository: Arc<dyn I18nService>,
|
||||
pub trash_repository: Option<Arc<dyn crate::domain::repositories::trash_repository::TrashRepository>>,
|
||||
pub trash_repository:
|
||||
Option<Arc<dyn crate::domain::repositories::trash_repository::TrashRepository>>,
|
||||
}
|
||||
|
||||
/// Container for application services
|
||||
@@ -652,11 +701,14 @@ pub struct AppState {
|
||||
pub share_service: Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>>,
|
||||
pub favorites_service: Option<Arc<dyn FavoritesUseCase>>,
|
||||
pub recent_service: Option<Arc<dyn RecentItemsUseCase>>,
|
||||
pub storage_usage_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>,
|
||||
pub storage_usage_service:
|
||||
Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>,
|
||||
pub calendar_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>,
|
||||
pub contact_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>,
|
||||
pub calendar_use_case: Option<Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>>,
|
||||
pub addressbook_use_case: Option<Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>>,
|
||||
pub calendar_use_case:
|
||||
Option<Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>>,
|
||||
pub addressbook_use_case:
|
||||
Option<Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>>,
|
||||
pub contact_use_case: Option<Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>>,
|
||||
}
|
||||
|
||||
@@ -668,16 +720,22 @@ impl Default for AppState {
|
||||
let config = crate::common::config::AppConfig::default();
|
||||
let path_service = Arc::new(
|
||||
crate::infrastructure::services::path_service::PathService::new(
|
||||
std::path::PathBuf::from("./storage")
|
||||
)
|
||||
std::path::PathBuf::from("./storage"),
|
||||
),
|
||||
);
|
||||
|
||||
let i18n_repository = Arc::new(StubI18nService) as Arc<dyn crate::domain::services::i18n_service::I18nService>;
|
||||
let folder_service = Arc::new(StubFolderUseCase) as Arc<dyn crate::application::ports::inbound::FolderUseCase>;
|
||||
let file_upload_service = Arc::new(StubFileUploadUseCase) as Arc<dyn crate::application::ports::file_ports::FileUploadUseCase>;
|
||||
let file_retrieval_service = Arc::new(StubFileRetrievalUseCase) as Arc<dyn crate::application::ports::file_ports::FileRetrievalUseCase>;
|
||||
let file_management_service = Arc::new(StubFileManagementUseCase) as Arc<dyn crate::application::ports::file_ports::FileManagementUseCase>;
|
||||
let file_use_case_factory = Arc::new(StubFileUseCaseFactory) as Arc<dyn crate::application::ports::file_ports::FileUseCaseFactory>;
|
||||
let i18n_repository = Arc::new(StubI18nService)
|
||||
as Arc<dyn crate::domain::services::i18n_service::I18nService>;
|
||||
let folder_service = Arc::new(StubFolderUseCase)
|
||||
as Arc<dyn crate::application::ports::inbound::FolderUseCase>;
|
||||
let file_upload_service = Arc::new(StubFileUploadUseCase)
|
||||
as Arc<dyn crate::application::ports::file_ports::FileUploadUseCase>;
|
||||
let file_retrieval_service = Arc::new(StubFileRetrievalUseCase)
|
||||
as Arc<dyn crate::application::ports::file_ports::FileRetrievalUseCase>;
|
||||
let file_management_service = Arc::new(StubFileManagementUseCase)
|
||||
as Arc<dyn crate::application::ports::file_ports::FileManagementUseCase>;
|
||||
let file_use_case_factory = Arc::new(StubFileUseCaseFactory)
|
||||
as Arc<dyn crate::application::ports::file_ports::FileUseCaseFactory>;
|
||||
|
||||
// Create file content cache for stub
|
||||
let file_content_cache = Arc::new(FileContentCache::new(FileContentCacheConfig::default()));
|
||||
@@ -688,14 +746,14 @@ impl Default for AppState {
|
||||
&std::path::PathBuf::from("./storage"),
|
||||
100,
|
||||
10 * 1024 * 1024,
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
// Create dummy chunked upload service
|
||||
let dummy_chunked_upload_service: Arc<dyn ChunkedUploadPort> = Arc::new(
|
||||
crate::infrastructure::services::chunked_upload_service::ChunkedUploadService::new(
|
||||
std::path::PathBuf::from("./storage/.uploads")
|
||||
)
|
||||
std::path::PathBuf::from("./storage/.uploads"),
|
||||
),
|
||||
);
|
||||
|
||||
// Create dummy image transcode service
|
||||
@@ -704,7 +762,7 @@ impl Default for AppState {
|
||||
&std::path::PathBuf::from("./storage"),
|
||||
100,
|
||||
10 * 1024 * 1024,
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
// Stub dedup service (Default is only used for routing stubs, never for real I/O)
|
||||
@@ -729,22 +787,28 @@ impl Default for AppState {
|
||||
|
||||
// Repository services using stubs
|
||||
let repository_services = RepositoryServices {
|
||||
folder_repository: Arc::new(StubFolderStoragePort) as Arc<dyn crate::application::ports::outbound::FolderStoragePort>,
|
||||
folder_repository: Arc::new(StubFolderStoragePort)
|
||||
as Arc<dyn crate::application::ports::outbound::FolderStoragePort>,
|
||||
folder_repo_concrete: dummy_folder_repo_concrete,
|
||||
file_read_repository: Arc::new(StubFileReadPort) as Arc<dyn crate::application::ports::storage_ports::FileReadPort>,
|
||||
file_write_repository: Arc::new(StubFileWritePort) as Arc<dyn crate::application::ports::storage_ports::FileWritePort>,
|
||||
file_read_repository: Arc::new(StubFileReadPort)
|
||||
as Arc<dyn crate::application::ports::storage_ports::FileReadPort>,
|
||||
file_write_repository: Arc::new(StubFileWritePort)
|
||||
as Arc<dyn crate::application::ports::storage_ports::FileWritePort>,
|
||||
i18n_repository,
|
||||
trash_repository: None,
|
||||
};
|
||||
|
||||
// Dummy concrete services for compatibility
|
||||
let dummy_folder_storage = Arc::new(StubFolderStoragePort) as Arc<dyn crate::application::ports::outbound::FolderStoragePort>;
|
||||
let dummy_folder_storage = Arc::new(StubFolderStoragePort)
|
||||
as Arc<dyn crate::application::ports::outbound::FolderStoragePort>;
|
||||
let folder_service_concrete = Arc::new(FolderService::new(dummy_folder_storage));
|
||||
|
||||
// Dummy I18nApplicationService
|
||||
let dummy_i18n_app_service = crate::application::services::i18n_application_service::I18nApplicationService::new(
|
||||
Arc::new(StubI18nService) as Arc<dyn crate::domain::services::i18n_service::I18nService>
|
||||
);
|
||||
let dummy_i18n_app_service =
|
||||
crate::application::services::i18n_application_service::I18nApplicationService::new(
|
||||
Arc::new(StubI18nService)
|
||||
as Arc<dyn crate::domain::services::i18n_service::I18nService>,
|
||||
);
|
||||
|
||||
// Application services using stubs
|
||||
let application_services = ApplicationServices {
|
||||
@@ -756,7 +820,8 @@ impl Default for AppState {
|
||||
file_use_case_factory,
|
||||
i18n_service: Arc::new(dummy_i18n_app_service),
|
||||
trash_service: None,
|
||||
search_service: Some(Arc::new(StubSearchUseCase) as Arc<dyn crate::application::ports::inbound::SearchUseCase>),
|
||||
search_service: Some(Arc::new(StubSearchUseCase)
|
||||
as Arc<dyn crate::application::ports::inbound::SearchUseCase>),
|
||||
share_service: None,
|
||||
favorites_service: None,
|
||||
recent_service: None,
|
||||
@@ -808,12 +873,12 @@ impl AppState {
|
||||
contact_use_case: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn with_database(mut self, db_pool: Arc<PgPool>) -> Self {
|
||||
self.db_pool = Some(db_pool);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Creates a minimal AppState for route construction.
|
||||
///
|
||||
/// Uses `Default` stubs for infrastructure services, then overlays the real
|
||||
@@ -821,11 +886,15 @@ impl AppState {
|
||||
/// This keeps `routes.rs` free of any `crate::infrastructure` references.
|
||||
pub fn for_routing(
|
||||
folder_service: Arc<FolderService>,
|
||||
file_retrieval_service: Arc<dyn crate::application::ports::file_ports::FileRetrievalUseCase>,
|
||||
file_retrieval_service: Arc<
|
||||
dyn crate::application::ports::file_ports::FileRetrievalUseCase,
|
||||
>,
|
||||
file_upload_service: Arc<dyn FileUploadUseCase>,
|
||||
file_management_service: Arc<dyn FileManagementUseCase>,
|
||||
folder_use_case: Arc<dyn crate::application::ports::inbound::FolderUseCase>,
|
||||
i18n_service: Option<Arc<crate::application::services::i18n_application_service::I18nApplicationService>>,
|
||||
i18n_service: Option<
|
||||
Arc<crate::application::services::i18n_application_service::I18nApplicationService>,
|
||||
>,
|
||||
trash_service: Option<Arc<dyn TrashUseCase>>,
|
||||
search_service: Option<Arc<dyn crate::application::ports::inbound::SearchUseCase>>,
|
||||
share_service: Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>>,
|
||||
@@ -833,98 +902,121 @@ impl AppState {
|
||||
recent_service: Option<Arc<dyn RecentItemsUseCase>>,
|
||||
) -> Self {
|
||||
let mut state = Self::default();
|
||||
|
||||
|
||||
// Override application services with real ones
|
||||
state.applications.folder_service_concrete = folder_service.clone();
|
||||
state.applications.folder_service = folder_use_case;
|
||||
state.applications.file_upload_service = file_upload_service;
|
||||
state.applications.file_retrieval_service = file_retrieval_service.clone();
|
||||
state.applications.file_management_service = file_management_service;
|
||||
|
||||
|
||||
if let Some(i18n) = i18n_service {
|
||||
state.applications.i18n_service = i18n;
|
||||
}
|
||||
|
||||
|
||||
state.applications.trash_service = trash_service.clone();
|
||||
state.applications.search_service = search_service.clone();
|
||||
state.applications.share_service = share_service.clone();
|
||||
state.applications.favorites_service = favorites_service.clone();
|
||||
state.applications.recent_service = recent_service.clone();
|
||||
|
||||
|
||||
// Also set top-level optional services
|
||||
state.trash_service = trash_service;
|
||||
state.share_service = share_service;
|
||||
state.favorites_service = favorites_service;
|
||||
state.recent_service = recent_service;
|
||||
|
||||
|
||||
// Create real ZipService with the actual file/folder services
|
||||
state.core.zip_service = Arc::new(
|
||||
crate::infrastructure::services::zip_service::ZipService::new(
|
||||
file_retrieval_service as Arc<dyn crate::application::ports::file_ports::FileRetrievalUseCase>,
|
||||
folder_service.clone() as Arc<dyn crate::application::ports::inbound::FolderUseCase>,
|
||||
)
|
||||
file_retrieval_service
|
||||
as Arc<dyn crate::application::ports::file_ports::FileRetrievalUseCase>,
|
||||
folder_service.clone()
|
||||
as Arc<dyn crate::application::ports::inbound::FolderUseCase>,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
|
||||
pub fn with_auth_services(mut self, auth_services: AuthServices) -> Self {
|
||||
self.auth_service = Some(auth_services);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn with_trash_service(mut self, trash_service: Arc<dyn TrashUseCase>) -> Self {
|
||||
self.trash_service = Some(trash_service);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_share_service(mut self, share_service: Arc<dyn crate::application::ports::share_ports::ShareUseCase>) -> Self {
|
||||
|
||||
pub fn with_share_service(
|
||||
mut self,
|
||||
share_service: Arc<dyn crate::application::ports::share_ports::ShareUseCase>,
|
||||
) -> Self {
|
||||
self.share_service = Some(share_service);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn with_favorites_service(mut self, favorites_service: Arc<dyn FavoritesUseCase>) -> Self {
|
||||
self.favorites_service = Some(favorites_service);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn with_recent_service(mut self, recent_service: Arc<dyn RecentItemsUseCase>) -> Self {
|
||||
self.recent_service = Some(recent_service);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_storage_usage_service(mut self, storage_usage_service: Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>) -> Self {
|
||||
|
||||
pub fn with_storage_usage_service(
|
||||
mut self,
|
||||
storage_usage_service: Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>,
|
||||
) -> Self {
|
||||
self.storage_usage_service = Some(storage_usage_service);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_calendar_service(mut self, calendar_service: Arc<dyn crate::application::ports::storage_ports::StorageUseCase>) -> Self {
|
||||
|
||||
pub fn with_calendar_service(
|
||||
mut self,
|
||||
calendar_service: Arc<dyn crate::application::ports::storage_ports::StorageUseCase>,
|
||||
) -> Self {
|
||||
self.calendar_service = Some(calendar_service);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_contact_service(mut self, contact_service: Arc<dyn crate::application::ports::storage_ports::StorageUseCase>) -> Self {
|
||||
|
||||
pub fn with_contact_service(
|
||||
mut self,
|
||||
contact_service: Arc<dyn crate::application::ports::storage_ports::StorageUseCase>,
|
||||
) -> Self {
|
||||
self.contact_service = Some(contact_service);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_calendar_use_case(mut self, calendar_use_case: Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>) -> Self {
|
||||
|
||||
pub fn with_calendar_use_case(
|
||||
mut self,
|
||||
calendar_use_case: Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>,
|
||||
) -> Self {
|
||||
self.calendar_use_case = Some(calendar_use_case);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_addressbook_use_case(mut self, addressbook_use_case: Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>) -> Self {
|
||||
|
||||
pub fn with_addressbook_use_case(
|
||||
mut self,
|
||||
addressbook_use_case: Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>,
|
||||
) -> Self {
|
||||
self.addressbook_use_case = Some(addressbook_use_case);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_contact_use_case(mut self, contact_use_case: Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>) -> Self {
|
||||
|
||||
pub fn with_contact_use_case(
|
||||
mut self,
|
||||
contact_use_case: Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>,
|
||||
) -> Self {
|
||||
self.contact_use_case = Some(contact_use_case);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn with_zip_service(mut self, zip_service: Arc<dyn ZipPort>) -> Self {
|
||||
self.core.zip_service = zip_service;
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,14 @@ impl FileWritePort for StubFileWritePort {
|
||||
Ok(File::default())
|
||||
}
|
||||
|
||||
async fn copy_file(
|
||||
&self,
|
||||
_file_id: &str,
|
||||
_target_folder_id: Option<String>,
|
||||
) -> Result<File, DomainError> {
|
||||
Ok(File::default())
|
||||
}
|
||||
|
||||
async fn rename_file(&self, _file_id: &str, _new_name: &str) -> Result<File, DomainError> {
|
||||
Ok(File::default())
|
||||
}
|
||||
@@ -483,6 +491,14 @@ impl FileManagementUseCase for StubFileManagementUseCase {
|
||||
Ok(FileDto::default())
|
||||
}
|
||||
|
||||
async fn copy_file(
|
||||
&self,
|
||||
_file_id: &str,
|
||||
_folder_id: Option<String>,
|
||||
) -> Result<FileDto, DomainError> {
|
||||
Ok(FileDto::default())
|
||||
}
|
||||
|
||||
async fn rename_file(&self, _file_id: &str, _new_name: &str) -> Result<FileDto, DomainError> {
|
||||
Ok(FileDto::default())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user