2026-02-14 20:22:19 +01:00
|
|
|
use sqlx::PgPool;
|
2025-03-19 00:44:27 +01:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::sync::Arc;
|
2025-03-20 09:22:31 +01:00
|
|
|
|
2026-02-24 19:28:00 +01:00
|
|
|
use crate::infrastructure::db::DbPools;
|
|
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
use crate::application::services::admin_settings_service::AdminSettingsService;
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::application::services::auth_application_service::AuthApplicationService;
|
2025-03-19 00:44:27 +01:00
|
|
|
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::application::ports::cache_ports::ContentCachePort;
|
|
|
|
|
use crate::application::ports::chunked_upload_ports::ChunkedUploadPort;
|
2026-02-22 14:12:53 +01:00
|
|
|
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::application::ports::dedup_ports::DedupPort;
|
|
|
|
|
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
|
|
|
|
use crate::application::ports::file_ports::{
|
|
|
|
|
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, FileUseCaseFactory,
|
2026-02-14 01:29:34 +01:00
|
|
|
};
|
2026-02-08 13:40:23 +01:00
|
|
|
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
|
|
|
|
|
use crate::application::ports::outbound::FolderStoragePort;
|
2025-04-02 05:08:30 +02:00
|
|
|
use crate::application::ports::recent_ports::RecentItemsUseCase;
|
2025-03-26 18:33:22 +01:00
|
|
|
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
2026-02-08 13:40:23 +01:00
|
|
|
use crate::application::ports::thumbnail_ports::ThumbnailPort;
|
|
|
|
|
use crate::application::ports::transcode_ports::ImageTranscodePort;
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::application::ports::trash_ports::TrashUseCase;
|
2026-02-08 13:40:23 +01:00
|
|
|
use crate::application::ports::zip_ports::ZipPort;
|
2026-02-14 20:22:19 +01:00
|
|
|
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;
|
2026-02-25 10:28:34 +01:00
|
|
|
use crate::infrastructure::repositories::pg::SharePgRepository;
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::infrastructure::repositories::pg::{
|
|
|
|
|
FileBlobReadRepository, FileBlobWriteRepository, FolderDbRepository, TrashDbRepository,
|
|
|
|
|
};
|
|
|
|
|
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;
|
2026-02-08 13:40:23 +01:00
|
|
|
|
2026-02-24 15:11:56 +01:00
|
|
|
use crate::common::stubs::StubZipPort;
|
2025-03-19 00:44:27 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Factory for the different application components
|
2026-02-14 20:22:19 +01:00
|
|
|
///
|
2026-02-12 09:41:25 +01:00
|
|
|
/// This factory centralizes the creation of all application services,
|
|
|
|
|
/// ensuring the correct initialization order and resolving circular dependencies.
|
2025-03-19 00:44:27 +01:00
|
|
|
pub struct AppServiceFactory {
|
|
|
|
|
storage_path: PathBuf,
|
|
|
|
|
locales_path: PathBuf,
|
2025-03-19 19:52:12 +01:00
|
|
|
config: AppConfig,
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AppServiceFactory {
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates a new service factory
|
2025-03-19 00:44:27 +01:00
|
|
|
pub fn new(storage_path: PathBuf, locales_path: PathBuf) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
storage_path,
|
|
|
|
|
locales_path,
|
2025-03-19 19:52:12 +01:00
|
|
|
config: AppConfig::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates a new service factory with custom configuration
|
2025-03-19 19:52:12 +01:00
|
|
|
pub fn with_config(storage_path: PathBuf, locales_path: PathBuf, config: AppConfig) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
storage_path,
|
|
|
|
|
locales_path,
|
|
|
|
|
config,
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Gets the configuration
|
2026-02-02 23:56:40 +01:00
|
|
|
pub fn config(&self) -> &AppConfig {
|
|
|
|
|
&self.config
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Gets the storage path
|
2026-02-02 23:56:40 +01:00
|
|
|
pub fn storage_path(&self) -> &PathBuf {
|
|
|
|
|
&self.storage_path
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 19:30:49 +01:00
|
|
|
/// Initializes the core system services.
|
|
|
|
|
///
|
|
|
|
|
/// Requires a `PgPool` because `DedupService` stores its index in PostgreSQL.
|
2026-02-24 19:28:00 +01:00
|
|
|
/// The `maintenance_pool` is given to `DedupService` for long-running
|
|
|
|
|
/// operations (verify_integrity, garbage_collect) so they cannot starve
|
|
|
|
|
/// the primary pool.
|
2026-02-14 20:22:19 +01:00
|
|
|
pub async fn create_core_services(
|
|
|
|
|
&self,
|
|
|
|
|
db_pool: &Arc<PgPool>,
|
2026-02-24 19:28:00 +01:00
|
|
|
maintenance_pool: &Arc<PgPool>,
|
2026-02-14 20:22:19 +01:00
|
|
|
) -> Result<CoreServices, DomainError> {
|
2026-02-14 17:54:25 +01:00
|
|
|
// Path service (still needed for blob storage root + thumbnails)
|
2025-03-19 00:44:27 +01:00
|
|
|
let path_service = Arc::new(PathService::new(self.storage_path.clone()));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-03 17:59:04 +01:00
|
|
|
// File content cache for ultra-fast file serving (hot files in RAM)
|
|
|
|
|
let file_content_cache = Arc::new(FileContentCache::new(FileContentCacheConfig {
|
2026-02-14 20:22:19 +01:00
|
|
|
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
|
2026-02-03 17:59:04 +01:00
|
|
|
}));
|
|
|
|
|
tracing::info!("FileContentCache initialized: max 10MB/file, 512MB total, 10k entries");
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Thumbnail service for thumbnail generation
|
2026-02-03 17:59:04 +01:00
|
|
|
let thumbnail_service = Arc::new(
|
|
|
|
|
crate::infrastructure::services::thumbnail_service::ThumbnailService::new(
|
|
|
|
|
&self.storage_path,
|
2026-02-14 20:22:19 +01:00
|
|
|
5000, // max 5000 thumbnails in cache
|
|
|
|
|
100 * 1024 * 1024, // max 100MB cache
|
|
|
|
|
),
|
2026-02-03 17:59:04 +01:00
|
|
|
);
|
2026-02-12 09:41:25 +01:00
|
|
|
// Initialize thumbnail directories
|
2026-02-03 17:59:04 +01:00
|
|
|
thumbnail_service.initialize().await?;
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Chunked upload service for large files (>10MB)
|
2026-02-03 17:59:04 +01:00
|
|
|
let chunked_temp_dir = std::path::PathBuf::from(&self.storage_path).join(".uploads");
|
|
|
|
|
let chunked_upload_service = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::infrastructure::services::chunked_upload_service::ChunkedUploadService::new(
|
|
|
|
|
chunked_temp_dir,
|
2026-02-22 14:12:53 +01:00
|
|
|
)
|
|
|
|
|
.await,
|
2026-02-03 17:59:04 +01:00
|
|
|
);
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Image transcoding service for automatic WebP conversion
|
2026-02-03 17:59:04 +01:00
|
|
|
let image_transcode_service = Arc::new(
|
|
|
|
|
crate::infrastructure::services::image_transcode_service::ImageTranscodeService::new(
|
|
|
|
|
&self.storage_path,
|
2026-02-14 20:22:19 +01:00
|
|
|
2000, // max 2000 transcoded images in cache
|
|
|
|
|
50 * 1024 * 1024, // max 50MB in-memory cache
|
|
|
|
|
),
|
2026-02-03 17:59:04 +01:00
|
|
|
);
|
|
|
|
|
image_transcode_service.initialize().await?;
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 19:30:49 +01:00
|
|
|
// Deduplication service — PRIMARY blob storage engine (PostgreSQL-backed index)
|
2026-02-03 17:59:04 +01:00
|
|
|
let dedup_service = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::infrastructure::services::dedup_service::DedupService::new(
|
|
|
|
|
&self.storage_path,
|
|
|
|
|
db_pool.clone(),
|
2026-02-24 19:28:00 +01:00
|
|
|
maintenance_pool.clone(),
|
2026-02-14 20:22:19 +01:00
|
|
|
),
|
2026-02-03 17:59:04 +01:00
|
|
|
);
|
|
|
|
|
dedup_service.initialize().await?;
|
2026-02-14 20:22:19 +01:00
|
|
|
|
|
|
|
|
tracing::info!(
|
2026-02-22 14:12:53 +01:00
|
|
|
"Core services initialized: path service, file content cache, thumbnails, chunked upload, image transcode, dedup (PRIMARY blob storage)"
|
2026-02-14 20:22:19 +01:00
|
|
|
);
|
|
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
Ok(CoreServices {
|
|
|
|
|
path_service,
|
2026-02-03 17:59:04 +01:00
|
|
|
file_content_cache,
|
|
|
|
|
thumbnail_service,
|
|
|
|
|
chunked_upload_service,
|
|
|
|
|
image_transcode_service,
|
|
|
|
|
dedup_service,
|
2026-02-14 20:22:19 +01:00
|
|
|
zip_service: Arc::new(StubZipPort), // Placeholder - replaced after app services init
|
2025-03-19 19:52:12 +01:00
|
|
|
config: self.config.clone(),
|
2025-03-19 00:44:27 +01:00
|
|
|
})
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
/// Initializes the repository services (blob-storage model).
|
|
|
|
|
///
|
|
|
|
|
/// Requires a PgPool since all metadata lives in PostgreSQL.
|
2026-02-14 20:22:19 +01:00
|
|
|
pub fn create_repository_services(
|
|
|
|
|
&self,
|
|
|
|
|
core: &CoreServices,
|
|
|
|
|
db_pool: &Arc<PgPool>,
|
|
|
|
|
) -> RepositoryServices {
|
2026-02-14 17:54:25 +01:00
|
|
|
// 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();
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// 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(),
|
2025-03-19 19:52:12 +01:00
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
let file_write_repository: Arc<dyn FileWritePort> = Arc::new(FileBlobWriteRepository::new(
|
|
|
|
|
db_pool.clone(),
|
|
|
|
|
core.dedup_service.clone(),
|
|
|
|
|
folder_repo_concrete.clone(),
|
2025-03-19 00:44:27 +01:00
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
// I18n repository
|
2026-02-14 20:22:19 +01:00
|
|
|
let i18n_repository = Arc::new(FileSystemI18nService::new(self.locales_path.clone()));
|
|
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// Trash repository — reads soft-delete flags from storage.files/folders
|
2025-03-24 16:47:42 +01:00
|
|
|
let trash_repository = if core.config.features.enable_trash {
|
2026-02-14 17:54:25 +01:00
|
|
|
Some(Arc::new(TrashDbRepository::new(
|
|
|
|
|
db_pool.clone(),
|
|
|
|
|
core.config.storage.trash_retention_days,
|
2026-02-14 20:22:19 +01:00
|
|
|
))
|
|
|
|
|
as Arc<
|
|
|
|
|
dyn crate::domain::repositories::trash_repository::TrashRepository,
|
|
|
|
|
>)
|
2025-03-24 16:47:42 +01:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
|
|
|
|
tracing::info!(
|
|
|
|
|
"Repository services initialized with 100% blob storage model (PG metadata + DedupService blobs)"
|
|
|
|
|
);
|
|
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
RepositoryServices {
|
|
|
|
|
folder_repository,
|
2026-02-14 17:54:25 +01:00
|
|
|
folder_repo_concrete,
|
2025-03-19 19:52:12 +01:00
|
|
|
file_read_repository,
|
|
|
|
|
file_write_repository,
|
2025-03-19 00:44:27 +01:00
|
|
|
i18n_repository,
|
2025-03-24 16:47:42 +01:00
|
|
|
trash_repository,
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Initializes the application services
|
2026-02-08 13:40:23 +01:00
|
|
|
pub fn create_application_services(
|
|
|
|
|
&self,
|
|
|
|
|
core: &CoreServices,
|
|
|
|
|
repos: &RepositoryServices,
|
|
|
|
|
trash_service: Option<Arc<dyn TrashUseCase>>,
|
|
|
|
|
) -> ApplicationServices {
|
2026-02-12 09:41:25 +01:00
|
|
|
// Main services
|
2026-02-14 20:22:19 +01:00
|
|
|
let folder_service = Arc::new(FolderService::new(repos.folder_repository.clone()));
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Refactored services with all infrastructure ports
|
2026-02-14 17:54:25 +01:00
|
|
|
// In blob model, dedup is handled by the repository — no separate write-behind needed
|
|
|
|
|
let file_upload_service = Arc::new(FileUploadService::new_with_read(
|
2026-02-08 13:40:23 +01:00
|
|
|
repos.file_write_repository.clone(),
|
|
|
|
|
repos.file_read_repository.clone(),
|
2025-03-19 19:52:12 +01:00
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
let file_retrieval_service = Arc::new(FileRetrievalService::new_with_cache(
|
2026-02-08 13:40:23 +01:00
|
|
|
repos.file_read_repository.clone(),
|
|
|
|
|
core.file_content_cache.clone(),
|
|
|
|
|
core.image_transcode_service.clone(),
|
2025-03-19 19:52:12 +01:00
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-22 22:07:46 +01:00
|
|
|
// FileManagementService — ref_count handled by PG trigger, no dedup port needed
|
|
|
|
|
let file_management_service = Arc::new(FileManagementService::with_trash(
|
2026-02-08 13:40:23 +01:00
|
|
|
repos.file_write_repository.clone(),
|
|
|
|
|
trash_service.clone(),
|
2025-03-19 19:52:12 +01:00
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-19 19:52:12 +01:00
|
|
|
let file_use_case_factory = Arc::new(AppFileUseCaseFactory::new(
|
|
|
|
|
repos.file_read_repository.clone(),
|
2026-02-14 20:22:19 +01:00
|
|
|
repos.file_write_repository.clone(),
|
2026-02-14 17:54:25 +01:00
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
|
|
|
|
let i18n_service = Arc::new(I18nApplicationService::new(repos.i18n_repository.clone()));
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Search service with cache
|
2026-02-02 23:56:40 +01:00
|
|
|
let search_service: Option<Arc<dyn SearchUseCase>> = Some(Arc::new(SearchService::new(
|
2026-02-08 13:40:23 +01:00
|
|
|
repos.file_read_repository.clone(),
|
2026-02-02 23:56:40 +01:00
|
|
|
repos.folder_repository.clone(),
|
2026-02-14 20:22:19 +01:00
|
|
|
300, // Cache TTL in seconds (5 minutes)
|
2026-02-02 23:56:40 +01:00
|
|
|
1000, // Maximum cache entries
|
|
|
|
|
)));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
tracing::info!("Application services initialized");
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
ApplicationServices {
|
2026-02-12 09:41:25 +01:00
|
|
|
// Concrete types for handlers that need them
|
2026-02-02 23:56:40 +01:00
|
|
|
folder_service_concrete: folder_service.clone(),
|
2026-02-12 09:41:25 +01:00
|
|
|
// Traits for abstraction
|
2025-03-19 00:44:27 +01:00
|
|
|
folder_service,
|
2025-03-19 19:52:12 +01:00
|
|
|
file_upload_service,
|
|
|
|
|
file_retrieval_service,
|
|
|
|
|
file_management_service,
|
|
|
|
|
file_use_case_factory,
|
2025-03-19 00:44:27 +01:00
|
|
|
i18n_service,
|
2026-02-08 13:40:23 +01:00
|
|
|
trash_service, // Already set via parameter
|
2025-03-27 01:13:34 +01:00
|
|
|
search_service,
|
2026-02-14 20:22:19 +01:00
|
|
|
share_service: None, // Configured later with create_share_service
|
2026-02-12 09:41:25 +01:00
|
|
|
favorites_service: None, // Configured later with create_favorites_service
|
2026-02-14 20:22:19 +01:00
|
|
|
recent_service: None, // Configured later with create_recent_service
|
2026-02-02 23:56:40 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates the trash service
|
2026-02-02 23:56:40 +01:00
|
|
|
pub async fn create_trash_service(
|
|
|
|
|
&self,
|
|
|
|
|
repos: &RepositoryServices,
|
|
|
|
|
) -> Option<Arc<dyn TrashUseCase>> {
|
|
|
|
|
if !self.config.features.enable_trash {
|
|
|
|
|
tracing::info!("Trash service is disabled in configuration");
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
let trash_repo = repos.trash_repository.as_ref()?;
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// Wire ports directly to TrashService — no adapter layer needed
|
2026-02-02 23:56:40 +01:00
|
|
|
let service = Arc::new(TrashService::new(
|
|
|
|
|
trash_repo.clone(),
|
2026-02-08 13:40:23 +01:00
|
|
|
repos.file_read_repository.clone(),
|
|
|
|
|
repos.file_write_repository.clone(),
|
|
|
|
|
repos.folder_repository.clone(),
|
2026-02-02 23:56:40 +01:00
|
|
|
self.config.storage.trash_retention_days,
|
|
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Initialize cleanup service
|
2026-02-02 23:56:40 +01:00
|
|
|
let cleanup_service = TrashCleanupService::new(
|
|
|
|
|
service.clone(),
|
|
|
|
|
trash_repo.clone(),
|
|
|
|
|
24, // Run cleanup every 24 hours
|
|
|
|
|
);
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
cleanup_service.start_cleanup_job().await;
|
|
|
|
|
tracing::info!("Trash service initialized with daily cleanup schedule");
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
Some(service as Arc<dyn TrashUseCase>)
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates the sharing service
|
2026-02-02 23:56:40 +01:00
|
|
|
pub fn create_share_service(
|
|
|
|
|
&self,
|
|
|
|
|
repos: &RepositoryServices,
|
2026-02-24 10:09:49 +01:00
|
|
|
db_pool: &Arc<PgPool>,
|
2026-02-02 23:56:40 +01:00
|
|
|
) -> Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>> {
|
|
|
|
|
if !self.config.features.enable_file_sharing {
|
|
|
|
|
tracing::info!("File sharing service is disabled in configuration");
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-24 10:09:49 +01:00
|
|
|
let share_repository = Arc::new(SharePgRepository::new(db_pool.clone()));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// Build a password hasher for share password verification
|
|
|
|
|
let password_hasher: Arc<dyn crate::application::ports::auth_ports::PasswordHasherPort> =
|
2026-02-25 10:28:34 +01:00
|
|
|
Arc::new(
|
|
|
|
|
crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new(
|
|
|
|
|
self.config.auth.hash_memory_cost,
|
|
|
|
|
self.config.auth.hash_time_cost,
|
|
|
|
|
self.config.auth.hash_parallelism,
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-02-08 13:40:23 +01:00
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
let service = Arc::new(ShareService::new(
|
|
|
|
|
Arc::new(self.config.clone()),
|
|
|
|
|
share_repository,
|
2026-02-08 13:40:23 +01:00
|
|
|
repos.file_read_repository.clone(),
|
|
|
|
|
repos.folder_repository.clone(),
|
|
|
|
|
password_hasher,
|
2026-02-02 23:56:40 +01:00
|
|
|
));
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
tracing::info!("File sharing service initialized");
|
|
|
|
|
Some(service)
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates the favorites service (requires database)
|
2026-02-14 20:22:19 +01:00
|
|
|
pub fn create_favorites_service(&self, db_pool: &Arc<PgPool>) -> Arc<dyn FavoritesUseCase> {
|
2026-02-08 13:40:23 +01:00
|
|
|
let repo = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::infrastructure::repositories::pg::FavoritesPgRepository::new(db_pool.clone()),
|
2026-02-08 13:40:23 +01:00
|
|
|
);
|
|
|
|
|
let service = Arc::new(FavoritesService::new(repo));
|
2026-02-02 23:56:40 +01:00
|
|
|
tracing::info!("Favorites service initialized");
|
|
|
|
|
service
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates the recent items service (requires database)
|
2026-02-14 20:22:19 +01:00
|
|
|
pub fn create_recent_service(&self, db_pool: &Arc<PgPool>) -> Arc<dyn RecentItemsUseCase> {
|
2026-02-08 13:40:23 +01:00
|
|
|
let repo = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::infrastructure::repositories::pg::RecentItemsPgRepository::new(db_pool.clone()),
|
2026-02-08 13:40:23 +01:00
|
|
|
);
|
2026-02-02 23:56:40 +01:00
|
|
|
let service = Arc::new(RecentService::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
repo, 50, // Maximum recent items per user
|
2026-02-02 23:56:40 +01:00
|
|
|
));
|
|
|
|
|
tracing::info!("Recent items service initialized");
|
|
|
|
|
service
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Preloads translations
|
2026-02-02 23:56:40 +01:00
|
|
|
pub async fn preload_translations(&self, i18n_service: &I18nApplicationService) {
|
|
|
|
|
use crate::domain::services::i18n_service::Locale;
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
if let Err(e) = i18n_service.load_translations(Locale::English).await {
|
|
|
|
|
tracing::warn!("Failed to load English translations: {}", e);
|
|
|
|
|
}
|
|
|
|
|
if let Err(e) = i18n_service.load_translations(Locale::Spanish).await {
|
|
|
|
|
tracing::warn!("Failed to load Spanish translations: {}", e);
|
|
|
|
|
}
|
2026-02-11 12:26:29 +01:00
|
|
|
if let Err(e) = i18n_service.load_translations(Locale::French).await {
|
|
|
|
|
tracing::warn!("Failed to load French translations: {}", e);
|
|
|
|
|
}
|
|
|
|
|
if let Err(e) = i18n_service.load_translations(Locale::German).await {
|
|
|
|
|
tracing::warn!("Failed to load German translations: {}", e);
|
|
|
|
|
}
|
|
|
|
|
if let Err(e) = i18n_service.load_translations(Locale::Portuguese).await {
|
|
|
|
|
tracing::warn!("Failed to load Portuguese translations: {}", e);
|
|
|
|
|
}
|
2026-02-02 23:56:40 +01:00
|
|
|
tracing::info!("Translations preloaded");
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates the storage usage service (requires database)
|
2026-02-24 19:28:00 +01:00
|
|
|
///
|
|
|
|
|
/// Uses the `maintenance_pool` for batch operations
|
|
|
|
|
/// (`update_all_users_storage_usage`) to avoid starving user requests.
|
2026-02-08 13:40:23 +01:00
|
|
|
pub fn create_storage_usage_service(
|
|
|
|
|
&self,
|
2026-02-21 17:30:32 -08:00
|
|
|
_repos: &RepositoryServices,
|
2026-02-08 13:40:23 +01:00
|
|
|
db_pool: &Arc<PgPool>,
|
2026-02-24 19:28:00 +01:00
|
|
|
maintenance_pool: &Arc<PgPool>,
|
2026-02-08 13:40:23 +01:00
|
|
|
) -> Arc<dyn crate::application::ports::storage_ports::StorageUsagePort> {
|
|
|
|
|
let user_repository = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::infrastructure::repositories::pg::UserPgRepository::new(db_pool.clone()),
|
2026-02-08 13:40:23 +01:00
|
|
|
);
|
|
|
|
|
let service = Arc::new(
|
|
|
|
|
crate::application::services::storage_usage_service::StorageUsageService::new(
|
2026-02-24 19:28:00 +01:00
|
|
|
maintenance_pool.clone(),
|
2026-02-08 13:40:23 +01:00
|
|
|
user_repository,
|
2026-02-14 20:22:19 +01:00
|
|
|
),
|
2026-02-08 13:40:23 +01:00
|
|
|
);
|
|
|
|
|
tracing::info!("Storage usage service initialized");
|
|
|
|
|
service
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Builds the complete AppState using all factory services.
|
2026-02-08 13:40:23 +01:00
|
|
|
///
|
2026-02-12 09:41:25 +01:00
|
|
|
/// This is the main entry point that replaces all manual logic in `main.rs`.
|
2026-02-08 13:40:23 +01:00
|
|
|
pub async fn build_app_state(
|
|
|
|
|
&self,
|
2026-02-24 19:28:00 +01:00
|
|
|
db_pools: Option<DbPools>,
|
2026-02-08 13:40:23 +01:00
|
|
|
) -> Result<AppState, DomainError> {
|
2026-02-14 17:54:25 +01:00
|
|
|
// Database is REQUIRED in 100% blob storage model
|
2026-02-24 19:28:00 +01:00
|
|
|
let pools = db_pools.ok_or_else(|| {
|
2026-02-14 20:22:19 +01:00
|
|
|
DomainError::internal_error(
|
|
|
|
|
"Database",
|
|
|
|
|
"PostgreSQL database is required for blob storage model",
|
|
|
|
|
)
|
2026-02-14 17:54:25 +01:00
|
|
|
})?;
|
|
|
|
|
|
2026-02-24 19:28:00 +01:00
|
|
|
let pool = Arc::new(pools.primary);
|
|
|
|
|
let maintenance_pool = Arc::new(pools.maintenance);
|
|
|
|
|
|
2026-02-14 19:30:49 +01:00
|
|
|
// 1. Core services (PgPool needed for DedupService index)
|
2026-02-24 19:28:00 +01:00
|
|
|
let core = self.create_core_services(&pool, &maintenance_pool).await?;
|
2026-02-08 13:40:23 +01:00
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// 2. Repository services (requires PgPool for all metadata)
|
|
|
|
|
let repos = self.create_repository_services(&core, &pool);
|
2026-02-08 13:40:23 +01:00
|
|
|
|
|
|
|
|
// 3. Trash service (needed before application services)
|
|
|
|
|
let trash_service = self.create_trash_service(&repos).await;
|
|
|
|
|
|
|
|
|
|
// 4. Application services (with trash already wired)
|
|
|
|
|
let mut apps = self.create_application_services(&core, &repos, trash_service.clone());
|
|
|
|
|
|
|
|
|
|
// 5. Share service
|
2026-02-24 10:09:49 +01:00
|
|
|
let share_service = self.create_share_service(&repos, &pool);
|
2026-02-08 13:40:23 +01:00
|
|
|
apps.share_service = share_service.clone();
|
|
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// 6. Database-dependent services (PgPool always available in blob model)
|
|
|
|
|
let favorites_service: Option<Arc<dyn FavoritesUseCase>>;
|
|
|
|
|
let recent_service: Option<Arc<dyn RecentItemsUseCase>>;
|
2026-02-14 20:22:19 +01:00
|
|
|
let storage_usage_service: Option<
|
|
|
|
|
Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>,
|
|
|
|
|
>;
|
2026-02-08 13:40:23 +01:00
|
|
|
let mut auth_services: Option<crate::common::di::AuthServices> = None;
|
|
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
{
|
|
|
|
|
let favs = self.create_favorites_service(&pool);
|
2026-02-08 13:40:23 +01:00
|
|
|
favorites_service = Some(favs.clone());
|
|
|
|
|
apps.favorites_service = Some(favs);
|
|
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
let recent = self.create_recent_service(&pool);
|
2026-02-08 13:40:23 +01:00
|
|
|
recent_service = Some(recent.clone());
|
|
|
|
|
apps.recent_service = Some(recent);
|
|
|
|
|
|
2026-02-25 10:28:34 +01:00
|
|
|
storage_usage_service =
|
|
|
|
|
Some(self.create_storage_usage_service(&repos, &pool, &maintenance_pool));
|
2026-02-08 13:40:23 +01:00
|
|
|
|
|
|
|
|
// Auth services
|
|
|
|
|
if self.config.features.enable_auth {
|
2026-02-22 22:40:55 +01:00
|
|
|
let services = crate::infrastructure::auth_factory::create_auth_services(
|
2026-02-08 13:40:23 +01:00
|
|
|
&self.config,
|
|
|
|
|
pool.clone(),
|
|
|
|
|
Some(apps.folder_service_concrete.clone()),
|
2026-02-14 20:22:19 +01:00
|
|
|
)
|
|
|
|
|
.await
|
2026-02-22 22:40:55 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
// SECURITY: fail-closed. If auth is required but the auth
|
|
|
|
|
// services cannot be created, propagate the error so the
|
|
|
|
|
// server refuses to start — never degrade to public mode.
|
|
|
|
|
tracing::error!(
|
|
|
|
|
"FATAL: enable_auth=true but auth services failed to initialize: {}",
|
|
|
|
|
e
|
|
|
|
|
);
|
|
|
|
|
DomainError::internal_error(
|
|
|
|
|
"AuthInit",
|
|
|
|
|
format!(
|
|
|
|
|
"Authentication is enabled but auth services failed: {}. \
|
|
|
|
|
Refusing to start without authentication.",
|
|
|
|
|
e
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
tracing::info!("Authentication services initialized successfully");
|
|
|
|
|
auth_services = Some(services);
|
2026-02-08 13:40:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. Preload translations
|
|
|
|
|
self.preload_translations(&apps.i18n_service).await;
|
|
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// 8. Build the ZipService with real application services
|
2026-02-08 13:40:23 +01:00
|
|
|
let zip_service: Arc<dyn crate::application::ports::zip_ports::ZipPort> = Arc::new(
|
|
|
|
|
crate::infrastructure::services::zip_service::ZipService::new(
|
|
|
|
|
apps.file_retrieval_service.clone(),
|
|
|
|
|
apps.folder_service.clone(),
|
2026-02-14 20:22:19 +01:00
|
|
|
),
|
2026-02-08 13:40:23 +01:00
|
|
|
);
|
|
|
|
|
let mut core = core;
|
|
|
|
|
core.zip_service = zip_service;
|
|
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// 9. Assemble final AppState
|
2026-02-10 18:46:59 +01:00
|
|
|
let mut app_state = AppState {
|
2026-02-08 13:40:23 +01:00
|
|
|
core,
|
|
|
|
|
repositories: repos,
|
|
|
|
|
applications: apps,
|
2026-02-24 19:28:00 +01:00
|
|
|
db_pool: Some(pool.clone()),
|
|
|
|
|
maintenance_pool: Some(maintenance_pool),
|
2026-02-08 13:40:23 +01:00
|
|
|
auth_service: auth_services,
|
2026-02-11 00:15:26 +01:00
|
|
|
admin_settings_service: None,
|
2026-02-08 13:40:23 +01:00
|
|
|
trash_service,
|
|
|
|
|
share_service,
|
|
|
|
|
favorites_service,
|
|
|
|
|
recent_service,
|
|
|
|
|
storage_usage_service,
|
|
|
|
|
calendar_service: None,
|
|
|
|
|
contact_service: None,
|
2026-02-10 18:46:59 +01:00
|
|
|
calendar_use_case: None,
|
|
|
|
|
addressbook_use_case: None,
|
|
|
|
|
contact_use_case: None,
|
2026-02-21 13:39:27 +01:00
|
|
|
wopi_token_service: None,
|
|
|
|
|
wopi_lock_service: None,
|
|
|
|
|
wopi_discovery_service: None,
|
2026-02-08 13:40:23 +01:00
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// 9b. Wire admin settings service when auth is available
|
|
|
|
|
if let Some(auth_svc) = &app_state.auth_service {
|
2026-02-11 00:15:26 +01:00
|
|
|
let settings_repo = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::infrastructure::repositories::pg::SettingsPgRepository::new(pool.clone()),
|
2026-02-11 00:15:26 +01:00
|
|
|
);
|
2026-02-14 00:18:59 +01:00
|
|
|
let server_base_url = self.config.base_url();
|
2026-02-11 00:15:26 +01:00
|
|
|
|
|
|
|
|
// Load OIDC config from env vars (the snapshot from startup)
|
|
|
|
|
let env_oidc = crate::common::config::OidcConfig::from_env();
|
|
|
|
|
|
|
|
|
|
let admin_svc = Arc::new(AdminSettingsService::new(
|
|
|
|
|
settings_repo.clone(),
|
|
|
|
|
env_oidc,
|
|
|
|
|
auth_svc.auth_application_service.clone(),
|
|
|
|
|
server_base_url,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
// Hot-reload OIDC from DB settings if configured
|
|
|
|
|
match admin_svc.load_effective_oidc_config().await {
|
2026-02-14 20:22:19 +01:00
|
|
|
Ok(eff)
|
|
|
|
|
if eff.enabled
|
|
|
|
|
&& !eff.issuer_url.is_empty()
|
|
|
|
|
&& !eff.client_id.is_empty()
|
|
|
|
|
&& !eff.client_secret.is_empty() =>
|
2026-02-11 00:15:26 +01:00
|
|
|
{
|
|
|
|
|
let oidc_svc = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::infrastructure::services::oidc_service::OidcService::new(
|
|
|
|
|
eff.clone(),
|
|
|
|
|
),
|
2026-02-11 00:15:26 +01:00
|
|
|
);
|
|
|
|
|
auth_svc.auth_application_service.reload_oidc(oidc_svc, eff);
|
|
|
|
|
tracing::info!("OIDC config loaded from admin settings (database)");
|
|
|
|
|
}
|
|
|
|
|
Ok(_) => {
|
2026-02-14 20:22:19 +01:00
|
|
|
tracing::info!(
|
|
|
|
|
"No active OIDC config in admin settings — using env vars or defaults"
|
|
|
|
|
);
|
2026-02-11 00:15:26 +01:00
|
|
|
}
|
|
|
|
|
Err(e) => {
|
2026-02-14 20:22:19 +01:00
|
|
|
tracing::warn!(
|
|
|
|
|
"Failed to load OIDC settings from database (table may not exist yet): {}",
|
|
|
|
|
e
|
|
|
|
|
);
|
2026-02-11 00:15:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app_state.admin_settings_service = Some(admin_svc);
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-14 17:54:25 +01:00
|
|
|
// 10. Wire CalDAV/CardDAV services
|
|
|
|
|
{
|
2026-02-10 18:46:59 +01:00
|
|
|
// CalDAV
|
2026-02-14 20:22:19 +01:00
|
|
|
let calendar_repo: Arc<
|
|
|
|
|
dyn crate::domain::repositories::calendar_repository::CalendarRepository,
|
|
|
|
|
> = Arc::new(
|
|
|
|
|
crate::infrastructure::repositories::pg::CalendarPgRepository::new(pool.clone()),
|
2026-02-10 18:46:59 +01:00
|
|
|
);
|
2026-02-14 20:22:19 +01:00
|
|
|
let event_repo: Arc<
|
|
|
|
|
dyn crate::domain::repositories::calendar_event_repository::CalendarEventRepository,
|
|
|
|
|
> = Arc::new(
|
|
|
|
|
crate::infrastructure::repositories::pg::CalendarEventPgRepository::new(
|
|
|
|
|
pool.clone(),
|
|
|
|
|
),
|
2026-02-10 18:46:59 +01:00
|
|
|
);
|
|
|
|
|
let calendar_storage = Arc::new(
|
|
|
|
|
crate::infrastructure::adapters::calendar_storage_adapter::CalendarStorageAdapter::new(
|
|
|
|
|
calendar_repo,
|
|
|
|
|
event_repo,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
let calendar_service = Arc::new(
|
2026-02-14 20:22:19 +01:00
|
|
|
crate::application::services::calendar_service::CalendarService::new(
|
|
|
|
|
calendar_storage,
|
|
|
|
|
),
|
2026-02-10 18:46:59 +01:00
|
|
|
);
|
2026-02-14 20:22:19 +01:00
|
|
|
app_state.calendar_use_case = Some(
|
|
|
|
|
calendar_service
|
|
|
|
|
as Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>,
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-10 18:46:59 +01:00
|
|
|
// CardDAV
|
2026-02-14 20:22:19 +01:00
|
|
|
let address_book_repo: Arc<
|
|
|
|
|
dyn crate::domain::repositories::address_book_repository::AddressBookRepository,
|
|
|
|
|
> = Arc::new(
|
|
|
|
|
crate::infrastructure::repositories::pg::AddressBookPgRepository::new(pool.clone()),
|
2026-02-10 18:46:59 +01:00
|
|
|
);
|
2026-02-14 20:22:19 +01:00
|
|
|
let contact_repo: Arc<
|
|
|
|
|
dyn crate::domain::repositories::contact_repository::ContactRepository,
|
|
|
|
|
> = Arc::new(
|
|
|
|
|
crate::infrastructure::repositories::pg::ContactPgRepository::new(pool.clone()),
|
2026-02-10 18:46:59 +01:00
|
|
|
);
|
2026-02-14 20:22:19 +01:00
|
|
|
let group_repo: Arc<
|
|
|
|
|
dyn crate::domain::repositories::contact_repository::ContactGroupRepository,
|
|
|
|
|
> = Arc::new(
|
|
|
|
|
crate::infrastructure::repositories::pg::ContactGroupPgRepository::new(
|
|
|
|
|
pool.clone(),
|
|
|
|
|
),
|
2026-02-10 18:46:59 +01:00
|
|
|
);
|
|
|
|
|
let contact_storage = Arc::new(
|
|
|
|
|
crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter::new(
|
|
|
|
|
address_book_repo,
|
|
|
|
|
contact_repo,
|
|
|
|
|
group_repo,
|
|
|
|
|
)
|
|
|
|
|
);
|
2026-02-14 20:22:19 +01:00
|
|
|
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>,
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-10 18:46:59 +01:00
|
|
|
tracing::info!("CalDAV and CardDAV services initialized with PostgreSQL repositories");
|
|
|
|
|
}
|
2026-02-08 13:40:23 +01:00
|
|
|
|
2026-02-21 13:39:27 +01:00
|
|
|
// 11. Wire WOPI services if enabled
|
|
|
|
|
if self.config.wopi.enabled {
|
|
|
|
|
let discovery_url = &self.config.wopi.discovery_url;
|
|
|
|
|
if discovery_url.is_empty() {
|
|
|
|
|
tracing::error!(
|
|
|
|
|
"WOPI is enabled but WOPI_DISCOVERY_URL is empty — WOPI services will NOT be available"
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
use crate::application::services::wopi_lock_service::WopiLockService;
|
|
|
|
|
use crate::application::services::wopi_token_service::WopiTokenService;
|
|
|
|
|
use crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService;
|
|
|
|
|
|
|
|
|
|
let wopi_secret = if self.config.wopi.secret.is_empty() {
|
|
|
|
|
self.config.auth.jwt_secret.clone()
|
|
|
|
|
} else {
|
|
|
|
|
self.config.wopi.secret.clone()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let wopi_token_service = Arc::new(WopiTokenService::new(
|
|
|
|
|
wopi_secret,
|
|
|
|
|
self.config.wopi.token_ttl_secs,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
let wopi_lock_service =
|
|
|
|
|
Arc::new(WopiLockService::new(self.config.wopi.lock_ttl_secs));
|
|
|
|
|
wopi_lock_service.start_cleanup_task();
|
|
|
|
|
|
|
|
|
|
let wopi_discovery_service = Arc::new(WopiDiscoveryService::new(
|
|
|
|
|
discovery_url.clone(),
|
|
|
|
|
86400, // 24 hour cache TTL
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
app_state.wopi_token_service = Some(wopi_token_service);
|
|
|
|
|
app_state.wopi_lock_service = Some(wopi_lock_service);
|
|
|
|
|
app_state.wopi_discovery_service = Some(wopi_discovery_service);
|
|
|
|
|
|
|
|
|
|
tracing::info!("WOPI services initialized (discovery: {})", discovery_url);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
Ok(app_state)
|
|
|
|
|
}
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Container for core services
|
2025-03-24 16:47:42 +01:00
|
|
|
#[derive(Clone)]
|
2025-03-19 00:44:27 +01:00
|
|
|
pub struct CoreServices {
|
|
|
|
|
pub path_service: Arc<PathService>,
|
2026-02-08 13:40:23 +01:00
|
|
|
pub file_content_cache: Arc<dyn ContentCachePort>,
|
|
|
|
|
pub thumbnail_service: Arc<dyn ThumbnailPort>,
|
|
|
|
|
pub chunked_upload_service: Arc<dyn ChunkedUploadPort>,
|
|
|
|
|
pub image_transcode_service: Arc<dyn ImageTranscodePort>,
|
|
|
|
|
pub dedup_service: Arc<dyn DedupPort>,
|
|
|
|
|
pub zip_service: Arc<dyn ZipPort>,
|
2025-03-19 19:52:12 +01:00
|
|
|
pub config: AppConfig,
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Container for repository services
|
2025-03-24 16:47:42 +01:00
|
|
|
#[derive(Clone)]
|
2025-03-19 00:44:27 +01:00
|
|
|
pub struct RepositoryServices {
|
|
|
|
|
pub folder_repository: Arc<dyn FolderStoragePort>,
|
2026-02-14 17:54:25 +01:00
|
|
|
pub folder_repo_concrete: Arc<FolderDbRepository>,
|
2025-03-19 19:52:12 +01:00
|
|
|
pub file_read_repository: Arc<dyn FileReadPort>,
|
|
|
|
|
pub file_write_repository: Arc<dyn FileWritePort>,
|
2025-03-19 00:44:27 +01:00
|
|
|
pub i18n_repository: Arc<dyn I18nService>,
|
2026-02-14 20:22:19 +01:00
|
|
|
pub trash_repository:
|
|
|
|
|
Option<Arc<dyn crate::domain::repositories::trash_repository::TrashRepository>>,
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Container for application services
|
2025-03-24 16:47:42 +01:00
|
|
|
#[derive(Clone)]
|
2025-03-19 00:44:27 +01:00
|
|
|
pub struct ApplicationServices {
|
2026-02-12 09:41:25 +01:00
|
|
|
// Concrete types for compatibility with existing handlers
|
2026-02-02 23:56:40 +01:00
|
|
|
pub folder_service_concrete: Arc<FolderService>,
|
2026-02-12 09:41:25 +01:00
|
|
|
// Traits for abstraction
|
2025-03-19 00:44:27 +01:00
|
|
|
pub folder_service: Arc<dyn FolderUseCase>,
|
2025-03-19 19:52:12 +01:00
|
|
|
pub file_upload_service: Arc<dyn FileUploadUseCase>,
|
|
|
|
|
pub file_retrieval_service: Arc<dyn FileRetrievalUseCase>,
|
|
|
|
|
pub file_management_service: Arc<dyn FileManagementUseCase>,
|
|
|
|
|
pub file_use_case_factory: Arc<dyn FileUseCaseFactory>,
|
2025-03-19 00:44:27 +01:00
|
|
|
pub i18n_service: Arc<I18nApplicationService>,
|
2025-03-24 16:47:42 +01:00
|
|
|
pub trash_service: Option<Arc<dyn TrashUseCase>>,
|
2025-03-27 01:13:34 +01:00
|
|
|
pub search_service: Option<Arc<dyn SearchUseCase>>,
|
2025-03-28 12:38:48 +01:00
|
|
|
pub share_service: Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>>,
|
2025-04-02 03:43:44 +02:00
|
|
|
pub favorites_service: Option<Arc<dyn FavoritesUseCase>>,
|
2025-04-02 05:08:30 +02:00
|
|
|
pub recent_service: Option<Arc<dyn RecentItemsUseCase>>,
|
2025-03-20 09:22:31 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Container for authentication services
|
2025-03-24 16:47:42 +01:00
|
|
|
#[derive(Clone)]
|
2025-03-20 09:22:31 +01:00
|
|
|
pub struct AuthServices {
|
2026-02-02 23:56:40 +01:00
|
|
|
pub token_service: Arc<dyn crate::application::ports::auth_ports::TokenServicePort>,
|
2025-03-20 09:22:31 +01:00
|
|
|
pub auth_application_service: Arc<AuthApplicationService>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Global application state for dependency injection
|
2025-03-24 16:47:42 +01:00
|
|
|
#[derive(Clone)]
|
2025-03-20 09:22:31 +01:00
|
|
|
pub struct AppState {
|
|
|
|
|
pub core: CoreServices,
|
|
|
|
|
pub repositories: RepositoryServices,
|
|
|
|
|
pub applications: ApplicationServices,
|
|
|
|
|
pub db_pool: Option<Arc<PgPool>>,
|
2026-02-24 19:28:00 +01:00
|
|
|
/// Isolated pool for background / batch operations.
|
|
|
|
|
pub maintenance_pool: Option<Arc<PgPool>>,
|
2025-03-20 09:22:31 +01:00
|
|
|
pub auth_service: Option<AuthServices>,
|
2026-02-11 00:15:26 +01:00
|
|
|
pub admin_settings_service: Option<Arc<AdminSettingsService>>,
|
2025-03-24 16:47:42 +01:00
|
|
|
pub trash_service: Option<Arc<dyn TrashUseCase>>,
|
2025-03-28 12:38:48 +01:00
|
|
|
pub share_service: Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>>,
|
2025-04-02 03:43:44 +02:00
|
|
|
pub favorites_service: Option<Arc<dyn FavoritesUseCase>>,
|
2025-04-02 05:08:30 +02:00
|
|
|
pub recent_service: Option<Arc<dyn RecentItemsUseCase>>,
|
2026-02-14 20:22:19 +01:00
|
|
|
pub storage_usage_service:
|
|
|
|
|
Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>,
|
2025-04-13 01:04:04 +02:00
|
|
|
pub calendar_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>,
|
|
|
|
|
pub contact_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>,
|
2026-02-14 20:22:19 +01:00
|
|
|
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>>,
|
2026-02-10 18:46:59 +01:00
|
|
|
pub contact_use_case: Option<Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>>,
|
2026-02-21 13:39:27 +01:00
|
|
|
pub wopi_token_service:
|
|
|
|
|
Option<Arc<crate::application::services::wopi_token_service::WopiTokenService>>,
|
|
|
|
|
pub wopi_lock_service:
|
|
|
|
|
Option<Arc<crate::application::services::wopi_lock_service::WopiLockService>>,
|
|
|
|
|
pub wopi_discovery_service:
|
|
|
|
|
Option<Arc<crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService>>,
|
2025-03-20 09:22:31 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 15:11:56 +01:00
|
|
|
// All AppState construction is done via struct literal in build_app_state().
|