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<dyn Trait> with Arc<ConcreteType> 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
This commit is contained in:
Claude
2026-03-03 15:36:42 +00:00
parent efcf88c4d7
commit 1b49135ca9
101 changed files with 346 additions and 576 deletions
-7
View File
@@ -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<String, DomainError>;
@@ -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<User, DomainError>;
@@ -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<Session, DomainError>;
@@ -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<DeviceCode, DomainError>;
@@ -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<AppPassword, DomainError>;
-4
View File
@@ -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<CachedMetadataDto>;
@@ -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;
-3
View File
@@ -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(
-3
View File
@@ -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<Vec<(String, bool)>, DomainError>;
}
#[async_trait]
pub trait ContactUseCase: Send + Sync + 'static {
// Contact operations
async fn create_contact(&self, dto: CreateContactDto) -> Result<ContactDto, DomainError>;
@@ -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.
///
@@ -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(
-2
View File
@@ -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).
///
-3
View File
@@ -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<Vec<FavoriteItemDto>>;
@@ -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<Vec<FavoriteItemDto>>;
+6 -7
View File
@@ -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<FileDto, DomainError>;
@@ -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<dyn FileUploadUseCase>;
fn create_file_retrieval_use_case(&self) -> Arc<dyn FileRetrievalUseCase>;
fn create_file_management_use_case(&self) -> Arc<dyn FileManagementUseCase>;
fn create_file_upload_use_case(&self) -> Arc<FileUploadService>;
fn create_file_retrieval_use_case(&self) -> Arc<FileRetrievalService>;
fn create_file_management_use_case(&self) -> Arc<FileManagementService>;
}
-3
View File
@@ -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<FolderDto, DomainError>;
@@ -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.
///
-2
View File
@@ -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;
-3
View File
@@ -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<Vec<RecentItemDto>>;
-3
View File
@@ -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,
-8
View File
@@ -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<File, DomainError>;
@@ -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<StoragePath, DomainError>;
@@ -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<bool, DomainError>;
@@ -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<i64, DomainError>;
@@ -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<Value, DomainError>;
-2
View File
@@ -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;
-2
View File
@@ -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;
-2
View File
@@ -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<Vec<TrashedItemDto>>;
-2
View File
@@ -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).
///
@@ -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<dyn SettingsRepository>,
settings_repo: Arc<SettingsPgRepository>,
env_oidc_config: OidcConfig,
auth_app_service: Arc<AuthApplicationService>,
server_base_url: String,
@@ -22,7 +23,7 @@ pub struct AdminSettingsService {
impl AdminSettingsService {
pub fn new(
settings_repo: Arc<dyn SettingsRepository>,
settings_repo: Arc<SettingsPgRepository>,
env_oidc_config: OidcConfig,
auth_app_service: Arc<AuthApplicationService>,
server_base_url: String,
@@ -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<dyn AppPasswordStoragePort>,
hasher: Arc<dyn PasswordHasherPort>,
user_repo: Arc<dyn UserStoragePort>,
repo: Arc<AppPasswordPgRepository>,
hasher: Arc<Argon2PasswordHasher>,
user_repo: Arc<UserPgRepository>,
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<dyn AppPasswordStoragePort>,
hasher: Arc<dyn PasswordHasherPort>,
user_repo: Arc<dyn UserStoragePort>,
repo: Arc<AppPasswordPgRepository>,
hasher: Arc<Argon2PasswordHasher>,
user_repo: Arc<UserPgRepository>,
base_url: String,
) -> Self {
let auth_cache = Cache::builder()
@@ -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<Arc<dyn OidcServicePort>>,
service: Option<Arc<OidcService>>,
config: Option<OidcConfig>,
}
@@ -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<dyn UserStoragePort>,
session_storage: Arc<dyn SessionStoragePort>,
password_hasher: Arc<dyn PasswordHasherPort>,
token_service: Arc<dyn TokenServicePort>,
folder_service: Option<Arc<dyn FolderUseCase>>,
user_storage: Arc<UserPgRepository>,
session_storage: Arc<SessionPgRepository>,
password_hasher: Arc<Argon2PasswordHasher>,
token_service: Arc<JwtTokenService>,
folder_service: Option<Arc<FolderService>>,
/// Path to the storage directory, used for disk-space–aware quota calculation
storage_path: PathBuf,
oidc: RwLock<OidcState>,
@@ -58,10 +64,10 @@ pub struct AuthApplicationService {
impl AuthApplicationService {
pub fn new(
user_storage: Arc<dyn UserStoragePort>,
session_storage: Arc<dyn SessionStoragePort>,
password_hasher: Arc<dyn PasswordHasherPort>,
token_service: Arc<dyn TokenServicePort>,
user_storage: Arc<UserPgRepository>,
session_storage: Arc<SessionPgRepository>,
password_hasher: Arc<Argon2PasswordHasher>,
token_service: Arc<JwtTokenService>,
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<dyn FolderUseCase>) -> Self {
pub fn with_folder_service(mut self, folder_service: Arc<FolderService>) -> 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<dyn OidcServicePort>,
oidc_service: Arc<OidcService>,
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<dyn OidcServicePort>, oidc_config: OidcConfig) {
pub fn reload_oidc(&self, oidc_service: Arc<OidcService>, 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<Arc<dyn OidcServicePort>> {
pub fn oidc_service(&self) -> Option<Arc<OidcService>> {
let state = self.oidc.read().unwrap();
state.service.clone()
}
+11 -8
View File
@@ -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<dyn FileRetrievalUseCase>,
file_management: Arc<dyn FileManagementUseCase>,
file_retrieval: Arc<FileRetrievalService>,
file_management: Arc<FileManagementService>,
folder_service: Arc<FolderService>,
trash_service: Option<Arc<dyn TrashUseCase>>,
trash_service: Option<Arc<TrashService>>,
config: AppConfig,
}
impl BatchOperationService {
/// Creates a new instance of the batch operations service
pub fn new(
file_retrieval: Arc<dyn FileRetrievalUseCase>,
file_management: Arc<dyn FileManagementUseCase>,
file_retrieval: Arc<FileRetrievalService>,
file_management: Arc<FileManagementService>,
folder_service: Arc<FolderService>,
config: AppConfig,
) -> Self {
@@ -91,8 +94,8 @@ impl BatchOperationService {
/// Creates a new instance with default configuration
pub fn default(
file_retrieval: Arc<dyn FileRetrievalUseCase>,
file_management: Arc<dyn FileManagementUseCase>,
file_retrieval: Arc<FileRetrievalService>,
file_management: Arc<FileManagementService>,
folder_service: Arc<FolderService>,
) -> 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<dyn TrashUseCase>) -> Self {
pub fn with_trash_service(mut self, trash_service: Arc<TrashService>) -> Self {
self.trash_service = Some(trash_service);
self
}
+3 -4
View File
@@ -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<dyn CalendarStoragePort>,
calendar_storage: Arc<CalendarStorageAdapter>,
}
impl CalendarService {
pub fn new(calendar_storage: Arc<dyn CalendarStoragePort>) -> Self {
pub fn new(calendar_storage: Arc<CalendarStorageAdapter>) -> Self {
Self { calendar_storage }
}
}
#[async_trait]
impl CalendarUseCase for CalendarService {
async fn create_calendar(
&self,
+9 -10
View File
@@ -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<dyn AddressBookRepository>,
contact_repository: Arc<dyn ContactRepository>,
contact_group_repository: Arc<dyn ContactGroupRepository>,
address_book_repository: Arc<AddressBookPgRepository>,
contact_repository: Arc<ContactPgRepository>,
contact_group_repository: Arc<ContactGroupPgRepository>,
}
impl ContactService {
pub fn new(
address_book_repository: Arc<dyn AddressBookRepository>,
contact_repository: Arc<dyn ContactRepository>,
contact_group_repository: Arc<dyn ContactGroupRepository>,
address_book_repository: Arc<AddressBookPgRepository>,
contact_repository: Arc<ContactPgRepository>,
contact_group_repository: Arc<ContactGroupPgRepository>,
) -> 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<ContactDto, DomainError> {
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,
@@ -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<dyn DeviceCodeStoragePort>,
token_service: Arc<dyn TokenServicePort>,
user_storage: Arc<dyn UserStoragePort>,
session_storage: Arc<dyn SessionStoragePort>,
device_code_storage: Arc<DeviceCodePgRepository>,
token_service: Arc<JwtTokenService>,
user_storage: Arc<UserPgRepository>,
session_storage: Arc<SessionPgRepository>,
/// Base URL of the server (e.g. "https://cloud.example.com")
base_url: String,
}
impl DeviceAuthService {
pub fn new(
device_code_storage: Arc<dyn DeviceCodeStoragePort>,
token_service: Arc<dyn TokenServicePort>,
user_storage: Arc<dyn UserStoragePort>,
session_storage: Arc<dyn SessionStoragePort>,
device_code_storage: Arc<DeviceCodePgRepository>,
token_service: Arc<JwtTokenService>,
user_storage: Arc<UserPgRepository>,
session_storage: Arc<SessionPgRepository>,
base_url: String,
) -> Self {
Self {
@@ -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<dyn FavoritesRepositoryPort>,
repo: Arc<FavoritesPgRepository>,
}
impl FavoritesService {
/// Create a new FavoritesService with the given repository port
pub fn new(repo: Arc<dyn FavoritesRepositoryPort>) -> Self {
pub fn new(repo: Arc<FavoritesPgRepository>) -> Self {
Self { repo }
}
}
#[async_trait]
impl FavoritesUseCase for FavoritesService {
/// Get all favorites for a user
async fn get_favorites(&self, user_id: &str) -> Result<Vec<FavoriteItemDto>> {
@@ -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<dyn FileWritePort>,
trash_service: Option<Arc<dyn TrashUseCase>>,
file_repository: Arc<FileBlobWriteRepository>,
trash_service: Option<Arc<TrashService>>,
}
impl FileManagementService {
/// Creates a new FileManagementService.
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self {
pub fn new(file_repository: Arc<FileBlobWriteRepository>) -> 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<dyn FileWritePort>,
trash_service: Option<Arc<dyn TrashUseCase>>,
file_repository: Arc<FileBlobWriteRepository>,
trash_service: Option<Arc<TrashService>>,
) -> Self {
Self {
file_repository,
@@ -40,7 +41,6 @@ impl FileManagementService {
}
}
#[async_trait]
impl FileManagementUseCase for FileManagementService {
async fn move_file(
&self,
@@ -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<dyn FileReadPort>,
write_behind: Option<Arc<dyn WriteBehindCachePort>>,
content_cache: Option<Arc<dyn ContentCachePort>>,
transcode: Option<Arc<dyn ImageTranscodePort>>,
file_read: Arc<FileBlobReadRepository>,
content_cache: Option<Arc<FileContentCache>>,
transcode: Option<Arc<ImageTranscodeService>>,
}
impl FileRetrievalService {
/// Backward-compatible constructor (simple pass-through).
pub fn new(file_repository: Arc<dyn FileReadPort>) -> Self {
pub fn new(file_repository: Arc<FileBlobReadRepository>) -> 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<dyn FileReadPort>,
write_behind: Arc<dyn WriteBehindCachePort>,
content_cache: Arc<dyn ContentCachePort>,
transcode: Arc<dyn ImageTranscodePort>,
) -> 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<dyn FileReadPort>,
content_cache: Arc<dyn ContentCachePort>,
transcode: Arc<dyn ImageTranscodePort>,
file_read: Arc<FileBlobReadRepository>,
content_cache: Arc<FileContentCache>,
transcode: Arc<ImageTranscodeService>,
) -> 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<FileDto, DomainError> {
let file = self.file_read.get_file(id).await?;
@@ -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<String> {
/// 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<dyn FileWritePort>,
file_write: Arc<FileBlobWriteRepository>,
/// Read port — needed for WebDAV create_file / update_file
file_read: Option<Arc<dyn FileReadPort>>,
file_read: Option<Arc<FileBlobReadRepository>>,
/// Optional storage usage tracking
storage_usage_service:
Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>,
Option<Arc<StorageUsageService>>,
}
impl FileUploadService {
/// Constructor with write port only (minimal).
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self {
pub fn new(file_repository: Arc<FileBlobWriteRepository>) -> 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<dyn FileWritePort>,
file_read: Arc<dyn FileReadPort>,
file_write: Arc<FileBlobWriteRepository>,
file_read: Arc<FileBlobReadRepository>,
) -> 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<dyn crate::application::ports::storage_ports::StorageUsagePort>,
storage_usage_service: Arc<StorageUsageService>,
) -> 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.
///
@@ -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<dyn FileReadPort>,
file_write_repository: Arc<dyn FileWritePort>,
file_read_repository: Arc<FileBlobReadRepository>,
file_write_repository: Arc<FileBlobWriteRepository>,
}
impl AppFileUseCaseFactory {
/// Creates a new factory for file use cases
pub fn new(
file_read_repository: Arc<dyn FileReadPort>,
file_write_repository: Arc<dyn FileWritePort>,
file_read_repository: Arc<FileBlobReadRepository>,
file_write_repository: Arc<FileBlobWriteRepository>,
) -> Self {
Self {
file_read_repository,
@@ -28,15 +27,15 @@ impl AppFileUseCaseFactory {
}
impl FileUseCaseFactory for AppFileUseCaseFactory {
fn create_file_upload_use_case(&self) -> Arc<dyn FileUploadUseCase> {
fn create_file_upload_use_case(&self) -> Arc<FileUploadService> {
Arc::new(FileUploadService::new(self.file_write_repository.clone()))
}
fn create_file_retrieval_use_case(&self) -> Arc<dyn FileRetrievalUseCase> {
fn create_file_retrieval_use_case(&self) -> Arc<FileRetrievalService> {
Arc::new(FileRetrievalService::new(self.file_read_repository.clone()))
}
fn create_file_management_use_case(&self) -> Arc<dyn FileManagementUseCase> {
fn create_file_management_use_case(&self) -> Arc<FileManagementService> {
Arc::new(FileManagementService::new(
self.file_write_repository.clone(),
))
+4 -6
View File
@@ -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<dyn FolderStoragePort>,
folder_storage: Arc<FolderDbRepository>,
}
impl FolderService {
/// Creates a new folder service
pub fn new(folder_storage: Arc<dyn FolderStoragePort>) -> Self {
pub fn new(folder_storage: Arc<FolderDbRepository>) -> 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<FolderDto, DomainError> {
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<FolderDto, DomainError> {
@@ -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<dyn I18nService>,
i18n_service: Arc<FileSystemI18nService>,
}
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<String> {
Ok("DUMMY_TRANSLATION".to_string())
}
async fn load_translations(&self, _locale: Locale) -> I18nResult<()> {
Ok(())
}
async fn available_locales(&self) -> Vec<Locale> {
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<dyn I18nService>) -> Self {
pub fn new(i18n_service: Arc<FileSystemI18nService>) -> Self {
Self { i18n_service }
}
+3 -4
View File
@@ -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<dyn RecentItemsRepositoryPort>,
repo: Arc<RecentItemsPgRepository>,
max_recent_items: i32,
}
impl RecentService {
/// Create a new recent items service
pub fn new(repo: Arc<dyn RecentItemsRepositoryPort>, max_recent_items: i32) -> Self {
pub fn new(repo: Arc<RecentItemsPgRepository>, 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(
+7 -8
View File
@@ -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<dyn FileReadPort>,
file_repository: Arc<FileBlobReadRepository>,
/// Repository for folder operations
folder_repository: Arc<dyn FolderStoragePort>,
folder_repository: Arc<FolderDbRepository>,
/// Lock-free concurrent cache with automatic TTL and LRU eviction (moka).
/// Values are `Arc<SearchResultsDto>` 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<dyn FileReadPort>,
folder_repository: Arc<dyn FolderStoragePort>,
file_repository: Arc<FileBlobReadRepository>,
folder_repository: Arc<FolderDbRepository>,
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,
+13 -16
View File
@@ -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<AppConfig>,
share_repository: Arc<dyn ShareStoragePort>,
file_repository: Arc<dyn FileReadPort>,
folder_repository: Arc<dyn FolderStoragePort>,
password_hasher: Arc<dyn PasswordHasherPort>,
share_repository: Arc<SharePgRepository>,
file_repository: Arc<FileBlobReadRepository>,
folder_repository: Arc<FolderDbRepository>,
password_hasher: Arc<Argon2PasswordHasher>,
/// Bounds the number of in-flight Argon2 password hashes to avoid
/// saturating the blocking thread pool and consuming excessive RAM.
hash_semaphore: Arc<Semaphore>,
@@ -79,10 +82,10 @@ pub struct ShareService {
impl ShareService {
pub fn new(
config: Arc<AppConfig>,
share_repository: Arc<dyn ShareStoragePort>,
file_repository: Arc<dyn FileReadPort>,
folder_repository: Arc<dyn FolderStoragePort>,
password_hasher: Arc<dyn PasswordHasherPort>,
share_repository: Arc<SharePgRepository>,
file_repository: Arc<FileBlobReadRepository>,
folder_repository: Arc<FolderDbRepository>,
password_hasher: Arc<Argon2PasswordHasher>,
) -> 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<String, DomainError> {
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<Share, DomainError> {
let mut shares = self.shares.lock().unwrap();
@@ -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<PgPool>,
user_repository: Arc<dyn UserStoragePort>,
user_repository: Arc<UserPgRepository>,
}
impl StorageUsageService {
/// Creates a new storage usage service
pub fn new(pool: Arc<PgPool>, user_repository: Arc<dyn UserStoragePort>) -> Self {
pub fn new(pool: Arc<PgPool>, user_repository: Arc<UserPgRepository>) -> 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<i64, DomainError> {
StorageUsageService::update_user_storage_usage(self, user_id).await
+13 -11
View File
@@ -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<dyn TrashRepository>,
trash_repository: Arc<TrashDbRepository>,
/// Port for file read operations (get file metadata)
file_read_port: Arc<dyn FileReadPort>,
file_read_port: Arc<FileBlobReadRepository>,
/// Port for file write operations (trash, restore, delete)
file_write_port: Arc<dyn FileWritePort>,
file_write_port: Arc<FileBlobWriteRepository>,
/// Port for folder operations (get folder, trash, restore, delete)
folder_storage_port: Arc<dyn FolderStoragePort>,
folder_storage_port: Arc<FolderDbRepository>,
/// 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<dyn TrashRepository>,
file_read_port: Arc<dyn FileReadPort>,
file_write_port: Arc<dyn FileWritePort>,
folder_storage_port: Arc<dyn FolderStoragePort>,
trash_repository: Arc<TrashDbRepository>,
file_read_port: Arc<FileBlobReadRepository>,
file_write_port: Arc<FileBlobWriteRepository>,
folder_storage_port: Arc<FolderDbRepository>,
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<Vec<TrashedItemDto>> {
+10 -15
View File
@@ -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<File, DomainError> {
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<dyn FileReadPort>,
file_repo.clone() as Arc<dyn FileWritePort>,
file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<FileBlobWriteRepository>,
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<dyn FileReadPort>,
file_repo.clone() as Arc<dyn FileWritePort>,
file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<FileBlobWriteRepository>,
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<dyn FileReadPort>,
file_repo.clone() as Arc<dyn FileWritePort>,
file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<FileBlobWriteRepository>,
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<dyn FileReadPort>,
file_repo.clone() as Arc<dyn FileWritePort>,
file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<FileBlobWriteRepository>,
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<dyn FileReadPort>,
file_repo.clone() as Arc<dyn FileWritePort>,
file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<FileBlobWriteRepository>,
folder_repo.clone(),
30, // 30 days retention
);