From 1b49135ca956f28c16e17500b618b66a82582772 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 15:36:42 +0000 Subject: [PATCH] perf: replace dyn trait objects with concrete types to eliminate vtable overhead Remove async-trait dependency and use native Rust async fn in traits. Replace Arc with Arc throughout the codebase to enable monomorphization and eliminate dynamic dispatch overhead. Key changes: - Remove write-behind cache (no implementation existed) - Fix should_transcode static method call - Use ContactStorageAdapter directly instead of dyn AddressBookUseCase - Clean up unused trait imports across services and DI https://claude.ai/code/session_01EbAFEfyJNLRmJHmmYDX3Tt --- Cargo.lock | 12 -- Cargo.toml | 1 - src/application/ports/auth_ports.rs | 7 - src/application/ports/cache_ports.rs | 4 - src/application/ports/calendar_ports.rs | 3 - src/application/ports/carddav_ports.rs | 3 - src/application/ports/chunked_upload_ports.rs | 2 - src/application/ports/compression_ports.rs | 2 - src/application/ports/dedup_ports.rs | 2 - src/application/ports/favorites_ports.rs | 3 - src/application/ports/file_ports.rs | 13 +- src/application/ports/inbound.rs | 3 - src/application/ports/outbound.rs | 2 - src/application/ports/recent_ports.rs | 3 - src/application/ports/share_ports.rs | 3 - src/application/ports/storage_ports.rs | 8 - src/application/ports/thumbnail_ports.rs | 2 - src/application/ports/transcode_ports.rs | 2 - src/application/ports/trash_ports.rs | 2 - src/application/ports/zip_ports.rs | 2 - .../services/admin_settings_service.rs | 5 +- .../services/app_password_service.rs | 15 +- .../services/auth_application_service.rs | 34 ++-- src/application/services/batch_operations.rs | 19 +- src/application/services/calendar_service.rs | 7 +- src/application/services/contact_service.rs | 19 +- .../services/device_auth_service.rs | 20 +- src/application/services/favorites_service.rs | 7 +- .../services/file_management_service.rs | 14 +- .../services/file_retrieval_service.rs | 73 ++----- .../services/file_upload_service.rs | 19 +- .../services/file_use_case_factory.rs | 21 +- src/application/services/folder_service.rs | 10 +- .../services/i18n_application_service.rs | 28 +-- src/application/services/recent_service.rs | 7 +- src/application/services/search_service.rs | 15 +- src/application/services/share_service.rs | 29 ++- .../services/storage_usage_service.rs | 7 +- src/application/services/trash_service.rs | 24 +-- .../services/trash_service_test.rs | 25 +-- src/common/di.rs | 180 +++++++++--------- src/common/stubs.rs | 36 +--- .../repositories/address_book_repository.rs | 2 - .../repositories/calendar_event_repository.rs | 2 - .../repositories/calendar_repository.rs | 2 - src/domain/repositories/contact_repository.rs | 3 - src/domain/repositories/file_repository.rs | 3 - src/domain/repositories/folder_repository.rs | 2 - src/domain/repositories/session_repository.rs | 2 - .../repositories/settings_repository.rs | 2 - src/domain/repositories/share_repository.rs | 2 - src/domain/repositories/trash_repository.rs | 2 - src/domain/repositories/user_repository.rs | 2 - src/domain/services/i18n_service.rs | 2 - .../adapters/calendar_storage_adapter.rs | 12 +- .../adapters/contact_storage_adapter.rs | 18 +- src/infrastructure/auth_factory.rs | 3 +- .../pg/address_book_pg_repository.rs | 2 - .../pg/app_password_pg_repository.rs | 2 - .../pg/calendar_event_pg_repository.rs | 2 - .../repositories/pg/calendar_pg_repository.rs | 2 - .../pg/contact_group_pg_repository.rs | 2 - .../repositories/pg/contact_pg_repository.rs | 2 - .../pg/device_code_pg_repository.rs | 2 - .../pg/favorites_pg_repository.rs | 2 - .../pg/file_blob_read_repository.rs | 11 +- .../pg/file_blob_write_repository.rs | 8 +- .../repositories/pg/folder_db_repository.rs | 2 - .../pg/recent_items_pg_repository.rs | 2 - .../repositories/pg/session_pg_repository.rs | 3 - .../repositories/pg/settings_pg_repository.rs | 2 - .../repositories/pg/share_pg_repository.rs | 2 - .../repositories/pg/trash_db_repository.rs | 2 - .../repositories/pg/user_pg_repository.rs | 3 - .../services/chunked_upload_service.rs | 2 - .../services/compression_service.rs | 4 - src/infrastructure/services/dedup_service.rs | 2 - .../services/file_content_cache.rs | 2 - .../services/file_system_i18n_service.rs | 2 - .../services/image_transcode_service.rs | 2 - src/infrastructure/services/oidc_service.rs | 2 - .../services/password_hasher.rs | 2 - src/infrastructure/services/path_service.rs | 2 - .../services/thumbnail_service.rs | 2 - .../services/trash_cleanup_service.rs | 7 +- src/infrastructure/services/zip_service.rs | 12 +- src/interfaces/api/handlers/admin_handler.rs | 1 + src/interfaces/api/handlers/caldav_handler.rs | 3 +- .../api/handlers/carddav_handler.rs | 5 +- .../api/handlers/chunked_upload_handler.rs | 3 + .../api/handlers/favorites_handler.rs | 9 +- src/interfaces/api/handlers/file_handler.rs | 5 +- src/interfaces/api/handlers/folder_handler.rs | 14 +- src/interfaces/api/handlers/recent_handler.rs | 9 +- src/interfaces/api/handlers/search_handler.rs | 1 + src/interfaces/api/handlers/share_handler.rs | 15 +- src/interfaces/api/handlers/trash_handler.rs | 2 +- src/interfaces/api/handlers/webdav_handler.rs | 7 +- src/interfaces/api/handlers/wopi_handler.rs | 1 + src/interfaces/api/routes.rs | 2 +- src/interfaces/middleware/auth.rs | 1 + 101 files changed, 346 insertions(+), 576 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61150d9d..a510da6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,17 +129,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async_zip" version = "0.0.18" @@ -1819,7 +1808,6 @@ dependencies = [ "argon2", "async-compression", "async-stream", - "async-trait", "async_zip", "axum", "base64", diff --git a/Cargo.toml b/Cargo.toml index 768ddf46..e1527e0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ futures = "0.3.32" async-stream = "0.3.6" mime_guess = "2.0.5" uuid = { version = "1.21.0", features = ["v4", "serde"] } -async-trait = "0.1.89" thiserror = "2.0.18" mockall = { version = "0.14.0", optional = true } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-rustls", "chrono", "uuid", "json"] } diff --git a/src/application/ports/auth_ports.rs b/src/application/ports/auth_ports.rs index 90b71f39..400d6053 100644 --- a/src/application/ports/auth_ports.rs +++ b/src/application/ports/auth_ports.rs @@ -3,7 +3,6 @@ use crate::domain::entities::app_password::AppPassword; use crate::domain::entities::device_code::DeviceCode; use crate::domain::entities::session::Session; use crate::domain::entities::user::User; -use async_trait::async_trait; // ============================================================================ // Cryptography Ports - Extracted from Domain to maintain Clean Architecture @@ -16,7 +15,6 @@ use async_trait::async_trait; /// /// Methods are async because implementations (e.g. Argon2) are CPU-intensive /// and must run on a blocking thread pool to avoid starving Tokio workers. -#[async_trait] pub trait PasswordHasherPort: Send + Sync + 'static { /// Hash a plain text password async fn hash_password(&self, password: &str) -> Result; @@ -69,7 +67,6 @@ pub trait TokenServicePort: Send + Sync + 'static { // Storage Ports // ============================================================================ -#[async_trait] pub trait UserStoragePort: Send + Sync + 'static { /// Creates a new user async fn create_user(&self, user: User) -> Result; @@ -153,7 +150,6 @@ pub struct OidcIdClaims { } /// Port for OIDC operations — implemented in infrastructure layer -#[async_trait] pub trait OidcServicePort: Send + Sync + 'static { /// Get the authorization URL for redirecting the user to the IdP. /// Includes PKCE code_challenge (S256) and nonce for ID token binding. @@ -187,7 +183,6 @@ pub trait OidcServicePort: Send + Sync + 'static { fn provider_name(&self) -> &str; } -#[async_trait] pub trait SessionStoragePort: Send + Sync + 'static { /// Creates a new session async fn create_session(&self, session: Session) -> Result; @@ -209,7 +204,6 @@ pub trait SessionStoragePort: Send + Sync + 'static { // Device Authorization Grant Port (RFC 8628) // ============================================================================ -#[async_trait] pub trait DeviceCodeStoragePort: Send + Sync + 'static { /// Persist a new device code flow async fn create_device_code(&self, device_code: DeviceCode) -> Result; @@ -238,7 +232,6 @@ pub trait DeviceCodeStoragePort: Send + Sync + 'static { // ============================================================================ /// Storage port for application-specific passwords (HTTP Basic Auth for DAV clients). -#[async_trait] pub trait AppPasswordStoragePort: Send + Sync + 'static { /// Persist a new app password (hash already computed). async fn create(&self, app_password: AppPassword) -> Result; diff --git a/src/application/ports/cache_ports.rs b/src/application/ports/cache_ports.rs index 6af6a8e2..a9e92bb6 100644 --- a/src/application/ports/cache_ports.rs +++ b/src/application/ports/cache_ports.rs @@ -9,7 +9,6 @@ //! implementation details. use crate::common::errors::DomainError; -use async_trait::async_trait; use bytes::Bytes; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -30,7 +29,6 @@ pub struct WriteBehindStatsDto { /// Provides deferred write semantics: small files are held in memory /// and the response is returned immediately, while actual disk writes /// happen asynchronously in the background. -#[async_trait] pub trait WriteBehindCachePort: Send + Sync + 'static { /// Check if a file size is eligible for write-behind caching. fn is_eligible_size(&self, size: usize) -> bool; @@ -82,7 +80,6 @@ pub struct CachedMetadataDto { /// /// Provides fast lookups for existence, size, timestamps and MIME types /// without hitting the filesystem on every request. -#[async_trait] pub trait MetadataCachePort: Send + Sync + 'static { /// Get cached metadata for a path, or `None` on miss / expired. async fn get_metadata(&self, path: &Path) -> Option; @@ -106,7 +103,6 @@ pub trait MetadataCachePort: Send + Sync + 'static { /// /// Implementations should use LRU eviction and respect size limits so that /// the application layer never needs to know the concrete cache type. -#[async_trait] pub trait ContentCachePort: Send + Sync + 'static { /// Check whether a file of the given size should be cached. fn should_cache(&self, size: usize) -> bool; diff --git a/src/application/ports/calendar_ports.rs b/src/application/ports/calendar_ports.rs index 667ead90..ca67bfde 100644 --- a/src/application/ports/calendar_ports.rs +++ b/src/application/ports/calendar_ports.rs @@ -3,11 +3,9 @@ use crate::application::dtos::calendar_dto::{ UpdateCalendarDto, UpdateEventDto, }; use crate::common::errors::DomainError; -use async_trait::async_trait; use chrono::{DateTime, Utc}; /// Port for external calendar storage mechanisms -#[async_trait] pub trait CalendarStoragePort: Send + Sync + 'static { // Calendar operations async fn create_calendar( @@ -111,7 +109,6 @@ pub trait CalendarStoragePort: Send + Sync + 'static { /// All methods require an explicit `user_id` parameter for authorization. /// The CalDAV protocol handler extracts the user identity from JWT claims /// and passes it through. -#[async_trait] pub trait CalendarUseCase: Send + Sync + 'static { // Calendar operations async fn create_calendar( diff --git a/src/application/ports/carddav_ports.rs b/src/application/ports/carddav_ports.rs index 8ca75dfe..c46beafc 100644 --- a/src/application/ports/carddav_ports.rs +++ b/src/application/ports/carddav_ports.rs @@ -7,11 +7,9 @@ use crate::application::dtos::contact_dto::{ GroupMembershipDto, UpdateContactDto, UpdateContactGroupDto, }; use crate::common::errors::DomainError; -use async_trait::async_trait; pub type CardDavRepositoryError = DomainError; -#[async_trait] pub trait AddressBookUseCase: Send + Sync + 'static { // Address Book operations async fn create_address_book( @@ -57,7 +55,6 @@ pub trait AddressBookUseCase: Send + Sync + 'static { ) -> Result, DomainError>; } -#[async_trait] pub trait ContactUseCase: Send + Sync + 'static { // Contact operations async fn create_contact(&self, dto: CreateContactDto) -> Result; diff --git a/src/application/ports/chunked_upload_ports.rs b/src/application/ports/chunked_upload_ports.rs index 59af4460..3414f35d 100644 --- a/src/application/ports/chunked_upload_ports.rs +++ b/src/application/ports/chunked_upload_ports.rs @@ -5,7 +5,6 @@ //! the specific upload implementation (TUS-like protocol, S3 multipart, etc.). use crate::common::errors::DomainError; -use async_trait::async_trait; use bytes::Bytes; use serde::Serialize; use std::path::PathBuf; @@ -53,7 +52,6 @@ pub struct UploadStatusResponseDto { /// Implementations manage upload sessions, chunk storage, reassembly, /// and cleanup, while the application layer only interacts through /// this abstraction. -#[async_trait] pub trait ChunkedUploadPort: Send + Sync + 'static { /// Create a new upload session. /// diff --git a/src/application/ports/compression_ports.rs b/src/application/ports/compression_ports.rs index 2de75c57..270eb149 100644 --- a/src/application/ports/compression_ports.rs +++ b/src/application/ports/compression_ports.rs @@ -5,7 +5,6 @@ //! compression implementations (gzip, zstd, etc.). use crate::common::errors::DomainError; -use async_trait::async_trait; /// Compression level settings for file compression operations. /// @@ -27,7 +26,6 @@ pub enum CompressionLevel { /// Implementations of this trait provide the actual compression logic /// (e.g., gzip, zstd) while the application layer remains agnostic /// of the specific algorithm used. -#[async_trait] pub trait CompressionPort: Send + Sync + 'static { /// Compress data in memory. async fn compress_data( diff --git a/src/application/ports/dedup_ports.rs b/src/application/ports/dedup_ports.rs index 4cc91b05..9986ecac 100644 --- a/src/application/ports/dedup_ports.rs +++ b/src/application/ports/dedup_ports.rs @@ -5,7 +5,6 @@ //! content-addressable storage implementation. use crate::common::errors::DomainError; -use async_trait::async_trait; use bytes::Bytes; use futures::Stream; use serde::Serialize; @@ -92,7 +91,6 @@ pub struct DedupStatsDto { /// Implementations store files by their content hash, eliminating /// duplicate storage automatically. Multiple file references can /// point to the same physical blob. -#[async_trait] pub trait DedupPort: Send + Sync + 'static { /// Store content with deduplication (from bytes). /// diff --git a/src/application/ports/favorites_ports.rs b/src/application/ports/favorites_ports.rs index cb8d71e6..654594ac 100644 --- a/src/application/ports/favorites_ports.rs +++ b/src/application/ports/favorites_ports.rs @@ -1,9 +1,7 @@ use crate::application::dtos::favorites_dto::{BatchFavoritesResult, FavoriteItemDto}; use crate::common::errors::Result; -use async_trait::async_trait; /// Defines operations for managing user favorites -#[async_trait] pub trait FavoritesUseCase: Send + Sync { /// Get all favorites for a user async fn get_favorites(&self, user_id: &str) -> Result>; @@ -40,7 +38,6 @@ pub trait FavoritesUseCase: Send + Sync { /// Application services depend on this trait instead of /// accessing `PgPool` directly. The concrete implementation /// lives in `infrastructure::repositories::pg`. -#[async_trait] pub trait FavoritesRepositoryPort: Send + Sync + 'static { /// Gets all favorites for a user. async fn get_favorites(&self, user_id: &str) -> Result>; diff --git a/src/application/ports/file_ports.rs b/src/application/ports/file_ports.rs index cfb6fc57..ba791c55 100644 --- a/src/application/ports/file_ports.rs +++ b/src/application/ports/file_ports.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use bytes::Bytes; use futures::Stream; use std::path::Path; @@ -8,6 +7,9 @@ use std::sync::Arc; use crate::application::dtos::file_dto::FileDto; use crate::application::ports::storage_ports::CopyFolderTreeResult; use crate::common::errors::DomainError; +use crate::application::services::file_management_service::FileManagementService; +use crate::application::services::file_retrieval_service::FileRetrievalService; +use crate::application::services::file_upload_service::FileUploadService; // ───────────────────────────────────────────────────── // Upload port @@ -24,7 +26,6 @@ use crate::common::errors::DomainError; /// - Chunked uploads: chunks already on disk → `upload_file_from_path` /// - WebDAV PUT (new): handler streams to temp file → `update_file_streaming` /// - WebDAV PUT (small/compat): `create_file` / `update_file` spool internally -#[async_trait] pub trait FileUploadUseCase: Send + Sync + 'static { /// Upload from a temp file already on disk (true streaming, ~256 KB RAM). /// @@ -102,7 +103,6 @@ pub enum OptimizedFileContent { } /// Primary port for file retrieval operations -#[async_trait] pub trait FileRetrievalUseCase: Send + Sync + 'static { /// Gets a file by its ID async fn get_file(&self, id: &str) -> Result; @@ -188,7 +188,6 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static { // ───────────────────────────────────────────────────── /// Primary port for file management operations -#[async_trait] pub trait FileManagementUseCase: Send + Sync + 'static { /// Moves a file to another folder async fn move_file( @@ -241,7 +240,7 @@ pub trait FileManagementUseCase: Send + Sync + 'static { /// Factory for creating file use case implementations pub trait FileUseCaseFactory: Send + Sync + 'static { - fn create_file_upload_use_case(&self) -> Arc; - fn create_file_retrieval_use_case(&self) -> Arc; - fn create_file_management_use_case(&self) -> Arc; + fn create_file_upload_use_case(&self) -> Arc; + fn create_file_retrieval_use_case(&self) -> Arc; + fn create_file_management_use_case(&self) -> Arc; } diff --git a/src/application/ports/inbound.rs b/src/application/ports/inbound.rs index 6cdba0c3..a5ca06f2 100644 --- a/src/application/ports/inbound.rs +++ b/src/application/ports/inbound.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use std::sync::Arc; use crate::application::dtos::folder_dto::{ @@ -10,7 +9,6 @@ use crate::application::dtos::search_dto::{ use crate::common::errors::DomainError; /// Primary port for folder operations -#[async_trait] pub trait FolderUseCase: Send + Sync + 'static { /// Creates a new folder async fn create_folder(&self, dto: CreateFolderDto) -> Result; @@ -89,7 +87,6 @@ pub trait FolderUseCase: Send + Sync + 'static { * All search processing (filtering, scoring, sorting, categorization) * is handled server-side in Rust for maximum efficiency. */ -#[async_trait] pub trait SearchUseCase: Send + Sync + 'static { /// Performs a full search based on the specified criteria. /// diff --git a/src/application/ports/outbound.rs b/src/application/ports/outbound.rs index e9ee2c30..9ac33e95 100644 --- a/src/application/ports/outbound.rs +++ b/src/application/ports/outbound.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use std::path::PathBuf; use crate::common::errors::DomainError; @@ -10,7 +9,6 @@ pub use crate::domain::repositories::folder_repository::FolderRepository; use super::storage_ports::{FileReadPort, FileWritePort}; /// Secondary port for storage operations -#[async_trait] pub trait StoragePort: Send + Sync + 'static { /// Resolves a domain path to a physical path fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf; diff --git a/src/application/ports/recent_ports.rs b/src/application/ports/recent_ports.rs index 5d6156ad..81f3b12a 100644 --- a/src/application/ports/recent_ports.rs +++ b/src/application/ports/recent_ports.rs @@ -1,9 +1,7 @@ use crate::application::dtos::recent_dto::RecentItemDto; use crate::common::errors::Result; -use async_trait::async_trait; /// Defines operations for managing user recent items -#[async_trait] pub trait RecentItemsUseCase: Send + Sync { /// Get all recent items for a user async fn get_recent_items( @@ -36,7 +34,6 @@ pub trait RecentItemsUseCase: Send + Sync { /// /// Abstracts access to the `auth.user_recent_files` table so that /// `RecentService` does not depend directly on `PgPool`. -#[async_trait] pub trait RecentItemsRepositoryPort: Send + Sync + 'static { /// Gets the latest recent items for a user (ordered by date desc). async fn get_recent_items(&self, user_id: &str, limit: i32) -> Result>; diff --git a/src/application/ports/share_ports.rs b/src/application/ports/share_ports.rs index 814b7213..7f9a5943 100644 --- a/src/application/ports/share_ports.rs +++ b/src/application/ports/share_ports.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use crate::{ application::dtos::{ @@ -9,7 +8,6 @@ use crate::{ domain::entities::share::ShareItemType, }; -#[async_trait] pub trait ShareUseCase: Send + Sync + 'static { /// Create a new shared link for a file or folder async fn create_shared_link( @@ -60,7 +58,6 @@ pub trait ShareUseCase: Send + Sync + 'static { async fn register_shared_link_access(&self, token: &str) -> Result<(), DomainError>; } -#[async_trait] pub trait ShareStoragePort: Send + Sync + 'static { async fn save_share( &self, diff --git a/src/application/ports/storage_ports.rs b/src/application/ports/storage_ports.rs index 3e98aefa..a8b5b9e7 100644 --- a/src/application/ports/storage_ports.rs +++ b/src/application/ports/storage_ports.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use bytes::Bytes; use futures::Stream; use serde_json::Value; @@ -25,7 +24,6 @@ pub use crate::domain::repositories::folder_repository::FolderRepository; /// /// Encapsulates every operation that queries state without modifying it: /// get, list, content, stream, mmap, range, path resolution. -#[async_trait] pub trait FileReadPort: Send + Sync + 'static { /// Gets a file by its ID. async fn get_file(&self, id: &str) -> Result; @@ -199,7 +197,6 @@ pub struct CopyFolderTreeResult { /// /// Covers: upload (buffered + streaming), move, delete, update, /// and deferred registration for the write-behind cache. -#[async_trait] pub trait FileWritePort: Send + Sync + 'static { /// Streaming upload — saves a file from a temp file already on disk. /// @@ -307,7 +304,6 @@ pub trait FileWritePort: Send + Sync + 'static { // ───────────────────────────────────────────────────── /// Secondary port for file path resolution -#[async_trait] pub trait FilePathResolutionPort: Send + Sync + 'static { /// Gets the storage path of a file async fn get_file_path(&self, id: &str) -> Result; @@ -317,7 +313,6 @@ pub trait FilePathResolutionPort: Send + Sync + 'static { } /// Secondary port for file/directory existence verification -#[async_trait] pub trait StorageVerificationPort: Send + Sync + 'static { /// Checks whether a file exists at the given path async fn file_exists(&self, storage_path: &StoragePath) -> Result; @@ -327,14 +322,12 @@ pub trait StorageVerificationPort: Send + Sync + 'static { } /// Secondary port for directory management -#[async_trait] pub trait DirectoryManagementPort: Send + Sync + 'static { /// Creates directories if they do not exist async fn ensure_directory(&self, storage_path: &StoragePath) -> Result<(), DomainError>; } /// Secondary port for storage usage management -#[async_trait] pub trait StorageUsagePort: Send + Sync + 'static { /// Updates storage usage statistics for a user async fn update_user_storage_usage(&self, user_id: &str) -> Result; @@ -362,7 +355,6 @@ pub trait StorageUsagePort: Send + Sync + 'static { } /// Generic storage service interface for calendar and contact services -#[async_trait] pub trait StorageUseCase: Send + Sync + 'static { /// Handle a request with the specified action and parameters async fn handle_request(&self, action: &str, params: Value) -> Result; diff --git a/src/application/ports/thumbnail_ports.rs b/src/application/ports/thumbnail_ports.rs index 66e20268..6074f625 100644 --- a/src/application/ports/thumbnail_ports.rs +++ b/src/application/ports/thumbnail_ports.rs @@ -5,7 +5,6 @@ //! image processing implementations. use crate::common::errors::DomainError; -use async_trait::async_trait; use bytes::Bytes; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -63,7 +62,6 @@ pub struct ThumbnailStatsDto { /// Implementations handle the actual image processing, caching, /// and storage of thumbnails, while the application layer only /// interacts through this abstraction. -#[async_trait] pub trait ThumbnailPort: Send + Sync + 'static { /// Check if a file is an image that can have thumbnails. fn is_supported_image(&self, mime_type: &str) -> bool; diff --git a/src/application/ports/transcode_ports.rs b/src/application/ports/transcode_ports.rs index c70d75a8..1e7ed3b9 100644 --- a/src/application/ports/transcode_ports.rs +++ b/src/application/ports/transcode_ports.rs @@ -5,7 +5,6 @@ //! independent of specific image processing implementations. use crate::common::errors::DomainError; -use async_trait::async_trait; use bytes::Bytes; /// Supported output formats for image transcoding. @@ -74,7 +73,6 @@ pub struct TranscodeStatsDto { /// Implementations handle the actual image conversion, caching, /// and format detection, while the application layer only interacts /// through this abstraction. -#[async_trait] pub trait ImageTranscodePort: Send + Sync + 'static { /// Check if a MIME type can be transcoded. fn can_transcode(&self, mime_type: &str) -> bool; diff --git a/src/application/ports/trash_ports.rs b/src/application/ports/trash_ports.rs index 52b00dac..b09466ce 100644 --- a/src/application/ports/trash_ports.rs +++ b/src/application/ports/trash_ports.rs @@ -1,10 +1,8 @@ -use async_trait::async_trait; use crate::application::dtos::trash_dto::TrashedItemDto; use crate::common::errors::Result; /// Port for trash-related use cases -#[async_trait] pub trait TrashUseCase: Send + Sync { /// List items in the user's trash async fn get_trash_items(&self, user_id: &str) -> Result>; diff --git a/src/application/ports/zip_ports.rs b/src/application/ports/zip_ports.rs index 4a43356d..e124f59f 100644 --- a/src/application/ports/zip_ports.rs +++ b/src/application/ports/zip_ports.rs @@ -5,14 +5,12 @@ //! implementation details. use crate::common::errors::DomainError; -use async_trait::async_trait; use tempfile::NamedTempFile; /// Port for ZIP archive operations. /// /// Implementations handle the actual ZIP file creation, compression, /// and recursive folder traversal. -#[async_trait] pub trait ZipPort: Send + Sync + 'static { /// Create a ZIP archive containing the contents of a folder (recursively). /// diff --git a/src/application/services/admin_settings_service.rs b/src/application/services/admin_settings_service.rs index e8bb104e..83642ee0 100644 --- a/src/application/services/admin_settings_service.rs +++ b/src/application/services/admin_settings_service.rs @@ -8,13 +8,14 @@ use crate::common::config::OidcConfig; use crate::common::errors::{DomainError, ErrorKind}; use crate::domain::repositories::settings_repository::SettingsRepository; use crate::infrastructure::services::oidc_service::OidcService; +use crate::infrastructure::repositories::pg::SettingsPgRepository; /// Admin settings service — manages platform configuration in the database. /// /// Configuration priority: **env vars > DB settings > defaults**. /// Supports hot-reloading OIDC configuration without server restart. pub struct AdminSettingsService { - settings_repo: Arc, + settings_repo: Arc, env_oidc_config: OidcConfig, auth_app_service: Arc, server_base_url: String, @@ -22,7 +23,7 @@ pub struct AdminSettingsService { impl AdminSettingsService { pub fn new( - settings_repo: Arc, + settings_repo: Arc, env_oidc_config: OidcConfig, auth_app_service: Arc, server_base_url: String, diff --git a/src/application/services/app_password_service.rs b/src/application/services/app_password_service.rs index 3f2a07ae..61e951e0 100644 --- a/src/application/services/app_password_service.rs +++ b/src/application/services/app_password_service.rs @@ -13,6 +13,9 @@ use chrono::{Duration, Utc}; use moka::future::Cache; use std::sync::Arc; use std::time::Duration as StdDuration; +use crate::infrastructure::repositories::pg::AppPasswordPgRepository; +use crate::infrastructure::services::password_hasher::Argon2PasswordHasher; +use crate::infrastructure::repositories::pg::UserPgRepository; /// App password token length (32 random alphanumeric chars after prefix). const TOKEN_LENGTH: usize = 32; @@ -39,9 +42,9 @@ struct CachedBasicAuthResult { } pub struct AppPasswordService { - repo: Arc, - hasher: Arc, - user_repo: Arc, + repo: Arc, + hasher: Arc, + user_repo: Arc, base_url: String, /// In-memory cache of successful Basic Auth verifications. @@ -59,9 +62,9 @@ pub struct AppPasswordService { impl AppPasswordService { pub fn new( - repo: Arc, - hasher: Arc, - user_repo: Arc, + repo: Arc, + hasher: Arc, + user_repo: Arc, base_url: String, ) -> Self { let auth_cache = Cache::builder() diff --git a/src/application/services/auth_application_service.rs b/src/application/services/auth_application_service.rs index e95d1ee1..f60986bc 100644 --- a/src/application/services/auth_application_service.rs +++ b/src/application/services/auth_application_service.rs @@ -15,6 +15,12 @@ use std::path::PathBuf; use std::sync::Arc; use std::sync::RwLock; use std::time::Duration; +use crate::infrastructure::services::password_hasher::Argon2PasswordHasher; +use crate::application::services::folder_service::FolderService; +use crate::infrastructure::services::jwt_service::JwtTokenService; +use crate::infrastructure::services::oidc_service::OidcService; +use crate::infrastructure::repositories::pg::SessionPgRepository; +use crate::infrastructure::repositories::pg::UserPgRepository; /// Tracks a pending OIDC authorization flow (CSRF + PKCE + nonce) #[derive(Clone)] @@ -31,7 +37,7 @@ struct PendingOidcToken { /// Interior state for OIDC — protected by RwLock for hot-reload. struct OidcState { - service: Option>, + service: Option>, config: Option, } @@ -40,11 +46,11 @@ const DEFAULT_ADMIN_QUOTA: i64 = 107_374_182_400; const DEFAULT_USER_QUOTA: i64 = 1_073_741_824; // 1 GB pub struct AuthApplicationService { - user_storage: Arc, - session_storage: Arc, - password_hasher: Arc, - token_service: Arc, - folder_service: Option>, + user_storage: Arc, + session_storage: Arc, + password_hasher: Arc, + token_service: Arc, + folder_service: Option>, /// Path to the storage directory, used for disk-space–aware quota calculation storage_path: PathBuf, oidc: RwLock, @@ -58,10 +64,10 @@ pub struct AuthApplicationService { impl AuthApplicationService { pub fn new( - user_storage: Arc, - session_storage: Arc, - password_hasher: Arc, - token_service: Arc, + user_storage: Arc, + session_storage: Arc, + password_hasher: Arc, + token_service: Arc, storage_path: PathBuf, ) -> Self { Self { @@ -133,7 +139,7 @@ impl AuthApplicationService { } /// Configures the folder service, needed to create personal folders - pub fn with_folder_service(mut self, folder_service: Arc) -> Self { + pub fn with_folder_service(mut self, folder_service: Arc) -> Self { self.folder_service = Some(folder_service); self } @@ -141,7 +147,7 @@ impl AuthApplicationService { /// Configures the OIDC service pub fn with_oidc( self, - oidc_service: Arc, + oidc_service: Arc, oidc_config: OidcConfig, ) -> Self { { @@ -153,7 +159,7 @@ impl AuthApplicationService { } /// Hot-reload OIDC configuration at runtime (called from admin settings service) - pub fn reload_oidc(&self, oidc_service: Arc, oidc_config: OidcConfig) { + pub fn reload_oidc(&self, oidc_service: Arc, oidc_config: OidcConfig) { let mut state = self.oidc.write().unwrap(); state.service = Some(oidc_service); state.config = Some(oidc_config); @@ -188,7 +194,7 @@ impl AuthApplicationService { } /// Returns an Arc clone of the OIDC service if available - pub fn oidc_service(&self) -> Option> { + pub fn oidc_service(&self) -> Option> { let state = self.oidc.read().unwrap(); state.service.clone() } diff --git a/src/application/services/batch_operations.rs b/src/application/services/batch_operations.rs index 91ceac45..3f8bde08 100644 --- a/src/application/services/batch_operations.rs +++ b/src/application/services/batch_operations.rs @@ -17,6 +17,9 @@ use crate::application::ports::trash_ports::TrashUseCase; use crate::application::services::folder_service::FolderService; use crate::common::config::AppConfig; use crate::common::errors::DomainError; +use crate::application::services::file_management_service::FileManagementService; +use crate::application::services::file_retrieval_service::FileRetrievalService; +use crate::application::services::trash_service::TrashService; /// Specific errors for batch operations #[derive(Debug, Error)] @@ -65,18 +68,18 @@ pub struct BatchStats { /// Batch operations service pub struct BatchOperationService { - file_retrieval: Arc, - file_management: Arc, + file_retrieval: Arc, + file_management: Arc, folder_service: Arc, - trash_service: Option>, + trash_service: Option>, config: AppConfig, } impl BatchOperationService { /// Creates a new instance of the batch operations service pub fn new( - file_retrieval: Arc, - file_management: Arc, + file_retrieval: Arc, + file_management: Arc, folder_service: Arc, config: AppConfig, ) -> Self { @@ -91,8 +94,8 @@ impl BatchOperationService { /// Creates a new instance with default configuration pub fn default( - file_retrieval: Arc, - file_management: Arc, + file_retrieval: Arc, + file_management: Arc, folder_service: Arc, ) -> Self { Self::new( @@ -104,7 +107,7 @@ impl BatchOperationService { } /// Set the optional trash service (enables batch trash operations) - pub fn with_trash_service(mut self, trash_service: Arc) -> Self { + pub fn with_trash_service(mut self, trash_service: Arc) -> Self { self.trash_service = Some(trash_service); self } diff --git a/src/application/services/calendar_service.rs b/src/application/services/calendar_service.rs index d4df1e7c..e887e8fa 100644 --- a/src/application/services/calendar_service.rs +++ b/src/application/services/calendar_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::{DateTime, Utc}; use std::sync::Arc; @@ -8,18 +7,18 @@ use crate::application::dtos::calendar_dto::{ }; use crate::application::ports::calendar_ports::{CalendarStoragePort, CalendarUseCase}; use crate::common::errors::{DomainError, ErrorKind}; +use crate::infrastructure::adapters::calendar_storage_adapter::CalendarStorageAdapter; pub struct CalendarService { - calendar_storage: Arc, + calendar_storage: Arc, } impl CalendarService { - pub fn new(calendar_storage: Arc) -> Self { + pub fn new(calendar_storage: Arc) -> Self { Self { calendar_storage } } } -#[async_trait] impl CalendarUseCase for CalendarService { async fn create_calendar( &self, diff --git a/src/application/services/contact_service.rs b/src/application/services/contact_service.rs index 36af14f9..33b8e933 100644 --- a/src/application/services/contact_service.rs +++ b/src/application/services/contact_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::Utc; use sqlx::types::Uuid; use std::sync::Arc; @@ -17,18 +16,21 @@ use crate::common::errors::DomainError; use crate::domain::entities::contact::{Address, AddressBook, Contact, ContactGroup, Email, Phone}; use crate::domain::repositories::address_book_repository::AddressBookRepository; use crate::domain::repositories::contact_repository::{ContactGroupRepository, ContactRepository}; +use crate::infrastructure::repositories::pg::AddressBookPgRepository; +use crate::infrastructure::repositories::pg::ContactGroupPgRepository; +use crate::infrastructure::repositories::pg::ContactPgRepository; pub struct ContactService { - address_book_repository: Arc, - contact_repository: Arc, - contact_group_repository: Arc, + address_book_repository: Arc, + contact_repository: Arc, + contact_group_repository: Arc, } impl ContactService { pub fn new( - address_book_repository: Arc, - contact_repository: Arc, - contact_group_repository: Arc, + address_book_repository: Arc, + contact_repository: Arc, + contact_group_repository: Arc, ) -> Self { Self { address_book_repository, @@ -264,7 +266,6 @@ impl ContactService { } } -#[async_trait] impl AddressBookUseCase for ContactService { async fn create_address_book( &self, @@ -514,7 +515,6 @@ impl AddressBookUseCase for ContactService { } } -#[async_trait] impl ContactUseCase for ContactService { async fn create_contact(&self, dto: CreateContactDto) -> Result { let address_book_id = Uuid::parse_str(&dto.address_book_id) @@ -1092,7 +1092,6 @@ impl ContactUseCase for ContactService { } } -#[async_trait] impl StorageUseCase for ContactService { async fn handle_request( &self, diff --git a/src/application/services/device_auth_service.rs b/src/application/services/device_auth_service.rs index e3e42623..2f03c950 100644 --- a/src/application/services/device_auth_service.rs +++ b/src/application/services/device_auth_service.rs @@ -18,6 +18,10 @@ use crate::application::ports::auth_ports::{ use crate::common::errors::{DomainError, ErrorKind}; use crate::domain::entities::device_code::{DeviceCode, DeviceCodeStatus}; use crate::domain::entities::session::Session; +use crate::infrastructure::repositories::pg::DeviceCodePgRepository; +use crate::infrastructure::services::jwt_service::JwtTokenService; +use crate::infrastructure::repositories::pg::SessionPgRepository; +use crate::infrastructure::repositories::pg::UserPgRepository; /// Default device code lifetime: 15 minutes (RFC 8628 recommends 5-30 min). const DEVICE_CODE_LIFETIME_SECS: i64 = 900; @@ -34,20 +38,20 @@ const USER_CODE_LETTER_LEN: usize = 4; const USER_CODE_DIGIT_LEN: usize = 4; pub struct DeviceAuthService { - device_code_storage: Arc, - token_service: Arc, - user_storage: Arc, - session_storage: Arc, + device_code_storage: Arc, + token_service: Arc, + user_storage: Arc, + session_storage: Arc, /// Base URL of the server (e.g. "https://cloud.example.com") base_url: String, } impl DeviceAuthService { pub fn new( - device_code_storage: Arc, - token_service: Arc, - user_storage: Arc, - session_storage: Arc, + device_code_storage: Arc, + token_service: Arc, + user_storage: Arc, + session_storage: Arc, base_url: String, ) -> Self { Self { diff --git a/src/application/services/favorites_service.rs b/src/application/services/favorites_service.rs index d498ee4e..e286f21a 100644 --- a/src/application/services/favorites_service.rs +++ b/src/application/services/favorites_service.rs @@ -3,26 +3,25 @@ use crate::application::dtos::favorites_dto::{ }; use crate::application::ports::favorites_ports::{FavoritesRepositoryPort, FavoritesUseCase}; use crate::common::errors::{DomainError, ErrorKind, Result}; -use async_trait::async_trait; use std::sync::Arc; use tracing::info; +use crate::infrastructure::repositories::pg::FavoritesPgRepository; /// Implementation of the FavoritesUseCase for managing user favorites. /// /// Depends on `FavoritesRepositoryPort` (outbound port) instead of /// accessing the database directly, following hexagonal architecture. pub struct FavoritesService { - repo: Arc, + repo: Arc, } impl FavoritesService { /// Create a new FavoritesService with the given repository port - pub fn new(repo: Arc) -> Self { + pub fn new(repo: Arc) -> Self { Self { repo } } } -#[async_trait] impl FavoritesUseCase for FavoritesService { /// Get all favorites for a user async fn get_favorites(&self, user_id: &str) -> Result> { diff --git a/src/application/services/file_management_service.rs b/src/application/services/file_management_service.rs index 397a3777..61b931a6 100644 --- a/src/application/services/file_management_service.rs +++ b/src/application/services/file_management_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use std::sync::Arc; use crate::application::dtos::file_dto::FileDto; @@ -7,6 +6,8 @@ use crate::application::ports::storage_ports::{CopyFolderTreeResult, FileWritePo use crate::application::ports::trash_ports::TrashUseCase; use crate::common::errors::DomainError; use tracing::{error, info, warn}; +use crate::infrastructure::repositories::pg::file_blob_write_repository::FileBlobWriteRepository; +use crate::application::services::trash_service::TrashService; /// Service for file management operations (move, delete). /// @@ -15,13 +16,13 @@ use tracing::{error, info, warn}; /// This service only orchestrates trash vs. permanent delete — it never /// touches ref_count directly. pub struct FileManagementService { - file_repository: Arc, - trash_service: Option>, + file_repository: Arc, + trash_service: Option>, } impl FileManagementService { /// Creates a new FileManagementService. - pub fn new(file_repository: Arc) -> Self { + pub fn new(file_repository: Arc) -> Self { Self { file_repository, trash_service: None, @@ -30,8 +31,8 @@ impl FileManagementService { /// Creates a FileManagementService with a trash service. pub fn with_trash( - file_repository: Arc, - trash_service: Option>, + file_repository: Arc, + trash_service: Option>, ) -> Self { Self { file_repository, @@ -40,7 +41,6 @@ impl FileManagementService { } } -#[async_trait] impl FileManagementUseCase for FileManagementService { async fn move_file( &self, diff --git a/src/application/services/file_retrieval_service.rs b/src/application/services/file_retrieval_service.rs index 296ea047..83666078 100644 --- a/src/application/services/file_retrieval_service.rs +++ b/src/application/services/file_retrieval_service.rs @@ -1,16 +1,16 @@ -use async_trait::async_trait; use bytes::{Bytes, BytesMut}; use futures::{Stream, StreamExt}; use std::pin::Pin; use std::sync::Arc; use crate::application::dtos::file_dto::FileDto; -use crate::application::ports::cache_ports::{ContentCachePort, WriteBehindCachePort}; use crate::application::ports::file_ports::{FileRetrievalUseCase, OptimizedFileContent}; use crate::application::ports::storage_ports::FileReadPort; -use crate::application::ports::transcode_ports::{ImageTranscodePort, OutputFormat}; use crate::common::errors::DomainError; use tracing::{debug, info}; +use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository; +use crate::infrastructure::services::file_content_cache::FileContentCache; +use crate::infrastructure::services::image_transcode_service::{ImageTranscodeService, OutputFormat}; /// Threshold below which files are served from RAM cache (10 MB). const CACHE_THRESHOLD: u64 = 10 * 1024 * 1024; @@ -23,48 +23,29 @@ const CACHE_THRESHOLD: u64 = 10 * 1024 * 1024; /// - Tier 2: Memory-mapped I/O (10–100 MB) /// - Tier 3: Streaming (≥100 MB) pub struct FileRetrievalService { - file_read: Arc, - write_behind: Option>, - content_cache: Option>, - transcode: Option>, + file_read: Arc, + content_cache: Option>, + transcode: Option>, } impl FileRetrievalService { /// Backward-compatible constructor (simple pass-through). - pub fn new(file_repository: Arc) -> Self { + pub fn new(file_repository: Arc) -> Self { Self { file_read: file_repository, - write_behind: None, content_cache: None, transcode: None, } } - /// Full constructor with all infrastructure ports. - pub fn new_full( - file_read: Arc, - write_behind: Arc, - content_cache: Arc, - transcode: Arc, - ) -> Self { - Self { - file_read, - write_behind: Some(write_behind), - content_cache: Some(content_cache), - transcode: Some(transcode), - } - } - /// Constructor for blob-storage model: read + content cache + transcode. - /// No write-behind needed — dedup handled at the repository layer. pub fn new_with_cache( - file_read: Arc, - content_cache: Arc, - transcode: Arc, + file_read: Arc, + content_cache: Arc, + transcode: Arc, ) -> Self { Self { file_read, - write_behind: None, content_cache: Some(content_cache), transcode: Some(transcode), } @@ -85,7 +66,7 @@ impl FileRetrievalService { return None; } let transcode = self.transcode.as_ref()?; - if !transcode.should_transcode(mime, file_size) { + if !ImageTranscodeService::should_transcode(mime, file_size) { return None; } let format = OutputFormat::WebP; @@ -121,37 +102,6 @@ impl FileRetrievalService { let modified_at = dto.modified_at; let do_transcode = accept_webp && !prefer_original; - // ── Tier 0: Write-behind cache ─────────────────────── - if let Some(wb) = &self.write_behind - && let Some(pending) = wb.get_pending(id).await - { - debug!( - "⚡ TIER 0 Write-Behind HIT: {} ({} bytes)", - file_name, - pending.len() - ); - let (data, mime) = if do_transcode { - if let Some((t, m)) = self - .try_transcode(id, &pending, &mime_type, file_size, true) - .await - { - (t, m) - } else { - (pending, mime_type.clone()) - } - } else { - (pending, mime_type.clone()) - }; - return Ok(( - dto, - OptimizedFileContent::Bytes { - data, - mime_type: mime, - was_transcoded: do_transcode, - }, - )); - } - // ── Tier 1: Hot cache + transcode (<10 MB) ────────── if file_size < CACHE_THRESHOLD { // Check content cache first @@ -243,7 +193,6 @@ impl FileRetrievalService { } } -#[async_trait] impl FileRetrievalUseCase for FileRetrievalService { async fn get_file(&self, id: &str) -> Result { let file = self.file_read.get_file(id).await?; diff --git a/src/application/services/file_upload_service.rs b/src/application/services/file_upload_service.rs index 7b288f29..87803a91 100644 --- a/src/application/services/file_upload_service.rs +++ b/src/application/services/file_upload_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use std::path::Path; use std::sync::Arc; @@ -8,6 +7,9 @@ use crate::application::ports::file_ports::FileUploadUseCase; use crate::application::ports::storage_ports::{FileReadPort, FileWritePort}; use crate::common::errors::DomainError; use tracing::{debug, info, warn}; +use crate::infrastructure::repositories::pg::FileBlobReadRepository; +use crate::infrastructure::repositories::pg::FileBlobWriteRepository; +use crate::application::services::storage_usage_service::StorageUsageService; /// Helper function to extract username from folder path string. /// e.g. "My Folder - user1/subfolder/file.txt" → "user1" @@ -42,17 +44,17 @@ fn extract_username_from_path(path: &str) -> Option { /// Peak RAM usage during any upload: ~256 KB (streaming hash) regardless of file size. pub struct FileUploadService { /// Write port — handles save, streaming, deferred registration - file_write: Arc, + file_write: Arc, /// Read port — needed for WebDAV create_file / update_file - file_read: Option>, + file_read: Option>, /// Optional storage usage tracking storage_usage_service: - Option>, + Option>, } impl FileUploadService { /// Constructor with write port only (minimal). - pub fn new(file_repository: Arc) -> Self { + pub fn new(file_repository: Arc) -> Self { Self { file_write: file_repository, file_read: None, @@ -62,8 +64,8 @@ impl FileUploadService { /// Constructor for blob-storage model: write + read ports. pub fn new_with_read( - file_write: Arc, - file_read: Arc, + file_write: Arc, + file_read: Arc, ) -> Self { Self { file_write, @@ -75,7 +77,7 @@ impl FileUploadService { /// Configures the storage usage service pub fn with_storage_usage_service( mut self, - storage_usage_service: Arc, + storage_usage_service: Arc, ) -> Self { self.storage_usage_service = Some(storage_usage_service); self @@ -106,7 +108,6 @@ impl FileUploadService { } } -#[async_trait] impl FileUploadUseCase for FileUploadService { /// Streaming upload from a temp file on disk. /// diff --git a/src/application/services/file_use_case_factory.rs b/src/application/services/file_use_case_factory.rs index 1e1373d6..fe092361 100644 --- a/src/application/services/file_use_case_factory.rs +++ b/src/application/services/file_use_case_factory.rs @@ -1,24 +1,23 @@ use std::sync::Arc; -use crate::application::ports::file_ports::{ - FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, FileUseCaseFactory, -}; -use crate::application::ports::storage_ports::{FileReadPort, FileWritePort}; +use crate::application::ports::file_ports::FileUseCaseFactory; use crate::application::services::file_management_service::FileManagementService; use crate::application::services::file_retrieval_service::FileRetrievalService; use crate::application::services::file_upload_service::FileUploadService; +use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository; +use crate::infrastructure::repositories::pg::file_blob_write_repository::FileBlobWriteRepository; /// Factory for creating file use case implementations pub struct AppFileUseCaseFactory { - file_read_repository: Arc, - file_write_repository: Arc, + file_read_repository: Arc, + file_write_repository: Arc, } impl AppFileUseCaseFactory { /// Creates a new factory for file use cases pub fn new( - file_read_repository: Arc, - file_write_repository: Arc, + file_read_repository: Arc, + file_write_repository: Arc, ) -> Self { Self { file_read_repository, @@ -28,15 +27,15 @@ impl AppFileUseCaseFactory { } impl FileUseCaseFactory for AppFileUseCaseFactory { - fn create_file_upload_use_case(&self) -> Arc { + fn create_file_upload_use_case(&self) -> Arc { Arc::new(FileUploadService::new(self.file_write_repository.clone())) } - fn create_file_retrieval_use_case(&self) -> Arc { + fn create_file_retrieval_use_case(&self) -> Arc { Arc::new(FileRetrievalService::new(self.file_read_repository.clone())) } - fn create_file_management_use_case(&self) -> Arc { + fn create_file_management_use_case(&self) -> Arc { Arc::new(FileManagementService::new( self.file_write_repository.clone(), )) diff --git a/src/application/services/folder_service.rs b/src/application/services/folder_service.rs index b8866358..6bbb627d 100644 --- a/src/application/services/folder_service.rs +++ b/src/application/services/folder_service.rs @@ -2,20 +2,20 @@ use crate::application::dtos::folder_dto::{ CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto, }; use crate::application::ports::inbound::FolderUseCase; -use crate::application::ports::outbound::FolderStoragePort; use crate::common::errors::{DomainError, ErrorKind}; use crate::domain::services::path_service::StoragePath; -use async_trait::async_trait; use std::sync::Arc; +use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository; +use crate::domain::repositories::folder_repository::FolderRepository; /// Implementation of the use case for folder operations pub struct FolderService { - folder_storage: Arc, + folder_storage: Arc, } impl FolderService { /// Creates a new folder service - pub fn new(folder_storage: Arc) -> Self { + pub fn new(folder_storage: Arc) -> Self { Self { folder_storage } } @@ -23,7 +23,6 @@ impl FolderService { pub fn new_stub() -> impl FolderUseCase { struct FolderServiceStub; - #[async_trait] impl FolderUseCase for FolderServiceStub { async fn create_folder(&self, _dto: CreateFolderDto) -> Result { Ok(FolderDto::empty()) @@ -124,7 +123,6 @@ impl FolderService { } } -#[async_trait] impl FolderUseCase for FolderService { /// Creates a new folder async fn create_folder(&self, dto: CreateFolderDto) -> Result { diff --git a/src/application/services/i18n_application_service.rs b/src/application/services/i18n_application_service.rs index 043a4485..11ea87f9 100644 --- a/src/application/services/i18n_application_service.rs +++ b/src/application/services/i18n_application_service.rs @@ -1,43 +1,23 @@ use std::sync::Arc; use crate::domain::services::i18n_service::{I18nResult, I18nService, Locale}; +use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService; /// Service for i18n operations pub struct I18nApplicationService { - i18n_service: Arc, + i18n_service: Arc, } impl I18nApplicationService { /// Creates a dummy service for testing pub fn dummy() -> Self { - struct DummyI18nService; - - #[async_trait::async_trait] - impl I18nService for DummyI18nService { - async fn translate(&self, _key: &str, _locale: Locale) -> I18nResult { - Ok("DUMMY_TRANSLATION".to_string()) - } - - async fn load_translations(&self, _locale: Locale) -> I18nResult<()> { - Ok(()) - } - - async fn available_locales(&self) -> Vec { - vec![Locale::English, Locale::Spanish] - } - - async fn is_supported(&self, _locale: Locale) -> bool { - true - } - } - Self { - i18n_service: Arc::new(DummyI18nService), + i18n_service: Arc::new(FileSystemI18nService::dummy()), } } /// Creates a new i18n application service - pub fn new(i18n_service: Arc) -> Self { + pub fn new(i18n_service: Arc) -> Self { Self { i18n_service } } diff --git a/src/application/services/recent_service.rs b/src/application/services/recent_service.rs index 2cdf3a21..f035f46a 100644 --- a/src/application/services/recent_service.rs +++ b/src/application/services/recent_service.rs @@ -1,22 +1,22 @@ use crate::application::dtos::recent_dto::RecentItemDto; use crate::application::ports::recent_ports::{RecentItemsRepositoryPort, RecentItemsUseCase}; use crate::common::errors::{DomainError, ErrorKind, Result}; -use async_trait::async_trait; use std::sync::Arc; use tracing::info; +use crate::infrastructure::repositories::pg::RecentItemsPgRepository; /// Implementation of the use case for managing recent items. /// /// Depends on `RecentItemsRepositoryPort` (outbound port) instead /// of accessing `PgPool` directly, following the hexagonal architecture. pub struct RecentService { - repo: Arc, + repo: Arc, max_recent_items: i32, } impl RecentService { /// Create a new recent items service - pub fn new(repo: Arc, max_recent_items: i32) -> Self { + pub fn new(repo: Arc, max_recent_items: i32) -> Self { Self { repo, max_recent_items: max_recent_items.clamp(1, 100), @@ -24,7 +24,6 @@ impl RecentService { } } -#[async_trait] impl RecentItemsUseCase for RecentService { /// Get recent items for a user async fn get_recent_items( diff --git a/src/application/services/search_service.rs b/src/application/services/search_service.rs index 0559b774..c0e9a9d7 100644 --- a/src/application/services/search_service.rs +++ b/src/application/services/search_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use std::cmp::Reverse; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -13,11 +12,13 @@ use crate::application::dtos::search_dto::{ SearchSuggestionItem, SearchSuggestionsDto, }; use crate::application::ports::inbound::SearchUseCase; -use crate::application::ports::outbound::FolderStoragePort; use crate::application::ports::storage_ports::FileReadPort; use crate::common::errors::Result; use crate::domain::entities::folder::Folder; use std::hash::{Hash, Hasher}; +use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository; +use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository; +use crate::domain::repositories::folder_repository::FolderRepository; /** * High-performance search service implementation for files and folders. @@ -37,10 +38,10 @@ use std::hash::{Hash, Hasher}; */ pub struct SearchService { /// Repository for file operations - file_repository: Arc, + file_repository: Arc, /// Repository for folder operations - folder_repository: Arc, + folder_repository: Arc, /// Lock-free concurrent cache with automatic TTL and LRU eviction (moka). /// Values are `Arc` so cache insert/hit is a single @@ -109,8 +110,8 @@ impl SearchService { * Creates a new instance of the search service. */ pub fn new( - file_repository: Arc, - folder_repository: Arc, + file_repository: Arc, + folder_repository: Arc, cache_ttl: u64, max_cache_size: usize, ) -> Self { @@ -255,7 +256,6 @@ impl SearchService { // ─── SearchUseCase trait implementation ────────────────────────────────── -#[async_trait] impl SearchUseCase for SearchService { /** * Performs a search based on the specified criteria. @@ -489,7 +489,6 @@ impl SearchService { pub fn new_stub() -> impl SearchUseCase { struct SearchServiceStub; - #[async_trait] impl SearchUseCase for SearchServiceStub { async fn search( &self, diff --git a/src/application/services/share_service.rs b/src/application/services/share_service.rs index d0ce5aa4..c41b3710 100644 --- a/src/application/services/share_service.rs +++ b/src/application/services/share_service.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use async_trait::async_trait; use thiserror::Error; use tokio::sync::Semaphore; @@ -12,7 +11,6 @@ use crate::{ }, ports::{ auth_ports::PasswordHasherPort, - outbound::FolderStoragePort, share_ports::{ShareStoragePort, ShareUseCase}, storage_ports::FileReadPort, }, @@ -20,6 +18,11 @@ use crate::{ common::{config::AppConfig, errors::DomainError}, domain::entities::share::{Share, ShareItemType, SharePermissions}, }; +use crate::infrastructure::services::password_hasher::Argon2PasswordHasher; +use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository; +use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository; +use crate::infrastructure::repositories::pg::SharePgRepository; +use crate::domain::repositories::folder_repository::FolderRepository; #[derive(Debug, Error)] pub enum ShareServiceError { @@ -67,10 +70,10 @@ const MAX_CONCURRENT_HASHES: usize = 2; pub struct ShareService { config: Arc, - share_repository: Arc, - file_repository: Arc, - folder_repository: Arc, - password_hasher: Arc, + share_repository: Arc, + file_repository: Arc, + folder_repository: Arc, + password_hasher: Arc, /// Bounds the number of in-flight Argon2 password hashes to avoid /// saturating the blocking thread pool and consuming excessive RAM. hash_semaphore: Arc, @@ -79,10 +82,10 @@ pub struct ShareService { impl ShareService { pub fn new( config: Arc, - share_repository: Arc, - file_repository: Arc, - folder_repository: Arc, - password_hasher: Arc, + share_repository: Arc, + file_repository: Arc, + folder_repository: Arc, + password_hasher: Arc, ) -> Self { Self { config, @@ -139,7 +142,6 @@ impl ShareService { } } -#[async_trait] impl ShareUseCase for ShareService { async fn create_shared_link( &self, @@ -400,13 +402,11 @@ mod tests { use crate::application::ports::share_ports::ShareStoragePort; use crate::common::config::AppConfig; use crate::domain::repositories::folder_repository::FolderRepository; - use async_trait::async_trait; use std::collections::HashMap; use std::sync::Mutex; struct MockPasswordHasher; - #[async_trait] impl PasswordHasherPort for MockPasswordHasher { async fn hash_password(&self, password: &str) -> Result { Ok(format!("hashed_{}", password)) @@ -420,7 +420,6 @@ mod tests { struct MockFileRepository; struct MockFolderRepository; - #[async_trait] impl FileReadPort for MockFileRepository { async fn get_file( &self, @@ -523,7 +522,6 @@ mod tests { } } - #[async_trait] impl FolderRepository for MockFolderRepository { async fn create_folder( &self, @@ -671,7 +669,6 @@ mod tests { } } - #[async_trait] impl ShareStoragePort for MockShareRepository { async fn save_share(&self, share: &Share) -> Result { let mut shares = self.shares.lock().unwrap(); diff --git a/src/application/services/storage_usage_service.rs b/src/application/services/storage_usage_service.rs index c2e8d405..09ce1bb0 100644 --- a/src/application/services/storage_usage_service.rs +++ b/src/application/services/storage_usage_service.rs @@ -1,11 +1,11 @@ use crate::application::ports::auth_ports::UserStoragePort; use crate::application::ports::storage_ports::StorageUsagePort; use crate::common::errors::DomainError; -use async_trait::async_trait; use sqlx::PgPool; use std::sync::Arc; use tokio::task; use tracing::{debug, error, info}; +use crate::infrastructure::repositories::pg::UserPgRepository; /** * Service for managing and updating user storage usage statistics. @@ -18,12 +18,12 @@ use tracing::{debug, error, info}; */ pub struct StorageUsageService { pool: Arc, - user_repository: Arc, + user_repository: Arc, } impl StorageUsageService { /// Creates a new storage usage service - pub fn new(pool: Arc, user_repository: Arc) -> Self { + pub fn new(pool: Arc, user_repository: Arc) -> Self { Self { pool, user_repository, @@ -111,7 +111,6 @@ impl StorageUsageService { * Implementation of the StorageUsagePort trait to expose storage usage services * to the application layer. */ -#[async_trait] impl StorageUsagePort for StorageUsageService { async fn update_user_storage_usage(&self, user_id: &str) -> Result { StorageUsageService::update_user_storage_usage(self, user_id).await diff --git a/src/application/services/trash_service.rs b/src/application/services/trash_service.rs index 581b2707..b3d09eef 100644 --- a/src/application/services/trash_service.rs +++ b/src/application/services/trash_service.rs @@ -1,15 +1,18 @@ -use async_trait::async_trait; use std::sync::Arc; use tracing::{debug, error, info, instrument}; use uuid::Uuid; use crate::application::dtos::trash_dto::TrashedItemDto; -use crate::application::ports::outbound::FolderStoragePort; use crate::application::ports::storage_ports::{FileReadPort, FileWritePort}; use crate::application::ports::trash_ports::TrashUseCase; use crate::common::errors::{DomainError, ErrorKind, Result}; use crate::domain::entities::trashed_item::{TrashedItem, TrashedItemType}; use crate::domain::repositories::trash_repository::TrashRepository; +use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository; +use crate::infrastructure::repositories::pg::file_blob_write_repository::FileBlobWriteRepository; +use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository; +use crate::infrastructure::repositories::pg::trash_db_repository::TrashDbRepository; +use crate::domain::repositories::folder_repository::FolderRepository; /** * Application service for trash operations. @@ -26,16 +29,16 @@ use crate::domain::repositories::trash_repository::TrashRepository; */ pub struct TrashService { /// Repository for trash-specific operations like listing and retrieving trashed items - trash_repository: Arc, + trash_repository: Arc, /// Port for file read operations (get file metadata) - file_read_port: Arc, + file_read_port: Arc, /// Port for file write operations (trash, restore, delete) - file_write_port: Arc, + file_write_port: Arc, /// Port for folder operations (get folder, trash, restore, delete) - folder_storage_port: Arc, + folder_storage_port: Arc, /// Number of days items should be kept in trash before automatic cleanup retention_days: u32, @@ -43,10 +46,10 @@ pub struct TrashService { impl TrashService { pub fn new( - trash_repository: Arc, - file_read_port: Arc, - file_write_port: Arc, - folder_storage_port: Arc, + trash_repository: Arc, + file_read_port: Arc, + file_write_port: Arc, + folder_storage_port: Arc, retention_days: u32, ) -> Self { Self { @@ -118,7 +121,6 @@ impl TrashService { } } -#[async_trait] impl TrashUseCase for TrashService { #[instrument(skip(self))] async fn get_trash_items(&self, user_id: &str) -> Result> { diff --git a/src/application/services/trash_service_test.rs b/src/application/services/trash_service_test.rs index 890e31af..5f41a30e 100644 --- a/src/application/services/trash_service_test.rs +++ b/src/application/services/trash_service_test.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use bytes::Bytes; use chrono::Utc; use futures::Stream; @@ -40,7 +39,6 @@ impl MockTrashRepository { } } -#[async_trait] impl TrashRepository for MockTrashRepository { async fn add_to_trash(&self, item: &TrashedItem) -> Result<()> { let mut items = self.trash_items.lock().unwrap(); @@ -135,7 +133,6 @@ impl MockFileRepository { } } -#[async_trait] impl FileReadPort for MockFileRepository { async fn get_file(&self, id: &str) -> std::result::Result { let files = self.files.lock().unwrap(); @@ -216,7 +213,6 @@ impl FileReadPort for MockFileRepository { } } -#[async_trait] impl FileWritePort for MockFileRepository { async fn save_file_from_temp( &self, @@ -350,7 +346,6 @@ impl MockFolderRepository { } } -#[async_trait] impl FolderRepository for MockFolderRepository { async fn create_folder( &self, @@ -514,8 +509,8 @@ mod tests { let service = TrashService::new( trash_repo.clone(), - file_repo.clone() as Arc, - file_repo.clone() as Arc, + file_repo.clone() as Arc, + file_repo.clone() as Arc, folder_repo.clone(), 30, // 30 days retention ); @@ -588,8 +583,8 @@ mod tests { let service = TrashService::new( trash_repo.clone(), - file_repo.clone() as Arc, - file_repo.clone() as Arc, + file_repo.clone() as Arc, + file_repo.clone() as Arc, folder_repo.clone(), 30, // 30 days retention ); @@ -653,8 +648,8 @@ mod tests { let service = TrashService::new( trash_repo.clone(), - file_repo.clone() as Arc, - file_repo.clone() as Arc, + file_repo.clone() as Arc, + file_repo.clone() as Arc, folder_repo.clone(), 30, // 30 days retention ); @@ -723,8 +718,8 @@ mod tests { let service = TrashService::new( trash_repo.clone(), - file_repo.clone() as Arc, - file_repo.clone() as Arc, + file_repo.clone() as Arc, + file_repo.clone() as Arc, folder_repo.clone(), 30, // 30 days retention ); @@ -792,8 +787,8 @@ mod tests { let service = TrashService::new( trash_repo.clone(), - file_repo.clone() as Arc, - file_repo.clone() as Arc, + file_repo.clone() as Arc, + file_repo.clone() as Arc, folder_repo.clone(), 30, // 30 days retention ); diff --git a/src/common/di.rs b/src/common/di.rs index 713048fe..1f48d756 100644 --- a/src/common/di.rs +++ b/src/common/di.rs @@ -7,22 +7,7 @@ use crate::infrastructure::db::DbPools; use crate::application::services::admin_settings_service::AdminSettingsService; use crate::application::services::auth_application_service::AuthApplicationService; -use crate::application::ports::cache_ports::ContentCachePort; -use crate::application::ports::chunked_upload_ports::ChunkedUploadPort; - -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::ports::file_ports::FileUseCaseFactory; use crate::application::services::favorites_service::FavoritesService; use crate::application::services::folder_service::FolderService; use crate::application::services::i18n_application_service::I18nApplicationService; @@ -35,7 +20,6 @@ use crate::application::services::{ }; use crate::common::config::AppConfig; use crate::common::errors::DomainError; -use crate::domain::services::i18n_service::I18nService; use crate::infrastructure::repositories::pg::SharePgRepository; use crate::infrastructure::repositories::pg::{ FileBlobReadRepository, FileBlobWriteRepository, FolderDbRepository, TrashDbRepository, @@ -47,7 +31,29 @@ use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nSer use crate::infrastructure::services::path_service::PathService; use crate::infrastructure::services::trash_cleanup_service::TrashCleanupService; -use crate::common::stubs::StubZipPort; + +use crate::application::services::device_auth_service::DeviceAuthService; +use crate::application::services::app_password_service::AppPasswordService; +use crate::application::services::calendar_service::CalendarService; +use crate::application::services::storage_usage_service::StorageUsageService; +use crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter; +use crate::infrastructure::repositories::DeviceCodePgRepository; +use crate::infrastructure::repositories::AppPasswordPgRepository; +use crate::infrastructure::repositories::pg::{ + AddressBookPgRepository, CalendarEventPgRepository, CalendarPgRepository, + ContactGroupPgRepository, ContactPgRepository, SessionPgRepository, UserPgRepository, +}; +use crate::infrastructure::services::password_hasher::Argon2PasswordHasher; +use crate::infrastructure::services::jwt_service::JwtTokenService; +use crate::infrastructure::services::path_resolver_service::PathResolverService; +use crate::application::services::wopi_lock_service::WopiLockService; +use crate::application::services::wopi_token_service::WopiTokenService; +use crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService; +use crate::infrastructure::services::chunked_upload_service::ChunkedUploadService; +use crate::infrastructure::services::dedup_service::DedupService; +use crate::infrastructure::services::image_transcode_service::ImageTranscodeService; +use crate::infrastructure::services::thumbnail_service::ThumbnailService; +use crate::infrastructure::services::zip_service::ZipService; /// Factory for the different application components /// @@ -161,7 +167,7 @@ impl AppServiceFactory { chunked_upload_service, image_transcode_service, dedup_service, - zip_service: Arc::new(StubZipPort), // Placeholder - replaced after app services init + zip_service: None, // Placeholder - replaced after app services init config: self.config.clone(), }) } @@ -176,16 +182,16 @@ impl AppServiceFactory { ) -> RepositoryServices { // Folder repository — PostgreSQL-backed virtual folders let folder_repo_concrete = Arc::new(FolderDbRepository::new(db_pool.clone())); - let folder_repository: Arc = folder_repo_concrete.clone(); + let folder_repository: Arc = folder_repo_concrete.clone(); // File repositories — PostgreSQL metadata + blob content via DedupService - let file_read_repository: Arc = Arc::new(FileBlobReadRepository::new( + let file_read_repository: Arc = Arc::new(FileBlobReadRepository::new( db_pool.clone(), core.dedup_service.clone(), folder_repo_concrete.clone(), )); - let file_write_repository: Arc = Arc::new(FileBlobWriteRepository::new( + let file_write_repository: Arc = Arc::new(FileBlobWriteRepository::new( db_pool.clone(), core.dedup_service.clone(), folder_repo_concrete.clone(), @@ -201,7 +207,7 @@ impl AppServiceFactory { core.config.storage.trash_retention_days, )) as Arc< - dyn crate::domain::repositories::trash_repository::TrashRepository, + TrashDbRepository, >) } else { None @@ -226,7 +232,7 @@ impl AppServiceFactory { &self, core: &CoreServices, repos: &RepositoryServices, - trash_service: Option>, + trash_service: Option>, ) -> ApplicationServices { // Main services let folder_service = Arc::new(FolderService::new(repos.folder_repository.clone())); @@ -258,7 +264,7 @@ impl AppServiceFactory { let i18n_service = Arc::new(I18nApplicationService::new(repos.i18n_repository.clone())); // Search service with cache - let search_service: Option> = Some(Arc::new(SearchService::new( + let search_service: Option> = Some(Arc::new(SearchService::new( repos.file_read_repository.clone(), repos.folder_repository.clone(), 300, // Cache TTL in seconds (5 minutes) @@ -289,7 +295,7 @@ impl AppServiceFactory { pub async fn create_trash_service( &self, repos: &RepositoryServices, - ) -> Option> { + ) -> Option> { if !self.config.features.enable_trash { tracing::info!("Trash service is disabled in configuration"); return None; @@ -315,7 +321,7 @@ impl AppServiceFactory { cleanup_service.start_cleanup_job().await; tracing::info!("Trash service initialized with daily cleanup schedule"); - Some(service as Arc) + Some(service as Arc) } /// Creates the sharing service @@ -323,7 +329,7 @@ impl AppServiceFactory { &self, repos: &RepositoryServices, db_pool: &Arc, - ) -> Option> { + ) -> Option> { if !self.config.features.enable_file_sharing { tracing::info!("File sharing service is disabled in configuration"); return None; @@ -332,7 +338,7 @@ impl AppServiceFactory { let share_repository = Arc::new(SharePgRepository::new(db_pool.clone())); // Build a password hasher for share password verification - let password_hasher: Arc = + let password_hasher: Arc = Arc::new( crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new( self.config.auth.hash_memory_cost, @@ -354,7 +360,7 @@ impl AppServiceFactory { } /// Creates the favorites service (requires database) - pub fn create_favorites_service(&self, db_pool: &Arc) -> Arc { + pub fn create_favorites_service(&self, db_pool: &Arc) -> Arc { let repo = Arc::new( crate::infrastructure::repositories::pg::FavoritesPgRepository::new(db_pool.clone()), ); @@ -364,7 +370,7 @@ impl AppServiceFactory { } /// Creates the recent items service (requires database) - pub fn create_recent_service(&self, db_pool: &Arc) -> Arc { + pub fn create_recent_service(&self, db_pool: &Arc) -> Arc { let repo = Arc::new( crate::infrastructure::repositories::pg::RecentItemsPgRepository::new(db_pool.clone()), ); @@ -406,7 +412,7 @@ impl AppServiceFactory { _repos: &RepositoryServices, db_pool: &Arc, maintenance_pool: &Arc, - ) -> Arc { + ) -> Arc { let user_repository = Arc::new( crate::infrastructure::repositories::pg::UserPgRepository::new(db_pool.clone()), ); @@ -455,10 +461,10 @@ impl AppServiceFactory { apps.share_service = share_service.clone(); // 6. Database-dependent services (PgPool always available in blob model) - let favorites_service: Option>; - let recent_service: Option>; + let favorites_service: Option>; + let recent_service: Option>; let storage_usage_service: Option< - Arc, + Arc, >; let mut auth_services: Option = None; @@ -509,14 +515,14 @@ impl AppServiceFactory { self.preload_translations(&apps.i18n_service).await; // 8. Build the ZipService with real application services - let zip_service: Arc = Arc::new( + let zip_service: Arc = Arc::new( 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; + core.zip_service = Some(zip_service); // 9. Assemble final AppState let mut app_state = AppState { @@ -595,16 +601,14 @@ impl AppServiceFactory { // 9c. Wire Device Authorization Grant (RFC 8628) service { - use crate::application::services::device_auth_service::DeviceAuthService; - use crate::infrastructure::repositories::DeviceCodePgRepository; let device_code_repo = Arc::new(DeviceCodePgRepository::new(pool.clone())); - let user_repo: Arc = + let user_repo: Arc = Arc::new(crate::infrastructure::repositories::UserPgRepository::new( pool.clone(), )); let session_repo: Arc< - dyn crate::application::ports::auth_ports::SessionStoragePort, + SessionPgRepository, > = Arc::new( crate::infrastructure::repositories::SessionPgRepository::new(pool.clone()), ); @@ -623,13 +627,11 @@ impl AppServiceFactory { // 9d. Wire App Password service { - use crate::application::services::app_password_service::AppPasswordService; - use crate::infrastructure::repositories::AppPasswordPgRepository; let app_pw_repo: Arc< - dyn crate::application::ports::auth_ports::AppPasswordStoragePort, + AppPasswordPgRepository, > = Arc::new(AppPasswordPgRepository::new(pool.clone())); - let hasher: Arc = + let hasher: Arc = Arc::new( crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new( self.config.auth.hash_memory_cost, @@ -637,7 +639,7 @@ impl AppServiceFactory { self.config.auth.hash_parallelism, ), ); - let user_repo: Arc = + let user_repo: Arc = Arc::new(crate::infrastructure::repositories::UserPgRepository::new( pool.clone(), )); @@ -656,7 +658,6 @@ impl AppServiceFactory { // 9e. Wire PathResolver for single-query WebDAV path resolution { - use crate::infrastructure::services::path_resolver_service::PathResolverService; app_state.path_resolver = Some(Arc::new(PathResolverService::new(pool.clone()))); tracing::info!("PathResolver service initialized"); } @@ -665,12 +666,12 @@ impl AppServiceFactory { { // CalDAV let calendar_repo: Arc< - dyn crate::domain::repositories::calendar_repository::CalendarRepository, + CalendarPgRepository, > = Arc::new( crate::infrastructure::repositories::pg::CalendarPgRepository::new(pool.clone()), ); let event_repo: Arc< - dyn crate::domain::repositories::calendar_event_repository::CalendarEventRepository, + CalendarEventPgRepository, > = Arc::new( crate::infrastructure::repositories::pg::CalendarEventPgRepository::new( pool.clone(), @@ -689,22 +690,22 @@ impl AppServiceFactory { ); app_state.calendar_use_case = Some( calendar_service - as Arc, + as Arc, ); // CardDAV let address_book_repo: Arc< - dyn crate::domain::repositories::address_book_repository::AddressBookRepository, + AddressBookPgRepository, > = Arc::new( crate::infrastructure::repositories::pg::AddressBookPgRepository::new(pool.clone()), ); let contact_repo: Arc< - dyn crate::domain::repositories::contact_repository::ContactRepository, + ContactPgRepository, > = Arc::new( crate::infrastructure::repositories::pg::ContactPgRepository::new(pool.clone()), ); let group_repo: Arc< - dyn crate::domain::repositories::contact_repository::ContactGroupRepository, + ContactGroupPgRepository, > = Arc::new( crate::infrastructure::repositories::pg::ContactGroupPgRepository::new( pool.clone(), @@ -717,12 +718,8 @@ impl AppServiceFactory { group_repo, ) ); - app_state.addressbook_use_case = Some(contact_storage.clone() - as Arc); - app_state.contact_use_case = Some( - contact_storage - as Arc, - ); + app_state.addressbook_use_case = Some(contact_storage.clone()); + app_state.contact_use_case = Some(contact_storage); tracing::info!("CalDAV and CardDAV services initialized with PostgreSQL repositories"); } @@ -735,9 +732,6 @@ impl AppServiceFactory { "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() @@ -775,25 +769,25 @@ impl AppServiceFactory { #[derive(Clone)] pub struct CoreServices { pub path_service: Arc, - pub file_content_cache: Arc, - pub thumbnail_service: Arc, - pub chunked_upload_service: Arc, - pub image_transcode_service: Arc, - pub dedup_service: Arc, - pub zip_service: Arc, + pub file_content_cache: Arc, + pub thumbnail_service: Arc, + pub chunked_upload_service: Arc, + pub image_transcode_service: Arc, + pub dedup_service: Arc, + pub zip_service: Option>, pub config: AppConfig, } /// Container for repository services #[derive(Clone)] pub struct RepositoryServices { - pub folder_repository: Arc, + pub folder_repository: Arc, pub folder_repo_concrete: Arc, - pub file_read_repository: Arc, - pub file_write_repository: Arc, - pub i18n_repository: Arc, + pub file_read_repository: Arc, + pub file_write_repository: Arc, + pub i18n_repository: Arc, pub trash_repository: - Option>, + Option>, } /// Container for application services @@ -802,23 +796,23 @@ pub struct ApplicationServices { // Concrete types for compatibility with existing handlers pub folder_service_concrete: Arc, // Traits for abstraction - pub folder_service: Arc, - pub file_upload_service: Arc, - pub file_retrieval_service: Arc, - pub file_management_service: Arc, + pub folder_service: Arc, + pub file_upload_service: Arc, + pub file_retrieval_service: Arc, + pub file_management_service: Arc, pub file_use_case_factory: Arc, pub i18n_service: Arc, - pub trash_service: Option>, - pub search_service: Option>, - pub share_service: Option>, - pub favorites_service: Option>, - pub recent_service: Option>, + pub trash_service: Option>, + pub search_service: Option>, + pub share_service: Option>, + pub favorites_service: Option>, + pub recent_service: Option>, } /// Container for authentication services #[derive(Clone)] pub struct AuthServices { - pub token_service: Arc, + pub token_service: Arc, pub auth_application_service: Arc, pub login_lockout: Arc, @@ -835,19 +829,19 @@ pub struct AppState { pub maintenance_pool: Option>, pub auth_service: Option, pub admin_settings_service: Option>, - pub trash_service: Option>, - pub share_service: Option>, - pub favorites_service: Option>, - pub recent_service: Option>, + pub trash_service: Option>, + pub share_service: Option>, + pub favorites_service: Option>, + pub recent_service: Option>, pub storage_usage_service: - Option>, - pub calendar_service: Option>, - pub contact_service: Option>, + Option>, + pub calendar_service: Option>, + pub contact_service: Option>, pub calendar_use_case: - Option>, + Option>, pub addressbook_use_case: - Option>, - pub contact_use_case: Option>, + Option>, + pub contact_use_case: Option>, pub wopi_token_service: Option>, pub wopi_lock_service: diff --git a/src/common/stubs.rs b/src/common/stubs.rs index bfa1ee05..5b0b8485 100644 --- a/src/common/stubs.rs +++ b/src/common/stubs.rs @@ -10,7 +10,6 @@ use std::path::{Path, PathBuf}; use std::pin::Pin; use std::sync::Arc; -use async_trait::async_trait; use bytes::Bytes; use futures::Stream; @@ -23,7 +22,7 @@ use crate::application::dtos::search_dto::{ SearchCriteriaDto, SearchResultsDto, SearchSuggestionsDto, }; use crate::application::ports::file_ports::{ - FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, FileUseCaseFactory, + FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, OptimizedFileContent, }; use crate::application::ports::inbound::{FolderUseCase, SearchUseCase}; @@ -44,7 +43,6 @@ use crate::domain::services::path_service::StoragePath; /// are fully initialised. pub struct StubZipPort; -#[async_trait] impl ZipPort for StubZipPort { async fn create_folder_zip( &self, @@ -64,7 +62,6 @@ impl ZipPort for StubZipPort { pub struct StubFileReadPort; -#[async_trait] impl FileReadPort for StubFileReadPort { async fn get_file(&self, _id: &str) -> Result { Ok(File::default()) @@ -136,7 +133,6 @@ impl FileReadPort for StubFileReadPort { pub struct StubFileWritePort; -#[async_trait] impl FileWritePort for StubFileWritePort { async fn save_file_from_temp( &self, @@ -218,7 +214,6 @@ impl FileWritePort for StubFileWritePort { pub struct StubFolderStoragePort; -#[async_trait] impl FolderRepository for StubFolderStoragePort { async fn create_folder( &self, @@ -324,7 +319,6 @@ impl FolderRepository for StubFolderStoragePort { pub struct StubI18nService; -#[async_trait] impl I18nService for StubI18nService { async fn translate(&self, _key: &str, _locale: Locale) -> I18nResult { Ok(String::new()) @@ -349,7 +343,6 @@ impl I18nService for StubI18nService { pub struct StubFolderUseCase; -#[async_trait] impl FolderUseCase for StubFolderUseCase { async fn create_folder(&self, _dto: CreateFolderDto) -> Result { Ok(FolderDto::default()) @@ -429,7 +422,6 @@ impl FolderUseCase for StubFolderUseCase { pub struct StubFileUploadUseCase; -#[async_trait] impl FileUploadUseCase for StubFileUploadUseCase { async fn upload_file_streaming( &self, @@ -486,7 +478,6 @@ impl FileUploadUseCase for StubFileUploadUseCase { pub struct StubFileRetrievalUseCase; -#[async_trait] impl FileRetrievalUseCase for StubFileRetrievalUseCase { async fn get_file(&self, _id: &str) -> Result { Ok(FileDto::default()) @@ -548,7 +539,6 @@ impl FileRetrievalUseCase for StubFileRetrievalUseCase { pub struct StubFileManagementUseCase; -#[async_trait] impl FileManagementUseCase for StubFileManagementUseCase { async fn move_file( &self, @@ -579,33 +569,12 @@ impl FileManagementUseCase for StubFileManagementUseCase { } } -// --------------------------------------------------------------------------- -// FileUseCaseFactory -// --------------------------------------------------------------------------- - -pub struct StubFileUseCaseFactory; - -impl FileUseCaseFactory for StubFileUseCaseFactory { - fn create_file_upload_use_case(&self) -> Arc { - Arc::new(StubFileUploadUseCase) - } - - fn create_file_retrieval_use_case(&self) -> Arc { - Arc::new(StubFileRetrievalUseCase) - } - - fn create_file_management_use_case(&self) -> Arc { - Arc::new(StubFileManagementUseCase) - } -} - // --------------------------------------------------------------------------- // SearchUseCase // --------------------------------------------------------------------------- pub struct StubSearchUseCase; -#[async_trait] impl SearchUseCase for StubSearchUseCase { async fn search( &self, @@ -642,7 +611,6 @@ use crate::application::ports::dedup_ports::{ pub struct StubDedupPort; -#[async_trait] impl DedupPort for StubDedupPort { async fn store_bytes( &self, @@ -741,7 +709,6 @@ use crate::application::ports::cache_ports::{ pub struct StubMetadataCachePort; -#[async_trait] impl MetadataCachePort for StubMetadataCachePort { async fn get_metadata(&self, _path: &Path) -> Option { None @@ -774,7 +741,6 @@ impl MetadataCachePort for StubMetadataCachePort { pub struct StubContentCachePort; -#[async_trait] impl ContentCachePort for StubContentCachePort { fn should_cache(&self, _size: usize) -> bool { false diff --git a/src/domain/repositories/address_book_repository.rs b/src/domain/repositories/address_book_repository.rs index d6e8391e..a08943e8 100644 --- a/src/domain/repositories/address_book_repository.rs +++ b/src/domain/repositories/address_book_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use std::result::Result; use uuid::Uuid; @@ -7,7 +6,6 @@ use crate::domain::entities::contact::AddressBook; pub type AddressBookRepositoryResult = Result; -#[async_trait] pub trait AddressBookRepository: Send + Sync + 'static { async fn create_address_book( &self, diff --git a/src/domain/repositories/calendar_event_repository.rs b/src/domain/repositories/calendar_event_repository.rs index d33b57fe..dde25fac 100644 --- a/src/domain/repositories/calendar_event_repository.rs +++ b/src/domain/repositories/calendar_event_repository.rs @@ -1,13 +1,11 @@ use crate::common::errors::DomainError; use crate::domain::entities::calendar_event::CalendarEvent; -use async_trait::async_trait; use chrono::{DateTime, Utc}; use uuid::Uuid; pub type CalendarEventRepositoryResult = Result; /// Repository interface for CalendarEvent entity operations -#[async_trait] pub trait CalendarEventRepository: Send + Sync + 'static { /// Creates a new calendar event async fn create_event( diff --git a/src/domain/repositories/calendar_repository.rs b/src/domain/repositories/calendar_repository.rs index 991d39b7..a5cba3e4 100644 --- a/src/domain/repositories/calendar_repository.rs +++ b/src/domain/repositories/calendar_repository.rs @@ -1,12 +1,10 @@ use crate::common::errors::DomainError; use crate::domain::entities::calendar::Calendar; -use async_trait::async_trait; use uuid::Uuid; pub type CalendarRepositoryResult = Result; /// Repository interface for Calendar entity operations -#[async_trait] pub trait CalendarRepository: Send + Sync + 'static { /// Creates a new calendar async fn create_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult; diff --git a/src/domain/repositories/contact_repository.rs b/src/domain/repositories/contact_repository.rs index 229b1e5e..76309ac2 100644 --- a/src/domain/repositories/contact_repository.rs +++ b/src/domain/repositories/contact_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use std::result::Result; use uuid::Uuid; @@ -7,7 +6,6 @@ use crate::domain::entities::contact::{Contact, ContactGroup}; pub type ContactRepositoryResult = Result; -#[async_trait] pub trait ContactRepository: Send + Sync + 'static { async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult; async fn update_contact(&self, contact: Contact) -> ContactRepositoryResult; @@ -32,7 +30,6 @@ pub trait ContactRepository: Send + Sync + 'static { ) -> ContactRepositoryResult>; } -#[async_trait] pub trait ContactGroupRepository: Send + Sync + 'static { async fn create_group(&self, group: ContactGroup) -> ContactRepositoryResult; async fn update_group(&self, group: ContactGroup) -> ContactRepositoryResult; diff --git a/src/domain/repositories/file_repository.rs b/src/domain/repositories/file_repository.rs index b5fe889d..4fba9aae 100644 --- a/src/domain/repositories/file_repository.rs +++ b/src/domain/repositories/file_repository.rs @@ -10,7 +10,6 @@ use std::path::PathBuf; -use async_trait::async_trait; use bytes::Bytes; use futures::Stream; @@ -26,7 +25,6 @@ use crate::domain::services::path_service::StoragePath; /// /// Encapsulates every operation that queries state without modifying it: /// get, list, content, stream, mmap, range, path resolution. -#[async_trait] pub trait FileReadRepository: Send + Sync + 'static { /// Gets a file by its ID. async fn get_file(&self, id: &str) -> Result; @@ -63,7 +61,6 @@ pub trait FileReadRepository: Send + Sync + 'static { /// /// Covers: upload (buffered + streaming), move, delete, update, /// and deferred registration for write-behind cache. -#[async_trait] pub trait FileWriteRepository: Send + Sync + 'static { /// Saves a new file from bytes. async fn save_file( diff --git a/src/domain/repositories/folder_repository.rs b/src/domain/repositories/folder_repository.rs index 58bd2f7b..a56fc3fb 100644 --- a/src/domain/repositories/folder_repository.rs +++ b/src/domain/repositories/folder_repository.rs @@ -8,7 +8,6 @@ //! Concrete implementations (filesystem, PostgreSQL, S3, etc.) live in //! the infrastructure layer. -use async_trait::async_trait; use crate::common::errors::DomainError; use crate::domain::entities::folder::Folder; @@ -18,7 +17,6 @@ use crate::domain::services::path_service::StoragePath; /// /// Defines the CRUD and management operations required for /// the Folder entity in the storage system. -#[async_trait] pub trait FolderRepository: Send + Sync + 'static { /// Creates a new folder async fn create_folder( diff --git a/src/domain/repositories/session_repository.rs b/src/domain/repositories/session_repository.rs index 6de796a8..1c2a747f 100644 --- a/src/domain/repositories/session_repository.rs +++ b/src/domain/repositories/session_repository.rs @@ -1,6 +1,5 @@ use crate::common::errors::DomainError; use crate::domain::entities::session::Session; -use async_trait::async_trait; #[derive(Debug, thiserror::Error)] pub enum SessionRepositoryError { @@ -29,7 +28,6 @@ impl From for DomainError { } } -#[async_trait] pub trait SessionRepository: Send + Sync + 'static { /// Creates a new session async fn create_session(&self, session: Session) -> SessionRepositoryResult; diff --git a/src/domain/repositories/settings_repository.rs b/src/domain/repositories/settings_repository.rs index 767cc309..b04e4ce2 100644 --- a/src/domain/repositories/settings_repository.rs +++ b/src/domain/repositories/settings_repository.rs @@ -1,10 +1,8 @@ use crate::common::errors::DomainError; -use async_trait::async_trait; use std::collections::HashMap; /// Repository for platform settings stored in the database. /// Settings are key-value pairs organized by category (e.g., "oidc", "general"). -#[async_trait] pub trait SettingsRepository: Send + Sync + 'static { /// Get a single setting value by key async fn get(&self, key: &str) -> Result, DomainError>; diff --git a/src/domain/repositories/share_repository.rs b/src/domain/repositories/share_repository.rs index 7ddd96b0..1639b1b1 100644 --- a/src/domain/repositories/share_repository.rs +++ b/src/domain/repositories/share_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use thiserror::Error; use crate::domain::{ @@ -20,7 +19,6 @@ pub enum ShareRepositoryError { AlreadyExists(String), } -#[async_trait] pub trait ShareRepository: Send + Sync + 'static { /// Save a new share or update an existing one async fn save(&self, share: &Share) -> Result; diff --git a/src/domain/repositories/trash_repository.rs b/src/domain/repositories/trash_repository.rs index 8cfff5af..7925c530 100644 --- a/src/domain/repositories/trash_repository.rs +++ b/src/domain/repositories/trash_repository.rs @@ -1,10 +1,8 @@ -use async_trait::async_trait; use uuid::Uuid; use crate::common::errors::Result; use crate::domain::entities::trashed_item::TrashedItem; -#[async_trait] pub trait TrashRepository: Send + Sync { async fn add_to_trash(&self, item: &TrashedItem) -> Result<()>; async fn get_trash_items(&self, user_id: &Uuid) -> Result>; diff --git a/src/domain/repositories/user_repository.rs b/src/domain/repositories/user_repository.rs index 369030d6..f7d4359c 100644 --- a/src/domain/repositories/user_repository.rs +++ b/src/domain/repositories/user_repository.rs @@ -1,6 +1,5 @@ use crate::common::errors::DomainError; use crate::domain::entities::user::{User, UserRole}; -use async_trait::async_trait; #[derive(Debug, thiserror::Error)] pub enum UserRepositoryError { @@ -41,7 +40,6 @@ impl From for DomainError { } } -#[async_trait] pub trait UserRepository: Send + Sync + 'static { /// Creates a new user async fn create_user(&self, user: User) -> UserRepositoryResult; diff --git a/src/domain/services/i18n_service.rs b/src/domain/services/i18n_service.rs index e3b43878..94cf94a2 100644 --- a/src/domain/services/i18n_service.rs +++ b/src/domain/services/i18n_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use thiserror::Error; /// Error types for i18n service operations @@ -54,7 +53,6 @@ impl Locale { } /// Interface for i18n service (primary port) -#[async_trait] pub trait I18nService: Send + Sync + 'static { /// Get a translation for a key and locale async fn translate(&self, key: &str, locale: Locale) -> I18nResult; diff --git a/src/infrastructure/adapters/calendar_storage_adapter.rs b/src/infrastructure/adapters/calendar_storage_adapter.rs index 0281a547..335931ab 100644 --- a/src/infrastructure/adapters/calendar_storage_adapter.rs +++ b/src/infrastructure/adapters/calendar_storage_adapter.rs @@ -4,7 +4,6 @@ //! the `CalendarRepository` and `CalendarEventRepository` domain repositories. //! It bridges the gap between the application layer and the infrastructure layer. -use async_trait::async_trait; use chrono::{DateTime, Utc}; use std::collections::HashMap; use std::sync::Arc; @@ -20,18 +19,20 @@ use crate::domain::entities::calendar::Calendar; use crate::domain::entities::calendar_event::CalendarEvent; use crate::domain::repositories::calendar_event_repository::CalendarEventRepository; use crate::domain::repositories::calendar_repository::CalendarRepository; +use crate::infrastructure::repositories::pg::CalendarEventPgRepository; +use crate::infrastructure::repositories::pg::CalendarPgRepository; /// Adapter that implements CalendarStoragePort using domain repositories pub struct CalendarStorageAdapter { - calendar_repository: Arc, - event_repository: Arc, + calendar_repository: Arc, + event_repository: Arc, } impl CalendarStorageAdapter { /// Creates a new CalendarStorageAdapter with the given repositories pub fn new( - calendar_repository: Arc, - event_repository: Arc, + calendar_repository: Arc, + event_repository: Arc, ) -> Self { Self { calendar_repository, @@ -40,7 +41,6 @@ impl CalendarStorageAdapter { } } -#[async_trait] impl CalendarStoragePort for CalendarStorageAdapter { // Calendar operations diff --git a/src/infrastructure/adapters/contact_storage_adapter.rs b/src/infrastructure/adapters/contact_storage_adapter.rs index 08d30010..999b4046 100644 --- a/src/infrastructure/adapters/contact_storage_adapter.rs +++ b/src/infrastructure/adapters/contact_storage_adapter.rs @@ -4,7 +4,6 @@ //! using the domain repositories. It bridges the gap between the application layer //! and the infrastructure layer for CardDAV functionality. -use async_trait::async_trait; use std::sync::Arc; use uuid::Uuid; @@ -22,20 +21,23 @@ use crate::common::errors::{DomainError, ErrorKind}; use crate::domain::entities::contact::{Address, AddressBook, Contact, ContactGroup, Email, Phone}; use crate::domain::repositories::address_book_repository::AddressBookRepository; use crate::domain::repositories::contact_repository::{ContactGroupRepository, ContactRepository}; +use crate::infrastructure::repositories::pg::AddressBookPgRepository; +use crate::infrastructure::repositories::pg::ContactGroupPgRepository; +use crate::infrastructure::repositories::pg::ContactPgRepository; /// Adapter that implements AddressBookUseCase and ContactUseCase using domain repositories pub struct ContactStorageAdapter { - address_book_repository: Arc, - contact_repository: Arc, - group_repository: Arc, + address_book_repository: Arc, + contact_repository: Arc, + group_repository: Arc, } impl ContactStorageAdapter { /// Creates a new ContactStorageAdapter with the given repositories pub fn new( - address_book_repository: Arc, - contact_repository: Arc, - group_repository: Arc, + address_book_repository: Arc, + contact_repository: Arc, + group_repository: Arc, ) -> Self { Self { address_book_repository, @@ -217,7 +219,6 @@ impl ContactStorageAdapter { } } -#[async_trait] impl AddressBookUseCase for ContactStorageAdapter { async fn create_address_book( &self, @@ -423,7 +424,6 @@ impl AddressBookUseCase for ContactStorageAdapter { } } -#[async_trait] impl ContactUseCase for ContactStorageAdapter { async fn create_contact(&self, dto: CreateContactDto) -> Result { let address_book_id = Self::parse_uuid(&dto.address_book_id, "AddressBook")?; diff --git a/src/infrastructure/auth_factory.rs b/src/infrastructure/auth_factory.rs index 489a3932..9b9fbf2f 100644 --- a/src/infrastructure/auth_factory.rs +++ b/src/infrastructure/auth_factory.rs @@ -2,7 +2,6 @@ use anyhow::Result; use sqlx::PgPool; use std::sync::Arc; -use crate::application::ports::auth_ports::TokenServicePort; use crate::application::services::auth_application_service::AuthApplicationService; use crate::application::services::folder_service::FolderService; use crate::common::config::AppConfig; @@ -18,7 +17,7 @@ pub async fn create_auth_services( folder_service: Option>, ) -> Result { // Create JWT token service (TokenServicePort implementation) - let token_service: Arc = Arc::new(JwtTokenService::new( + let token_service: Arc = Arc::new(JwtTokenService::new( config.auth.jwt_secret.clone(), config.auth.access_token_expiry_secs, config.auth.refresh_token_expiry_secs, diff --git a/src/infrastructure/repositories/pg/address_book_pg_repository.rs b/src/infrastructure/repositories/pg/address_book_pg_repository.rs index 475e137d..f77f83d1 100644 --- a/src/infrastructure/repositories/pg/address_book_pg_repository.rs +++ b/src/infrastructure/repositories/pg/address_book_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::Utc; use sqlx::{PgPool, Row, types::Uuid}; use std::sync::Arc; @@ -19,7 +18,6 @@ impl AddressBookPgRepository { } } -#[async_trait] impl AddressBookRepository for AddressBookPgRepository { async fn create_address_book( &self, diff --git a/src/infrastructure/repositories/pg/app_password_pg_repository.rs b/src/infrastructure/repositories/pg/app_password_pg_repository.rs index 51ee2c18..7690f014 100644 --- a/src/infrastructure/repositories/pg/app_password_pg_repository.rs +++ b/src/infrastructure/repositories/pg/app_password_pg_repository.rs @@ -3,7 +3,6 @@ use crate::application::ports::auth_ports::AppPasswordStoragePort; use crate::common::errors::DomainError; use crate::domain::entities::app_password::AppPassword; -use async_trait::async_trait; use chrono::{DateTime, Utc}; use sqlx::PgPool; use std::sync::Arc; @@ -22,7 +21,6 @@ impl AppPasswordPgRepository { } } -#[async_trait] impl AppPasswordStoragePort for AppPasswordPgRepository { async fn create(&self, ap: AppPassword) -> Result { sqlx::query( diff --git a/src/infrastructure/repositories/pg/calendar_event_pg_repository.rs b/src/infrastructure/repositories/pg/calendar_event_pg_repository.rs index 6949d08e..6436b7db 100644 --- a/src/infrastructure/repositories/pg/calendar_event_pg_repository.rs +++ b/src/infrastructure/repositories/pg/calendar_event_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::{DateTime, Utc}; use sqlx::{PgPool, Row, types::Uuid}; use std::sync::Arc; @@ -19,7 +18,6 @@ impl CalendarEventPgRepository { } } -#[async_trait] impl CalendarEventRepository for CalendarEventPgRepository { async fn create_event( &self, diff --git a/src/infrastructure/repositories/pg/calendar_pg_repository.rs b/src/infrastructure/repositories/pg/calendar_pg_repository.rs index efb646c1..74e83e62 100644 --- a/src/infrastructure/repositories/pg/calendar_pg_repository.rs +++ b/src/infrastructure/repositories/pg/calendar_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::Utc; use sqlx::{PgPool, Row, types::Uuid}; use std::sync::Arc; @@ -19,7 +18,6 @@ impl CalendarPgRepository { } } -#[async_trait] impl CalendarRepository for CalendarPgRepository { async fn create_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult { let row = sqlx::query( diff --git a/src/infrastructure/repositories/pg/contact_group_pg_repository.rs b/src/infrastructure/repositories/pg/contact_group_pg_repository.rs index a9579e3a..349c95c0 100644 --- a/src/infrastructure/repositories/pg/contact_group_pg_repository.rs +++ b/src/infrastructure/repositories/pg/contact_group_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::Utc; use serde_json::Value as JsonValue; use sqlx::{PgPool, Row, types::Uuid}; @@ -24,7 +23,6 @@ impl ContactGroupPgRepository { } } -#[async_trait] impl ContactGroupRepository for ContactGroupPgRepository { async fn create_group(&self, group: ContactGroup) -> ContactRepositoryResult { sqlx::query( diff --git a/src/infrastructure/repositories/pg/contact_pg_repository.rs b/src/infrastructure/repositories/pg/contact_pg_repository.rs index bb2ed57a..5aa9e454 100644 --- a/src/infrastructure/repositories/pg/contact_pg_repository.rs +++ b/src/infrastructure/repositories/pg/contact_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::Utc; use serde_json::Value as JsonValue; use sqlx::{PgPool, Row, types::Uuid}; @@ -63,7 +62,6 @@ impl ContactPgRepository { } } -#[async_trait] impl ContactRepository for ContactPgRepository { async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult { // Convert domain entities to persistence DTOs for JSONB serialization diff --git a/src/infrastructure/repositories/pg/device_code_pg_repository.rs b/src/infrastructure/repositories/pg/device_code_pg_repository.rs index c34d375a..9cab8d48 100644 --- a/src/infrastructure/repositories/pg/device_code_pg_repository.rs +++ b/src/infrastructure/repositories/pg/device_code_pg_repository.rs @@ -1,6 +1,5 @@ //! PostgreSQL repository for Device Authorization Grant (RFC 8628) codes. -use async_trait::async_trait; use sqlx::{PgPool, Row}; use std::sync::Arc; @@ -49,7 +48,6 @@ impl DeviceCodePgRepository { } } -#[async_trait] impl DeviceCodeStoragePort for DeviceCodePgRepository { async fn create_device_code(&self, dc: DeviceCode) -> Result { sqlx::query( diff --git a/src/infrastructure/repositories/pg/favorites_pg_repository.rs b/src/infrastructure/repositories/pg/favorites_pg_repository.rs index 039bf16e..c5310088 100644 --- a/src/infrastructure/repositories/pg/favorites_pg_repository.rs +++ b/src/infrastructure/repositories/pg/favorites_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use sqlx::{PgPool, Row}; use std::sync::Arc; use tracing::error; @@ -19,7 +18,6 @@ impl FavoritesPgRepository { } } -#[async_trait] impl FavoritesRepositoryPort for FavoritesPgRepository { async fn get_favorites(&self, user_id: &str) -> Result> { let user_uuid = Uuid::parse_str(user_id)?; diff --git a/src/infrastructure/repositories/pg/file_blob_read_repository.rs b/src/infrastructure/repositories/pg/file_blob_read_repository.rs index 40d41e07..84ef6cd5 100644 --- a/src/infrastructure/repositories/pg/file_blob_read_repository.rs +++ b/src/infrastructure/repositories/pg/file_blob_read_repository.rs @@ -7,7 +7,6 @@ //! File paths are resolved by JOINing with `storage.folders.path` (the //! materialized path column), so no recursive CTEs or N+1 queries are needed. -use async_trait::async_trait; use bytes::Bytes; use futures::{Stream, TryStreamExt}; use moka::sync::Cache; @@ -17,16 +16,16 @@ use std::sync::Arc; use std::time::Duration; use crate::application::dtos::search_dto::SearchCriteriaDto; -use crate::application::ports::dedup_ports::DedupPort; use crate::application::ports::storage_ports::FileReadPort; use crate::common::errors::DomainError; use crate::domain::entities::file::File; use crate::domain::services::path_service::StoragePath; +use crate::infrastructure::services::dedup_service::DedupService; /// File read repository backed by PostgreSQL metadata + blob storage. pub struct FileBlobReadRepository { pool: Arc, - dedup: Arc, + dedup: Arc, /// Lock-free cache: file_id → blob_hash. /// Populated by `get_file()` and `resolve_blob_hash()` (slow path). /// Entries persist until TTI expiry (30 s idle) or capacity eviction — @@ -37,7 +36,7 @@ pub struct FileBlobReadRepository { impl FileBlobReadRepository { pub fn new( pool: Arc, - dedup: Arc, + dedup: Arc, _folder_repo: Arc, ) -> Self { Self { @@ -115,7 +114,6 @@ impl FileBlobReadRepository { } } -#[async_trait] impl FileReadPort for FileBlobReadRepository { async fn get_file(&self, id: &str) -> Result { let row = sqlx::query_as::< @@ -890,14 +888,13 @@ impl FileReadPort for FileBlobReadRepository { mod tests { use super::*; use crate::common::stubs::StubDedupPort; - use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository; /// Helper: build a `FileBlobReadRepository` without a real PgPool. /// Only the moka `hash_cache` is exercised — no SQL is executed. fn make_repo() -> FileBlobReadRepository { let _folder_repo = Arc::new(FolderDbRepository::new_stub()); // StubDedupPort satisfies the trait but is never called in cache-only tests - let dedup: Arc = Arc::new(StubDedupPort); + let dedup: Arc = Arc::new(StubDedupPort); // PgPool is required by the struct but we won't hit any SQL in these tests. // We create a repo with a stub pool placeholder — only hash_cache is tested. FileBlobReadRepository { diff --git a/src/infrastructure/repositories/pg/file_blob_write_repository.rs b/src/infrastructure/repositories/pg/file_blob_write_repository.rs index 212a8c54..2f912479 100644 --- a/src/infrastructure/repositories/pg/file_blob_write_repository.rs +++ b/src/infrastructure/repositories/pg/file_blob_write_repository.rs @@ -7,30 +7,29 @@ //! File paths are resolved by querying the materialized `storage.folders.path` //! column (O(1) per lookup), so no recursive CTEs are needed. -use async_trait::async_trait; use sqlx::PgPool; use std::path::PathBuf; use std::sync::Arc; -use crate::application::ports::dedup_ports::DedupPort; use crate::application::ports::storage_ports::{CopyFolderTreeResult, FileWritePort}; use crate::common::errors::DomainError; use crate::domain::entities::file::File; use crate::domain::services::path_service::StoragePath; use super::folder_db_repository::FolderDbRepository; +use crate::infrastructure::services::dedup_service::DedupService; /// File write repository backed by PostgreSQL metadata + blob storage. pub struct FileBlobWriteRepository { pool: Arc, - dedup: Arc, + dedup: Arc, folder_repo: Arc, } impl FileBlobWriteRepository { pub fn new( pool: Arc, - dedup: Arc, + dedup: Arc, folder_repo: Arc, ) -> Self { Self { @@ -180,7 +179,6 @@ impl FileBlobWriteRepository { } } -#[async_trait] impl FileWritePort for FileBlobWriteRepository { async fn save_file_from_temp( &self, diff --git a/src/infrastructure/repositories/pg/folder_db_repository.rs b/src/infrastructure/repositories/pg/folder_db_repository.rs index 4a615b45..7575895c 100644 --- a/src/infrastructure/repositories/pg/folder_db_repository.rs +++ b/src/infrastructure/repositories/pg/folder_db_repository.rs @@ -8,7 +8,6 @@ //! database triggers, so reading a folder's full path is always O(1) — no //! recursive CTEs or N+1 queries. -use async_trait::async_trait; use sqlx::PgPool; use std::sync::Arc; @@ -72,7 +71,6 @@ impl FolderDbRepository { } } -#[async_trait] impl FolderRepository for FolderDbRepository { async fn create_folder( &self, diff --git a/src/infrastructure/repositories/pg/recent_items_pg_repository.rs b/src/infrastructure/repositories/pg/recent_items_pg_repository.rs index 5e69365f..f7a1f92d 100644 --- a/src/infrastructure/repositories/pg/recent_items_pg_repository.rs +++ b/src/infrastructure/repositories/pg/recent_items_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use sqlx::{PgPool, Row}; use std::sync::Arc; use tracing::error; @@ -19,7 +18,6 @@ impl RecentItemsPgRepository { } } -#[async_trait] impl RecentItemsRepositoryPort for RecentItemsPgRepository { async fn get_recent_items(&self, user_id: &str, limit: i32) -> Result> { let user_uuid = Uuid::parse_str(user_id)?; diff --git a/src/infrastructure/repositories/pg/session_pg_repository.rs b/src/infrastructure/repositories/pg/session_pg_repository.rs index 7155d093..f3cafd5c 100644 --- a/src/infrastructure/repositories/pg/session_pg_repository.rs +++ b/src/infrastructure/repositories/pg/session_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use chrono::Utc; use futures::future::BoxFuture; use sqlx::{PgPool, Row}; @@ -39,7 +38,6 @@ impl SessionPgRepository { } } -#[async_trait] impl SessionRepository for SessionPgRepository { /// Creates a new session using a transaction async fn create_session(&self, session: Session) -> SessionRepositoryResult { @@ -291,7 +289,6 @@ impl SessionRepository for SessionPgRepository { } // Implementation of the storage port for the application layer -#[async_trait] impl SessionStoragePort for SessionPgRepository { async fn create_session(&self, session: Session) -> Result { SessionRepository::create_session(self, session) diff --git a/src/infrastructure/repositories/pg/settings_pg_repository.rs b/src/infrastructure/repositories/pg/settings_pg_repository.rs index aaa5ea38..98a1d241 100644 --- a/src/infrastructure/repositories/pg/settings_pg_repository.rs +++ b/src/infrastructure/repositories/pg/settings_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use sqlx::PgPool; use std::collections::HashMap; use std::sync::Arc; @@ -16,7 +15,6 @@ impl SettingsPgRepository { } } -#[async_trait] impl SettingsRepository for SettingsPgRepository { async fn get(&self, key: &str) -> Result, DomainError> { let row = diff --git a/src/infrastructure/repositories/pg/share_pg_repository.rs b/src/infrastructure/repositories/pg/share_pg_repository.rs index 069fea77..c33404e5 100644 --- a/src/infrastructure/repositories/pg/share_pg_repository.rs +++ b/src/infrastructure/repositories/pg/share_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use sqlx::{PgPool, Row}; use std::sync::Arc; @@ -72,7 +71,6 @@ impl SharePgRepository { } } -#[async_trait] impl ShareStoragePort for SharePgRepository { async fn save_share(&self, share: &Share) -> Result { let row = sqlx::query( diff --git a/src/infrastructure/repositories/pg/trash_db_repository.rs b/src/infrastructure/repositories/pg/trash_db_repository.rs index 6ce2c41a..e72aad1d 100644 --- a/src/infrastructure/repositories/pg/trash_db_repository.rs +++ b/src/infrastructure/repositories/pg/trash_db_repository.rs @@ -4,7 +4,6 @@ //! and `storage.folders`. There is no separate trash table — trashed items //! are files/folders with `is_trashed = TRUE`. -use async_trait::async_trait; use chrono::{DateTime, Utc}; use sqlx::PgPool; use std::sync::Arc; @@ -65,7 +64,6 @@ impl TrashDbRepository { } } -#[async_trait] impl TrashRepository for TrashDbRepository { async fn add_to_trash(&self, _item: &TrashedItem) -> Result<()> { // No-op: the actual flagging is done by FileWritePort::move_to_trash diff --git a/src/infrastructure/repositories/pg/user_pg_repository.rs b/src/infrastructure/repositories/pg/user_pg_repository.rs index bab4c2ff..0f78309d 100644 --- a/src/infrastructure/repositories/pg/user_pg_repository.rs +++ b/src/infrastructure/repositories/pg/user_pg_repository.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use futures::future::BoxFuture; use sqlx::{PgPool, Row}; use std::sync::Arc; @@ -44,7 +43,6 @@ impl UserPgRepository { } } -#[async_trait] impl UserRepository for UserPgRepository { /// Creates a new user using a transaction async fn create_user(&self, user: User) -> UserRepositoryResult { @@ -619,7 +617,6 @@ impl UserRepository for UserPgRepository { } // Storage port implementation for the application layer -#[async_trait] impl UserStoragePort for UserPgRepository { async fn create_user(&self, user: User) -> Result { UserRepository::create_user(self, user) diff --git a/src/infrastructure/services/chunked_upload_service.rs b/src/infrastructure/services/chunked_upload_service.rs index a999265b..e7c08c8e 100644 --- a/src/infrastructure/services/chunked_upload_service.rs +++ b/src/infrastructure/services/chunked_upload_service.rs @@ -15,7 +15,6 @@ //! 3. HEAD /api/uploads/:id → Check progress //! 4. POST /api/uploads/:id/complete → Finalize and assemble -use async_trait::async_trait; use chrono::{DateTime, Utc}; use dashmap::DashMap; use serde::{Deserialize, Serialize}; @@ -755,7 +754,6 @@ impl ChunkedUploadService { // ─── Port implementation ───────────────────────────────────────────────────── -#[async_trait] impl ChunkedUploadPort for ChunkedUploadService { async fn create_session( &self, diff --git a/src/infrastructure/services/compression_service.rs b/src/infrastructure/services/compression_service.rs index cc1b0050..3a26fd3b 100644 --- a/src/infrastructure/services/compression_service.rs +++ b/src/infrastructure/services/compression_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use bytes::Bytes; use flate2::Compression; use flate2::bufread::GzDecoder; @@ -43,7 +42,6 @@ impl From for Compression { const COMPRESSION_SIZE_THRESHOLD: u64 = 1024 * 50; // 50KB /// Interface for compression services -#[async_trait] pub trait CompressionService: Send + Sync { /// Compresses data in memory async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result>; @@ -88,7 +86,6 @@ impl GzipCompressionService { } } -#[async_trait] impl CompressionService for GzipCompressionService { /// Compresses data in memory using Gzip async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result> { @@ -258,7 +255,6 @@ impl From for CompressionLevel { } } -#[async_trait] impl CompressionPort for GzipCompressionService { async fn compress_data( &self, diff --git a/src/infrastructure/services/dedup_service.rs b/src/infrastructure/services/dedup_service.rs index 583d5578..8e9f887a 100644 --- a/src/infrastructure/services/dedup_service.rs +++ b/src/infrastructure/services/dedup_service.rs @@ -31,7 +31,6 @@ //! - 30-50% storage reduction typical //! - Faster uploads for existing content (instant dedup) -use async_trait::async_trait; use bytes::Bytes; use futures::stream::{self, StreamExt}; use futures::{Stream, TryStreamExt}; @@ -802,7 +801,6 @@ impl DedupService { // ─── Port implementation ───────────────────────────────────────────────────── -#[async_trait] impl DedupPort for DedupService { async fn store_bytes( &self, diff --git a/src/infrastructure/services/file_content_cache.rs b/src/infrastructure/services/file_content_cache.rs index b60e8506..ebf36a02 100644 --- a/src/infrastructure/services/file_content_cache.rs +++ b/src/infrastructure/services/file_content_cache.rs @@ -198,9 +198,7 @@ pub type SharedFileContentCache = Arc; // ─── ContentCachePort implementation ───────────────────────── use crate::application::ports::cache_ports::ContentCachePort; -use async_trait::async_trait; -#[async_trait] impl ContentCachePort for FileContentCache { fn should_cache(&self, size: usize) -> bool { FileContentCache::should_cache(self, size) diff --git a/src/infrastructure/services/file_system_i18n_service.rs b/src/infrastructure/services/file_system_i18n_service.rs index 74220596..d0c1df99 100644 --- a/src/infrastructure/services/file_system_i18n_service.rs +++ b/src/infrastructure/services/file_system_i18n_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use serde_json::Value; use std::collections::HashMap; use std::path::PathBuf; @@ -63,7 +62,6 @@ impl FileSystemI18nService { } } -#[async_trait] impl I18nService for FileSystemI18nService { async fn translate(&self, key: &str, locale: Locale) -> I18nResult { // Check if translations are cached diff --git a/src/infrastructure/services/image_transcode_service.rs b/src/infrastructure/services/image_transcode_service.rs index 757b1ea0..e38058e7 100644 --- a/src/infrastructure/services/image_transcode_service.rs +++ b/src/infrastructure/services/image_transcode_service.rs @@ -10,7 +10,6 @@ //! - Supports JPEG, PNG, GIF → WebP conversion //! - Falls back to original if conversion fails or result is larger -use async_trait::async_trait; use bytes::Bytes; use image::ImageFormat; use std::path::{Path, PathBuf}; @@ -392,7 +391,6 @@ impl From for OutputFormat { } } -#[async_trait] impl ImageTranscodePort for ImageTranscodeService { fn can_transcode(&self, mime_type: &str) -> bool { ImageTranscodeService::can_transcode(mime_type) diff --git a/src/infrastructure/services/oidc_service.rs b/src/infrastructure/services/oidc_service.rs index f4b810d3..26f7ddd5 100644 --- a/src/infrastructure/services/oidc_service.rs +++ b/src/infrastructure/services/oidc_service.rs @@ -5,7 +5,6 @@ //! Supports both RSA (RS256, RS384, RS512) and EC (ES256, ES384) algorithms. //! Compatible with Authentik, Keycloak, and any standard OIDC provider. -use async_trait::async_trait; use serde::Deserialize; use std::sync::RwLock; use std::time::{Duration, Instant}; @@ -293,7 +292,6 @@ impl OidcService { } } -#[async_trait] impl OidcServicePort for OidcService { async fn get_authorize_url( &self, diff --git a/src/infrastructure/services/password_hasher.rs b/src/infrastructure/services/password_hasher.rs index 4ab1a338..fae77d8f 100644 --- a/src/infrastructure/services/password_hasher.rs +++ b/src/infrastructure/services/password_hasher.rs @@ -12,7 +12,6 @@ use argon2::password_hash::SaltString; use argon2::{Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version}; -use async_trait::async_trait; use rand_core::OsRng; use crate::application::ports::auth_ports::PasswordHasherPort; @@ -57,7 +56,6 @@ impl Argon2PasswordHasher { } } -#[async_trait] impl PasswordHasherPort for Argon2PasswordHasher { async fn hash_password(&self, password: &str) -> Result { let pwd = password.to_owned(); diff --git a/src/infrastructure/services/path_service.rs b/src/infrastructure/services/path_service.rs index b3e5f067..7cb7005d 100644 --- a/src/infrastructure/services/path_service.rs +++ b/src/infrastructure/services/path_service.rs @@ -5,7 +5,6 @@ //! //! StoragePath (Value Object) remains in domain/services/path_service.rs -use async_trait::async_trait; use std::path::{Path, PathBuf}; use tokio::fs; @@ -116,7 +115,6 @@ impl PathService { } } -#[async_trait] impl StoragePort for PathService { fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf { let mut path = self.root_path.clone(); diff --git a/src/infrastructure/services/thumbnail_service.rs b/src/infrastructure/services/thumbnail_service.rs index 130bb73c..1ca81b25 100644 --- a/src/infrastructure/services/thumbnail_service.rs +++ b/src/infrastructure/services/thumbnail_service.rs @@ -1,4 +1,3 @@ -use async_trait::async_trait; use bytes::Bytes; use image::{ImageFormat, imageops::FilterType}; /** @@ -458,7 +457,6 @@ impl From for ThumbnailSize { } } -#[async_trait] impl ThumbnailPort for ThumbnailService { fn is_supported_image(&self, mime_type: &str) -> bool { ThumbnailService::is_supported_image(mime_type) diff --git a/src/infrastructure/services/trash_cleanup_service.rs b/src/infrastructure/services/trash_cleanup_service.rs index 66ced330..96f62d2d 100644 --- a/src/infrastructure/services/trash_cleanup_service.rs +++ b/src/infrastructure/services/trash_cleanup_service.rs @@ -5,6 +5,7 @@ use tracing::{debug, error, info, instrument}; use crate::common::errors::Result; use crate::domain::repositories::trash_repository::TrashRepository; +use crate::infrastructure::repositories::pg::trash_db_repository::TrashDbRepository; /// Service for automatic cleanup of expired items in the trash. /// @@ -12,12 +13,12 @@ use crate::domain::repositories::trash_repository::TrashRepository; /// in **2 SQL statements inside a single transaction**, instead of the /// previous N+1 pattern that issued 3 queries per expired item. pub struct TrashCleanupService { - trash_repository: Arc, + trash_repository: Arc, cleanup_interval_hours: u64, } impl TrashCleanupService { - pub fn new(trash_repository: Arc, cleanup_interval_hours: u64) -> Self { + pub fn new(trash_repository: Arc, cleanup_interval_hours: u64) -> Self { Self { trash_repository, cleanup_interval_hours: cleanup_interval_hours.max(1), // Minimum 1 hour @@ -57,7 +58,7 @@ impl TrashCleanupService { /// Bulk-delete all expired trash items in a single transaction. #[instrument(skip(trash_repository))] - async fn cleanup_expired_items(trash_repository: Arc) -> Result<()> { + async fn cleanup_expired_items(trash_repository: Arc) -> Result<()> { debug!("Starting bulk cleanup of expired trash items"); let (files, folders) = trash_repository.delete_expired_bulk().await?; diff --git a/src/infrastructure/services/zip_service.rs b/src/infrastructure/services/zip_service.rs index e4c15586..a5410ae1 100644 --- a/src/infrastructure/services/zip_service.rs +++ b/src/infrastructure/services/zip_service.rs @@ -5,7 +5,6 @@ use crate::{ application::ports::zip_ports::ZipPort, common::errors::{DomainError, ErrorKind, Result}, }; -use async_trait::async_trait; use async_zip::base::write::ZipFileWriter; use async_zip::{Compression, ZipEntryBuilder}; use futures::StreamExt; @@ -17,6 +16,8 @@ use thiserror::Error; use tokio::io::BufWriter; use tokio_util::compat::Compat; use tracing::*; +use crate::application::services::file_retrieval_service::FileRetrievalService; +use crate::application::services::folder_service::FolderService; /// Error related to ZIP file creation #[derive(Debug, Error)] @@ -53,15 +54,15 @@ type AsyncZipWriter = ZipFileWriter>>; /// `tokio::io::BufWriter` → `tokio::fs::File`, so **no Tokio worker is ever /// blocked** by disk I/O or compression. pub struct ZipService { - file_service: Arc, - folder_service: Arc, + file_service: Arc, + folder_service: Arc, } impl ZipService { /// Creates a new instance of the ZIP service pub fn new( - file_service: Arc, - folder_service: Arc, + file_service: Arc, + folder_service: Arc, ) -> Self { Self { file_service, @@ -239,7 +240,6 @@ impl ZipService { // ─── Port implementation ───────────────────────────────────────────────────── -#[async_trait] impl ZipPort for ZipService { async fn create_folder_zip( &self, diff --git a/src/interfaces/api/handlers/admin_handler.rs b/src/interfaces/api/handlers/admin_handler.rs index c90b11f9..692162d1 100644 --- a/src/interfaces/api/handlers/admin_handler.rs +++ b/src/interfaces/api/handlers/admin_handler.rs @@ -14,6 +14,7 @@ use crate::application::dtos::settings_dto::{ use crate::common::di::AppState; use crate::interfaces::errors::AppError; use std::sync::Arc; +use crate::application::ports::auth_ports::TokenServicePort; /// Admin API routes — all require admin role. pub fn admin_routes() -> Router> { diff --git a/src/interfaces/api/handlers/caldav_handler.rs b/src/interfaces/api/handlers/caldav_handler.rs index 2191ad0b..56ec60b1 100644 --- a/src/interfaces/api/handlers/caldav_handler.rs +++ b/src/interfaces/api/handlers/caldav_handler.rs @@ -35,6 +35,7 @@ use crate::application::ports::calendar_ports::CalendarUseCase; use crate::common::di::AppState; use crate::interfaces::errors::AppError; use crate::interfaces::middleware::auth::CurrentUser; +use crate::application::services::calendar_service::CalendarService; const HEADER_DAV: HeaderName = HeaderName::from_static("dav"); @@ -127,7 +128,7 @@ fn extract_user(req: &Request) -> Result { .ok_or_else(|| AppError::unauthorized("Authentication required")) } -fn get_calendar_service(state: &AppState) -> Result<&Arc, AppError> { +fn get_calendar_service(state: &AppState) -> Result<&Arc, AppError> { state.calendar_use_case.as_ref().ok_or_else(|| { AppError::new( StatusCode::NOT_IMPLEMENTED, diff --git a/src/interfaces/api/handlers/carddav_handler.rs b/src/interfaces/api/handlers/carddav_handler.rs index a5685b70..348fe0f2 100644 --- a/src/interfaces/api/handlers/carddav_handler.rs +++ b/src/interfaces/api/handlers/carddav_handler.rs @@ -35,6 +35,7 @@ use crate::application::ports::carddav_ports::{AddressBookUseCase, ContactUseCas use crate::common::di::AppState; use crate::interfaces::errors::AppError; use crate::interfaces::middleware::auth::CurrentUser; +use crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter; const HEADER_DAV: HeaderName = HeaderName::from_static("dav"); @@ -115,7 +116,7 @@ fn extract_user(req: &Request) -> Result { .ok_or_else(|| AppError::unauthorized("Authentication required")) } -fn get_addressbook_service(state: &AppState) -> Result<&Arc, AppError> { +fn get_addressbook_service(state: &AppState) -> Result<&Arc, AppError> { state.addressbook_use_case.as_ref().ok_or_else(|| { AppError::new( StatusCode::NOT_IMPLEMENTED, @@ -125,7 +126,7 @@ fn get_addressbook_service(state: &AppState) -> Result<&Arc Result<&Arc, AppError> { +fn get_contact_service(state: &AppState) -> Result<&Arc, AppError> { state.contact_use_case.as_ref().ok_or_else(|| { AppError::new( StatusCode::NOT_IMPLEMENTED, diff --git a/src/interfaces/api/handlers/chunked_upload_handler.rs b/src/interfaces/api/handlers/chunked_upload_handler.rs index 3b7e7924..50c505fd 100644 --- a/src/interfaces/api/handlers/chunked_upload_handler.rs +++ b/src/interfaces/api/handlers/chunked_upload_handler.rs @@ -21,6 +21,9 @@ use crate::application::ports::chunked_upload_ports::DEFAULT_CHUNK_SIZE; use crate::common::di::AppState; use crate::domain::errors::ErrorKind; use crate::interfaces::middleware::auth::AuthUser; +use crate::application::ports::chunked_upload_ports::ChunkedUploadPort; +use crate::application::ports::file_ports::FileUploadUseCase; +use crate::application::ports::storage_ports::StorageUsagePort; /// Request body for creating an upload session #[derive(Debug, Deserialize)] diff --git a/src/interfaces/api/handlers/favorites_handler.rs b/src/interfaces/api/handlers/favorites_handler.rs index 948504b8..e04f829d 100644 --- a/src/interfaces/api/handlers/favorites_handler.rs +++ b/src/interfaces/api/handlers/favorites_handler.rs @@ -10,6 +10,7 @@ use tracing::{error, info}; use crate::application::ports::favorites_ports::FavoritesUseCase; use crate::interfaces::middleware::auth::AuthUser; +use crate::application::services::favorites_service::FavoritesService; /// Single item in a batch-add-favorites request. #[derive(Debug, Deserialize)] @@ -26,7 +27,7 @@ pub struct BatchFavoritesRequest { /// Handler for favorite-related API endpoints pub async fn get_favorites( - State(favorites_service): State>, + State(favorites_service): State>, auth_user: AuthUser, ) -> impl IntoResponse { let user_id = &auth_user.id; @@ -51,7 +52,7 @@ pub async fn get_favorites( /// Add an item to user's favorites pub async fn add_favorite( - State(favorites_service): State>, + State(favorites_service): State>, auth_user: AuthUser, Path((item_type, item_id)): Path<(String, String)>, ) -> impl IntoResponse { @@ -94,7 +95,7 @@ pub async fn add_favorite( /// Remove an item from user's favorites pub async fn remove_favorite( - State(favorites_service): State>, + State(favorites_service): State>, auth_user: AuthUser, Path((item_type, item_id)): Path<(String, String)>, ) -> impl IntoResponse { @@ -138,7 +139,7 @@ pub async fn remove_favorite( /// Add multiple items to favourites in a single transaction. /// POST /api/favorites/batch pub async fn batch_add_favorites( - State(favorites_service): State>, + State(favorites_service): State>, auth_user: AuthUser, Json(body): Json, ) -> impl IntoResponse { diff --git a/src/interfaces/api/handlers/file_handler.rs b/src/interfaces/api/handlers/file_handler.rs index 026aa8f9..26c95a28 100644 --- a/src/interfaces/api/handlers/file_handler.rs +++ b/src/interfaces/api/handlers/file_handler.rs @@ -14,6 +14,9 @@ use crate::application::ports::file_ports::OptimizedFileContent; use crate::common::di::AppState; use crate::interfaces::middleware::auth::{AuthUser, OptionalUserId}; use std::sync::Arc; +use crate::application::ports::file_ports::{FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase}; +use crate::application::ports::storage_ports::StorageUsagePort; +use crate::application::ports::thumbnail_ports::ThumbnailPort; /** * Type aliases for dependency injection state. @@ -306,7 +309,7 @@ impl FileHandler { let file_path = storage_root.join(&file.path); match thumbnail_service - .get_thumbnail(&id, thumb_size, &file_path) + .get_thumbnail(&id, thumb_size.into(), &file_path) .await { Ok(data) => { diff --git a/src/interfaces/api/handlers/folder_handler.rs b/src/interfaces/api/handlers/folder_handler.rs index 716cf6d3..da1449ef 100644 --- a/src/interfaces/api/handlers/folder_handler.rs +++ b/src/interfaces/api/handlers/folder_handler.rs @@ -18,6 +18,8 @@ use crate::application::services::folder_service::FolderService; use crate::common::di::AppState as GlobalAppState; use crate::common::errors::ErrorKind; use crate::interfaces::middleware::auth::AuthUser; +use crate::application::ports::file_ports::FileRetrievalUseCase; +use crate::application::ports::trash_ports::TrashUseCase; type AppState = Arc; @@ -423,7 +425,17 @@ impl FolderHandler { tracing::info!("Preparing ZIP for folder: {} ({})", folder.name, id); // Use ZIP service from DI container - let zip_service = &state.core.zip_service; + let zip_service = match &state.core.zip_service { + Some(svc) => svc, + None => { + tracing::error!("ZipService not initialized"); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({ "error": "ZipService not initialized" })), + ) + .into_response(); + } + }; // Create the ZIP archive (written to a temp file, O(1) RAM) match zip_service.create_folder_zip(&id, &folder.name).await { diff --git a/src/interfaces/api/handlers/recent_handler.rs b/src/interfaces/api/handlers/recent_handler.rs index 96caf6a6..d103c023 100644 --- a/src/interfaces/api/handlers/recent_handler.rs +++ b/src/interfaces/api/handlers/recent_handler.rs @@ -10,6 +10,7 @@ use tracing::{error, info}; use crate::application::ports::recent_ports::RecentItemsUseCase; use crate::interfaces::middleware::auth::AuthUser; +use crate::application::services::recent_service::RecentService; /// Query parameters for getting recent items #[derive(Deserialize)] @@ -20,7 +21,7 @@ pub struct GetRecentParams { /// Get user's recent items pub async fn get_recent_items( - State(recent_service): State>, + State(recent_service): State>, auth_user: AuthUser, Query(params): Query, ) -> impl IntoResponse { @@ -46,7 +47,7 @@ pub async fn get_recent_items( /// Record access to an item pub async fn record_item_access( - State(recent_service): State>, + State(recent_service): State>, auth_user: AuthUser, Path((item_type, item_id)): Path<(String, String)>, ) -> impl IntoResponse { @@ -92,7 +93,7 @@ pub async fn record_item_access( /// Remove an item from recents pub async fn remove_from_recent( - State(recent_service): State>, + State(recent_service): State>, auth_user: AuthUser, Path((item_type, item_id)): Path<(String, String)>, ) -> impl IntoResponse { @@ -138,7 +139,7 @@ pub async fn remove_from_recent( /// Clear all recent items pub async fn clear_recent_items( - State(recent_service): State>, + State(recent_service): State>, auth_user: AuthUser, ) -> impl IntoResponse { let user_id = &auth_user.id; diff --git a/src/interfaces/api/handlers/search_handler.rs b/src/interfaces/api/handlers/search_handler.rs index be17646a..6f370b95 100644 --- a/src/interfaces/api/handlers/search_handler.rs +++ b/src/interfaces/api/handlers/search_handler.rs @@ -10,6 +10,7 @@ use crate::application::dtos::search_dto::SearchCriteriaDto; use crate::common::di::AppState; use crate::interfaces::middleware::auth::AuthUser; use std::sync::Arc; +use crate::application::ports::inbound::SearchUseCase; /** * Handler for search operations through the API. diff --git a/src/interfaces/api/handlers/share_handler.rs b/src/interfaces/api/handlers/share_handler.rs index 0dfb93c9..83853381 100644 --- a/src/interfaces/api/handlers/share_handler.rs +++ b/src/interfaces/api/handlers/share_handler.rs @@ -18,6 +18,7 @@ use crate::{ domain::entities::share::ShareItemType, interfaces::middleware::auth::OptionalAuthUser, }; +use crate::application::services::share_service::ShareService; #[derive(Debug, Deserialize)] pub struct GetSharesQuery { @@ -34,7 +35,7 @@ pub struct VerifyPasswordRequest { /// Create a new shared link pub async fn create_shared_link( - State(share_use_case): State>, + State(share_use_case): State>, auth_user: OptionalAuthUser, Json(dto): Json, ) -> impl IntoResponse { @@ -57,7 +58,7 @@ pub async fn create_shared_link( /// Get information about a specific shared link by ID pub async fn get_shared_link( - State(share_use_case): State>, + State(share_use_case): State>, Path(id): Path, ) -> impl IntoResponse { match share_use_case.get_shared_link(&id).await { @@ -75,7 +76,7 @@ pub async fn get_shared_link( /// Get all shared links created by the current user. /// Supports optional filtering by item_id + item_type query params. pub async fn get_user_shares( - State(share_use_case): State>, + State(share_use_case): State>, auth_user: OptionalAuthUser, Query(query): Query, ) -> impl IntoResponse { @@ -128,7 +129,7 @@ pub async fn get_user_shares( /// Update a shared link's properties pub async fn update_shared_link( - State(share_use_case): State>, + State(share_use_case): State>, Path(id): Path, Json(dto): Json, ) -> impl IntoResponse { @@ -148,7 +149,7 @@ pub async fn update_shared_link( /// Delete a shared link pub async fn delete_shared_link( - State(share_use_case): State>, + State(share_use_case): State>, Path(id): Path, ) -> impl IntoResponse { match share_use_case.delete_shared_link(&id).await { @@ -166,7 +167,7 @@ pub async fn delete_shared_link( /// Access a shared item via its token pub async fn access_shared_item( - State(share_use_case): State>, + State(share_use_case): State>, Path(token): Path, ) -> impl IntoResponse { // Register the access @@ -204,7 +205,7 @@ pub async fn access_shared_item( /// Verify password for a password-protected shared item pub async fn verify_shared_item_password( - State(share_use_case): State>, + State(share_use_case): State>, Path(token): Path, Json(req): Json, ) -> impl IntoResponse { diff --git a/src/interfaces/api/handlers/trash_handler.rs b/src/interfaces/api/handlers/trash_handler.rs index f8dee010..187b6446 100644 --- a/src/interfaces/api/handlers/trash_handler.rs +++ b/src/interfaces/api/handlers/trash_handler.rs @@ -4,7 +4,7 @@ use axum::http::StatusCode; use serde_json::json; use tracing::{debug, error, instrument, warn}; -// use crate::application::ports::trash_ports::TrashUseCase; +use crate::application::ports::trash_ports::TrashUseCase; use crate::common::di::AppState; use crate::interfaces::middleware::auth::{AuthUser, OptionalAuthUser}; use std::sync::Arc; diff --git a/src/interfaces/api/handlers/webdav_handler.rs b/src/interfaces/api/handlers/webdav_handler.rs index 1957bb46..03620572 100644 --- a/src/interfaces/api/handlers/webdav_handler.rs +++ b/src/interfaces/api/handlers/webdav_handler.rs @@ -28,8 +28,11 @@ use crate::common::di::AppState; use crate::infrastructure::services::path_resolver_service::ResolvedResource; use crate::interfaces::errors::AppError; use crate::interfaces::middleware::auth::CurrentUser; +use crate::application::services::file_retrieval_service::FileRetrievalService; +use crate::application::services::folder_service::FolderService; use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, percent_decode_str, utf8_percent_encode}; use std::sync::Arc; +use crate::application::ports::file_ports::{FileManagementUseCase, FileUploadUseCase}; /// Characters that MUST NOT be percent-encoded inside a URI path segment. /// RFC 3986 §3.3 pchar = unreserved / pct-encoded / sub-delims / ":" / "@" @@ -394,8 +397,8 @@ async fn build_streaming_propfind_response( depth: &str, base_href: &str, propfind_request: PropFindRequest, - folder_service: std::sync::Arc, - file_retrieval_service: std::sync::Arc, + folder_service: std::sync::Arc, + file_retrieval_service: std::sync::Arc, ) -> Result, AppError> { let depth = depth.to_string(); let base_href = base_href.to_string(); diff --git a/src/interfaces/api/handlers/wopi_handler.rs b/src/interfaces/api/handlers/wopi_handler.rs index f160b9b3..796a45ed 100644 --- a/src/interfaces/api/handlers/wopi_handler.rs +++ b/src/interfaces/api/handlers/wopi_handler.rs @@ -23,6 +23,7 @@ use std::sync::Arc; use crate::application::services::wopi_lock_service::WopiLockService; use crate::application::services::wopi_token_service::WopiTokenService; use crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService; +use crate::application::ports::file_ports::{FileRetrievalUseCase, FileUploadUseCase}; /// Shared state for WOPI handlers. #[derive(Clone)] diff --git a/src/interfaces/api/routes.rs b/src/interfaces/api/routes.rs index f459cf94..884f6a3e 100644 --- a/src/interfaces/api/routes.rs +++ b/src/interfaces/api/routes.rs @@ -8,6 +8,7 @@ use axum::{ use serde_json::json; use std::sync::Arc; use tower_http::{compression::CompressionLayer, trace::TraceLayer}; +use crate::application::services::batch_operations::BatchOperationService; /// Returns the application version from Cargo.toml (compile-time constant) async fn get_version() -> AxumJson { @@ -17,7 +18,6 @@ async fn get_version() -> AxumJson { })) } -use crate::application::services::batch_operations::BatchOperationService; use crate::interfaces::api::handlers::admin_handler; use crate::interfaces::api::handlers::batch_handler::{self, BatchHandlerState}; diff --git a/src/interfaces/middleware/auth.rs b/src/interfaces/middleware/auth.rs index 1e6e412f..85d53a38 100644 --- a/src/interfaces/middleware/auth.rs +++ b/src/interfaces/middleware/auth.rs @@ -11,6 +11,7 @@ use crate::common::di::AppState; // Re-export CurrentUser from application layer for use in handlers pub use crate::application::dtos::user_dto::CurrentUser; +use crate::application::ports::auth_ports::TokenServicePort; /// Marker inserted into request extensions when the user was authenticated /// via the `oxicloud_access` HttpOnly cookie rather than a Bearer/Basic header.