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
Generated
-12
View File
@@ -129,17 +129,6 @@ dependencies = [
"syn", "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]] [[package]]
name = "async_zip" name = "async_zip"
version = "0.0.18" version = "0.0.18"
@@ -1819,7 +1808,6 @@ dependencies = [
"argon2", "argon2",
"async-compression", "async-compression",
"async-stream", "async-stream",
"async-trait",
"async_zip", "async_zip",
"axum", "axum",
"base64", "base64",
-1
View File
@@ -26,7 +26,6 @@ futures = "0.3.32"
async-stream = "0.3.6" async-stream = "0.3.6"
mime_guess = "2.0.5" mime_guess = "2.0.5"
uuid = { version = "1.21.0", features = ["v4", "serde"] } uuid = { version = "1.21.0", features = ["v4", "serde"] }
async-trait = "0.1.89"
thiserror = "2.0.18" thiserror = "2.0.18"
mockall = { version = "0.14.0", optional = true } mockall = { version = "0.14.0", optional = true }
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-rustls", "chrono", "uuid", "json"] } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-rustls", "chrono", "uuid", "json"] }
-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::device_code::DeviceCode;
use crate::domain::entities::session::Session; use crate::domain::entities::session::Session;
use crate::domain::entities::user::User; use crate::domain::entities::user::User;
use async_trait::async_trait;
// ============================================================================ // ============================================================================
// Cryptography Ports - Extracted from Domain to maintain Clean Architecture // 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 /// Methods are async because implementations (e.g. Argon2) are CPU-intensive
/// and must run on a blocking thread pool to avoid starving Tokio workers. /// and must run on a blocking thread pool to avoid starving Tokio workers.
#[async_trait]
pub trait PasswordHasherPort: Send + Sync + 'static { pub trait PasswordHasherPort: Send + Sync + 'static {
/// Hash a plain text password /// Hash a plain text password
async fn hash_password(&self, password: &str) -> Result<String, DomainError>; async fn hash_password(&self, password: &str) -> Result<String, DomainError>;
@@ -69,7 +67,6 @@ pub trait TokenServicePort: Send + Sync + 'static {
// Storage Ports // Storage Ports
// ============================================================================ // ============================================================================
#[async_trait]
pub trait UserStoragePort: Send + Sync + 'static { pub trait UserStoragePort: Send + Sync + 'static {
/// Creates a new user /// Creates a new user
async fn create_user(&self, user: User) -> Result<User, DomainError>; 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 /// Port for OIDC operations — implemented in infrastructure layer
#[async_trait]
pub trait OidcServicePort: Send + Sync + 'static { pub trait OidcServicePort: Send + Sync + 'static {
/// Get the authorization URL for redirecting the user to the IdP. /// Get the authorization URL for redirecting the user to the IdP.
/// Includes PKCE code_challenge (S256) and nonce for ID token binding. /// 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; fn provider_name(&self) -> &str;
} }
#[async_trait]
pub trait SessionStoragePort: Send + Sync + 'static { pub trait SessionStoragePort: Send + Sync + 'static {
/// Creates a new session /// Creates a new session
async fn create_session(&self, session: Session) -> Result<Session, DomainError>; 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) // Device Authorization Grant Port (RFC 8628)
// ============================================================================ // ============================================================================
#[async_trait]
pub trait DeviceCodeStoragePort: Send + Sync + 'static { pub trait DeviceCodeStoragePort: Send + Sync + 'static {
/// Persist a new device code flow /// Persist a new device code flow
async fn create_device_code(&self, device_code: DeviceCode) -> Result<DeviceCode, DomainError>; 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). /// Storage port for application-specific passwords (HTTP Basic Auth for DAV clients).
#[async_trait]
pub trait AppPasswordStoragePort: Send + Sync + 'static { pub trait AppPasswordStoragePort: Send + Sync + 'static {
/// Persist a new app password (hash already computed). /// Persist a new app password (hash already computed).
async fn create(&self, app_password: AppPassword) -> Result<AppPassword, DomainError>; async fn create(&self, app_password: AppPassword) -> Result<AppPassword, DomainError>;
-4
View File
@@ -9,7 +9,6 @@
//! implementation details. //! implementation details.
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
@@ -30,7 +29,6 @@ pub struct WriteBehindStatsDto {
/// Provides deferred write semantics: small files are held in memory /// Provides deferred write semantics: small files are held in memory
/// and the response is returned immediately, while actual disk writes /// and the response is returned immediately, while actual disk writes
/// happen asynchronously in the background. /// happen asynchronously in the background.
#[async_trait]
pub trait WriteBehindCachePort: Send + Sync + 'static { pub trait WriteBehindCachePort: Send + Sync + 'static {
/// Check if a file size is eligible for write-behind caching. /// Check if a file size is eligible for write-behind caching.
fn is_eligible_size(&self, size: usize) -> bool; 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 /// Provides fast lookups for existence, size, timestamps and MIME types
/// without hitting the filesystem on every request. /// without hitting the filesystem on every request.
#[async_trait]
pub trait MetadataCachePort: Send + Sync + 'static { pub trait MetadataCachePort: Send + Sync + 'static {
/// Get cached metadata for a path, or `None` on miss / expired. /// Get cached metadata for a path, or `None` on miss / expired.
async fn get_metadata(&self, path: &Path) -> Option<CachedMetadataDto>; 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 /// Implementations should use LRU eviction and respect size limits so that
/// the application layer never needs to know the concrete cache type. /// the application layer never needs to know the concrete cache type.
#[async_trait]
pub trait ContentCachePort: Send + Sync + 'static { pub trait ContentCachePort: Send + Sync + 'static {
/// Check whether a file of the given size should be cached. /// Check whether a file of the given size should be cached.
fn should_cache(&self, size: usize) -> bool; fn should_cache(&self, size: usize) -> bool;
-3
View File
@@ -3,11 +3,9 @@ use crate::application::dtos::calendar_dto::{
UpdateCalendarDto, UpdateEventDto, UpdateCalendarDto, UpdateEventDto,
}; };
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
/// Port for external calendar storage mechanisms /// Port for external calendar storage mechanisms
#[async_trait]
pub trait CalendarStoragePort: Send + Sync + 'static { pub trait CalendarStoragePort: Send + Sync + 'static {
// Calendar operations // Calendar operations
async fn create_calendar( async fn create_calendar(
@@ -111,7 +109,6 @@ pub trait CalendarStoragePort: Send + Sync + 'static {
/// All methods require an explicit `user_id` parameter for authorization. /// All methods require an explicit `user_id` parameter for authorization.
/// The CalDAV protocol handler extracts the user identity from JWT claims /// The CalDAV protocol handler extracts the user identity from JWT claims
/// and passes it through. /// and passes it through.
#[async_trait]
pub trait CalendarUseCase: Send + Sync + 'static { pub trait CalendarUseCase: Send + Sync + 'static {
// Calendar operations // Calendar operations
async fn create_calendar( async fn create_calendar(
-3
View File
@@ -7,11 +7,9 @@ use crate::application::dtos::contact_dto::{
GroupMembershipDto, UpdateContactDto, UpdateContactGroupDto, GroupMembershipDto, UpdateContactDto, UpdateContactGroupDto,
}; };
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
pub type CardDavRepositoryError = DomainError; pub type CardDavRepositoryError = DomainError;
#[async_trait]
pub trait AddressBookUseCase: Send + Sync + 'static { pub trait AddressBookUseCase: Send + Sync + 'static {
// Address Book operations // Address Book operations
async fn create_address_book( async fn create_address_book(
@@ -57,7 +55,6 @@ pub trait AddressBookUseCase: Send + Sync + 'static {
) -> Result<Vec<(String, bool)>, DomainError>; ) -> Result<Vec<(String, bool)>, DomainError>;
} }
#[async_trait]
pub trait ContactUseCase: Send + Sync + 'static { pub trait ContactUseCase: Send + Sync + 'static {
// Contact operations // Contact operations
async fn create_contact(&self, dto: CreateContactDto) -> Result<ContactDto, DomainError>; async fn create_contact(&self, dto: CreateContactDto) -> Result<ContactDto, DomainError>;
@@ -5,7 +5,6 @@
//! the specific upload implementation (TUS-like protocol, S3 multipart, etc.). //! the specific upload implementation (TUS-like protocol, S3 multipart, etc.).
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use serde::Serialize; use serde::Serialize;
use std::path::PathBuf; use std::path::PathBuf;
@@ -53,7 +52,6 @@ pub struct UploadStatusResponseDto {
/// Implementations manage upload sessions, chunk storage, reassembly, /// Implementations manage upload sessions, chunk storage, reassembly,
/// and cleanup, while the application layer only interacts through /// and cleanup, while the application layer only interacts through
/// this abstraction. /// this abstraction.
#[async_trait]
pub trait ChunkedUploadPort: Send + Sync + 'static { pub trait ChunkedUploadPort: Send + Sync + 'static {
/// Create a new upload session. /// Create a new upload session.
/// ///
@@ -5,7 +5,6 @@
//! compression implementations (gzip, zstd, etc.). //! compression implementations (gzip, zstd, etc.).
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
/// Compression level settings for file compression operations. /// Compression level settings for file compression operations.
/// ///
@@ -27,7 +26,6 @@ pub enum CompressionLevel {
/// Implementations of this trait provide the actual compression logic /// Implementations of this trait provide the actual compression logic
/// (e.g., gzip, zstd) while the application layer remains agnostic /// (e.g., gzip, zstd) while the application layer remains agnostic
/// of the specific algorithm used. /// of the specific algorithm used.
#[async_trait]
pub trait CompressionPort: Send + Sync + 'static { pub trait CompressionPort: Send + Sync + 'static {
/// Compress data in memory. /// Compress data in memory.
async fn compress_data( async fn compress_data(
-2
View File
@@ -5,7 +5,6 @@
//! content-addressable storage implementation. //! content-addressable storage implementation.
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures::Stream;
use serde::Serialize; use serde::Serialize;
@@ -92,7 +91,6 @@ pub struct DedupStatsDto {
/// Implementations store files by their content hash, eliminating /// Implementations store files by their content hash, eliminating
/// duplicate storage automatically. Multiple file references can /// duplicate storage automatically. Multiple file references can
/// point to the same physical blob. /// point to the same physical blob.
#[async_trait]
pub trait DedupPort: Send + Sync + 'static { pub trait DedupPort: Send + Sync + 'static {
/// Store content with deduplication (from bytes). /// Store content with deduplication (from bytes).
/// ///
-3
View File
@@ -1,9 +1,7 @@
use crate::application::dtos::favorites_dto::{BatchFavoritesResult, FavoriteItemDto}; use crate::application::dtos::favorites_dto::{BatchFavoritesResult, FavoriteItemDto};
use crate::common::errors::Result; use crate::common::errors::Result;
use async_trait::async_trait;
/// Defines operations for managing user favorites /// Defines operations for managing user favorites
#[async_trait]
pub trait FavoritesUseCase: Send + Sync { pub trait FavoritesUseCase: Send + Sync {
/// Get all favorites for a user /// Get all favorites for a user
async fn get_favorites(&self, user_id: &str) -> Result<Vec<FavoriteItemDto>>; 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 /// Application services depend on this trait instead of
/// accessing `PgPool` directly. The concrete implementation /// accessing `PgPool` directly. The concrete implementation
/// lives in `infrastructure::repositories::pg`. /// lives in `infrastructure::repositories::pg`.
#[async_trait]
pub trait FavoritesRepositoryPort: Send + Sync + 'static { pub trait FavoritesRepositoryPort: Send + Sync + 'static {
/// Gets all favorites for a user. /// Gets all favorites for a user.
async fn get_favorites(&self, user_id: &str) -> Result<Vec<FavoriteItemDto>>; 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 bytes::Bytes;
use futures::Stream; use futures::Stream;
use std::path::Path; use std::path::Path;
@@ -8,6 +7,9 @@ use std::sync::Arc;
use crate::application::dtos::file_dto::FileDto; use crate::application::dtos::file_dto::FileDto;
use crate::application::ports::storage_ports::CopyFolderTreeResult; use crate::application::ports::storage_ports::CopyFolderTreeResult;
use crate::common::errors::DomainError; 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 // Upload port
@@ -24,7 +26,6 @@ use crate::common::errors::DomainError;
/// - Chunked uploads: chunks already on disk → `upload_file_from_path` /// - Chunked uploads: chunks already on disk → `upload_file_from_path`
/// - WebDAV PUT (new): handler streams to temp file → `update_file_streaming` /// - WebDAV PUT (new): handler streams to temp file → `update_file_streaming`
/// - WebDAV PUT (small/compat): `create_file` / `update_file` spool internally /// - WebDAV PUT (small/compat): `create_file` / `update_file` spool internally
#[async_trait]
pub trait FileUploadUseCase: Send + Sync + 'static { pub trait FileUploadUseCase: Send + Sync + 'static {
/// Upload from a temp file already on disk (true streaming, ~256 KB RAM). /// 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 /// Primary port for file retrieval operations
#[async_trait]
pub trait FileRetrievalUseCase: Send + Sync + 'static { pub trait FileRetrievalUseCase: Send + Sync + 'static {
/// Gets a file by its ID /// Gets a file by its ID
async fn get_file(&self, id: &str) -> Result<FileDto, DomainError>; 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 /// Primary port for file management operations
#[async_trait]
pub trait FileManagementUseCase: Send + Sync + 'static { pub trait FileManagementUseCase: Send + Sync + 'static {
/// Moves a file to another folder /// Moves a file to another folder
async fn move_file( async fn move_file(
@@ -241,7 +240,7 @@ pub trait FileManagementUseCase: Send + Sync + 'static {
/// Factory for creating file use case implementations /// Factory for creating file use case implementations
pub trait FileUseCaseFactory: Send + Sync + 'static { pub trait FileUseCaseFactory: Send + Sync + 'static {
fn create_file_upload_use_case(&self) -> Arc<dyn FileUploadUseCase>; fn create_file_upload_use_case(&self) -> Arc<FileUploadService>;
fn create_file_retrieval_use_case(&self) -> Arc<dyn FileRetrievalUseCase>; fn create_file_retrieval_use_case(&self) -> Arc<FileRetrievalService>;
fn create_file_management_use_case(&self) -> Arc<dyn FileManagementUseCase>; 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 std::sync::Arc;
use crate::application::dtos::folder_dto::{ use crate::application::dtos::folder_dto::{
@@ -10,7 +9,6 @@ use crate::application::dtos::search_dto::{
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
/// Primary port for folder operations /// Primary port for folder operations
#[async_trait]
pub trait FolderUseCase: Send + Sync + 'static { pub trait FolderUseCase: Send + Sync + 'static {
/// Creates a new folder /// Creates a new folder
async fn create_folder(&self, dto: CreateFolderDto) -> Result<FolderDto, DomainError>; 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) * All search processing (filtering, scoring, sorting, categorization)
* is handled server-side in Rust for maximum efficiency. * is handled server-side in Rust for maximum efficiency.
*/ */
#[async_trait]
pub trait SearchUseCase: Send + Sync + 'static { pub trait SearchUseCase: Send + Sync + 'static {
/// Performs a full search based on the specified criteria. /// 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 std::path::PathBuf;
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
@@ -10,7 +9,6 @@ pub use crate::domain::repositories::folder_repository::FolderRepository;
use super::storage_ports::{FileReadPort, FileWritePort}; use super::storage_ports::{FileReadPort, FileWritePort};
/// Secondary port for storage operations /// Secondary port for storage operations
#[async_trait]
pub trait StoragePort: Send + Sync + 'static { pub trait StoragePort: Send + Sync + 'static {
/// Resolves a domain path to a physical path /// Resolves a domain path to a physical path
fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf; fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf;
-3
View File
@@ -1,9 +1,7 @@
use crate::application::dtos::recent_dto::RecentItemDto; use crate::application::dtos::recent_dto::RecentItemDto;
use crate::common::errors::Result; use crate::common::errors::Result;
use async_trait::async_trait;
/// Defines operations for managing user recent items /// Defines operations for managing user recent items
#[async_trait]
pub trait RecentItemsUseCase: Send + Sync { pub trait RecentItemsUseCase: Send + Sync {
/// Get all recent items for a user /// Get all recent items for a user
async fn get_recent_items( 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 /// Abstracts access to the `auth.user_recent_files` table so that
/// `RecentService` does not depend directly on `PgPool`. /// `RecentService` does not depend directly on `PgPool`.
#[async_trait]
pub trait RecentItemsRepositoryPort: Send + Sync + 'static { pub trait RecentItemsRepositoryPort: Send + Sync + 'static {
/// Gets the latest recent items for a user (ordered by date desc). /// 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>>; 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::{ use crate::{
application::dtos::{ application::dtos::{
@@ -9,7 +8,6 @@ use crate::{
domain::entities::share::ShareItemType, domain::entities::share::ShareItemType,
}; };
#[async_trait]
pub trait ShareUseCase: Send + Sync + 'static { pub trait ShareUseCase: Send + Sync + 'static {
/// Create a new shared link for a file or folder /// Create a new shared link for a file or folder
async fn create_shared_link( 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 fn register_shared_link_access(&self, token: &str) -> Result<(), DomainError>;
} }
#[async_trait]
pub trait ShareStoragePort: Send + Sync + 'static { pub trait ShareStoragePort: Send + Sync + 'static {
async fn save_share( async fn save_share(
&self, &self,
-8
View File
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures::Stream;
use serde_json::Value; 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: /// Encapsulates every operation that queries state without modifying it:
/// get, list, content, stream, mmap, range, path resolution. /// get, list, content, stream, mmap, range, path resolution.
#[async_trait]
pub trait FileReadPort: Send + Sync + 'static { pub trait FileReadPort: Send + Sync + 'static {
/// Gets a file by its ID. /// Gets a file by its ID.
async fn get_file(&self, id: &str) -> Result<File, DomainError>; async fn get_file(&self, id: &str) -> Result<File, DomainError>;
@@ -199,7 +197,6 @@ pub struct CopyFolderTreeResult {
/// ///
/// Covers: upload (buffered + streaming), move, delete, update, /// Covers: upload (buffered + streaming), move, delete, update,
/// and deferred registration for the write-behind cache. /// and deferred registration for the write-behind cache.
#[async_trait]
pub trait FileWritePort: Send + Sync + 'static { pub trait FileWritePort: Send + Sync + 'static {
/// Streaming upload — saves a file from a temp file already on disk. /// 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 /// Secondary port for file path resolution
#[async_trait]
pub trait FilePathResolutionPort: Send + Sync + 'static { pub trait FilePathResolutionPort: Send + Sync + 'static {
/// Gets the storage path of a file /// Gets the storage path of a file
async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError>; 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 /// Secondary port for file/directory existence verification
#[async_trait]
pub trait StorageVerificationPort: Send + Sync + 'static { pub trait StorageVerificationPort: Send + Sync + 'static {
/// Checks whether a file exists at the given path /// Checks whether a file exists at the given path
async fn file_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>; 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 /// Secondary port for directory management
#[async_trait]
pub trait DirectoryManagementPort: Send + Sync + 'static { pub trait DirectoryManagementPort: Send + Sync + 'static {
/// Creates directories if they do not exist /// Creates directories if they do not exist
async fn ensure_directory(&self, storage_path: &StoragePath) -> Result<(), DomainError>; async fn ensure_directory(&self, storage_path: &StoragePath) -> Result<(), DomainError>;
} }
/// Secondary port for storage usage management /// Secondary port for storage usage management
#[async_trait]
pub trait StorageUsagePort: Send + Sync + 'static { pub trait StorageUsagePort: Send + Sync + 'static {
/// Updates storage usage statistics for a user /// Updates storage usage statistics for a user
async fn update_user_storage_usage(&self, user_id: &str) -> Result<i64, DomainError>; 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 /// Generic storage service interface for calendar and contact services
#[async_trait]
pub trait StorageUseCase: Send + Sync + 'static { pub trait StorageUseCase: Send + Sync + 'static {
/// Handle a request with the specified action and parameters /// Handle a request with the specified action and parameters
async fn handle_request(&self, action: &str, params: Value) -> Result<Value, DomainError>; async fn handle_request(&self, action: &str, params: Value) -> Result<Value, DomainError>;
-2
View File
@@ -5,7 +5,6 @@
//! image processing implementations. //! image processing implementations.
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
@@ -63,7 +62,6 @@ pub struct ThumbnailStatsDto {
/// Implementations handle the actual image processing, caching, /// Implementations handle the actual image processing, caching,
/// and storage of thumbnails, while the application layer only /// and storage of thumbnails, while the application layer only
/// interacts through this abstraction. /// interacts through this abstraction.
#[async_trait]
pub trait ThumbnailPort: Send + Sync + 'static { pub trait ThumbnailPort: Send + Sync + 'static {
/// Check if a file is an image that can have thumbnails. /// Check if a file is an image that can have thumbnails.
fn is_supported_image(&self, mime_type: &str) -> bool; fn is_supported_image(&self, mime_type: &str) -> bool;
-2
View File
@@ -5,7 +5,6 @@
//! independent of specific image processing implementations. //! independent of specific image processing implementations.
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
/// Supported output formats for image transcoding. /// Supported output formats for image transcoding.
@@ -74,7 +73,6 @@ pub struct TranscodeStatsDto {
/// Implementations handle the actual image conversion, caching, /// Implementations handle the actual image conversion, caching,
/// and format detection, while the application layer only interacts /// and format detection, while the application layer only interacts
/// through this abstraction. /// through this abstraction.
#[async_trait]
pub trait ImageTranscodePort: Send + Sync + 'static { pub trait ImageTranscodePort: Send + Sync + 'static {
/// Check if a MIME type can be transcoded. /// Check if a MIME type can be transcoded.
fn can_transcode(&self, mime_type: &str) -> bool; 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::application::dtos::trash_dto::TrashedItemDto;
use crate::common::errors::Result; use crate::common::errors::Result;
/// Port for trash-related use cases /// Port for trash-related use cases
#[async_trait]
pub trait TrashUseCase: Send + Sync { pub trait TrashUseCase: Send + Sync {
/// List items in the user's trash /// List items in the user's trash
async fn get_trash_items(&self, user_id: &str) -> Result<Vec<TrashedItemDto>>; async fn get_trash_items(&self, user_id: &str) -> Result<Vec<TrashedItemDto>>;
-2
View File
@@ -5,14 +5,12 @@
//! implementation details. //! implementation details.
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
/// Port for ZIP archive operations. /// Port for ZIP archive operations.
/// ///
/// Implementations handle the actual ZIP file creation, compression, /// Implementations handle the actual ZIP file creation, compression,
/// and recursive folder traversal. /// and recursive folder traversal.
#[async_trait]
pub trait ZipPort: Send + Sync + 'static { pub trait ZipPort: Send + Sync + 'static {
/// Create a ZIP archive containing the contents of a folder (recursively). /// 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::common::errors::{DomainError, ErrorKind};
use crate::domain::repositories::settings_repository::SettingsRepository; use crate::domain::repositories::settings_repository::SettingsRepository;
use crate::infrastructure::services::oidc_service::OidcService; use crate::infrastructure::services::oidc_service::OidcService;
use crate::infrastructure::repositories::pg::SettingsPgRepository;
/// Admin settings service — manages platform configuration in the database. /// Admin settings service — manages platform configuration in the database.
/// ///
/// Configuration priority: **env vars > DB settings > defaults**. /// Configuration priority: **env vars > DB settings > defaults**.
/// Supports hot-reloading OIDC configuration without server restart. /// Supports hot-reloading OIDC configuration without server restart.
pub struct AdminSettingsService { pub struct AdminSettingsService {
settings_repo: Arc<dyn SettingsRepository>, settings_repo: Arc<SettingsPgRepository>,
env_oidc_config: OidcConfig, env_oidc_config: OidcConfig,
auth_app_service: Arc<AuthApplicationService>, auth_app_service: Arc<AuthApplicationService>,
server_base_url: String, server_base_url: String,
@@ -22,7 +23,7 @@ pub struct AdminSettingsService {
impl AdminSettingsService { impl AdminSettingsService {
pub fn new( pub fn new(
settings_repo: Arc<dyn SettingsRepository>, settings_repo: Arc<SettingsPgRepository>,
env_oidc_config: OidcConfig, env_oidc_config: OidcConfig,
auth_app_service: Arc<AuthApplicationService>, auth_app_service: Arc<AuthApplicationService>,
server_base_url: String, server_base_url: String,
@@ -13,6 +13,9 @@ use chrono::{Duration, Utc};
use moka::future::Cache; use moka::future::Cache;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration as StdDuration; 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). /// App password token length (32 random alphanumeric chars after prefix).
const TOKEN_LENGTH: usize = 32; const TOKEN_LENGTH: usize = 32;
@@ -39,9 +42,9 @@ struct CachedBasicAuthResult {
} }
pub struct AppPasswordService { pub struct AppPasswordService {
repo: Arc<dyn AppPasswordStoragePort>, repo: Arc<AppPasswordPgRepository>,
hasher: Arc<dyn PasswordHasherPort>, hasher: Arc<Argon2PasswordHasher>,
user_repo: Arc<dyn UserStoragePort>, user_repo: Arc<UserPgRepository>,
base_url: String, base_url: String,
/// In-memory cache of successful Basic Auth verifications. /// In-memory cache of successful Basic Auth verifications.
@@ -59,9 +62,9 @@ pub struct AppPasswordService {
impl AppPasswordService { impl AppPasswordService {
pub fn new( pub fn new(
repo: Arc<dyn AppPasswordStoragePort>, repo: Arc<AppPasswordPgRepository>,
hasher: Arc<dyn PasswordHasherPort>, hasher: Arc<Argon2PasswordHasher>,
user_repo: Arc<dyn UserStoragePort>, user_repo: Arc<UserPgRepository>,
base_url: String, base_url: String,
) -> Self { ) -> Self {
let auth_cache = Cache::builder() let auth_cache = Cache::builder()
@@ -15,6 +15,12 @@ use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock; use std::sync::RwLock;
use std::time::Duration; 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) /// Tracks a pending OIDC authorization flow (CSRF + PKCE + nonce)
#[derive(Clone)] #[derive(Clone)]
@@ -31,7 +37,7 @@ struct PendingOidcToken {
/// Interior state for OIDC — protected by RwLock for hot-reload. /// Interior state for OIDC — protected by RwLock for hot-reload.
struct OidcState { struct OidcState {
service: Option<Arc<dyn OidcServicePort>>, service: Option<Arc<OidcService>>,
config: Option<OidcConfig>, 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 const DEFAULT_USER_QUOTA: i64 = 1_073_741_824; // 1 GB
pub struct AuthApplicationService { pub struct AuthApplicationService {
user_storage: Arc<dyn UserStoragePort>, user_storage: Arc<UserPgRepository>,
session_storage: Arc<dyn SessionStoragePort>, session_storage: Arc<SessionPgRepository>,
password_hasher: Arc<dyn PasswordHasherPort>, password_hasher: Arc<Argon2PasswordHasher>,
token_service: Arc<dyn TokenServicePort>, token_service: Arc<JwtTokenService>,
folder_service: Option<Arc<dyn FolderUseCase>>, folder_service: Option<Arc<FolderService>>,
/// Path to the storage directory, used for disk-space–aware quota calculation /// Path to the storage directory, used for disk-space–aware quota calculation
storage_path: PathBuf, storage_path: PathBuf,
oidc: RwLock<OidcState>, oidc: RwLock<OidcState>,
@@ -58,10 +64,10 @@ pub struct AuthApplicationService {
impl AuthApplicationService { impl AuthApplicationService {
pub fn new( pub fn new(
user_storage: Arc<dyn UserStoragePort>, user_storage: Arc<UserPgRepository>,
session_storage: Arc<dyn SessionStoragePort>, session_storage: Arc<SessionPgRepository>,
password_hasher: Arc<dyn PasswordHasherPort>, password_hasher: Arc<Argon2PasswordHasher>,
token_service: Arc<dyn TokenServicePort>, token_service: Arc<JwtTokenService>,
storage_path: PathBuf, storage_path: PathBuf,
) -> Self { ) -> Self {
Self { Self {
@@ -133,7 +139,7 @@ impl AuthApplicationService {
} }
/// Configures the folder service, needed to create personal folders /// 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.folder_service = Some(folder_service);
self self
} }
@@ -141,7 +147,7 @@ impl AuthApplicationService {
/// Configures the OIDC service /// Configures the OIDC service
pub fn with_oidc( pub fn with_oidc(
self, self,
oidc_service: Arc<dyn OidcServicePort>, oidc_service: Arc<OidcService>,
oidc_config: OidcConfig, oidc_config: OidcConfig,
) -> Self { ) -> Self {
{ {
@@ -153,7 +159,7 @@ impl AuthApplicationService {
} }
/// Hot-reload OIDC configuration at runtime (called from admin settings service) /// 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(); let mut state = self.oidc.write().unwrap();
state.service = Some(oidc_service); state.service = Some(oidc_service);
state.config = Some(oidc_config); state.config = Some(oidc_config);
@@ -188,7 +194,7 @@ impl AuthApplicationService {
} }
/// Returns an Arc clone of the OIDC service if available /// 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(); let state = self.oidc.read().unwrap();
state.service.clone() 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::application::services::folder_service::FolderService;
use crate::common::config::AppConfig; use crate::common::config::AppConfig;
use crate::common::errors::DomainError; 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 /// Specific errors for batch operations
#[derive(Debug, Error)] #[derive(Debug, Error)]
@@ -65,18 +68,18 @@ pub struct BatchStats {
/// Batch operations service /// Batch operations service
pub struct BatchOperationService { pub struct BatchOperationService {
file_retrieval: Arc<dyn FileRetrievalUseCase>, file_retrieval: Arc<FileRetrievalService>,
file_management: Arc<dyn FileManagementUseCase>, file_management: Arc<FileManagementService>,
folder_service: Arc<FolderService>, folder_service: Arc<FolderService>,
trash_service: Option<Arc<dyn TrashUseCase>>, trash_service: Option<Arc<TrashService>>,
config: AppConfig, config: AppConfig,
} }
impl BatchOperationService { impl BatchOperationService {
/// Creates a new instance of the batch operations service /// Creates a new instance of the batch operations service
pub fn new( pub fn new(
file_retrieval: Arc<dyn FileRetrievalUseCase>, file_retrieval: Arc<FileRetrievalService>,
file_management: Arc<dyn FileManagementUseCase>, file_management: Arc<FileManagementService>,
folder_service: Arc<FolderService>, folder_service: Arc<FolderService>,
config: AppConfig, config: AppConfig,
) -> Self { ) -> Self {
@@ -91,8 +94,8 @@ impl BatchOperationService {
/// Creates a new instance with default configuration /// Creates a new instance with default configuration
pub fn default( pub fn default(
file_retrieval: Arc<dyn FileRetrievalUseCase>, file_retrieval: Arc<FileRetrievalService>,
file_management: Arc<dyn FileManagementUseCase>, file_management: Arc<FileManagementService>,
folder_service: Arc<FolderService>, folder_service: Arc<FolderService>,
) -> Self { ) -> Self {
Self::new( Self::new(
@@ -104,7 +107,7 @@ impl BatchOperationService {
} }
/// Set the optional trash service (enables batch trash operations) /// 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.trash_service = Some(trash_service);
self self
} }
+3 -4
View File
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::sync::Arc; use std::sync::Arc;
@@ -8,18 +7,18 @@ use crate::application::dtos::calendar_dto::{
}; };
use crate::application::ports::calendar_ports::{CalendarStoragePort, CalendarUseCase}; use crate::application::ports::calendar_ports::{CalendarStoragePort, CalendarUseCase};
use crate::common::errors::{DomainError, ErrorKind}; use crate::common::errors::{DomainError, ErrorKind};
use crate::infrastructure::adapters::calendar_storage_adapter::CalendarStorageAdapter;
pub struct CalendarService { pub struct CalendarService {
calendar_storage: Arc<dyn CalendarStoragePort>, calendar_storage: Arc<CalendarStorageAdapter>,
} }
impl CalendarService { impl CalendarService {
pub fn new(calendar_storage: Arc<dyn CalendarStoragePort>) -> Self { pub fn new(calendar_storage: Arc<CalendarStorageAdapter>) -> Self {
Self { calendar_storage } Self { calendar_storage }
} }
} }
#[async_trait]
impl CalendarUseCase for CalendarService { impl CalendarUseCase for CalendarService {
async fn create_calendar( async fn create_calendar(
&self, &self,
+9 -10
View File
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use sqlx::types::Uuid; use sqlx::types::Uuid;
use std::sync::Arc; 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::entities::contact::{Address, AddressBook, Contact, ContactGroup, Email, Phone};
use crate::domain::repositories::address_book_repository::AddressBookRepository; use crate::domain::repositories::address_book_repository::AddressBookRepository;
use crate::domain::repositories::contact_repository::{ContactGroupRepository, ContactRepository}; 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 { pub struct ContactService {
address_book_repository: Arc<dyn AddressBookRepository>, address_book_repository: Arc<AddressBookPgRepository>,
contact_repository: Arc<dyn ContactRepository>, contact_repository: Arc<ContactPgRepository>,
contact_group_repository: Arc<dyn ContactGroupRepository>, contact_group_repository: Arc<ContactGroupPgRepository>,
} }
impl ContactService { impl ContactService {
pub fn new( pub fn new(
address_book_repository: Arc<dyn AddressBookRepository>, address_book_repository: Arc<AddressBookPgRepository>,
contact_repository: Arc<dyn ContactRepository>, contact_repository: Arc<ContactPgRepository>,
contact_group_repository: Arc<dyn ContactGroupRepository>, contact_group_repository: Arc<ContactGroupPgRepository>,
) -> Self { ) -> Self {
Self { Self {
address_book_repository, address_book_repository,
@@ -264,7 +266,6 @@ impl ContactService {
} }
} }
#[async_trait]
impl AddressBookUseCase for ContactService { impl AddressBookUseCase for ContactService {
async fn create_address_book( async fn create_address_book(
&self, &self,
@@ -514,7 +515,6 @@ impl AddressBookUseCase for ContactService {
} }
} }
#[async_trait]
impl ContactUseCase for ContactService { impl ContactUseCase for ContactService {
async fn create_contact(&self, dto: CreateContactDto) -> Result<ContactDto, DomainError> { async fn create_contact(&self, dto: CreateContactDto) -> Result<ContactDto, DomainError> {
let address_book_id = Uuid::parse_str(&dto.address_book_id) 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 { impl StorageUseCase for ContactService {
async fn handle_request( async fn handle_request(
&self, &self,
@@ -18,6 +18,10 @@ use crate::application::ports::auth_ports::{
use crate::common::errors::{DomainError, ErrorKind}; use crate::common::errors::{DomainError, ErrorKind};
use crate::domain::entities::device_code::{DeviceCode, DeviceCodeStatus}; use crate::domain::entities::device_code::{DeviceCode, DeviceCodeStatus};
use crate::domain::entities::session::Session; 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). /// Default device code lifetime: 15 minutes (RFC 8628 recommends 5-30 min).
const DEVICE_CODE_LIFETIME_SECS: i64 = 900; 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; const USER_CODE_DIGIT_LEN: usize = 4;
pub struct DeviceAuthService { pub struct DeviceAuthService {
device_code_storage: Arc<dyn DeviceCodeStoragePort>, device_code_storage: Arc<DeviceCodePgRepository>,
token_service: Arc<dyn TokenServicePort>, token_service: Arc<JwtTokenService>,
user_storage: Arc<dyn UserStoragePort>, user_storage: Arc<UserPgRepository>,
session_storage: Arc<dyn SessionStoragePort>, session_storage: Arc<SessionPgRepository>,
/// Base URL of the server (e.g. "https://cloud.example.com") /// Base URL of the server (e.g. "https://cloud.example.com")
base_url: String, base_url: String,
} }
impl DeviceAuthService { impl DeviceAuthService {
pub fn new( pub fn new(
device_code_storage: Arc<dyn DeviceCodeStoragePort>, device_code_storage: Arc<DeviceCodePgRepository>,
token_service: Arc<dyn TokenServicePort>, token_service: Arc<JwtTokenService>,
user_storage: Arc<dyn UserStoragePort>, user_storage: Arc<UserPgRepository>,
session_storage: Arc<dyn SessionStoragePort>, session_storage: Arc<SessionPgRepository>,
base_url: String, base_url: String,
) -> Self { ) -> Self {
Self { Self {
@@ -3,26 +3,25 @@ use crate::application::dtos::favorites_dto::{
}; };
use crate::application::ports::favorites_ports::{FavoritesRepositoryPort, FavoritesUseCase}; use crate::application::ports::favorites_ports::{FavoritesRepositoryPort, FavoritesUseCase};
use crate::common::errors::{DomainError, ErrorKind, Result}; use crate::common::errors::{DomainError, ErrorKind, Result};
use async_trait::async_trait;
use std::sync::Arc; use std::sync::Arc;
use tracing::info; use tracing::info;
use crate::infrastructure::repositories::pg::FavoritesPgRepository;
/// Implementation of the FavoritesUseCase for managing user favorites. /// Implementation of the FavoritesUseCase for managing user favorites.
/// ///
/// Depends on `FavoritesRepositoryPort` (outbound port) instead of /// Depends on `FavoritesRepositoryPort` (outbound port) instead of
/// accessing the database directly, following hexagonal architecture. /// accessing the database directly, following hexagonal architecture.
pub struct FavoritesService { pub struct FavoritesService {
repo: Arc<dyn FavoritesRepositoryPort>, repo: Arc<FavoritesPgRepository>,
} }
impl FavoritesService { impl FavoritesService {
/// Create a new FavoritesService with the given repository port /// 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 } Self { repo }
} }
} }
#[async_trait]
impl FavoritesUseCase for FavoritesService { impl FavoritesUseCase for FavoritesService {
/// Get all favorites for a user /// Get all favorites for a user
async fn get_favorites(&self, user_id: &str) -> Result<Vec<FavoriteItemDto>> { 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 std::sync::Arc;
use crate::application::dtos::file_dto::FileDto; 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::application::ports::trash_ports::TrashUseCase;
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use tracing::{error, info, warn}; 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). /// 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 /// This service only orchestrates trash vs. permanent delete — it never
/// touches ref_count directly. /// touches ref_count directly.
pub struct FileManagementService { pub struct FileManagementService {
file_repository: Arc<dyn FileWritePort>, file_repository: Arc<FileBlobWriteRepository>,
trash_service: Option<Arc<dyn TrashUseCase>>, trash_service: Option<Arc<TrashService>>,
} }
impl FileManagementService { impl FileManagementService {
/// Creates a new FileManagementService. /// Creates a new FileManagementService.
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self { pub fn new(file_repository: Arc<FileBlobWriteRepository>) -> Self {
Self { Self {
file_repository, file_repository,
trash_service: None, trash_service: None,
@@ -30,8 +31,8 @@ impl FileManagementService {
/// Creates a FileManagementService with a trash service. /// Creates a FileManagementService with a trash service.
pub fn with_trash( pub fn with_trash(
file_repository: Arc<dyn FileWritePort>, file_repository: Arc<FileBlobWriteRepository>,
trash_service: Option<Arc<dyn TrashUseCase>>, trash_service: Option<Arc<TrashService>>,
) -> Self { ) -> Self {
Self { Self {
file_repository, file_repository,
@@ -40,7 +41,6 @@ impl FileManagementService {
} }
} }
#[async_trait]
impl FileManagementUseCase for FileManagementService { impl FileManagementUseCase for FileManagementService {
async fn move_file( async fn move_file(
&self, &self,
@@ -1,16 +1,16 @@
use async_trait::async_trait;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use crate::application::dtos::file_dto::FileDto; 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::file_ports::{FileRetrievalUseCase, OptimizedFileContent};
use crate::application::ports::storage_ports::FileReadPort; use crate::application::ports::storage_ports::FileReadPort;
use crate::application::ports::transcode_ports::{ImageTranscodePort, OutputFormat};
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use tracing::{debug, info}; 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). /// Threshold below which files are served from RAM cache (10 MB).
const CACHE_THRESHOLD: u64 = 10 * 1024 * 1024; 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 2: Memory-mapped I/O (10–100 MB)
/// - Tier 3: Streaming (≥100 MB) /// - Tier 3: Streaming (≥100 MB)
pub struct FileRetrievalService { pub struct FileRetrievalService {
file_read: Arc<dyn FileReadPort>, file_read: Arc<FileBlobReadRepository>,
write_behind: Option<Arc<dyn WriteBehindCachePort>>, content_cache: Option<Arc<FileContentCache>>,
content_cache: Option<Arc<dyn ContentCachePort>>, transcode: Option<Arc<ImageTranscodeService>>,
transcode: Option<Arc<dyn ImageTranscodePort>>,
} }
impl FileRetrievalService { impl FileRetrievalService {
/// Backward-compatible constructor (simple pass-through). /// Backward-compatible constructor (simple pass-through).
pub fn new(file_repository: Arc<dyn FileReadPort>) -> Self { pub fn new(file_repository: Arc<FileBlobReadRepository>) -> Self {
Self { Self {
file_read: file_repository, file_read: file_repository,
write_behind: None,
content_cache: None, content_cache: None,
transcode: 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. /// Constructor for blob-storage model: read + content cache + transcode.
/// No write-behind needed — dedup handled at the repository layer.
pub fn new_with_cache( pub fn new_with_cache(
file_read: Arc<dyn FileReadPort>, file_read: Arc<FileBlobReadRepository>,
content_cache: Arc<dyn ContentCachePort>, content_cache: Arc<FileContentCache>,
transcode: Arc<dyn ImageTranscodePort>, transcode: Arc<ImageTranscodeService>,
) -> Self { ) -> Self {
Self { Self {
file_read, file_read,
write_behind: None,
content_cache: Some(content_cache), content_cache: Some(content_cache),
transcode: Some(transcode), transcode: Some(transcode),
} }
@@ -85,7 +66,7 @@ impl FileRetrievalService {
return None; return None;
} }
let transcode = self.transcode.as_ref()?; let transcode = self.transcode.as_ref()?;
if !transcode.should_transcode(mime, file_size) { if !ImageTranscodeService::should_transcode(mime, file_size) {
return None; return None;
} }
let format = OutputFormat::WebP; let format = OutputFormat::WebP;
@@ -121,37 +102,6 @@ impl FileRetrievalService {
let modified_at = dto.modified_at; let modified_at = dto.modified_at;
let do_transcode = accept_webp && !prefer_original; 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) ────────── // ── Tier 1: Hot cache + transcode (<10 MB) ──────────
if file_size < CACHE_THRESHOLD { if file_size < CACHE_THRESHOLD {
// Check content cache first // Check content cache first
@@ -243,7 +193,6 @@ impl FileRetrievalService {
} }
} }
#[async_trait]
impl FileRetrievalUseCase for FileRetrievalService { impl FileRetrievalUseCase for FileRetrievalService {
async fn get_file(&self, id: &str) -> Result<FileDto, DomainError> { async fn get_file(&self, id: &str) -> Result<FileDto, DomainError> {
let file = self.file_read.get_file(id).await?; let file = self.file_read.get_file(id).await?;
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use std::path::Path; use std::path::Path;
use std::sync::Arc; 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::application::ports::storage_ports::{FileReadPort, FileWritePort};
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use tracing::{debug, info, warn}; 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. /// Helper function to extract username from folder path string.
/// e.g. "My Folder - user1/subfolder/file.txt" → "user1" /// 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. /// Peak RAM usage during any upload: ~256 KB (streaming hash) regardless of file size.
pub struct FileUploadService { pub struct FileUploadService {
/// Write port — handles save, streaming, deferred registration /// 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 /// Read port — needed for WebDAV create_file / update_file
file_read: Option<Arc<dyn FileReadPort>>, file_read: Option<Arc<FileBlobReadRepository>>,
/// Optional storage usage tracking /// Optional storage usage tracking
storage_usage_service: storage_usage_service:
Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>, Option<Arc<StorageUsageService>>,
} }
impl FileUploadService { impl FileUploadService {
/// Constructor with write port only (minimal). /// Constructor with write port only (minimal).
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self { pub fn new(file_repository: Arc<FileBlobWriteRepository>) -> Self {
Self { Self {
file_write: file_repository, file_write: file_repository,
file_read: None, file_read: None,
@@ -62,8 +64,8 @@ impl FileUploadService {
/// Constructor for blob-storage model: write + read ports. /// Constructor for blob-storage model: write + read ports.
pub fn new_with_read( pub fn new_with_read(
file_write: Arc<dyn FileWritePort>, file_write: Arc<FileBlobWriteRepository>,
file_read: Arc<dyn FileReadPort>, file_read: Arc<FileBlobReadRepository>,
) -> Self { ) -> Self {
Self { Self {
file_write, file_write,
@@ -75,7 +77,7 @@ impl FileUploadService {
/// Configures the storage usage service /// Configures the storage usage service
pub fn with_storage_usage_service( pub fn with_storage_usage_service(
mut self, mut self,
storage_usage_service: Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>, storage_usage_service: Arc<StorageUsageService>,
) -> Self { ) -> Self {
self.storage_usage_service = Some(storage_usage_service); self.storage_usage_service = Some(storage_usage_service);
self self
@@ -106,7 +108,6 @@ impl FileUploadService {
} }
} }
#[async_trait]
impl FileUploadUseCase for FileUploadService { impl FileUploadUseCase for FileUploadService {
/// Streaming upload from a temp file on disk. /// Streaming upload from a temp file on disk.
/// ///
@@ -1,24 +1,23 @@
use std::sync::Arc; use std::sync::Arc;
use crate::application::ports::file_ports::{ use crate::application::ports::file_ports::FileUseCaseFactory;
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, FileUseCaseFactory,
};
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
use crate::application::services::file_management_service::FileManagementService; use crate::application::services::file_management_service::FileManagementService;
use crate::application::services::file_retrieval_service::FileRetrievalService; use crate::application::services::file_retrieval_service::FileRetrievalService;
use crate::application::services::file_upload_service::FileUploadService; 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 /// Factory for creating file use case implementations
pub struct AppFileUseCaseFactory { pub struct AppFileUseCaseFactory {
file_read_repository: Arc<dyn FileReadPort>, file_read_repository: Arc<FileBlobReadRepository>,
file_write_repository: Arc<dyn FileWritePort>, file_write_repository: Arc<FileBlobWriteRepository>,
} }
impl AppFileUseCaseFactory { impl AppFileUseCaseFactory {
/// Creates a new factory for file use cases /// Creates a new factory for file use cases
pub fn new( pub fn new(
file_read_repository: Arc<dyn FileReadPort>, file_read_repository: Arc<FileBlobReadRepository>,
file_write_repository: Arc<dyn FileWritePort>, file_write_repository: Arc<FileBlobWriteRepository>,
) -> Self { ) -> Self {
Self { Self {
file_read_repository, file_read_repository,
@@ -28,15 +27,15 @@ impl AppFileUseCaseFactory {
} }
impl FileUseCaseFactory for 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())) 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())) 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( Arc::new(FileManagementService::new(
self.file_write_repository.clone(), self.file_write_repository.clone(),
)) ))
+4 -6
View File
@@ -2,20 +2,20 @@ use crate::application::dtos::folder_dto::{
CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto, CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto,
}; };
use crate::application::ports::inbound::FolderUseCase; use crate::application::ports::inbound::FolderUseCase;
use crate::application::ports::outbound::FolderStoragePort;
use crate::common::errors::{DomainError, ErrorKind}; use crate::common::errors::{DomainError, ErrorKind};
use crate::domain::services::path_service::StoragePath; use crate::domain::services::path_service::StoragePath;
use async_trait::async_trait;
use std::sync::Arc; 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 /// Implementation of the use case for folder operations
pub struct FolderService { pub struct FolderService {
folder_storage: Arc<dyn FolderStoragePort>, folder_storage: Arc<FolderDbRepository>,
} }
impl FolderService { impl FolderService {
/// Creates a new folder service /// 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 } Self { folder_storage }
} }
@@ -23,7 +23,6 @@ impl FolderService {
pub fn new_stub() -> impl FolderUseCase { pub fn new_stub() -> impl FolderUseCase {
struct FolderServiceStub; struct FolderServiceStub;
#[async_trait]
impl FolderUseCase for FolderServiceStub { impl FolderUseCase for FolderServiceStub {
async fn create_folder(&self, _dto: CreateFolderDto) -> Result<FolderDto, DomainError> { async fn create_folder(&self, _dto: CreateFolderDto) -> Result<FolderDto, DomainError> {
Ok(FolderDto::empty()) Ok(FolderDto::empty())
@@ -124,7 +123,6 @@ impl FolderService {
} }
} }
#[async_trait]
impl FolderUseCase for FolderService { impl FolderUseCase for FolderService {
/// Creates a new folder /// Creates a new folder
async fn create_folder(&self, dto: CreateFolderDto) -> Result<FolderDto, DomainError> { async fn create_folder(&self, dto: CreateFolderDto) -> Result<FolderDto, DomainError> {
@@ -1,43 +1,23 @@
use std::sync::Arc; use std::sync::Arc;
use crate::domain::services::i18n_service::{I18nResult, I18nService, Locale}; use crate::domain::services::i18n_service::{I18nResult, I18nService, Locale};
use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService;
/// Service for i18n operations /// Service for i18n operations
pub struct I18nApplicationService { pub struct I18nApplicationService {
i18n_service: Arc<dyn I18nService>, i18n_service: Arc<FileSystemI18nService>,
} }
impl I18nApplicationService { impl I18nApplicationService {
/// Creates a dummy service for testing /// Creates a dummy service for testing
pub fn dummy() -> Self { 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 { Self {
i18n_service: Arc::new(DummyI18nService), i18n_service: Arc::new(FileSystemI18nService::dummy()),
} }
} }
/// Creates a new i18n application service /// 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 } Self { i18n_service }
} }
+3 -4
View File
@@ -1,22 +1,22 @@
use crate::application::dtos::recent_dto::RecentItemDto; use crate::application::dtos::recent_dto::RecentItemDto;
use crate::application::ports::recent_ports::{RecentItemsRepositoryPort, RecentItemsUseCase}; use crate::application::ports::recent_ports::{RecentItemsRepositoryPort, RecentItemsUseCase};
use crate::common::errors::{DomainError, ErrorKind, Result}; use crate::common::errors::{DomainError, ErrorKind, Result};
use async_trait::async_trait;
use std::sync::Arc; use std::sync::Arc;
use tracing::info; use tracing::info;
use crate::infrastructure::repositories::pg::RecentItemsPgRepository;
/// Implementation of the use case for managing recent items. /// Implementation of the use case for managing recent items.
/// ///
/// Depends on `RecentItemsRepositoryPort` (outbound port) instead /// Depends on `RecentItemsRepositoryPort` (outbound port) instead
/// of accessing `PgPool` directly, following the hexagonal architecture. /// of accessing `PgPool` directly, following the hexagonal architecture.
pub struct RecentService { pub struct RecentService {
repo: Arc<dyn RecentItemsRepositoryPort>, repo: Arc<RecentItemsPgRepository>,
max_recent_items: i32, max_recent_items: i32,
} }
impl RecentService { impl RecentService {
/// Create a new recent items service /// 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 { Self {
repo, repo,
max_recent_items: max_recent_items.clamp(1, 100), max_recent_items: max_recent_items.clamp(1, 100),
@@ -24,7 +24,6 @@ impl RecentService {
} }
} }
#[async_trait]
impl RecentItemsUseCase for RecentService { impl RecentItemsUseCase for RecentService {
/// Get recent items for a user /// Get recent items for a user
async fn get_recent_items( async fn get_recent_items(
+7 -8
View File
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use std::cmp::Reverse; use std::cmp::Reverse;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -13,11 +12,13 @@ use crate::application::dtos::search_dto::{
SearchSuggestionItem, SearchSuggestionsDto, SearchSuggestionItem, SearchSuggestionsDto,
}; };
use crate::application::ports::inbound::SearchUseCase; use crate::application::ports::inbound::SearchUseCase;
use crate::application::ports::outbound::FolderStoragePort;
use crate::application::ports::storage_ports::FileReadPort; use crate::application::ports::storage_ports::FileReadPort;
use crate::common::errors::Result; use crate::common::errors::Result;
use crate::domain::entities::folder::Folder; use crate::domain::entities::folder::Folder;
use std::hash::{Hash, Hasher}; 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. * High-performance search service implementation for files and folders.
@@ -37,10 +38,10 @@ use std::hash::{Hash, Hasher};
*/ */
pub struct SearchService { pub struct SearchService {
/// Repository for file operations /// Repository for file operations
file_repository: Arc<dyn FileReadPort>, file_repository: Arc<FileBlobReadRepository>,
/// Repository for folder operations /// Repository for folder operations
folder_repository: Arc<dyn FolderStoragePort>, folder_repository: Arc<FolderDbRepository>,
/// Lock-free concurrent cache with automatic TTL and LRU eviction (moka). /// Lock-free concurrent cache with automatic TTL and LRU eviction (moka).
/// Values are `Arc<SearchResultsDto>` so cache insert/hit is a single /// 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. * Creates a new instance of the search service.
*/ */
pub fn new( pub fn new(
file_repository: Arc<dyn FileReadPort>, file_repository: Arc<FileBlobReadRepository>,
folder_repository: Arc<dyn FolderStoragePort>, folder_repository: Arc<FolderDbRepository>,
cache_ttl: u64, cache_ttl: u64,
max_cache_size: usize, max_cache_size: usize,
) -> Self { ) -> Self {
@@ -255,7 +256,6 @@ impl SearchService {
// ─── SearchUseCase trait implementation ────────────────────────────────── // ─── SearchUseCase trait implementation ──────────────────────────────────
#[async_trait]
impl SearchUseCase for SearchService { impl SearchUseCase for SearchService {
/** /**
* Performs a search based on the specified criteria. * Performs a search based on the specified criteria.
@@ -489,7 +489,6 @@ impl SearchService {
pub fn new_stub() -> impl SearchUseCase { pub fn new_stub() -> impl SearchUseCase {
struct SearchServiceStub; struct SearchServiceStub;
#[async_trait]
impl SearchUseCase for SearchServiceStub { impl SearchUseCase for SearchServiceStub {
async fn search( async fn search(
&self, &self,
+13 -16
View File
@@ -1,6 +1,5 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
@@ -12,7 +11,6 @@ use crate::{
}, },
ports::{ ports::{
auth_ports::PasswordHasherPort, auth_ports::PasswordHasherPort,
outbound::FolderStoragePort,
share_ports::{ShareStoragePort, ShareUseCase}, share_ports::{ShareStoragePort, ShareUseCase},
storage_ports::FileReadPort, storage_ports::FileReadPort,
}, },
@@ -20,6 +18,11 @@ use crate::{
common::{config::AppConfig, errors::DomainError}, common::{config::AppConfig, errors::DomainError},
domain::entities::share::{Share, ShareItemType, SharePermissions}, 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)] #[derive(Debug, Error)]
pub enum ShareServiceError { pub enum ShareServiceError {
@@ -67,10 +70,10 @@ const MAX_CONCURRENT_HASHES: usize = 2;
pub struct ShareService { pub struct ShareService {
config: Arc<AppConfig>, config: Arc<AppConfig>,
share_repository: Arc<dyn ShareStoragePort>, share_repository: Arc<SharePgRepository>,
file_repository: Arc<dyn FileReadPort>, file_repository: Arc<FileBlobReadRepository>,
folder_repository: Arc<dyn FolderStoragePort>, folder_repository: Arc<FolderDbRepository>,
password_hasher: Arc<dyn PasswordHasherPort>, password_hasher: Arc<Argon2PasswordHasher>,
/// Bounds the number of in-flight Argon2 password hashes to avoid /// Bounds the number of in-flight Argon2 password hashes to avoid
/// saturating the blocking thread pool and consuming excessive RAM. /// saturating the blocking thread pool and consuming excessive RAM.
hash_semaphore: Arc<Semaphore>, hash_semaphore: Arc<Semaphore>,
@@ -79,10 +82,10 @@ pub struct ShareService {
impl ShareService { impl ShareService {
pub fn new( pub fn new(
config: Arc<AppConfig>, config: Arc<AppConfig>,
share_repository: Arc<dyn ShareStoragePort>, share_repository: Arc<SharePgRepository>,
file_repository: Arc<dyn FileReadPort>, file_repository: Arc<FileBlobReadRepository>,
folder_repository: Arc<dyn FolderStoragePort>, folder_repository: Arc<FolderDbRepository>,
password_hasher: Arc<dyn PasswordHasherPort>, password_hasher: Arc<Argon2PasswordHasher>,
) -> Self { ) -> Self {
Self { Self {
config, config,
@@ -139,7 +142,6 @@ impl ShareService {
} }
} }
#[async_trait]
impl ShareUseCase for ShareService { impl ShareUseCase for ShareService {
async fn create_shared_link( async fn create_shared_link(
&self, &self,
@@ -400,13 +402,11 @@ mod tests {
use crate::application::ports::share_ports::ShareStoragePort; use crate::application::ports::share_ports::ShareStoragePort;
use crate::common::config::AppConfig; use crate::common::config::AppConfig;
use crate::domain::repositories::folder_repository::FolderRepository; use crate::domain::repositories::folder_repository::FolderRepository;
use async_trait::async_trait;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex; use std::sync::Mutex;
struct MockPasswordHasher; struct MockPasswordHasher;
#[async_trait]
impl PasswordHasherPort for MockPasswordHasher { impl PasswordHasherPort for MockPasswordHasher {
async fn hash_password(&self, password: &str) -> Result<String, DomainError> { async fn hash_password(&self, password: &str) -> Result<String, DomainError> {
Ok(format!("hashed_{}", password)) Ok(format!("hashed_{}", password))
@@ -420,7 +420,6 @@ mod tests {
struct MockFileRepository; struct MockFileRepository;
struct MockFolderRepository; struct MockFolderRepository;
#[async_trait]
impl FileReadPort for MockFileRepository { impl FileReadPort for MockFileRepository {
async fn get_file( async fn get_file(
&self, &self,
@@ -523,7 +522,6 @@ mod tests {
} }
} }
#[async_trait]
impl FolderRepository for MockFolderRepository { impl FolderRepository for MockFolderRepository {
async fn create_folder( async fn create_folder(
&self, &self,
@@ -671,7 +669,6 @@ mod tests {
} }
} }
#[async_trait]
impl ShareStoragePort for MockShareRepository { impl ShareStoragePort for MockShareRepository {
async fn save_share(&self, share: &Share) -> Result<Share, DomainError> { async fn save_share(&self, share: &Share) -> Result<Share, DomainError> {
let mut shares = self.shares.lock().unwrap(); let mut shares = self.shares.lock().unwrap();
@@ -1,11 +1,11 @@
use crate::application::ports::auth_ports::UserStoragePort; use crate::application::ports::auth_ports::UserStoragePort;
use crate::application::ports::storage_ports::StorageUsagePort; use crate::application::ports::storage_ports::StorageUsagePort;
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use sqlx::PgPool; use sqlx::PgPool;
use std::sync::Arc; use std::sync::Arc;
use tokio::task; use tokio::task;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
use crate::infrastructure::repositories::pg::UserPgRepository;
/** /**
* Service for managing and updating user storage usage statistics. * Service for managing and updating user storage usage statistics.
@@ -18,12 +18,12 @@ use tracing::{debug, error, info};
*/ */
pub struct StorageUsageService { pub struct StorageUsageService {
pool: Arc<PgPool>, pool: Arc<PgPool>,
user_repository: Arc<dyn UserStoragePort>, user_repository: Arc<UserPgRepository>,
} }
impl StorageUsageService { impl StorageUsageService {
/// Creates a new storage usage service /// 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 { Self {
pool, pool,
user_repository, user_repository,
@@ -111,7 +111,6 @@ impl StorageUsageService {
* Implementation of the StorageUsagePort trait to expose storage usage services * Implementation of the StorageUsagePort trait to expose storage usage services
* to the application layer. * to the application layer.
*/ */
#[async_trait]
impl StorageUsagePort for StorageUsageService { impl StorageUsagePort for StorageUsageService {
async fn update_user_storage_usage(&self, user_id: &str) -> Result<i64, DomainError> { async fn update_user_storage_usage(&self, user_id: &str) -> Result<i64, DomainError> {
StorageUsageService::update_user_storage_usage(self, user_id).await 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 std::sync::Arc;
use tracing::{debug, error, info, instrument}; use tracing::{debug, error, info, instrument};
use uuid::Uuid; use uuid::Uuid;
use crate::application::dtos::trash_dto::TrashedItemDto; 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::storage_ports::{FileReadPort, FileWritePort};
use crate::application::ports::trash_ports::TrashUseCase; use crate::application::ports::trash_ports::TrashUseCase;
use crate::common::errors::{DomainError, ErrorKind, Result}; use crate::common::errors::{DomainError, ErrorKind, Result};
use crate::domain::entities::trashed_item::{TrashedItem, TrashedItemType}; use crate::domain::entities::trashed_item::{TrashedItem, TrashedItemType};
use crate::domain::repositories::trash_repository::TrashRepository; 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. * Application service for trash operations.
@@ -26,16 +29,16 @@ use crate::domain::repositories::trash_repository::TrashRepository;
*/ */
pub struct TrashService { pub struct TrashService {
/// Repository for trash-specific operations like listing and retrieving trashed items /// 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) /// 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) /// 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) /// 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 /// Number of days items should be kept in trash before automatic cleanup
retention_days: u32, retention_days: u32,
@@ -43,10 +46,10 @@ pub struct TrashService {
impl TrashService { impl TrashService {
pub fn new( pub fn new(
trash_repository: Arc<dyn TrashRepository>, trash_repository: Arc<TrashDbRepository>,
file_read_port: Arc<dyn FileReadPort>, file_read_port: Arc<FileBlobReadRepository>,
file_write_port: Arc<dyn FileWritePort>, file_write_port: Arc<FileBlobWriteRepository>,
folder_storage_port: Arc<dyn FolderStoragePort>, folder_storage_port: Arc<FolderDbRepository>,
retention_days: u32, retention_days: u32,
) -> Self { ) -> Self {
Self { Self {
@@ -118,7 +121,6 @@ impl TrashService {
} }
} }
#[async_trait]
impl TrashUseCase for TrashService { impl TrashUseCase for TrashService {
#[instrument(skip(self))] #[instrument(skip(self))]
async fn get_trash_items(&self, user_id: &str) -> Result<Vec<TrashedItemDto>> { 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 bytes::Bytes;
use chrono::Utc; use chrono::Utc;
use futures::Stream; use futures::Stream;
@@ -40,7 +39,6 @@ impl MockTrashRepository {
} }
} }
#[async_trait]
impl TrashRepository for MockTrashRepository { impl TrashRepository for MockTrashRepository {
async fn add_to_trash(&self, item: &TrashedItem) -> Result<()> { async fn add_to_trash(&self, item: &TrashedItem) -> Result<()> {
let mut items = self.trash_items.lock().unwrap(); let mut items = self.trash_items.lock().unwrap();
@@ -135,7 +133,6 @@ impl MockFileRepository {
} }
} }
#[async_trait]
impl FileReadPort for MockFileRepository { impl FileReadPort for MockFileRepository {
async fn get_file(&self, id: &str) -> std::result::Result<File, DomainError> { async fn get_file(&self, id: &str) -> std::result::Result<File, DomainError> {
let files = self.files.lock().unwrap(); let files = self.files.lock().unwrap();
@@ -216,7 +213,6 @@ impl FileReadPort for MockFileRepository {
} }
} }
#[async_trait]
impl FileWritePort for MockFileRepository { impl FileWritePort for MockFileRepository {
async fn save_file_from_temp( async fn save_file_from_temp(
&self, &self,
@@ -350,7 +346,6 @@ impl MockFolderRepository {
} }
} }
#[async_trait]
impl FolderRepository for MockFolderRepository { impl FolderRepository for MockFolderRepository {
async fn create_folder( async fn create_folder(
&self, &self,
@@ -514,8 +509,8 @@ mod tests {
let service = TrashService::new( let service = TrashService::new(
trash_repo.clone(), trash_repo.clone(),
file_repo.clone() as Arc<dyn FileReadPort>, file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<dyn FileWritePort>, file_repo.clone() as Arc<FileBlobWriteRepository>,
folder_repo.clone(), folder_repo.clone(),
30, // 30 days retention 30, // 30 days retention
); );
@@ -588,8 +583,8 @@ mod tests {
let service = TrashService::new( let service = TrashService::new(
trash_repo.clone(), trash_repo.clone(),
file_repo.clone() as Arc<dyn FileReadPort>, file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<dyn FileWritePort>, file_repo.clone() as Arc<FileBlobWriteRepository>,
folder_repo.clone(), folder_repo.clone(),
30, // 30 days retention 30, // 30 days retention
); );
@@ -653,8 +648,8 @@ mod tests {
let service = TrashService::new( let service = TrashService::new(
trash_repo.clone(), trash_repo.clone(),
file_repo.clone() as Arc<dyn FileReadPort>, file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<dyn FileWritePort>, file_repo.clone() as Arc<FileBlobWriteRepository>,
folder_repo.clone(), folder_repo.clone(),
30, // 30 days retention 30, // 30 days retention
); );
@@ -723,8 +718,8 @@ mod tests {
let service = TrashService::new( let service = TrashService::new(
trash_repo.clone(), trash_repo.clone(),
file_repo.clone() as Arc<dyn FileReadPort>, file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<dyn FileWritePort>, file_repo.clone() as Arc<FileBlobWriteRepository>,
folder_repo.clone(), folder_repo.clone(),
30, // 30 days retention 30, // 30 days retention
); );
@@ -792,8 +787,8 @@ mod tests {
let service = TrashService::new( let service = TrashService::new(
trash_repo.clone(), trash_repo.clone(),
file_repo.clone() as Arc<dyn FileReadPort>, file_repo.clone() as Arc<FileBlobReadRepository>,
file_repo.clone() as Arc<dyn FileWritePort>, file_repo.clone() as Arc<FileBlobWriteRepository>,
folder_repo.clone(), folder_repo.clone(),
30, // 30 days retention 30, // 30 days retention
); );
+87 -93
View File
@@ -7,22 +7,7 @@ use crate::infrastructure::db::DbPools;
use crate::application::services::admin_settings_service::AdminSettingsService; use crate::application::services::admin_settings_service::AdminSettingsService;
use crate::application::services::auth_application_service::AuthApplicationService; use crate::application::services::auth_application_service::AuthApplicationService;
use crate::application::ports::cache_ports::ContentCachePort; use crate::application::ports::file_ports::FileUseCaseFactory;
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::services::favorites_service::FavoritesService; use crate::application::services::favorites_service::FavoritesService;
use crate::application::services::folder_service::FolderService; use crate::application::services::folder_service::FolderService;
use crate::application::services::i18n_application_service::I18nApplicationService; use crate::application::services::i18n_application_service::I18nApplicationService;
@@ -35,7 +20,6 @@ use crate::application::services::{
}; };
use crate::common::config::AppConfig; use crate::common::config::AppConfig;
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::services::i18n_service::I18nService;
use crate::infrastructure::repositories::pg::SharePgRepository; use crate::infrastructure::repositories::pg::SharePgRepository;
use crate::infrastructure::repositories::pg::{ use crate::infrastructure::repositories::pg::{
FileBlobReadRepository, FileBlobWriteRepository, FolderDbRepository, TrashDbRepository, 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::path_service::PathService;
use crate::infrastructure::services::trash_cleanup_service::TrashCleanupService; 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 /// Factory for the different application components
/// ///
@@ -161,7 +167,7 @@ impl AppServiceFactory {
chunked_upload_service, chunked_upload_service,
image_transcode_service, image_transcode_service,
dedup_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(), config: self.config.clone(),
}) })
} }
@@ -176,16 +182,16 @@ impl AppServiceFactory {
) -> RepositoryServices { ) -> RepositoryServices {
// Folder repository — PostgreSQL-backed virtual folders // Folder repository — PostgreSQL-backed virtual folders
let folder_repo_concrete = Arc::new(FolderDbRepository::new(db_pool.clone())); let folder_repo_concrete = Arc::new(FolderDbRepository::new(db_pool.clone()));
let folder_repository: Arc<dyn FolderStoragePort> = folder_repo_concrete.clone(); let folder_repository: Arc<FolderDbRepository> = folder_repo_concrete.clone();
// File repositories — PostgreSQL metadata + blob content via DedupService // File repositories — PostgreSQL metadata + blob content via DedupService
let file_read_repository: Arc<dyn FileReadPort> = Arc::new(FileBlobReadRepository::new( let file_read_repository: Arc<FileBlobReadRepository> = Arc::new(FileBlobReadRepository::new(
db_pool.clone(), db_pool.clone(),
core.dedup_service.clone(), core.dedup_service.clone(),
folder_repo_concrete.clone(), folder_repo_concrete.clone(),
)); ));
let file_write_repository: Arc<dyn FileWritePort> = Arc::new(FileBlobWriteRepository::new( let file_write_repository: Arc<FileBlobWriteRepository> = Arc::new(FileBlobWriteRepository::new(
db_pool.clone(), db_pool.clone(),
core.dedup_service.clone(), core.dedup_service.clone(),
folder_repo_concrete.clone(), folder_repo_concrete.clone(),
@@ -201,7 +207,7 @@ impl AppServiceFactory {
core.config.storage.trash_retention_days, core.config.storage.trash_retention_days,
)) ))
as Arc< as Arc<
dyn crate::domain::repositories::trash_repository::TrashRepository, TrashDbRepository,
>) >)
} else { } else {
None None
@@ -226,7 +232,7 @@ impl AppServiceFactory {
&self, &self,
core: &CoreServices, core: &CoreServices,
repos: &RepositoryServices, repos: &RepositoryServices,
trash_service: Option<Arc<dyn TrashUseCase>>, trash_service: Option<Arc<TrashService>>,
) -> ApplicationServices { ) -> ApplicationServices {
// Main services // Main services
let folder_service = Arc::new(FolderService::new(repos.folder_repository.clone())); 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())); let i18n_service = Arc::new(I18nApplicationService::new(repos.i18n_repository.clone()));
// Search service with cache // Search service with cache
let search_service: Option<Arc<dyn SearchUseCase>> = Some(Arc::new(SearchService::new( let search_service: Option<Arc<SearchService>> = Some(Arc::new(SearchService::new(
repos.file_read_repository.clone(), repos.file_read_repository.clone(),
repos.folder_repository.clone(), repos.folder_repository.clone(),
300, // Cache TTL in seconds (5 minutes) 300, // Cache TTL in seconds (5 minutes)
@@ -289,7 +295,7 @@ impl AppServiceFactory {
pub async fn create_trash_service( pub async fn create_trash_service(
&self, &self,
repos: &RepositoryServices, repos: &RepositoryServices,
) -> Option<Arc<dyn TrashUseCase>> { ) -> Option<Arc<TrashService>> {
if !self.config.features.enable_trash { if !self.config.features.enable_trash {
tracing::info!("Trash service is disabled in configuration"); tracing::info!("Trash service is disabled in configuration");
return None; return None;
@@ -315,7 +321,7 @@ impl AppServiceFactory {
cleanup_service.start_cleanup_job().await; cleanup_service.start_cleanup_job().await;
tracing::info!("Trash service initialized with daily cleanup schedule"); tracing::info!("Trash service initialized with daily cleanup schedule");
Some(service as Arc<dyn TrashUseCase>) Some(service as Arc<TrashService>)
} }
/// Creates the sharing service /// Creates the sharing service
@@ -323,7 +329,7 @@ impl AppServiceFactory {
&self, &self,
repos: &RepositoryServices, repos: &RepositoryServices,
db_pool: &Arc<PgPool>, db_pool: &Arc<PgPool>,
) -> Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>> { ) -> Option<Arc<ShareService>> {
if !self.config.features.enable_file_sharing { if !self.config.features.enable_file_sharing {
tracing::info!("File sharing service is disabled in configuration"); tracing::info!("File sharing service is disabled in configuration");
return None; return None;
@@ -332,7 +338,7 @@ impl AppServiceFactory {
let share_repository = Arc::new(SharePgRepository::new(db_pool.clone())); let share_repository = Arc::new(SharePgRepository::new(db_pool.clone()));
// Build a password hasher for share password verification // Build a password hasher for share password verification
let password_hasher: Arc<dyn crate::application::ports::auth_ports::PasswordHasherPort> = let password_hasher: Arc<Argon2PasswordHasher> =
Arc::new( Arc::new(
crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new( crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new(
self.config.auth.hash_memory_cost, self.config.auth.hash_memory_cost,
@@ -354,7 +360,7 @@ impl AppServiceFactory {
} }
/// Creates the favorites service (requires database) /// Creates the favorites service (requires database)
pub fn create_favorites_service(&self, db_pool: &Arc<PgPool>) -> Arc<dyn FavoritesUseCase> { pub fn create_favorites_service(&self, db_pool: &Arc<PgPool>) -> Arc<FavoritesService> {
let repo = Arc::new( let repo = Arc::new(
crate::infrastructure::repositories::pg::FavoritesPgRepository::new(db_pool.clone()), crate::infrastructure::repositories::pg::FavoritesPgRepository::new(db_pool.clone()),
); );
@@ -364,7 +370,7 @@ impl AppServiceFactory {
} }
/// Creates the recent items service (requires database) /// Creates the recent items service (requires database)
pub fn create_recent_service(&self, db_pool: &Arc<PgPool>) -> Arc<dyn RecentItemsUseCase> { pub fn create_recent_service(&self, db_pool: &Arc<PgPool>) -> Arc<RecentService> {
let repo = Arc::new( let repo = Arc::new(
crate::infrastructure::repositories::pg::RecentItemsPgRepository::new(db_pool.clone()), crate::infrastructure::repositories::pg::RecentItemsPgRepository::new(db_pool.clone()),
); );
@@ -406,7 +412,7 @@ impl AppServiceFactory {
_repos: &RepositoryServices, _repos: &RepositoryServices,
db_pool: &Arc<PgPool>, db_pool: &Arc<PgPool>,
maintenance_pool: &Arc<PgPool>, maintenance_pool: &Arc<PgPool>,
) -> Arc<dyn crate::application::ports::storage_ports::StorageUsagePort> { ) -> Arc<StorageUsageService> {
let user_repository = Arc::new( let user_repository = Arc::new(
crate::infrastructure::repositories::pg::UserPgRepository::new(db_pool.clone()), crate::infrastructure::repositories::pg::UserPgRepository::new(db_pool.clone()),
); );
@@ -455,10 +461,10 @@ impl AppServiceFactory {
apps.share_service = share_service.clone(); apps.share_service = share_service.clone();
// 6. Database-dependent services (PgPool always available in blob model) // 6. Database-dependent services (PgPool always available in blob model)
let favorites_service: Option<Arc<dyn FavoritesUseCase>>; let favorites_service: Option<Arc<FavoritesService>>;
let recent_service: Option<Arc<dyn RecentItemsUseCase>>; let recent_service: Option<Arc<RecentService>>;
let storage_usage_service: Option< let storage_usage_service: Option<
Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>, Arc<StorageUsageService>,
>; >;
let mut auth_services: Option<crate::common::di::AuthServices> = None; let mut auth_services: Option<crate::common::di::AuthServices> = None;
@@ -509,14 +515,14 @@ impl AppServiceFactory {
self.preload_translations(&apps.i18n_service).await; self.preload_translations(&apps.i18n_service).await;
// 8. Build the ZipService with real application services // 8. Build the ZipService with real application services
let zip_service: Arc<dyn crate::application::ports::zip_ports::ZipPort> = Arc::new( let zip_service: Arc<ZipService> = Arc::new(
crate::infrastructure::services::zip_service::ZipService::new( crate::infrastructure::services::zip_service::ZipService::new(
apps.file_retrieval_service.clone(), apps.file_retrieval_service.clone(),
apps.folder_service.clone(), apps.folder_service.clone(),
), ),
); );
let mut core = core; let mut core = core;
core.zip_service = zip_service; core.zip_service = Some(zip_service);
// 9. Assemble final AppState // 9. Assemble final AppState
let mut app_state = AppState { let mut app_state = AppState {
@@ -595,16 +601,14 @@ impl AppServiceFactory {
// 9c. Wire Device Authorization Grant (RFC 8628) service // 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 device_code_repo = Arc::new(DeviceCodePgRepository::new(pool.clone()));
let user_repo: Arc<dyn crate::application::ports::auth_ports::UserStoragePort> = let user_repo: Arc<UserPgRepository> =
Arc::new(crate::infrastructure::repositories::UserPgRepository::new( Arc::new(crate::infrastructure::repositories::UserPgRepository::new(
pool.clone(), pool.clone(),
)); ));
let session_repo: Arc< let session_repo: Arc<
dyn crate::application::ports::auth_ports::SessionStoragePort, SessionPgRepository,
> = Arc::new( > = Arc::new(
crate::infrastructure::repositories::SessionPgRepository::new(pool.clone()), crate::infrastructure::repositories::SessionPgRepository::new(pool.clone()),
); );
@@ -623,13 +627,11 @@ impl AppServiceFactory {
// 9d. Wire App Password service // 9d. Wire App Password service
{ {
use crate::application::services::app_password_service::AppPasswordService;
use crate::infrastructure::repositories::AppPasswordPgRepository;
let app_pw_repo: Arc< let app_pw_repo: Arc<
dyn crate::application::ports::auth_ports::AppPasswordStoragePort, AppPasswordPgRepository,
> = Arc::new(AppPasswordPgRepository::new(pool.clone())); > = Arc::new(AppPasswordPgRepository::new(pool.clone()));
let hasher: Arc<dyn crate::application::ports::auth_ports::PasswordHasherPort> = let hasher: Arc<Argon2PasswordHasher> =
Arc::new( Arc::new(
crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new( crate::infrastructure::services::password_hasher::Argon2PasswordHasher::new(
self.config.auth.hash_memory_cost, self.config.auth.hash_memory_cost,
@@ -637,7 +639,7 @@ impl AppServiceFactory {
self.config.auth.hash_parallelism, self.config.auth.hash_parallelism,
), ),
); );
let user_repo: Arc<dyn crate::application::ports::auth_ports::UserStoragePort> = let user_repo: Arc<UserPgRepository> =
Arc::new(crate::infrastructure::repositories::UserPgRepository::new( Arc::new(crate::infrastructure::repositories::UserPgRepository::new(
pool.clone(), pool.clone(),
)); ));
@@ -656,7 +658,6 @@ impl AppServiceFactory {
// 9e. Wire PathResolver for single-query WebDAV path resolution // 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()))); app_state.path_resolver = Some(Arc::new(PathResolverService::new(pool.clone())));
tracing::info!("PathResolver service initialized"); tracing::info!("PathResolver service initialized");
} }
@@ -665,12 +666,12 @@ impl AppServiceFactory {
{ {
// CalDAV // CalDAV
let calendar_repo: Arc< let calendar_repo: Arc<
dyn crate::domain::repositories::calendar_repository::CalendarRepository, CalendarPgRepository,
> = Arc::new( > = Arc::new(
crate::infrastructure::repositories::pg::CalendarPgRepository::new(pool.clone()), crate::infrastructure::repositories::pg::CalendarPgRepository::new(pool.clone()),
); );
let event_repo: Arc< let event_repo: Arc<
dyn crate::domain::repositories::calendar_event_repository::CalendarEventRepository, CalendarEventPgRepository,
> = Arc::new( > = Arc::new(
crate::infrastructure::repositories::pg::CalendarEventPgRepository::new( crate::infrastructure::repositories::pg::CalendarEventPgRepository::new(
pool.clone(), pool.clone(),
@@ -689,22 +690,22 @@ impl AppServiceFactory {
); );
app_state.calendar_use_case = Some( app_state.calendar_use_case = Some(
calendar_service calendar_service
as Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>, as Arc<CalendarService>,
); );
// CardDAV // CardDAV
let address_book_repo: Arc< let address_book_repo: Arc<
dyn crate::domain::repositories::address_book_repository::AddressBookRepository, AddressBookPgRepository,
> = Arc::new( > = Arc::new(
crate::infrastructure::repositories::pg::AddressBookPgRepository::new(pool.clone()), crate::infrastructure::repositories::pg::AddressBookPgRepository::new(pool.clone()),
); );
let contact_repo: Arc< let contact_repo: Arc<
dyn crate::domain::repositories::contact_repository::ContactRepository, ContactPgRepository,
> = Arc::new( > = Arc::new(
crate::infrastructure::repositories::pg::ContactPgRepository::new(pool.clone()), crate::infrastructure::repositories::pg::ContactPgRepository::new(pool.clone()),
); );
let group_repo: Arc< let group_repo: Arc<
dyn crate::domain::repositories::contact_repository::ContactGroupRepository, ContactGroupPgRepository,
> = Arc::new( > = Arc::new(
crate::infrastructure::repositories::pg::ContactGroupPgRepository::new( crate::infrastructure::repositories::pg::ContactGroupPgRepository::new(
pool.clone(), pool.clone(),
@@ -717,12 +718,8 @@ impl AppServiceFactory {
group_repo, group_repo,
) )
); );
app_state.addressbook_use_case = Some(contact_storage.clone() app_state.addressbook_use_case = Some(contact_storage.clone());
as Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>); app_state.contact_use_case = Some(contact_storage);
app_state.contact_use_case = Some(
contact_storage
as Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>,
);
tracing::info!("CalDAV and CardDAV services initialized with PostgreSQL repositories"); 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" "WOPI is enabled but WOPI_DISCOVERY_URL is empty — WOPI services will NOT be available"
); );
} else { } 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() { let wopi_secret = if self.config.wopi.secret.is_empty() {
self.config.auth.jwt_secret.clone() self.config.auth.jwt_secret.clone()
@@ -775,25 +769,25 @@ impl AppServiceFactory {
#[derive(Clone)] #[derive(Clone)]
pub struct CoreServices { pub struct CoreServices {
pub path_service: Arc<PathService>, pub path_service: Arc<PathService>,
pub file_content_cache: Arc<dyn ContentCachePort>, pub file_content_cache: Arc<FileContentCache>,
pub thumbnail_service: Arc<dyn ThumbnailPort>, pub thumbnail_service: Arc<ThumbnailService>,
pub chunked_upload_service: Arc<dyn ChunkedUploadPort>, pub chunked_upload_service: Arc<ChunkedUploadService>,
pub image_transcode_service: Arc<dyn ImageTranscodePort>, pub image_transcode_service: Arc<ImageTranscodeService>,
pub dedup_service: Arc<dyn DedupPort>, pub dedup_service: Arc<DedupService>,
pub zip_service: Arc<dyn ZipPort>, pub zip_service: Option<Arc<ZipService>>,
pub config: AppConfig, pub config: AppConfig,
} }
/// Container for repository services /// Container for repository services
#[derive(Clone)] #[derive(Clone)]
pub struct RepositoryServices { pub struct RepositoryServices {
pub folder_repository: Arc<dyn FolderStoragePort>, pub folder_repository: Arc<FolderDbRepository>,
pub folder_repo_concrete: Arc<FolderDbRepository>, pub folder_repo_concrete: Arc<FolderDbRepository>,
pub file_read_repository: Arc<dyn FileReadPort>, pub file_read_repository: Arc<FileBlobReadRepository>,
pub file_write_repository: Arc<dyn FileWritePort>, pub file_write_repository: Arc<FileBlobWriteRepository>,
pub i18n_repository: Arc<dyn I18nService>, pub i18n_repository: Arc<FileSystemI18nService>,
pub trash_repository: pub trash_repository:
Option<Arc<dyn crate::domain::repositories::trash_repository::TrashRepository>>, Option<Arc<TrashDbRepository>>,
} }
/// Container for application services /// Container for application services
@@ -802,23 +796,23 @@ pub struct ApplicationServices {
// Concrete types for compatibility with existing handlers // Concrete types for compatibility with existing handlers
pub folder_service_concrete: Arc<FolderService>, pub folder_service_concrete: Arc<FolderService>,
// Traits for abstraction // Traits for abstraction
pub folder_service: Arc<dyn FolderUseCase>, pub folder_service: Arc<FolderService>,
pub file_upload_service: Arc<dyn FileUploadUseCase>, pub file_upload_service: Arc<FileUploadService>,
pub file_retrieval_service: Arc<dyn FileRetrievalUseCase>, pub file_retrieval_service: Arc<FileRetrievalService>,
pub file_management_service: Arc<dyn FileManagementUseCase>, pub file_management_service: Arc<FileManagementService>,
pub file_use_case_factory: Arc<dyn FileUseCaseFactory>, pub file_use_case_factory: Arc<dyn FileUseCaseFactory>,
pub i18n_service: Arc<I18nApplicationService>, pub i18n_service: Arc<I18nApplicationService>,
pub trash_service: Option<Arc<dyn TrashUseCase>>, pub trash_service: Option<Arc<TrashService>>,
pub search_service: Option<Arc<dyn SearchUseCase>>, pub search_service: Option<Arc<SearchService>>,
pub share_service: Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>>, pub share_service: Option<Arc<ShareService>>,
pub favorites_service: Option<Arc<dyn FavoritesUseCase>>, pub favorites_service: Option<Arc<FavoritesService>>,
pub recent_service: Option<Arc<dyn RecentItemsUseCase>>, pub recent_service: Option<Arc<RecentService>>,
} }
/// Container for authentication services /// Container for authentication services
#[derive(Clone)] #[derive(Clone)]
pub struct AuthServices { pub struct AuthServices {
pub token_service: Arc<dyn crate::application::ports::auth_ports::TokenServicePort>, pub token_service: Arc<JwtTokenService>,
pub auth_application_service: Arc<AuthApplicationService>, pub auth_application_service: Arc<AuthApplicationService>,
pub login_lockout: pub login_lockout:
Arc<crate::infrastructure::services::login_lockout_service::LoginLockoutService>, Arc<crate::infrastructure::services::login_lockout_service::LoginLockoutService>,
@@ -835,19 +829,19 @@ pub struct AppState {
pub maintenance_pool: Option<Arc<PgPool>>, pub maintenance_pool: Option<Arc<PgPool>>,
pub auth_service: Option<AuthServices>, pub auth_service: Option<AuthServices>,
pub admin_settings_service: Option<Arc<AdminSettingsService>>, pub admin_settings_service: Option<Arc<AdminSettingsService>>,
pub trash_service: Option<Arc<dyn TrashUseCase>>, pub trash_service: Option<Arc<TrashService>>,
pub share_service: Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>>, pub share_service: Option<Arc<ShareService>>,
pub favorites_service: Option<Arc<dyn FavoritesUseCase>>, pub favorites_service: Option<Arc<FavoritesService>>,
pub recent_service: Option<Arc<dyn RecentItemsUseCase>>, pub recent_service: Option<Arc<RecentService>>,
pub storage_usage_service: pub storage_usage_service:
Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>, Option<Arc<StorageUsageService>>,
pub calendar_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>, pub calendar_service: Option<Arc<CalendarService>>,
pub contact_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>, pub contact_service: Option<Arc<ContactStorageAdapter>>,
pub calendar_use_case: pub calendar_use_case:
Option<Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>>, Option<Arc<CalendarService>>,
pub addressbook_use_case: pub addressbook_use_case:
Option<Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>>, Option<Arc<ContactStorageAdapter>>,
pub contact_use_case: Option<Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>>, pub contact_use_case: Option<Arc<ContactStorageAdapter>>,
pub wopi_token_service: pub wopi_token_service:
Option<Arc<crate::application::services::wopi_token_service::WopiTokenService>>, Option<Arc<crate::application::services::wopi_token_service::WopiTokenService>>,
pub wopi_lock_service: pub wopi_lock_service:
+1 -35
View File
@@ -10,7 +10,6 @@ use std::path::{Path, PathBuf};
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures::Stream;
@@ -23,7 +22,7 @@ use crate::application::dtos::search_dto::{
SearchCriteriaDto, SearchResultsDto, SearchSuggestionsDto, SearchCriteriaDto, SearchResultsDto, SearchSuggestionsDto,
}; };
use crate::application::ports::file_ports::{ use crate::application::ports::file_ports::{
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, FileUseCaseFactory, FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase,
OptimizedFileContent, OptimizedFileContent,
}; };
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase}; use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
@@ -44,7 +43,6 @@ use crate::domain::services::path_service::StoragePath;
/// are fully initialised. /// are fully initialised.
pub struct StubZipPort; pub struct StubZipPort;
#[async_trait]
impl ZipPort for StubZipPort { impl ZipPort for StubZipPort {
async fn create_folder_zip( async fn create_folder_zip(
&self, &self,
@@ -64,7 +62,6 @@ impl ZipPort for StubZipPort {
pub struct StubFileReadPort; pub struct StubFileReadPort;
#[async_trait]
impl FileReadPort for StubFileReadPort { impl FileReadPort for StubFileReadPort {
async fn get_file(&self, _id: &str) -> Result<File, DomainError> { async fn get_file(&self, _id: &str) -> Result<File, DomainError> {
Ok(File::default()) Ok(File::default())
@@ -136,7 +133,6 @@ impl FileReadPort for StubFileReadPort {
pub struct StubFileWritePort; pub struct StubFileWritePort;
#[async_trait]
impl FileWritePort for StubFileWritePort { impl FileWritePort for StubFileWritePort {
async fn save_file_from_temp( async fn save_file_from_temp(
&self, &self,
@@ -218,7 +214,6 @@ impl FileWritePort for StubFileWritePort {
pub struct StubFolderStoragePort; pub struct StubFolderStoragePort;
#[async_trait]
impl FolderRepository for StubFolderStoragePort { impl FolderRepository for StubFolderStoragePort {
async fn create_folder( async fn create_folder(
&self, &self,
@@ -324,7 +319,6 @@ impl FolderRepository for StubFolderStoragePort {
pub struct StubI18nService; pub struct StubI18nService;
#[async_trait]
impl I18nService for StubI18nService { impl I18nService for StubI18nService {
async fn translate(&self, _key: &str, _locale: Locale) -> I18nResult<String> { async fn translate(&self, _key: &str, _locale: Locale) -> I18nResult<String> {
Ok(String::new()) Ok(String::new())
@@ -349,7 +343,6 @@ impl I18nService for StubI18nService {
pub struct StubFolderUseCase; pub struct StubFolderUseCase;
#[async_trait]
impl FolderUseCase for StubFolderUseCase { impl FolderUseCase for StubFolderUseCase {
async fn create_folder(&self, _dto: CreateFolderDto) -> Result<FolderDto, DomainError> { async fn create_folder(&self, _dto: CreateFolderDto) -> Result<FolderDto, DomainError> {
Ok(FolderDto::default()) Ok(FolderDto::default())
@@ -429,7 +422,6 @@ impl FolderUseCase for StubFolderUseCase {
pub struct StubFileUploadUseCase; pub struct StubFileUploadUseCase;
#[async_trait]
impl FileUploadUseCase for StubFileUploadUseCase { impl FileUploadUseCase for StubFileUploadUseCase {
async fn upload_file_streaming( async fn upload_file_streaming(
&self, &self,
@@ -486,7 +478,6 @@ impl FileUploadUseCase for StubFileUploadUseCase {
pub struct StubFileRetrievalUseCase; pub struct StubFileRetrievalUseCase;
#[async_trait]
impl FileRetrievalUseCase for StubFileRetrievalUseCase { impl FileRetrievalUseCase for StubFileRetrievalUseCase {
async fn get_file(&self, _id: &str) -> Result<FileDto, DomainError> { async fn get_file(&self, _id: &str) -> Result<FileDto, DomainError> {
Ok(FileDto::default()) Ok(FileDto::default())
@@ -548,7 +539,6 @@ impl FileRetrievalUseCase for StubFileRetrievalUseCase {
pub struct StubFileManagementUseCase; pub struct StubFileManagementUseCase;
#[async_trait]
impl FileManagementUseCase for StubFileManagementUseCase { impl FileManagementUseCase for StubFileManagementUseCase {
async fn move_file( async fn move_file(
&self, &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<dyn FileUploadUseCase> {
Arc::new(StubFileUploadUseCase)
}
fn create_file_retrieval_use_case(&self) -> Arc<dyn FileRetrievalUseCase> {
Arc::new(StubFileRetrievalUseCase)
}
fn create_file_management_use_case(&self) -> Arc<dyn FileManagementUseCase> {
Arc::new(StubFileManagementUseCase)
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// SearchUseCase // SearchUseCase
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
pub struct StubSearchUseCase; pub struct StubSearchUseCase;
#[async_trait]
impl SearchUseCase for StubSearchUseCase { impl SearchUseCase for StubSearchUseCase {
async fn search( async fn search(
&self, &self,
@@ -642,7 +611,6 @@ use crate::application::ports::dedup_ports::{
pub struct StubDedupPort; pub struct StubDedupPort;
#[async_trait]
impl DedupPort for StubDedupPort { impl DedupPort for StubDedupPort {
async fn store_bytes( async fn store_bytes(
&self, &self,
@@ -741,7 +709,6 @@ use crate::application::ports::cache_ports::{
pub struct StubMetadataCachePort; pub struct StubMetadataCachePort;
#[async_trait]
impl MetadataCachePort for StubMetadataCachePort { impl MetadataCachePort for StubMetadataCachePort {
async fn get_metadata(&self, _path: &Path) -> Option<CachedMetadataDto> { async fn get_metadata(&self, _path: &Path) -> Option<CachedMetadataDto> {
None None
@@ -774,7 +741,6 @@ impl MetadataCachePort for StubMetadataCachePort {
pub struct StubContentCachePort; pub struct StubContentCachePort;
#[async_trait]
impl ContentCachePort for StubContentCachePort { impl ContentCachePort for StubContentCachePort {
fn should_cache(&self, _size: usize) -> bool { fn should_cache(&self, _size: usize) -> bool {
false false
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use std::result::Result; use std::result::Result;
use uuid::Uuid; use uuid::Uuid;
@@ -7,7 +6,6 @@ use crate::domain::entities::contact::AddressBook;
pub type AddressBookRepositoryResult<T> = Result<T, DomainError>; pub type AddressBookRepositoryResult<T> = Result<T, DomainError>;
#[async_trait]
pub trait AddressBookRepository: Send + Sync + 'static { pub trait AddressBookRepository: Send + Sync + 'static {
async fn create_address_book( async fn create_address_book(
&self, &self,
@@ -1,13 +1,11 @@
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::calendar_event::CalendarEvent; use crate::domain::entities::calendar_event::CalendarEvent;
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use uuid::Uuid; use uuid::Uuid;
pub type CalendarEventRepositoryResult<T> = Result<T, DomainError>; pub type CalendarEventRepositoryResult<T> = Result<T, DomainError>;
/// Repository interface for CalendarEvent entity operations /// Repository interface for CalendarEvent entity operations
#[async_trait]
pub trait CalendarEventRepository: Send + Sync + 'static { pub trait CalendarEventRepository: Send + Sync + 'static {
/// Creates a new calendar event /// Creates a new calendar event
async fn create_event( async fn create_event(
@@ -1,12 +1,10 @@
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::calendar::Calendar; use crate::domain::entities::calendar::Calendar;
use async_trait::async_trait;
use uuid::Uuid; use uuid::Uuid;
pub type CalendarRepositoryResult<T> = Result<T, DomainError>; pub type CalendarRepositoryResult<T> = Result<T, DomainError>;
/// Repository interface for Calendar entity operations /// Repository interface for Calendar entity operations
#[async_trait]
pub trait CalendarRepository: Send + Sync + 'static { pub trait CalendarRepository: Send + Sync + 'static {
/// Creates a new calendar /// Creates a new calendar
async fn create_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult<Calendar>; async fn create_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult<Calendar>;
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use std::result::Result; use std::result::Result;
use uuid::Uuid; use uuid::Uuid;
@@ -7,7 +6,6 @@ use crate::domain::entities::contact::{Contact, ContactGroup};
pub type ContactRepositoryResult<T> = Result<T, DomainError>; pub type ContactRepositoryResult<T> = Result<T, DomainError>;
#[async_trait]
pub trait ContactRepository: Send + Sync + 'static { pub trait ContactRepository: Send + Sync + 'static {
async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>; async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>;
async fn update_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>; async fn update_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact>;
@@ -32,7 +30,6 @@ pub trait ContactRepository: Send + Sync + 'static {
) -> ContactRepositoryResult<Vec<Contact>>; ) -> ContactRepositoryResult<Vec<Contact>>;
} }
#[async_trait]
pub trait ContactGroupRepository: Send + Sync + 'static { pub trait ContactGroupRepository: Send + Sync + 'static {
async fn create_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup>; async fn create_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup>;
async fn update_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup>; async fn update_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup>;
@@ -10,7 +10,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures::Stream;
@@ -26,7 +25,6 @@ use crate::domain::services::path_service::StoragePath;
/// ///
/// Encapsulates every operation that queries state without modifying it: /// Encapsulates every operation that queries state without modifying it:
/// get, list, content, stream, mmap, range, path resolution. /// get, list, content, stream, mmap, range, path resolution.
#[async_trait]
pub trait FileReadRepository: Send + Sync + 'static { pub trait FileReadRepository: Send + Sync + 'static {
/// Gets a file by its ID. /// Gets a file by its ID.
async fn get_file(&self, id: &str) -> Result<File, DomainError>; async fn get_file(&self, id: &str) -> Result<File, DomainError>;
@@ -63,7 +61,6 @@ pub trait FileReadRepository: Send + Sync + 'static {
/// ///
/// Covers: upload (buffered + streaming), move, delete, update, /// Covers: upload (buffered + streaming), move, delete, update,
/// and deferred registration for write-behind cache. /// and deferred registration for write-behind cache.
#[async_trait]
pub trait FileWriteRepository: Send + Sync + 'static { pub trait FileWriteRepository: Send + Sync + 'static {
/// Saves a new file from bytes. /// Saves a new file from bytes.
async fn save_file( async fn save_file(
@@ -8,7 +8,6 @@
//! Concrete implementations (filesystem, PostgreSQL, S3, etc.) live in //! Concrete implementations (filesystem, PostgreSQL, S3, etc.) live in
//! the infrastructure layer. //! the infrastructure layer.
use async_trait::async_trait;
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::folder::Folder; 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 /// Defines the CRUD and management operations required for
/// the Folder entity in the storage system. /// the Folder entity in the storage system.
#[async_trait]
pub trait FolderRepository: Send + Sync + 'static { pub trait FolderRepository: Send + Sync + 'static {
/// Creates a new folder /// Creates a new folder
async fn create_folder( async fn create_folder(
@@ -1,6 +1,5 @@
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::session::Session; use crate::domain::entities::session::Session;
use async_trait::async_trait;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum SessionRepositoryError { pub enum SessionRepositoryError {
@@ -29,7 +28,6 @@ impl From<SessionRepositoryError> for DomainError {
} }
} }
#[async_trait]
pub trait SessionRepository: Send + Sync + 'static { pub trait SessionRepository: Send + Sync + 'static {
/// Creates a new session /// Creates a new session
async fn create_session(&self, session: Session) -> SessionRepositoryResult<Session>; async fn create_session(&self, session: Session) -> SessionRepositoryResult<Session>;
@@ -1,10 +1,8 @@
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use async_trait::async_trait;
use std::collections::HashMap; use std::collections::HashMap;
/// Repository for platform settings stored in the database. /// Repository for platform settings stored in the database.
/// Settings are key-value pairs organized by category (e.g., "oidc", "general"). /// Settings are key-value pairs organized by category (e.g., "oidc", "general").
#[async_trait]
pub trait SettingsRepository: Send + Sync + 'static { pub trait SettingsRepository: Send + Sync + 'static {
/// Get a single setting value by key /// Get a single setting value by key
async fn get(&self, key: &str) -> Result<Option<String>, DomainError>; async fn get(&self, key: &str) -> Result<Option<String>, DomainError>;
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use thiserror::Error; use thiserror::Error;
use crate::domain::{ use crate::domain::{
@@ -20,7 +19,6 @@ pub enum ShareRepositoryError {
AlreadyExists(String), AlreadyExists(String),
} }
#[async_trait]
pub trait ShareRepository: Send + Sync + 'static { pub trait ShareRepository: Send + Sync + 'static {
/// Save a new share or update an existing one /// Save a new share or update an existing one
async fn save(&self, share: &Share) -> Result<Share, ShareRepositoryError>; async fn save(&self, share: &Share) -> Result<Share, ShareRepositoryError>;
@@ -1,10 +1,8 @@
use async_trait::async_trait;
use uuid::Uuid; use uuid::Uuid;
use crate::common::errors::Result; use crate::common::errors::Result;
use crate::domain::entities::trashed_item::TrashedItem; use crate::domain::entities::trashed_item::TrashedItem;
#[async_trait]
pub trait TrashRepository: Send + Sync { pub trait TrashRepository: Send + Sync {
async fn add_to_trash(&self, item: &TrashedItem) -> Result<()>; async fn add_to_trash(&self, item: &TrashedItem) -> Result<()>;
async fn get_trash_items(&self, user_id: &Uuid) -> Result<Vec<TrashedItem>>; async fn get_trash_items(&self, user_id: &Uuid) -> Result<Vec<TrashedItem>>;
@@ -1,6 +1,5 @@
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::user::{User, UserRole}; use crate::domain::entities::user::{User, UserRole};
use async_trait::async_trait;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum UserRepositoryError { pub enum UserRepositoryError {
@@ -41,7 +40,6 @@ impl From<UserRepositoryError> for DomainError {
} }
} }
#[async_trait]
pub trait UserRepository: Send + Sync + 'static { pub trait UserRepository: Send + Sync + 'static {
/// Creates a new user /// Creates a new user
async fn create_user(&self, user: User) -> UserRepositoryResult<User>; async fn create_user(&self, user: User) -> UserRepositoryResult<User>;
-2
View File
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use thiserror::Error; use thiserror::Error;
/// Error types for i18n service operations /// Error types for i18n service operations
@@ -54,7 +53,6 @@ impl Locale {
} }
/// Interface for i18n service (primary port) /// Interface for i18n service (primary port)
#[async_trait]
pub trait I18nService: Send + Sync + 'static { pub trait I18nService: Send + Sync + 'static {
/// Get a translation for a key and locale /// Get a translation for a key and locale
async fn translate(&self, key: &str, locale: Locale) -> I18nResult<String>; async fn translate(&self, key: &str, locale: Locale) -> I18nResult<String>;
@@ -4,7 +4,6 @@
//! the `CalendarRepository` and `CalendarEventRepository` domain repositories. //! the `CalendarRepository` and `CalendarEventRepository` domain repositories.
//! It bridges the gap between the application layer and the infrastructure layer. //! It bridges the gap between the application layer and the infrastructure layer.
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@@ -20,18 +19,20 @@ use crate::domain::entities::calendar::Calendar;
use crate::domain::entities::calendar_event::CalendarEvent; use crate::domain::entities::calendar_event::CalendarEvent;
use crate::domain::repositories::calendar_event_repository::CalendarEventRepository; use crate::domain::repositories::calendar_event_repository::CalendarEventRepository;
use crate::domain::repositories::calendar_repository::CalendarRepository; 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 /// Adapter that implements CalendarStoragePort using domain repositories
pub struct CalendarStorageAdapter { pub struct CalendarStorageAdapter {
calendar_repository: Arc<dyn CalendarRepository>, calendar_repository: Arc<CalendarPgRepository>,
event_repository: Arc<dyn CalendarEventRepository>, event_repository: Arc<CalendarEventPgRepository>,
} }
impl CalendarStorageAdapter { impl CalendarStorageAdapter {
/// Creates a new CalendarStorageAdapter with the given repositories /// Creates a new CalendarStorageAdapter with the given repositories
pub fn new( pub fn new(
calendar_repository: Arc<dyn CalendarRepository>, calendar_repository: Arc<CalendarPgRepository>,
event_repository: Arc<dyn CalendarEventRepository>, event_repository: Arc<CalendarEventPgRepository>,
) -> Self { ) -> Self {
Self { Self {
calendar_repository, calendar_repository,
@@ -40,7 +41,6 @@ impl CalendarStorageAdapter {
} }
} }
#[async_trait]
impl CalendarStoragePort for CalendarStorageAdapter { impl CalendarStoragePort for CalendarStorageAdapter {
// Calendar operations // Calendar operations
@@ -4,7 +4,6 @@
//! using the domain repositories. It bridges the gap between the application layer //! using the domain repositories. It bridges the gap between the application layer
//! and the infrastructure layer for CardDAV functionality. //! and the infrastructure layer for CardDAV functionality.
use async_trait::async_trait;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; 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::entities::contact::{Address, AddressBook, Contact, ContactGroup, Email, Phone};
use crate::domain::repositories::address_book_repository::AddressBookRepository; use crate::domain::repositories::address_book_repository::AddressBookRepository;
use crate::domain::repositories::contact_repository::{ContactGroupRepository, ContactRepository}; 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 /// Adapter that implements AddressBookUseCase and ContactUseCase using domain repositories
pub struct ContactStorageAdapter { pub struct ContactStorageAdapter {
address_book_repository: Arc<dyn AddressBookRepository>, address_book_repository: Arc<AddressBookPgRepository>,
contact_repository: Arc<dyn ContactRepository>, contact_repository: Arc<ContactPgRepository>,
group_repository: Arc<dyn ContactGroupRepository>, group_repository: Arc<ContactGroupPgRepository>,
} }
impl ContactStorageAdapter { impl ContactStorageAdapter {
/// Creates a new ContactStorageAdapter with the given repositories /// Creates a new ContactStorageAdapter with the given repositories
pub fn new( pub fn new(
address_book_repository: Arc<dyn AddressBookRepository>, address_book_repository: Arc<AddressBookPgRepository>,
contact_repository: Arc<dyn ContactRepository>, contact_repository: Arc<ContactPgRepository>,
group_repository: Arc<dyn ContactGroupRepository>, group_repository: Arc<ContactGroupPgRepository>,
) -> Self { ) -> Self {
Self { Self {
address_book_repository, address_book_repository,
@@ -217,7 +219,6 @@ impl ContactStorageAdapter {
} }
} }
#[async_trait]
impl AddressBookUseCase for ContactStorageAdapter { impl AddressBookUseCase for ContactStorageAdapter {
async fn create_address_book( async fn create_address_book(
&self, &self,
@@ -423,7 +424,6 @@ impl AddressBookUseCase for ContactStorageAdapter {
} }
} }
#[async_trait]
impl ContactUseCase for ContactStorageAdapter { impl ContactUseCase for ContactStorageAdapter {
async fn create_contact(&self, dto: CreateContactDto) -> Result<ContactDto, DomainError> { async fn create_contact(&self, dto: CreateContactDto) -> Result<ContactDto, DomainError> {
let address_book_id = Self::parse_uuid(&dto.address_book_id, "AddressBook")?; let address_book_id = Self::parse_uuid(&dto.address_book_id, "AddressBook")?;
+1 -2
View File
@@ -2,7 +2,6 @@ use anyhow::Result;
use sqlx::PgPool; use sqlx::PgPool;
use std::sync::Arc; use std::sync::Arc;
use crate::application::ports::auth_ports::TokenServicePort;
use crate::application::services::auth_application_service::AuthApplicationService; use crate::application::services::auth_application_service::AuthApplicationService;
use crate::application::services::folder_service::FolderService; use crate::application::services::folder_service::FolderService;
use crate::common::config::AppConfig; use crate::common::config::AppConfig;
@@ -18,7 +17,7 @@ pub async fn create_auth_services(
folder_service: Option<Arc<FolderService>>, folder_service: Option<Arc<FolderService>>,
) -> Result<AuthServices> { ) -> Result<AuthServices> {
// Create JWT token service (TokenServicePort implementation) // Create JWT token service (TokenServicePort implementation)
let token_service: Arc<dyn TokenServicePort> = Arc::new(JwtTokenService::new( let token_service: Arc<JwtTokenService> = Arc::new(JwtTokenService::new(
config.auth.jwt_secret.clone(), config.auth.jwt_secret.clone(),
config.auth.access_token_expiry_secs, config.auth.access_token_expiry_secs,
config.auth.refresh_token_expiry_secs, config.auth.refresh_token_expiry_secs,
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use sqlx::{PgPool, Row, types::Uuid}; use sqlx::{PgPool, Row, types::Uuid};
use std::sync::Arc; use std::sync::Arc;
@@ -19,7 +18,6 @@ impl AddressBookPgRepository {
} }
} }
#[async_trait]
impl AddressBookRepository for AddressBookPgRepository { impl AddressBookRepository for AddressBookPgRepository {
async fn create_address_book( async fn create_address_book(
&self, &self,
@@ -3,7 +3,6 @@
use crate::application::ports::auth_ports::AppPasswordStoragePort; use crate::application::ports::auth_ports::AppPasswordStoragePort;
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::app_password::AppPassword; use crate::domain::entities::app_password::AppPassword;
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use sqlx::PgPool; use sqlx::PgPool;
use std::sync::Arc; use std::sync::Arc;
@@ -22,7 +21,6 @@ impl AppPasswordPgRepository {
} }
} }
#[async_trait]
impl AppPasswordStoragePort for AppPasswordPgRepository { impl AppPasswordStoragePort for AppPasswordPgRepository {
async fn create(&self, ap: AppPassword) -> Result<AppPassword, DomainError> { async fn create(&self, ap: AppPassword) -> Result<AppPassword, DomainError> {
sqlx::query( sqlx::query(
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use sqlx::{PgPool, Row, types::Uuid}; use sqlx::{PgPool, Row, types::Uuid};
use std::sync::Arc; use std::sync::Arc;
@@ -19,7 +18,6 @@ impl CalendarEventPgRepository {
} }
} }
#[async_trait]
impl CalendarEventRepository for CalendarEventPgRepository { impl CalendarEventRepository for CalendarEventPgRepository {
async fn create_event( async fn create_event(
&self, &self,
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use sqlx::{PgPool, Row, types::Uuid}; use sqlx::{PgPool, Row, types::Uuid};
use std::sync::Arc; use std::sync::Arc;
@@ -19,7 +18,6 @@ impl CalendarPgRepository {
} }
} }
#[async_trait]
impl CalendarRepository for CalendarPgRepository { impl CalendarRepository for CalendarPgRepository {
async fn create_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult<Calendar> { async fn create_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult<Calendar> {
let row = sqlx::query( let row = sqlx::query(
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use sqlx::{PgPool, Row, types::Uuid}; use sqlx::{PgPool, Row, types::Uuid};
@@ -24,7 +23,6 @@ impl ContactGroupPgRepository {
} }
} }
#[async_trait]
impl ContactGroupRepository for ContactGroupPgRepository { impl ContactGroupRepository for ContactGroupPgRepository {
async fn create_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup> { async fn create_group(&self, group: ContactGroup) -> ContactRepositoryResult<ContactGroup> {
sqlx::query( sqlx::query(
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use sqlx::{PgPool, Row, types::Uuid}; use sqlx::{PgPool, Row, types::Uuid};
@@ -63,7 +62,6 @@ impl ContactPgRepository {
} }
} }
#[async_trait]
impl ContactRepository for ContactPgRepository { impl ContactRepository for ContactPgRepository {
async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact> { async fn create_contact(&self, contact: Contact) -> ContactRepositoryResult<Contact> {
// Convert domain entities to persistence DTOs for JSONB serialization // Convert domain entities to persistence DTOs for JSONB serialization
@@ -1,6 +1,5 @@
//! PostgreSQL repository for Device Authorization Grant (RFC 8628) codes. //! PostgreSQL repository for Device Authorization Grant (RFC 8628) codes.
use async_trait::async_trait;
use sqlx::{PgPool, Row}; use sqlx::{PgPool, Row};
use std::sync::Arc; use std::sync::Arc;
@@ -49,7 +48,6 @@ impl DeviceCodePgRepository {
} }
} }
#[async_trait]
impl DeviceCodeStoragePort for DeviceCodePgRepository { impl DeviceCodeStoragePort for DeviceCodePgRepository {
async fn create_device_code(&self, dc: DeviceCode) -> Result<DeviceCode, DomainError> { async fn create_device_code(&self, dc: DeviceCode) -> Result<DeviceCode, DomainError> {
sqlx::query( sqlx::query(
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use sqlx::{PgPool, Row}; use sqlx::{PgPool, Row};
use std::sync::Arc; use std::sync::Arc;
use tracing::error; use tracing::error;
@@ -19,7 +18,6 @@ impl FavoritesPgRepository {
} }
} }
#[async_trait]
impl FavoritesRepositoryPort for FavoritesPgRepository { impl FavoritesRepositoryPort for FavoritesPgRepository {
async fn get_favorites(&self, user_id: &str) -> Result<Vec<FavoriteItemDto>> { async fn get_favorites(&self, user_id: &str) -> Result<Vec<FavoriteItemDto>> {
let user_uuid = Uuid::parse_str(user_id)?; let user_uuid = Uuid::parse_str(user_id)?;
@@ -7,7 +7,6 @@
//! File paths are resolved by JOINing with `storage.folders.path` (the //! File paths are resolved by JOINing with `storage.folders.path` (the
//! materialized path column), so no recursive CTEs or N+1 queries are needed. //! materialized path column), so no recursive CTEs or N+1 queries are needed.
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures::{Stream, TryStreamExt}; use futures::{Stream, TryStreamExt};
use moka::sync::Cache; use moka::sync::Cache;
@@ -17,16 +16,16 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use crate::application::dtos::search_dto::SearchCriteriaDto; use crate::application::dtos::search_dto::SearchCriteriaDto;
use crate::application::ports::dedup_ports::DedupPort;
use crate::application::ports::storage_ports::FileReadPort; use crate::application::ports::storage_ports::FileReadPort;
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::file::File; use crate::domain::entities::file::File;
use crate::domain::services::path_service::StoragePath; use crate::domain::services::path_service::StoragePath;
use crate::infrastructure::services::dedup_service::DedupService;
/// File read repository backed by PostgreSQL metadata + blob storage. /// File read repository backed by PostgreSQL metadata + blob storage.
pub struct FileBlobReadRepository { pub struct FileBlobReadRepository {
pool: Arc<PgPool>, pool: Arc<PgPool>,
dedup: Arc<dyn DedupPort>, dedup: Arc<DedupService>,
/// Lock-free cache: file_id → blob_hash. /// Lock-free cache: file_id → blob_hash.
/// Populated by `get_file()` and `resolve_blob_hash()` (slow path). /// Populated by `get_file()` and `resolve_blob_hash()` (slow path).
/// Entries persist until TTI expiry (30 s idle) or capacity eviction — /// Entries persist until TTI expiry (30 s idle) or capacity eviction —
@@ -37,7 +36,7 @@ pub struct FileBlobReadRepository {
impl FileBlobReadRepository { impl FileBlobReadRepository {
pub fn new( pub fn new(
pool: Arc<PgPool>, pool: Arc<PgPool>,
dedup: Arc<dyn DedupPort>, dedup: Arc<DedupService>,
_folder_repo: Arc<super::folder_db_repository::FolderDbRepository>, _folder_repo: Arc<super::folder_db_repository::FolderDbRepository>,
) -> Self { ) -> Self {
Self { Self {
@@ -115,7 +114,6 @@ impl FileBlobReadRepository {
} }
} }
#[async_trait]
impl FileReadPort for FileBlobReadRepository { impl FileReadPort for FileBlobReadRepository {
async fn get_file(&self, id: &str) -> Result<File, DomainError> { async fn get_file(&self, id: &str) -> Result<File, DomainError> {
let row = sqlx::query_as::< let row = sqlx::query_as::<
@@ -890,14 +888,13 @@ impl FileReadPort for FileBlobReadRepository {
mod tests { mod tests {
use super::*; use super::*;
use crate::common::stubs::StubDedupPort; use crate::common::stubs::StubDedupPort;
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
/// Helper: build a `FileBlobReadRepository` without a real PgPool. /// Helper: build a `FileBlobReadRepository` without a real PgPool.
/// Only the moka `hash_cache` is exercised — no SQL is executed. /// Only the moka `hash_cache` is exercised — no SQL is executed.
fn make_repo() -> FileBlobReadRepository { fn make_repo() -> FileBlobReadRepository {
let _folder_repo = Arc::new(FolderDbRepository::new_stub()); let _folder_repo = Arc::new(FolderDbRepository::new_stub());
// StubDedupPort satisfies the trait but is never called in cache-only tests // StubDedupPort satisfies the trait but is never called in cache-only tests
let dedup: Arc<dyn DedupPort> = Arc::new(StubDedupPort); let dedup: Arc<DedupService> = Arc::new(StubDedupPort);
// PgPool is required by the struct but we won't hit any SQL in these tests. // 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. // We create a repo with a stub pool placeholder — only hash_cache is tested.
FileBlobReadRepository { FileBlobReadRepository {
@@ -7,30 +7,29 @@
//! File paths are resolved by querying the materialized `storage.folders.path` //! File paths are resolved by querying the materialized `storage.folders.path`
//! column (O(1) per lookup), so no recursive CTEs are needed. //! column (O(1) per lookup), so no recursive CTEs are needed.
use async_trait::async_trait;
use sqlx::PgPool; use sqlx::PgPool;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use crate::application::ports::dedup_ports::DedupPort;
use crate::application::ports::storage_ports::{CopyFolderTreeResult, FileWritePort}; use crate::application::ports::storage_ports::{CopyFolderTreeResult, FileWritePort};
use crate::common::errors::DomainError; use crate::common::errors::DomainError;
use crate::domain::entities::file::File; use crate::domain::entities::file::File;
use crate::domain::services::path_service::StoragePath; use crate::domain::services::path_service::StoragePath;
use super::folder_db_repository::FolderDbRepository; use super::folder_db_repository::FolderDbRepository;
use crate::infrastructure::services::dedup_service::DedupService;
/// File write repository backed by PostgreSQL metadata + blob storage. /// File write repository backed by PostgreSQL metadata + blob storage.
pub struct FileBlobWriteRepository { pub struct FileBlobWriteRepository {
pool: Arc<PgPool>, pool: Arc<PgPool>,
dedup: Arc<dyn DedupPort>, dedup: Arc<DedupService>,
folder_repo: Arc<FolderDbRepository>, folder_repo: Arc<FolderDbRepository>,
} }
impl FileBlobWriteRepository { impl FileBlobWriteRepository {
pub fn new( pub fn new(
pool: Arc<PgPool>, pool: Arc<PgPool>,
dedup: Arc<dyn DedupPort>, dedup: Arc<DedupService>,
folder_repo: Arc<FolderDbRepository>, folder_repo: Arc<FolderDbRepository>,
) -> Self { ) -> Self {
Self { Self {
@@ -180,7 +179,6 @@ impl FileBlobWriteRepository {
} }
} }
#[async_trait]
impl FileWritePort for FileBlobWriteRepository { impl FileWritePort for FileBlobWriteRepository {
async fn save_file_from_temp( async fn save_file_from_temp(
&self, &self,
@@ -8,7 +8,6 @@
//! database triggers, so reading a folder's full path is always O(1) — no //! database triggers, so reading a folder's full path is always O(1) — no
//! recursive CTEs or N+1 queries. //! recursive CTEs or N+1 queries.
use async_trait::async_trait;
use sqlx::PgPool; use sqlx::PgPool;
use std::sync::Arc; use std::sync::Arc;
@@ -72,7 +71,6 @@ impl FolderDbRepository {
} }
} }
#[async_trait]
impl FolderRepository for FolderDbRepository { impl FolderRepository for FolderDbRepository {
async fn create_folder( async fn create_folder(
&self, &self,
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use sqlx::{PgPool, Row}; use sqlx::{PgPool, Row};
use std::sync::Arc; use std::sync::Arc;
use tracing::error; use tracing::error;
@@ -19,7 +18,6 @@ impl RecentItemsPgRepository {
} }
} }
#[async_trait]
impl RecentItemsRepositoryPort for RecentItemsPgRepository { impl RecentItemsRepositoryPort for RecentItemsPgRepository {
async fn get_recent_items(&self, user_id: &str, limit: i32) -> Result<Vec<RecentItemDto>> { async fn get_recent_items(&self, user_id: &str, limit: i32) -> Result<Vec<RecentItemDto>> {
let user_uuid = Uuid::parse_str(user_id)?; let user_uuid = Uuid::parse_str(user_id)?;
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use sqlx::{PgPool, Row}; use sqlx::{PgPool, Row};
@@ -39,7 +38,6 @@ impl SessionPgRepository {
} }
} }
#[async_trait]
impl SessionRepository for SessionPgRepository { impl SessionRepository for SessionPgRepository {
/// Creates a new session using a transaction /// Creates a new session using a transaction
async fn create_session(&self, session: Session) -> SessionRepositoryResult<Session> { async fn create_session(&self, session: Session) -> SessionRepositoryResult<Session> {
@@ -291,7 +289,6 @@ impl SessionRepository for SessionPgRepository {
} }
// Implementation of the storage port for the application layer // Implementation of the storage port for the application layer
#[async_trait]
impl SessionStoragePort for SessionPgRepository { impl SessionStoragePort for SessionPgRepository {
async fn create_session(&self, session: Session) -> Result<Session, DomainError> { async fn create_session(&self, session: Session) -> Result<Session, DomainError> {
SessionRepository::create_session(self, session) SessionRepository::create_session(self, session)
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use sqlx::PgPool; use sqlx::PgPool;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@@ -16,7 +15,6 @@ impl SettingsPgRepository {
} }
} }
#[async_trait]
impl SettingsRepository for SettingsPgRepository { impl SettingsRepository for SettingsPgRepository {
async fn get(&self, key: &str) -> Result<Option<String>, DomainError> { async fn get(&self, key: &str) -> Result<Option<String>, DomainError> {
let row = let row =
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use sqlx::{PgPool, Row}; use sqlx::{PgPool, Row};
use std::sync::Arc; use std::sync::Arc;
@@ -72,7 +71,6 @@ impl SharePgRepository {
} }
} }
#[async_trait]
impl ShareStoragePort for SharePgRepository { impl ShareStoragePort for SharePgRepository {
async fn save_share(&self, share: &Share) -> Result<Share, DomainError> { async fn save_share(&self, share: &Share) -> Result<Share, DomainError> {
let row = sqlx::query( let row = sqlx::query(
@@ -4,7 +4,6 @@
//! and `storage.folders`. There is no separate trash table — trashed items //! and `storage.folders`. There is no separate trash table — trashed items
//! are files/folders with `is_trashed = TRUE`. //! are files/folders with `is_trashed = TRUE`.
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use sqlx::PgPool; use sqlx::PgPool;
use std::sync::Arc; use std::sync::Arc;
@@ -65,7 +64,6 @@ impl TrashDbRepository {
} }
} }
#[async_trait]
impl TrashRepository for TrashDbRepository { impl TrashRepository for TrashDbRepository {
async fn add_to_trash(&self, _item: &TrashedItem) -> Result<()> { async fn add_to_trash(&self, _item: &TrashedItem) -> Result<()> {
// No-op: the actual flagging is done by FileWritePort::move_to_trash // No-op: the actual flagging is done by FileWritePort::move_to_trash
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use sqlx::{PgPool, Row}; use sqlx::{PgPool, Row};
use std::sync::Arc; use std::sync::Arc;
@@ -44,7 +43,6 @@ impl UserPgRepository {
} }
} }
#[async_trait]
impl UserRepository for UserPgRepository { impl UserRepository for UserPgRepository {
/// Creates a new user using a transaction /// Creates a new user using a transaction
async fn create_user(&self, user: User) -> UserRepositoryResult<User> { async fn create_user(&self, user: User) -> UserRepositoryResult<User> {
@@ -619,7 +617,6 @@ impl UserRepository for UserPgRepository {
} }
// Storage port implementation for the application layer // Storage port implementation for the application layer
#[async_trait]
impl UserStoragePort for UserPgRepository { impl UserStoragePort for UserPgRepository {
async fn create_user(&self, user: User) -> Result<User, DomainError> { async fn create_user(&self, user: User) -> Result<User, DomainError> {
UserRepository::create_user(self, user) UserRepository::create_user(self, user)
@@ -15,7 +15,6 @@
//! 3. HEAD /api/uploads/:id → Check progress //! 3. HEAD /api/uploads/:id → Check progress
//! 4. POST /api/uploads/:id/complete → Finalize and assemble //! 4. POST /api/uploads/:id/complete → Finalize and assemble
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use dashmap::DashMap; use dashmap::DashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -755,7 +754,6 @@ impl ChunkedUploadService {
// ─── Port implementation ───────────────────────────────────────────────────── // ─── Port implementation ─────────────────────────────────────────────────────
#[async_trait]
impl ChunkedUploadPort for ChunkedUploadService { impl ChunkedUploadPort for ChunkedUploadService {
async fn create_session( async fn create_session(
&self, &self,
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use flate2::Compression; use flate2::Compression;
use flate2::bufread::GzDecoder; use flate2::bufread::GzDecoder;
@@ -43,7 +42,6 @@ impl From<CompressionLevel> for Compression {
const COMPRESSION_SIZE_THRESHOLD: u64 = 1024 * 50; // 50KB const COMPRESSION_SIZE_THRESHOLD: u64 = 1024 * 50; // 50KB
/// Interface for compression services /// Interface for compression services
#[async_trait]
pub trait CompressionService: Send + Sync { pub trait CompressionService: Send + Sync {
/// Compresses data in memory /// Compresses data in memory
async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result<Vec<u8>>; async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result<Vec<u8>>;
@@ -88,7 +86,6 @@ impl GzipCompressionService {
} }
} }
#[async_trait]
impl CompressionService for GzipCompressionService { impl CompressionService for GzipCompressionService {
/// Compresses data in memory using Gzip /// Compresses data in memory using Gzip
async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result<Vec<u8>> { async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result<Vec<u8>> {
@@ -258,7 +255,6 @@ impl From<PortCompressionLevel> for CompressionLevel {
} }
} }
#[async_trait]
impl CompressionPort for GzipCompressionService { impl CompressionPort for GzipCompressionService {
async fn compress_data( async fn compress_data(
&self, &self,
@@ -31,7 +31,6 @@
//! - 30-50% storage reduction typical //! - 30-50% storage reduction typical
//! - Faster uploads for existing content (instant dedup) //! - Faster uploads for existing content (instant dedup)
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures::stream::{self, StreamExt}; use futures::stream::{self, StreamExt};
use futures::{Stream, TryStreamExt}; use futures::{Stream, TryStreamExt};
@@ -802,7 +801,6 @@ impl DedupService {
// ─── Port implementation ───────────────────────────────────────────────────── // ─── Port implementation ─────────────────────────────────────────────────────
#[async_trait]
impl DedupPort for DedupService { impl DedupPort for DedupService {
async fn store_bytes( async fn store_bytes(
&self, &self,
@@ -198,9 +198,7 @@ pub type SharedFileContentCache = Arc<FileContentCache>;
// ─── ContentCachePort implementation ───────────────────────── // ─── ContentCachePort implementation ─────────────────────────
use crate::application::ports::cache_ports::ContentCachePort; use crate::application::ports::cache_ports::ContentCachePort;
use async_trait::async_trait;
#[async_trait]
impl ContentCachePort for FileContentCache { impl ContentCachePort for FileContentCache {
fn should_cache(&self, size: usize) -> bool { fn should_cache(&self, size: usize) -> bool {
FileContentCache::should_cache(self, size) FileContentCache::should_cache(self, size)
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
@@ -63,7 +62,6 @@ impl FileSystemI18nService {
} }
} }
#[async_trait]
impl I18nService for FileSystemI18nService { impl I18nService for FileSystemI18nService {
async fn translate(&self, key: &str, locale: Locale) -> I18nResult<String> { async fn translate(&self, key: &str, locale: Locale) -> I18nResult<String> {
// Check if translations are cached // Check if translations are cached
@@ -10,7 +10,6 @@
//! - Supports JPEG, PNG, GIF → WebP conversion //! - Supports JPEG, PNG, GIF → WebP conversion
//! - Falls back to original if conversion fails or result is larger //! - Falls back to original if conversion fails or result is larger
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use image::ImageFormat; use image::ImageFormat;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -392,7 +391,6 @@ impl From<PortOutputFormat> for OutputFormat {
} }
} }
#[async_trait]
impl ImageTranscodePort for ImageTranscodeService { impl ImageTranscodePort for ImageTranscodeService {
fn can_transcode(&self, mime_type: &str) -> bool { fn can_transcode(&self, mime_type: &str) -> bool {
ImageTranscodeService::can_transcode(mime_type) ImageTranscodeService::can_transcode(mime_type)
@@ -5,7 +5,6 @@
//! Supports both RSA (RS256, RS384, RS512) and EC (ES256, ES384) algorithms. //! Supports both RSA (RS256, RS384, RS512) and EC (ES256, ES384) algorithms.
//! Compatible with Authentik, Keycloak, and any standard OIDC provider. //! Compatible with Authentik, Keycloak, and any standard OIDC provider.
use async_trait::async_trait;
use serde::Deserialize; use serde::Deserialize;
use std::sync::RwLock; use std::sync::RwLock;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -293,7 +292,6 @@ impl OidcService {
} }
} }
#[async_trait]
impl OidcServicePort for OidcService { impl OidcServicePort for OidcService {
async fn get_authorize_url( async fn get_authorize_url(
&self, &self,
@@ -12,7 +12,6 @@
use argon2::password_hash::SaltString; use argon2::password_hash::SaltString;
use argon2::{Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version}; use argon2::{Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version};
use async_trait::async_trait;
use rand_core::OsRng; use rand_core::OsRng;
use crate::application::ports::auth_ports::PasswordHasherPort; use crate::application::ports::auth_ports::PasswordHasherPort;
@@ -57,7 +56,6 @@ impl Argon2PasswordHasher {
} }
} }
#[async_trait]
impl PasswordHasherPort for Argon2PasswordHasher { impl PasswordHasherPort for Argon2PasswordHasher {
async fn hash_password(&self, password: &str) -> Result<String, DomainError> { async fn hash_password(&self, password: &str) -> Result<String, DomainError> {
let pwd = password.to_owned(); let pwd = password.to_owned();
@@ -5,7 +5,6 @@
//! //!
//! StoragePath (Value Object) remains in domain/services/path_service.rs //! StoragePath (Value Object) remains in domain/services/path_service.rs
use async_trait::async_trait;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tokio::fs; use tokio::fs;
@@ -116,7 +115,6 @@ impl PathService {
} }
} }
#[async_trait]
impl StoragePort for PathService { impl StoragePort for PathService {
fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf { fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf {
let mut path = self.root_path.clone(); let mut path = self.root_path.clone();
@@ -1,4 +1,3 @@
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use image::{ImageFormat, imageops::FilterType}; use image::{ImageFormat, imageops::FilterType};
/** /**
@@ -458,7 +457,6 @@ impl From<PortThumbnailSize> for ThumbnailSize {
} }
} }
#[async_trait]
impl ThumbnailPort for ThumbnailService { impl ThumbnailPort for ThumbnailService {
fn is_supported_image(&self, mime_type: &str) -> bool { fn is_supported_image(&self, mime_type: &str) -> bool {
ThumbnailService::is_supported_image(mime_type) ThumbnailService::is_supported_image(mime_type)
@@ -5,6 +5,7 @@ use tracing::{debug, error, info, instrument};
use crate::common::errors::Result; use crate::common::errors::Result;
use crate::domain::repositories::trash_repository::TrashRepository; 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. /// 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 /// in **2 SQL statements inside a single transaction**, instead of the
/// previous N+1 pattern that issued 3 queries per expired item. /// previous N+1 pattern that issued 3 queries per expired item.
pub struct TrashCleanupService { pub struct TrashCleanupService {
trash_repository: Arc<dyn TrashRepository>, trash_repository: Arc<TrashDbRepository>,
cleanup_interval_hours: u64, cleanup_interval_hours: u64,
} }
impl TrashCleanupService { impl TrashCleanupService {
pub fn new(trash_repository: Arc<dyn TrashRepository>, cleanup_interval_hours: u64) -> Self { pub fn new(trash_repository: Arc<TrashDbRepository>, cleanup_interval_hours: u64) -> Self {
Self { Self {
trash_repository, trash_repository,
cleanup_interval_hours: cleanup_interval_hours.max(1), // Minimum 1 hour 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. /// Bulk-delete all expired trash items in a single transaction.
#[instrument(skip(trash_repository))] #[instrument(skip(trash_repository))]
async fn cleanup_expired_items(trash_repository: Arc<dyn TrashRepository>) -> Result<()> { async fn cleanup_expired_items(trash_repository: Arc<TrashDbRepository>) -> Result<()> {
debug!("Starting bulk cleanup of expired trash items"); debug!("Starting bulk cleanup of expired trash items");
let (files, folders) = trash_repository.delete_expired_bulk().await?; let (files, folders) = trash_repository.delete_expired_bulk().await?;
+6 -6
View File
@@ -5,7 +5,6 @@ use crate::{
application::ports::zip_ports::ZipPort, application::ports::zip_ports::ZipPort,
common::errors::{DomainError, ErrorKind, Result}, common::errors::{DomainError, ErrorKind, Result},
}; };
use async_trait::async_trait;
use async_zip::base::write::ZipFileWriter; use async_zip::base::write::ZipFileWriter;
use async_zip::{Compression, ZipEntryBuilder}; use async_zip::{Compression, ZipEntryBuilder};
use futures::StreamExt; use futures::StreamExt;
@@ -17,6 +16,8 @@ use thiserror::Error;
use tokio::io::BufWriter; use tokio::io::BufWriter;
use tokio_util::compat::Compat; use tokio_util::compat::Compat;
use tracing::*; use tracing::*;
use crate::application::services::file_retrieval_service::FileRetrievalService;
use crate::application::services::folder_service::FolderService;
/// Error related to ZIP file creation /// Error related to ZIP file creation
#[derive(Debug, Error)] #[derive(Debug, Error)]
@@ -53,15 +54,15 @@ type AsyncZipWriter = ZipFileWriter<Compat<BufWriter<tokio::fs::File>>>;
/// `tokio::io::BufWriter` → `tokio::fs::File`, so **no Tokio worker is ever /// `tokio::io::BufWriter` → `tokio::fs::File`, so **no Tokio worker is ever
/// blocked** by disk I/O or compression. /// blocked** by disk I/O or compression.
pub struct ZipService { pub struct ZipService {
file_service: Arc<dyn FileRetrievalUseCase>, file_service: Arc<FileRetrievalService>,
folder_service: Arc<dyn FolderUseCase>, folder_service: Arc<FolderService>,
} }
impl ZipService { impl ZipService {
/// Creates a new instance of the ZIP service /// Creates a new instance of the ZIP service
pub fn new( pub fn new(
file_service: Arc<dyn FileRetrievalUseCase>, file_service: Arc<FileRetrievalService>,
folder_service: Arc<dyn FolderUseCase>, folder_service: Arc<FolderService>,
) -> Self { ) -> Self {
Self { Self {
file_service, file_service,
@@ -239,7 +240,6 @@ impl ZipService {
// ─── Port implementation ───────────────────────────────────────────────────── // ─── Port implementation ─────────────────────────────────────────────────────
#[async_trait]
impl ZipPort for ZipService { impl ZipPort for ZipService {
async fn create_folder_zip( async fn create_folder_zip(
&self, &self,
@@ -14,6 +14,7 @@ use crate::application::dtos::settings_dto::{
use crate::common::di::AppState; use crate::common::di::AppState;
use crate::interfaces::errors::AppError; use crate::interfaces::errors::AppError;
use std::sync::Arc; use std::sync::Arc;
use crate::application::ports::auth_ports::TokenServicePort;
/// Admin API routes — all require admin role. /// Admin API routes — all require admin role.
pub fn admin_routes() -> Router<Arc<AppState>> { pub fn admin_routes() -> Router<Arc<AppState>> {
@@ -35,6 +35,7 @@ use crate::application::ports::calendar_ports::CalendarUseCase;
use crate::common::di::AppState; use crate::common::di::AppState;
use crate::interfaces::errors::AppError; use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::CurrentUser; use crate::interfaces::middleware::auth::CurrentUser;
use crate::application::services::calendar_service::CalendarService;
const HEADER_DAV: HeaderName = HeaderName::from_static("dav"); const HEADER_DAV: HeaderName = HeaderName::from_static("dav");
@@ -127,7 +128,7 @@ fn extract_user(req: &Request<Body>) -> Result<CurrentUser, AppError> {
.ok_or_else(|| AppError::unauthorized("Authentication required")) .ok_or_else(|| AppError::unauthorized("Authentication required"))
} }
fn get_calendar_service(state: &AppState) -> Result<&Arc<dyn CalendarUseCase>, AppError> { fn get_calendar_service(state: &AppState) -> Result<&Arc<CalendarService>, AppError> {
state.calendar_use_case.as_ref().ok_or_else(|| { state.calendar_use_case.as_ref().ok_or_else(|| {
AppError::new( AppError::new(
StatusCode::NOT_IMPLEMENTED, StatusCode::NOT_IMPLEMENTED,
@@ -35,6 +35,7 @@ use crate::application::ports::carddav_ports::{AddressBookUseCase, ContactUseCas
use crate::common::di::AppState; use crate::common::di::AppState;
use crate::interfaces::errors::AppError; use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::CurrentUser; use crate::interfaces::middleware::auth::CurrentUser;
use crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter;
const HEADER_DAV: HeaderName = HeaderName::from_static("dav"); const HEADER_DAV: HeaderName = HeaderName::from_static("dav");
@@ -115,7 +116,7 @@ fn extract_user(req: &Request<Body>) -> Result<CurrentUser, AppError> {
.ok_or_else(|| AppError::unauthorized("Authentication required")) .ok_or_else(|| AppError::unauthorized("Authentication required"))
} }
fn get_addressbook_service(state: &AppState) -> Result<&Arc<dyn AddressBookUseCase>, AppError> { fn get_addressbook_service(state: &AppState) -> Result<&Arc<ContactStorageAdapter>, AppError> {
state.addressbook_use_case.as_ref().ok_or_else(|| { state.addressbook_use_case.as_ref().ok_or_else(|| {
AppError::new( AppError::new(
StatusCode::NOT_IMPLEMENTED, StatusCode::NOT_IMPLEMENTED,
@@ -125,7 +126,7 @@ fn get_addressbook_service(state: &AppState) -> Result<&Arc<dyn AddressBookUseCa
}) })
} }
fn get_contact_service(state: &AppState) -> Result<&Arc<dyn ContactUseCase>, AppError> { fn get_contact_service(state: &AppState) -> Result<&Arc<ContactStorageAdapter>, AppError> {
state.contact_use_case.as_ref().ok_or_else(|| { state.contact_use_case.as_ref().ok_or_else(|| {
AppError::new( AppError::new(
StatusCode::NOT_IMPLEMENTED, StatusCode::NOT_IMPLEMENTED,
@@ -21,6 +21,9 @@ use crate::application::ports::chunked_upload_ports::DEFAULT_CHUNK_SIZE;
use crate::common::di::AppState; use crate::common::di::AppState;
use crate::domain::errors::ErrorKind; use crate::domain::errors::ErrorKind;
use crate::interfaces::middleware::auth::AuthUser; 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 /// Request body for creating an upload session
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -10,6 +10,7 @@ use tracing::{error, info};
use crate::application::ports::favorites_ports::FavoritesUseCase; use crate::application::ports::favorites_ports::FavoritesUseCase;
use crate::interfaces::middleware::auth::AuthUser; use crate::interfaces::middleware::auth::AuthUser;
use crate::application::services::favorites_service::FavoritesService;
/// Single item in a batch-add-favorites request. /// Single item in a batch-add-favorites request.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -26,7 +27,7 @@ pub struct BatchFavoritesRequest {
/// Handler for favorite-related API endpoints /// Handler for favorite-related API endpoints
pub async fn get_favorites( pub async fn get_favorites(
State(favorites_service): State<Arc<dyn FavoritesUseCase>>, State(favorites_service): State<Arc<FavoritesService>>,
auth_user: AuthUser, auth_user: AuthUser,
) -> impl IntoResponse { ) -> impl IntoResponse {
let user_id = &auth_user.id; let user_id = &auth_user.id;
@@ -51,7 +52,7 @@ pub async fn get_favorites(
/// Add an item to user's favorites /// Add an item to user's favorites
pub async fn add_favorite( pub async fn add_favorite(
State(favorites_service): State<Arc<dyn FavoritesUseCase>>, State(favorites_service): State<Arc<FavoritesService>>,
auth_user: AuthUser, auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>, Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -94,7 +95,7 @@ pub async fn add_favorite(
/// Remove an item from user's favorites /// Remove an item from user's favorites
pub async fn remove_favorite( pub async fn remove_favorite(
State(favorites_service): State<Arc<dyn FavoritesUseCase>>, State(favorites_service): State<Arc<FavoritesService>>,
auth_user: AuthUser, auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>, Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -138,7 +139,7 @@ pub async fn remove_favorite(
/// Add multiple items to favourites in a single transaction. /// Add multiple items to favourites in a single transaction.
/// POST /api/favorites/batch /// POST /api/favorites/batch
pub async fn batch_add_favorites( pub async fn batch_add_favorites(
State(favorites_service): State<Arc<dyn FavoritesUseCase>>, State(favorites_service): State<Arc<FavoritesService>>,
auth_user: AuthUser, auth_user: AuthUser,
Json(body): Json<BatchFavoritesRequest>, Json(body): Json<BatchFavoritesRequest>,
) -> impl IntoResponse { ) -> impl IntoResponse {
+4 -1
View File
@@ -14,6 +14,9 @@ use crate::application::ports::file_ports::OptimizedFileContent;
use crate::common::di::AppState; use crate::common::di::AppState;
use crate::interfaces::middleware::auth::{AuthUser, OptionalUserId}; use crate::interfaces::middleware::auth::{AuthUser, OptionalUserId};
use std::sync::Arc; 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. * Type aliases for dependency injection state.
@@ -306,7 +309,7 @@ impl FileHandler {
let file_path = storage_root.join(&file.path); let file_path = storage_root.join(&file.path);
match thumbnail_service match thumbnail_service
.get_thumbnail(&id, thumb_size, &file_path) .get_thumbnail(&id, thumb_size.into(), &file_path)
.await .await
{ {
Ok(data) => { Ok(data) => {
+13 -1
View File
@@ -18,6 +18,8 @@ use crate::application::services::folder_service::FolderService;
use crate::common::di::AppState as GlobalAppState; use crate::common::di::AppState as GlobalAppState;
use crate::common::errors::ErrorKind; use crate::common::errors::ErrorKind;
use crate::interfaces::middleware::auth::AuthUser; use crate::interfaces::middleware::auth::AuthUser;
use crate::application::ports::file_ports::FileRetrievalUseCase;
use crate::application::ports::trash_ports::TrashUseCase;
type AppState = Arc<FolderService>; type AppState = Arc<FolderService>;
@@ -423,7 +425,17 @@ impl FolderHandler {
tracing::info!("Preparing ZIP for folder: {} ({})", folder.name, id); tracing::info!("Preparing ZIP for folder: {} ({})", folder.name, id);
// Use ZIP service from DI container // 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) // Create the ZIP archive (written to a temp file, O(1) RAM)
match zip_service.create_folder_zip(&id, &folder.name).await { match zip_service.create_folder_zip(&id, &folder.name).await {
@@ -10,6 +10,7 @@ use tracing::{error, info};
use crate::application::ports::recent_ports::RecentItemsUseCase; use crate::application::ports::recent_ports::RecentItemsUseCase;
use crate::interfaces::middleware::auth::AuthUser; use crate::interfaces::middleware::auth::AuthUser;
use crate::application::services::recent_service::RecentService;
/// Query parameters for getting recent items /// Query parameters for getting recent items
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -20,7 +21,7 @@ pub struct GetRecentParams {
/// Get user's recent items /// Get user's recent items
pub async fn get_recent_items( pub async fn get_recent_items(
State(recent_service): State<Arc<dyn RecentItemsUseCase>>, State(recent_service): State<Arc<RecentService>>,
auth_user: AuthUser, auth_user: AuthUser,
Query(params): Query<GetRecentParams>, Query(params): Query<GetRecentParams>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -46,7 +47,7 @@ pub async fn get_recent_items(
/// Record access to an item /// Record access to an item
pub async fn record_item_access( pub async fn record_item_access(
State(recent_service): State<Arc<dyn RecentItemsUseCase>>, State(recent_service): State<Arc<RecentService>>,
auth_user: AuthUser, auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>, Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -92,7 +93,7 @@ pub async fn record_item_access(
/// Remove an item from recents /// Remove an item from recents
pub async fn remove_from_recent( pub async fn remove_from_recent(
State(recent_service): State<Arc<dyn RecentItemsUseCase>>, State(recent_service): State<Arc<RecentService>>,
auth_user: AuthUser, auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>, Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -138,7 +139,7 @@ pub async fn remove_from_recent(
/// Clear all recent items /// Clear all recent items
pub async fn clear_recent_items( pub async fn clear_recent_items(
State(recent_service): State<Arc<dyn RecentItemsUseCase>>, State(recent_service): State<Arc<RecentService>>,
auth_user: AuthUser, auth_user: AuthUser,
) -> impl IntoResponse { ) -> impl IntoResponse {
let user_id = &auth_user.id; let user_id = &auth_user.id;
@@ -10,6 +10,7 @@ use crate::application::dtos::search_dto::SearchCriteriaDto;
use crate::common::di::AppState; use crate::common::di::AppState;
use crate::interfaces::middleware::auth::AuthUser; use crate::interfaces::middleware::auth::AuthUser;
use std::sync::Arc; use std::sync::Arc;
use crate::application::ports::inbound::SearchUseCase;
/** /**
* Handler for search operations through the API. * Handler for search operations through the API.
+8 -7
View File
@@ -18,6 +18,7 @@ use crate::{
domain::entities::share::ShareItemType, domain::entities::share::ShareItemType,
interfaces::middleware::auth::OptionalAuthUser, interfaces::middleware::auth::OptionalAuthUser,
}; };
use crate::application::services::share_service::ShareService;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct GetSharesQuery { pub struct GetSharesQuery {
@@ -34,7 +35,7 @@ pub struct VerifyPasswordRequest {
/// Create a new shared link /// Create a new shared link
pub async fn create_shared_link( pub async fn create_shared_link(
State(share_use_case): State<Arc<dyn ShareUseCase>>, State(share_use_case): State<Arc<ShareService>>,
auth_user: OptionalAuthUser, auth_user: OptionalAuthUser,
Json(dto): Json<CreateShareDto>, Json(dto): Json<CreateShareDto>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -57,7 +58,7 @@ pub async fn create_shared_link(
/// Get information about a specific shared link by ID /// Get information about a specific shared link by ID
pub async fn get_shared_link( pub async fn get_shared_link(
State(share_use_case): State<Arc<dyn ShareUseCase>>, State(share_use_case): State<Arc<ShareService>>,
Path(id): Path<String>, Path(id): Path<String>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match share_use_case.get_shared_link(&id).await { 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. /// Get all shared links created by the current user.
/// Supports optional filtering by item_id + item_type query params. /// Supports optional filtering by item_id + item_type query params.
pub async fn get_user_shares( pub async fn get_user_shares(
State(share_use_case): State<Arc<dyn ShareUseCase>>, State(share_use_case): State<Arc<ShareService>>,
auth_user: OptionalAuthUser, auth_user: OptionalAuthUser,
Query(query): Query<GetSharesQuery>, Query(query): Query<GetSharesQuery>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -128,7 +129,7 @@ pub async fn get_user_shares(
/// Update a shared link's properties /// Update a shared link's properties
pub async fn update_shared_link( pub async fn update_shared_link(
State(share_use_case): State<Arc<dyn ShareUseCase>>, State(share_use_case): State<Arc<ShareService>>,
Path(id): Path<String>, Path(id): Path<String>,
Json(dto): Json<UpdateShareDto>, Json(dto): Json<UpdateShareDto>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -148,7 +149,7 @@ pub async fn update_shared_link(
/// Delete a shared link /// Delete a shared link
pub async fn delete_shared_link( pub async fn delete_shared_link(
State(share_use_case): State<Arc<dyn ShareUseCase>>, State(share_use_case): State<Arc<ShareService>>,
Path(id): Path<String>, Path(id): Path<String>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match share_use_case.delete_shared_link(&id).await { 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 /// Access a shared item via its token
pub async fn access_shared_item( pub async fn access_shared_item(
State(share_use_case): State<Arc<dyn ShareUseCase>>, State(share_use_case): State<Arc<ShareService>>,
Path(token): Path<String>, Path(token): Path<String>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Register the access // Register the access
@@ -204,7 +205,7 @@ pub async fn access_shared_item(
/// Verify password for a password-protected shared item /// Verify password for a password-protected shared item
pub async fn verify_shared_item_password( pub async fn verify_shared_item_password(
State(share_use_case): State<Arc<dyn ShareUseCase>>, State(share_use_case): State<Arc<ShareService>>,
Path(token): Path<String>, Path(token): Path<String>,
Json(req): Json<VerifyPasswordRequest>, Json(req): Json<VerifyPasswordRequest>,
) -> impl IntoResponse { ) -> impl IntoResponse {
+1 -1
View File
@@ -4,7 +4,7 @@ use axum::http::StatusCode;
use serde_json::json; use serde_json::json;
use tracing::{debug, error, instrument, warn}; 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::common::di::AppState;
use crate::interfaces::middleware::auth::{AuthUser, OptionalAuthUser}; use crate::interfaces::middleware::auth::{AuthUser, OptionalAuthUser};
use std::sync::Arc; use std::sync::Arc;
@@ -28,8 +28,11 @@ use crate::common::di::AppState;
use crate::infrastructure::services::path_resolver_service::ResolvedResource; use crate::infrastructure::services::path_resolver_service::ResolvedResource;
use crate::interfaces::errors::AppError; use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::CurrentUser; 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 percent_encoding::{AsciiSet, NON_ALPHANUMERIC, percent_decode_str, utf8_percent_encode};
use std::sync::Arc; use std::sync::Arc;
use crate::application::ports::file_ports::{FileManagementUseCase, FileUploadUseCase};
/// Characters that MUST NOT be percent-encoded inside a URI path segment. /// Characters that MUST NOT be percent-encoded inside a URI path segment.
/// RFC 3986 §3.3 pchar = unreserved / pct-encoded / sub-delims / ":" / "@" /// RFC 3986 §3.3 pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
@@ -394,8 +397,8 @@ async fn build_streaming_propfind_response(
depth: &str, depth: &str,
base_href: &str, base_href: &str,
propfind_request: PropFindRequest, propfind_request: PropFindRequest,
folder_service: std::sync::Arc<dyn FolderUseCase>, folder_service: std::sync::Arc<FolderService>,
file_retrieval_service: std::sync::Arc<dyn FileRetrievalUseCase>, file_retrieval_service: std::sync::Arc<FileRetrievalService>,
) -> Result<Response<Body>, AppError> { ) -> Result<Response<Body>, AppError> {
let depth = depth.to_string(); let depth = depth.to_string();
let base_href = base_href.to_string(); let base_href = base_href.to_string();
@@ -23,6 +23,7 @@ use std::sync::Arc;
use crate::application::services::wopi_lock_service::WopiLockService; use crate::application::services::wopi_lock_service::WopiLockService;
use crate::application::services::wopi_token_service::WopiTokenService; use crate::application::services::wopi_token_service::WopiTokenService;
use crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService; use crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService;
use crate::application::ports::file_ports::{FileRetrievalUseCase, FileUploadUseCase};
/// Shared state for WOPI handlers. /// Shared state for WOPI handlers.
#[derive(Clone)] #[derive(Clone)]
+1 -1
View File
@@ -8,6 +8,7 @@ use axum::{
use serde_json::json; use serde_json::json;
use std::sync::Arc; use std::sync::Arc;
use tower_http::{compression::CompressionLayer, trace::TraceLayer}; use tower_http::{compression::CompressionLayer, trace::TraceLayer};
use crate::application::services::batch_operations::BatchOperationService;
/// Returns the application version from Cargo.toml (compile-time constant) /// Returns the application version from Cargo.toml (compile-time constant)
async fn get_version() -> AxumJson<serde_json::Value> { async fn get_version() -> AxumJson<serde_json::Value> {
@@ -17,7 +18,6 @@ async fn get_version() -> AxumJson<serde_json::Value> {
})) }))
} }
use crate::application::services::batch_operations::BatchOperationService;
use crate::interfaces::api::handlers::admin_handler; use crate::interfaces::api::handlers::admin_handler;
use crate::interfaces::api::handlers::batch_handler::{self, BatchHandlerState}; use crate::interfaces::api::handlers::batch_handler::{self, BatchHandlerState};

Some files were not shown because too many files have changed in this diff Show More