fix: resolve all clippy warnings and convert integration_tests to custom cfg
- Add type aliases (FileRow, FolderRow, FolderRowPaginated, FolderRowOptUser) to reduce type complexity - Simplify redundant closures in app_password_handler and webdav_handler - Remove needless borrow in auth_handler - Collapse nested if/let chains in login_lockout, webdav_lock, auth, rate_limit - Box LockEntry in acquire() Err variant to fix large enum variant warning - Rename DeviceCodeStatus::from_str to parse to avoid should_implement_trait lint - Add #[allow(clippy::too_many_arguments)] and #[allow(clippy::result_unit_err)] where appropriate - Convert integration_tests from cargo feature to custom cfg attribute - Add check-cfg lint config in Cargo.toml for integration_tests cfg
This commit is contained in:
+3
-1
@@ -57,7 +57,9 @@ socket2 = { version = "0.6.2", features = ["all"] }
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
test_utils = ["mockall"]
|
test_utils = ["mockall"]
|
||||||
integration_tests = []
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(integration_tests)'] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ 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::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::common::errors::DomainError;
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────
|
||||||
// Upload port
|
// Upload port
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
application::dtos::{
|
application::dtos::{
|
||||||
pagination::PaginatedResponseDto,
|
pagination::PaginatedResponseDto,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
use crate::application::dtos::trash_dto::TrashedItemDto;
|
use crate::application::dtos::trash_dto::TrashedItemDto;
|
||||||
use crate::common::errors::Result;
|
use crate::common::errors::Result;
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use crate::application::services::auth_application_service::AuthApplicationServi
|
|||||||
use crate::common::config::OidcConfig;
|
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::repositories::pg::SettingsPgRepository;
|
use crate::infrastructure::repositories::pg::SettingsPgRepository;
|
||||||
|
use crate::infrastructure::services::oidc_service::OidcService;
|
||||||
|
|
||||||
/// Admin settings service — manages platform configuration in the database.
|
/// Admin settings service — manages platform configuration in the database.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ use crate::application::ports::auth_ports::{
|
|||||||
};
|
};
|
||||||
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 crate::infrastructure::repositories::pg::AppPasswordPgRepository;
|
||||||
|
use crate::infrastructure::repositories::pg::UserPgRepository;
|
||||||
|
use crate::infrastructure::services::password_hasher::Argon2PasswordHasher;
|
||||||
use chrono::{Duration, Utc};
|
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;
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ use crate::application::ports::auth_ports::{
|
|||||||
UserStoragePort,
|
UserStoragePort,
|
||||||
};
|
};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::inbound::FolderUseCase;
|
||||||
|
use crate::application::services::folder_service::FolderService;
|
||||||
use crate::common::config::OidcConfig;
|
use crate::common::config::OidcConfig;
|
||||||
use crate::common::errors::{DomainError, ErrorKind};
|
use crate::common::errors::{DomainError, ErrorKind};
|
||||||
use crate::domain::entities::session::Session;
|
use crate::domain::entities::session::Session;
|
||||||
use crate::domain::entities::user::{User, UserRole};
|
use crate::domain::entities::user::{User, UserRole};
|
||||||
|
use crate::infrastructure::repositories::pg::SessionPgRepository;
|
||||||
|
use crate::infrastructure::repositories::pg::UserPgRepository;
|
||||||
|
use crate::infrastructure::services::jwt_service::JwtTokenService;
|
||||||
|
use crate::infrastructure::services::oidc_service::OidcService;
|
||||||
|
use crate::infrastructure::services::password_hasher::Argon2PasswordHasher;
|
||||||
use moka::sync::Cache;
|
use moka::sync::Cache;
|
||||||
use std::path::PathBuf;
|
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)]
|
||||||
@@ -145,11 +145,7 @@ impl AuthApplicationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the OIDC service
|
/// Configures the OIDC service
|
||||||
pub fn with_oidc(
|
pub fn with_oidc(self, oidc_service: Arc<OidcService>, oidc_config: OidcConfig) -> Self {
|
||||||
self,
|
|
||||||
oidc_service: Arc<OidcService>,
|
|
||||||
oidc_config: OidcConfig,
|
|
||||||
) -> Self {
|
|
||||||
{
|
{
|
||||||
let mut state = self.oidc.write().unwrap();
|
let mut state = self.oidc.write().unwrap();
|
||||||
state.service = Some(oidc_service);
|
state.service = Some(oidc_service);
|
||||||
@@ -308,12 +304,7 @@ impl AuthApplicationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check email uniqueness
|
// Check email uniqueness
|
||||||
if self
|
if self.user_storage.get_user_by_email(&email).await.is_ok() {
|
||||||
.user_storage
|
|
||||||
.get_user_by_email(&email)
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
return Err(DomainError::new(
|
return Err(DomainError::new(
|
||||||
ErrorKind::AlreadyExists,
|
ErrorKind::AlreadyExists,
|
||||||
"User",
|
"User",
|
||||||
@@ -614,7 +605,6 @@ impl AuthApplicationService {
|
|||||||
Ok(users.into_iter().map(UserDto::from).collect())
|
Ok(users.into_iter().map(UserDto::from).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Admin User Management Methods
|
// Admin User Management Methods
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ use crate::application::dtos::folder_dto::{FolderDto, MoveFolderDto};
|
|||||||
use crate::application::ports::file_ports::{FileManagementUseCase, FileRetrievalUseCase};
|
use crate::application::ports::file_ports::{FileManagementUseCase, FileRetrievalUseCase};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::inbound::FolderUseCase;
|
||||||
use crate::application::ports::trash_ports::TrashUseCase;
|
use crate::application::ports::trash_ports::TrashUseCase;
|
||||||
use crate::application::services::folder_service::FolderService;
|
|
||||||
use crate::common::config::AppConfig;
|
|
||||||
use crate::common::errors::DomainError;
|
|
||||||
use crate::application::services::file_management_service::FileManagementService;
|
use crate::application::services::file_management_service::FileManagementService;
|
||||||
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
||||||
|
use crate::application::services::folder_service::FolderService;
|
||||||
use crate::application::services::trash_service::TrashService;
|
use crate::application::services::trash_service::TrashService;
|
||||||
|
use crate::common::config::AppConfig;
|
||||||
|
use crate::common::errors::DomainError;
|
||||||
|
|
||||||
/// Specific errors for batch operations
|
/// Specific errors for batch operations
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -1007,7 +1007,7 @@ impl BatchOperationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "integration_tests")]
|
#[cfg(integration_tests)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::common::stubs::{StubFileManagementUseCase, StubFileRetrievalUseCase};
|
use crate::common::stubs::{StubFileManagementUseCase, StubFileRetrievalUseCase};
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ 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::repositories::pg::DeviceCodePgRepository;
|
||||||
use crate::infrastructure::services::jwt_service::JwtTokenService;
|
|
||||||
use crate::infrastructure::repositories::pg::SessionPgRepository;
|
use crate::infrastructure::repositories::pg::SessionPgRepository;
|
||||||
use crate::infrastructure::repositories::pg::UserPgRepository;
|
use crate::infrastructure::repositories::pg::UserPgRepository;
|
||||||
|
use crate::infrastructure::services::jwt_service::JwtTokenService;
|
||||||
|
|
||||||
/// 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;
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ 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 crate::infrastructure::repositories::pg::FavoritesPgRepository;
|
||||||
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.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use crate::application::dtos::file_dto::FileDto;
|
|||||||
use crate::application::ports::file_ports::FileManagementUseCase;
|
use crate::application::ports::file_ports::FileManagementUseCase;
|
||||||
use crate::application::ports::storage_ports::{CopyFolderTreeResult, FileReadPort, FileWritePort};
|
use crate::application::ports::storage_ports::{CopyFolderTreeResult, FileReadPort, FileWritePort};
|
||||||
use crate::application::ports::trash_ports::TrashUseCase;
|
use crate::application::ports::trash_ports::TrashUseCase;
|
||||||
|
use crate::application::services::trash_service::TrashService;
|
||||||
use crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
use tracing::{error, info, warn};
|
|
||||||
use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository;
|
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::file_blob_write_repository::FileBlobWriteRepository;
|
||||||
use crate::application::services::trash_service::TrashService;
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
/// Service for file management operations (move, delete).
|
/// Service for file management operations (move, delete).
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ use crate::application::dtos::file_dto::FileDto;
|
|||||||
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::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
use tracing::{debug, info};
|
|
||||||
use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository;
|
use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository;
|
||||||
use crate::infrastructure::services::file_content_cache::FileContentCache;
|
use crate::infrastructure::services::file_content_cache::FileContentCache;
|
||||||
use crate::infrastructure::services::image_transcode_service::{ImageTranscodeService, OutputFormat};
|
use crate::infrastructure::services::image_transcode_service::{
|
||||||
|
ImageTranscodeService, OutputFormat,
|
||||||
|
};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
/// 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;
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
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::file_ports::FileUploadUseCase;
|
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::application::services::storage_usage_service::StorageUsageService;
|
||||||
use crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
use tracing::{debug, info, warn};
|
|
||||||
use crate::infrastructure::repositories::pg::FileBlobReadRepository;
|
use crate::infrastructure::repositories::pg::FileBlobReadRepository;
|
||||||
use crate::infrastructure::repositories::pg::FileBlobWriteRepository;
|
use crate::infrastructure::repositories::pg::FileBlobWriteRepository;
|
||||||
use crate::application::services::storage_usage_service::StorageUsageService;
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
/// 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"
|
||||||
@@ -48,8 +47,7 @@ pub struct FileUploadService {
|
|||||||
/// Read port — needed for WebDAV create_file / update_file
|
/// Read port — needed for WebDAV create_file / update_file
|
||||||
file_read: Option<Arc<FileBlobReadRepository>>,
|
file_read: Option<Arc<FileBlobReadRepository>>,
|
||||||
/// Optional storage usage tracking
|
/// Optional storage usage tracking
|
||||||
storage_usage_service:
|
storage_usage_service: Option<Arc<StorageUsageService>>,
|
||||||
Option<Arc<StorageUsageService>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileUploadService {
|
impl FileUploadService {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ use crate::application::dtos::folder_dto::{
|
|||||||
};
|
};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::inbound::FolderUseCase;
|
||||||
use crate::common::errors::{DomainError, ErrorKind};
|
use crate::common::errors::{DomainError, ErrorKind};
|
||||||
use crate::domain::services::path_service::StoragePath;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
|
|
||||||
use crate::domain::repositories::folder_repository::FolderRepository;
|
use crate::domain::repositories::folder_repository::FolderRepository;
|
||||||
|
use crate::domain::services::path_service::StoragePath;
|
||||||
|
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Implementation of the use case for folder operations
|
/// Implementation of the use case for folder operations
|
||||||
pub struct FolderService {
|
pub struct FolderService {
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ use std::collections::HashMap;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::application::ports::storage_ports::{
|
use crate::application::ports::storage_ports::FileReadPort;
|
||||||
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;
|
||||||
@@ -122,10 +120,7 @@ impl FileReadPort for MockFileReadPort {
|
|||||||
async fn stream_files_in_subtree(
|
async fn stream_files_in_subtree(
|
||||||
&self,
|
&self,
|
||||||
_folder_id: &str,
|
_folder_id: &str,
|
||||||
) -> Result<
|
) -> Result<Pin<Box<dyn Stream<Item = Result<File, DomainError>> + Send>>, DomainError> {
|
||||||
Pin<Box<dyn Stream<Item = Result<File, DomainError>> + Send>>,
|
|
||||||
DomainError,
|
|
||||||
> {
|
|
||||||
Ok(Box::pin(futures::stream::empty()))
|
Ok(Box::pin(futures::stream::empty()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,8 +240,13 @@ async fn stub_move_file_owned_returns_ok() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn stub_rename_file_owned_returns_ok() {
|
async fn stub_rename_file_owned_returns_ok() {
|
||||||
let stub = StubFileManagementUseCase;
|
let stub = StubFileManagementUseCase;
|
||||||
let result = stub.rename_file_owned("file-1", "user-1", "new-name.txt").await;
|
let result = stub
|
||||||
assert!(result.is_ok(), "stub should return Ok for rename_file_owned");
|
.rename_file_owned("file-1", "user-1", "new-name.txt")
|
||||||
|
.await;
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"stub should return Ok for rename_file_owned"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ pub mod trash_service;
|
|||||||
pub mod wopi_lock_service;
|
pub mod wopi_lock_service;
|
||||||
pub mod wopi_token_service;
|
pub mod wopi_token_service;
|
||||||
|
|
||||||
#[cfg(all(test, feature = "integration_tests"))]
|
|
||||||
mod trash_service_test;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod idor_protection_test;
|
mod idor_protection_test;
|
||||||
|
#[cfg(all(test, integration_tests))]
|
||||||
|
mod trash_service_test;
|
||||||
|
|
||||||
// Re-exportar para facilitar acceso
|
// Re-exportar para facilitar acceso
|
||||||
pub use file_management_service::FileManagementService;
|
pub use file_management_service::FileManagementService;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
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 crate::infrastructure::repositories::pg::RecentItemsPgRepository;
|
||||||
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.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ use crate::application::ports::inbound::SearchUseCase;
|
|||||||
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 crate::domain::repositories::folder_repository::FolderRepository;
|
||||||
use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository;
|
use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository;
|
||||||
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
|
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
|
||||||
use crate::domain::repositories::folder_repository::FolderRepository;
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* High-performance search service implementation for files and folders.
|
* High-performance search service implementation for files and folders.
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ use std::sync::Arc;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
|
use crate::domain::repositories::folder_repository::FolderRepository;
|
||||||
|
use crate::infrastructure::repositories::pg::SharePgRepository;
|
||||||
|
use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository;
|
||||||
|
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
|
||||||
|
use crate::infrastructure::services::password_hasher::Argon2PasswordHasher;
|
||||||
use crate::{
|
use crate::{
|
||||||
application::{
|
application::{
|
||||||
dtos::{
|
dtos::{
|
||||||
@@ -18,11 +23,6 @@ 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 {
|
||||||
@@ -394,7 +394,7 @@ impl ShareUseCase for ShareService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "integration_tests")]
|
#[cfg(integration_tests)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::application::dtos::share_dto::SharePermissionsDto;
|
use crate::application::dtos::share_dto::SharePermissionsDto;
|
||||||
|
|||||||
@@ -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 crate::infrastructure::repositories::pg::UserPgRepository;
|
||||||
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.
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ 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::folder_repository::FolderRepository;
|
||||||
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_read_repository::FileBlobReadRepository;
|
||||||
use crate::infrastructure::repositories::pg::file_blob_write_repository::FileBlobWriteRepository;
|
use crate::infrastructure::repositories::pg::file_blob_write_repository::FileBlobWriteRepository;
|
||||||
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
|
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
|
||||||
use crate::infrastructure::repositories::pg::trash_db_repository::TrashDbRepository;
|
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.
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ impl FolderRepository for MockFolderRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "integration_tests")]
|
#[cfg(integration_tests)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::application::ports::trash_ports::TrashUseCase;
|
use crate::application::ports::trash_ports::TrashUseCase;
|
||||||
|
|||||||
+37
-67
@@ -31,28 +31,27 @@ 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::application::services::device_auth_service::DeviceAuthService;
|
|
||||||
use crate::application::services::app_password_service::AppPasswordService;
|
use crate::application::services::app_password_service::AppPasswordService;
|
||||||
use crate::application::services::calendar_service::CalendarService;
|
use crate::application::services::calendar_service::CalendarService;
|
||||||
|
use crate::application::services::device_auth_service::DeviceAuthService;
|
||||||
use crate::application::services::storage_usage_service::StorageUsageService;
|
use crate::application::services::storage_usage_service::StorageUsageService;
|
||||||
|
use crate::application::services::wopi_lock_service::WopiLockService;
|
||||||
|
use crate::application::services::wopi_token_service::WopiTokenService;
|
||||||
use crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter;
|
use crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter;
|
||||||
use crate::infrastructure::repositories::DeviceCodePgRepository;
|
|
||||||
use crate::infrastructure::repositories::AppPasswordPgRepository;
|
use crate::infrastructure::repositories::AppPasswordPgRepository;
|
||||||
|
use crate::infrastructure::repositories::DeviceCodePgRepository;
|
||||||
use crate::infrastructure::repositories::pg::{
|
use crate::infrastructure::repositories::pg::{
|
||||||
AddressBookPgRepository, CalendarEventPgRepository, CalendarPgRepository,
|
AddressBookPgRepository, CalendarEventPgRepository, CalendarPgRepository,
|
||||||
ContactGroupPgRepository, ContactPgRepository, SessionPgRepository, UserPgRepository,
|
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::chunked_upload_service::ChunkedUploadService;
|
||||||
use crate::infrastructure::services::dedup_service::DedupService;
|
use crate::infrastructure::services::dedup_service::DedupService;
|
||||||
use crate::infrastructure::services::image_transcode_service::ImageTranscodeService;
|
use crate::infrastructure::services::image_transcode_service::ImageTranscodeService;
|
||||||
|
use crate::infrastructure::services::jwt_service::JwtTokenService;
|
||||||
|
use crate::infrastructure::services::password_hasher::Argon2PasswordHasher;
|
||||||
|
use crate::infrastructure::services::path_resolver_service::PathResolverService;
|
||||||
use crate::infrastructure::services::thumbnail_service::ThumbnailService;
|
use crate::infrastructure::services::thumbnail_service::ThumbnailService;
|
||||||
|
use crate::infrastructure::services::wopi_discovery_service::WopiDiscoveryService;
|
||||||
use crate::infrastructure::services::zip_service::ZipService;
|
use crate::infrastructure::services::zip_service::ZipService;
|
||||||
|
|
||||||
/// Factory for the different application components
|
/// Factory for the different application components
|
||||||
@@ -185,13 +184,15 @@ impl AppServiceFactory {
|
|||||||
let folder_repository: Arc<FolderDbRepository> = 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<FileBlobReadRepository> = 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<FileBlobWriteRepository> = 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(),
|
||||||
@@ -205,10 +206,7 @@ impl AppServiceFactory {
|
|||||||
Some(Arc::new(TrashDbRepository::new(
|
Some(Arc::new(TrashDbRepository::new(
|
||||||
db_pool.clone(),
|
db_pool.clone(),
|
||||||
core.config.storage.trash_retention_days,
|
core.config.storage.trash_retention_days,
|
||||||
))
|
)) as Arc<TrashDbRepository>)
|
||||||
as Arc<
|
|
||||||
TrashDbRepository,
|
|
||||||
>)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -339,8 +337,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<Argon2PasswordHasher> =
|
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,
|
||||||
self.config.auth.hash_time_cost,
|
self.config.auth.hash_time_cost,
|
||||||
@@ -464,9 +461,7 @@ impl AppServiceFactory {
|
|||||||
// 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<FavoritesService>>;
|
let favorites_service: Option<Arc<FavoritesService>>;
|
||||||
let recent_service: Option<Arc<RecentService>>;
|
let recent_service: Option<Arc<RecentService>>;
|
||||||
let storage_usage_service: Option<
|
let storage_usage_service: Option<Arc<StorageUsageService>>;
|
||||||
Arc<StorageUsageService>,
|
|
||||||
>;
|
|
||||||
let mut auth_services: Option<crate::common::di::AuthServices> = None;
|
let mut auth_services: Option<crate::common::di::AuthServices> = None;
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -550,7 +545,8 @@ impl AppServiceFactory {
|
|||||||
device_auth_service: None,
|
device_auth_service: None,
|
||||||
app_password_service: None,
|
app_password_service: None,
|
||||||
path_resolver: None,
|
path_resolver: None,
|
||||||
webdav_lock_store: crate::infrastructure::services::webdav_lock_service::create_webdav_lock_store(),
|
webdav_lock_store:
|
||||||
|
crate::infrastructure::services::webdav_lock_service::create_webdav_lock_store(),
|
||||||
setup_token: None,
|
setup_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -626,15 +622,11 @@ impl AppServiceFactory {
|
|||||||
|
|
||||||
// 9c. Wire Device Authorization Grant (RFC 8628) service
|
// 9c. Wire Device Authorization Grant (RFC 8628) service
|
||||||
{
|
{
|
||||||
|
|
||||||
let device_code_repo = Arc::new(DeviceCodePgRepository::new(pool.clone()));
|
let device_code_repo = Arc::new(DeviceCodePgRepository::new(pool.clone()));
|
||||||
let user_repo: Arc<UserPgRepository> =
|
let user_repo: Arc<UserPgRepository> = Arc::new(
|
||||||
Arc::new(crate::infrastructure::repositories::UserPgRepository::new(
|
crate::infrastructure::repositories::UserPgRepository::new(pool.clone()),
|
||||||
pool.clone(),
|
);
|
||||||
));
|
let session_repo: Arc<SessionPgRepository> = Arc::new(
|
||||||
let session_repo: Arc<
|
|
||||||
SessionPgRepository,
|
|
||||||
> = Arc::new(
|
|
||||||
crate::infrastructure::repositories::SessionPgRepository::new(pool.clone()),
|
crate::infrastructure::repositories::SessionPgRepository::new(pool.clone()),
|
||||||
);
|
);
|
||||||
let base_url = self.config.base_url();
|
let base_url = self.config.base_url();
|
||||||
@@ -652,22 +644,18 @@ impl AppServiceFactory {
|
|||||||
|
|
||||||
// 9d. Wire App Password service
|
// 9d. Wire App Password service
|
||||||
{
|
{
|
||||||
|
let app_pw_repo: Arc<AppPasswordPgRepository> =
|
||||||
let app_pw_repo: Arc<
|
Arc::new(AppPasswordPgRepository::new(pool.clone()));
|
||||||
AppPasswordPgRepository,
|
let hasher: Arc<Argon2PasswordHasher> = Arc::new(
|
||||||
> = Arc::new(AppPasswordPgRepository::new(pool.clone()));
|
|
||||||
let hasher: Arc<Argon2PasswordHasher> =
|
|
||||||
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,
|
||||||
self.config.auth.hash_time_cost,
|
self.config.auth.hash_time_cost,
|
||||||
self.config.auth.hash_parallelism,
|
self.config.auth.hash_parallelism,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
let user_repo: Arc<UserPgRepository> =
|
let user_repo: Arc<UserPgRepository> = Arc::new(
|
||||||
Arc::new(crate::infrastructure::repositories::UserPgRepository::new(
|
crate::infrastructure::repositories::UserPgRepository::new(pool.clone()),
|
||||||
pool.clone(),
|
);
|
||||||
));
|
|
||||||
let base_url = self.config.base_url();
|
let base_url = self.config.base_url();
|
||||||
|
|
||||||
let app_pw_svc = Arc::new(AppPasswordService::new(
|
let app_pw_svc = Arc::new(AppPasswordService::new(
|
||||||
@@ -690,14 +678,10 @@ impl AppServiceFactory {
|
|||||||
// 10. Wire CalDAV/CardDAV services
|
// 10. Wire CalDAV/CardDAV services
|
||||||
{
|
{
|
||||||
// CalDAV
|
// CalDAV
|
||||||
let calendar_repo: Arc<
|
let calendar_repo: Arc<CalendarPgRepository> = Arc::new(
|
||||||
CalendarPgRepository,
|
|
||||||
> = 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<CalendarEventPgRepository> = Arc::new(
|
||||||
CalendarEventPgRepository,
|
|
||||||
> = Arc::new(
|
|
||||||
crate::infrastructure::repositories::pg::CalendarEventPgRepository::new(
|
crate::infrastructure::repositories::pg::CalendarEventPgRepository::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
),
|
),
|
||||||
@@ -713,25 +697,16 @@ impl AppServiceFactory {
|
|||||||
calendar_storage,
|
calendar_storage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
app_state.calendar_use_case = Some(
|
app_state.calendar_use_case = Some(calendar_service as Arc<CalendarService>);
|
||||||
calendar_service
|
|
||||||
as Arc<CalendarService>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// CardDAV
|
// CardDAV
|
||||||
let address_book_repo: Arc<
|
let address_book_repo: Arc<AddressBookPgRepository> = Arc::new(
|
||||||
AddressBookPgRepository,
|
|
||||||
> = 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<ContactPgRepository> = Arc::new(
|
||||||
ContactPgRepository,
|
|
||||||
> = 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<ContactGroupPgRepository> = Arc::new(
|
||||||
ContactGroupPgRepository,
|
|
||||||
> = Arc::new(
|
|
||||||
crate::infrastructure::repositories::pg::ContactGroupPgRepository::new(
|
crate::infrastructure::repositories::pg::ContactGroupPgRepository::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
),
|
),
|
||||||
@@ -757,7 +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 {
|
||||||
|
|
||||||
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()
|
||||||
} else {
|
} else {
|
||||||
@@ -811,8 +785,7 @@ pub struct RepositoryServices {
|
|||||||
pub file_read_repository: Arc<FileBlobReadRepository>,
|
pub file_read_repository: Arc<FileBlobReadRepository>,
|
||||||
pub file_write_repository: Arc<FileBlobWriteRepository>,
|
pub file_write_repository: Arc<FileBlobWriteRepository>,
|
||||||
pub i18n_repository: Arc<FileSystemI18nService>,
|
pub i18n_repository: Arc<FileSystemI18nService>,
|
||||||
pub trash_repository:
|
pub trash_repository: Option<Arc<TrashDbRepository>>,
|
||||||
Option<Arc<TrashDbRepository>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Container for application services
|
/// Container for application services
|
||||||
@@ -858,14 +831,11 @@ pub struct AppState {
|
|||||||
pub share_service: Option<Arc<ShareService>>,
|
pub share_service: Option<Arc<ShareService>>,
|
||||||
pub favorites_service: Option<Arc<FavoritesService>>,
|
pub favorites_service: Option<Arc<FavoritesService>>,
|
||||||
pub recent_service: Option<Arc<RecentService>>,
|
pub recent_service: Option<Arc<RecentService>>,
|
||||||
pub storage_usage_service:
|
pub storage_usage_service: Option<Arc<StorageUsageService>>,
|
||||||
Option<Arc<StorageUsageService>>,
|
|
||||||
pub calendar_service: Option<Arc<CalendarService>>,
|
pub calendar_service: Option<Arc<CalendarService>>,
|
||||||
pub contact_service: Option<Arc<ContactStorageAdapter>>,
|
pub contact_service: Option<Arc<ContactStorageAdapter>>,
|
||||||
pub calendar_use_case:
|
pub calendar_use_case: Option<Arc<CalendarService>>,
|
||||||
Option<Arc<CalendarService>>,
|
pub addressbook_use_case: Option<Arc<ContactStorageAdapter>>,
|
||||||
pub addressbook_use_case:
|
|
||||||
Option<Arc<ContactStorageAdapter>>,
|
|
||||||
pub contact_use_case: Option<Arc<ContactStorageAdapter>>,
|
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>>,
|
||||||
|
|||||||
+1
-2
@@ -22,8 +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,
|
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase, OptimizedFileContent,
|
||||||
OptimizedFileContent,
|
|
||||||
};
|
};
|
||||||
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
|
use crate::application::ports::inbound::{FolderUseCase, SearchUseCase};
|
||||||
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl DeviceCodeStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_str(s: &str) -> Option<Self> {
|
pub fn parse(s: &str) -> Option<Self> {
|
||||||
match s {
|
match s {
|
||||||
"pending" => Some(Self::Pending),
|
"pending" => Some(Self::Pending),
|
||||||
"authorized" => Some(Self::Authorized),
|
"authorized" => Some(Self::Authorized),
|
||||||
@@ -76,6 +76,7 @@ impl DeviceCode {
|
|||||||
/// * `verification_uri` — URL the user must visit
|
/// * `verification_uri` — URL the user must visit
|
||||||
/// * `expires_in_secs` — TTL for the device code
|
/// * `expires_in_secs` — TTL for the device code
|
||||||
/// * `poll_interval_secs` — minimum polling interval
|
/// * `poll_interval_secs` — minimum polling interval
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
device_code: String,
|
device_code: String,
|
||||||
user_code: String,
|
user_code: String,
|
||||||
|
|||||||
@@ -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 crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
use crate::domain::entities::folder::Folder;
|
use crate::domain::entities::folder::Folder;
|
||||||
use crate::domain::services::path_service::StoragePath;
|
use crate::domain::services::path_service::StoragePath;
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ impl StoragePath {
|
|||||||
/// Creates a new storage path, silently dropping any traversal segments
|
/// Creates a new storage path, silently dropping any traversal segments
|
||||||
pub fn new(segments: Vec<String>) -> Self {
|
pub fn new(segments: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
segments: segments.into_iter().filter(|s| Self::is_safe_segment(s)).collect(),
|
segments: segments
|
||||||
|
.into_iter()
|
||||||
|
.filter(|s| Self::is_safe_segment(s))
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,12 +189,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_new_strips_traversal_segments() {
|
fn test_new_strips_traversal_segments() {
|
||||||
let path = StoragePath::new(vec![
|
let path = StoragePath::new(vec!["..".into(), "etc".into(), ".".into(), "passwd".into()]);
|
||||||
"..".into(),
|
|
||||||
"etc".into(),
|
|
||||||
".".into(),
|
|
||||||
"passwd".into(),
|
|
||||||
]);
|
|
||||||
assert_eq!(path.segments(), &["etc", "passwd"]);
|
assert_eq!(path.segments(), &["etc", "passwd"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,8 +126,7 @@ async fn create_pool_with_retries(
|
|||||||
if attempt >= MAX_ATTEMPTS {
|
if attempt >= MAX_ATTEMPTS {
|
||||||
return Err(DbError(format!(
|
return Err(DbError(format!(
|
||||||
"Error verifying PostgreSQL {} pool connection: {}",
|
"Error verifying PostgreSQL {} pool connection: {}",
|
||||||
label,
|
label, e
|
||||||
e
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,8 +142,7 @@ async fn create_pool_with_retries(
|
|||||||
if attempt >= MAX_ATTEMPTS {
|
if attempt >= MAX_ATTEMPTS {
|
||||||
return Err(DbError(format!(
|
return Err(DbError(format!(
|
||||||
"Error in PostgreSQL {} pool connection: {}",
|
"Error in PostgreSQL {} pool connection: {}",
|
||||||
label,
|
label, e
|
||||||
e
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
@@ -154,8 +152,7 @@ async fn create_pool_with_retries(
|
|||||||
|
|
||||||
Err(DbError(format!(
|
Err(DbError(format!(
|
||||||
"Could not establish PostgreSQL {} pool connection after {} attempts",
|
"Could not establish PostgreSQL {} pool connection after {} attempts",
|
||||||
label,
|
label, MAX_ATTEMPTS
|
||||||
MAX_ATTEMPTS
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ impl DeviceCodePgRepository {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let status = DeviceCodeStatus::from_str(&status_str).unwrap_or(DeviceCodeStatus::Expired);
|
let status = DeviceCodeStatus::parse(&status_str).unwrap_or(DeviceCodeStatus::Expired);
|
||||||
|
|
||||||
Ok(DeviceCode::from_raw(
|
Ok(DeviceCode::from_raw(
|
||||||
row.try_get("id").unwrap_or_default(),
|
row.try_get("id").unwrap_or_default(),
|
||||||
|
|||||||
@@ -22,6 +22,19 @@ 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;
|
use crate::infrastructure::services::dedup_service::DedupService;
|
||||||
|
|
||||||
|
/// Type alias for file metadata rows from SQL queries.
|
||||||
|
type FileRow = (
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
Option<String>,
|
||||||
|
Option<String>,
|
||||||
|
i64,
|
||||||
|
String,
|
||||||
|
i64,
|
||||||
|
i64,
|
||||||
|
Option<String>,
|
||||||
|
);
|
||||||
|
|
||||||
/// 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>,
|
||||||
@@ -204,17 +217,7 @@ impl FileReadPort for FileBlobReadRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<File>, DomainError> {
|
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<File>, DomainError> {
|
||||||
let rows: Vec<(
|
let rows: Vec<FileRow> = if let Some(fid) = folder_id {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
Option<String>,
|
|
||||||
i64,
|
|
||||||
String,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
Option<String>,
|
|
||||||
)> = if let Some(fid) = folder_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
|
SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
|
||||||
@@ -271,17 +274,7 @@ impl FileReadPort for FileBlobReadRepository {
|
|||||||
offset: i64,
|
offset: i64,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<Vec<File>, DomainError> {
|
) -> Result<Vec<File>, DomainError> {
|
||||||
let rows: Vec<(
|
let rows: Vec<FileRow> = if let Some(fid) = folder_id {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
Option<String>,
|
|
||||||
i64,
|
|
||||||
String,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
Option<String>,
|
|
||||||
)> = if let Some(fid) = folder_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
|
SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
|
||||||
@@ -853,17 +846,7 @@ impl FileReadPort for FileBlobReadRepository {
|
|||||||
let pattern = format!("%{}%", query);
|
let pattern = format!("%{}%", query);
|
||||||
let limit_i64 = limit as i64;
|
let limit_i64 = limit as i64;
|
||||||
|
|
||||||
let rows: Vec<(
|
let rows: Vec<FileRow> = if let Some(fid) = folder_id {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
Option<String>,
|
|
||||||
i64,
|
|
||||||
String,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
Option<String>,
|
|
||||||
)> = if let Some(fid) = folder_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
|
SELECT fi.id::text, fi.name, fi.folder_id::text, fo.path,
|
||||||
@@ -929,7 +912,7 @@ impl FileReadPort for FileBlobReadRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "integration_tests")]
|
#[cfg(integration_tests)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::common::stubs::StubDedupPort;
|
use crate::common::stubs::StubDedupPort;
|
||||||
|
|||||||
@@ -16,6 +16,32 @@ use crate::domain::entities::folder::Folder;
|
|||||||
use crate::domain::repositories::folder_repository::FolderRepository;
|
use crate::domain::repositories::folder_repository::FolderRepository;
|
||||||
use crate::domain::services::path_service::StoragePath;
|
use crate::domain::services::path_service::StoragePath;
|
||||||
|
|
||||||
|
/// Type alias for folder metadata rows from SQL queries.
|
||||||
|
type FolderRow = (String, String, String, Option<String>, String, i64, i64);
|
||||||
|
|
||||||
|
/// Type alias for paginated folder rows (includes total_count).
|
||||||
|
type FolderRowPaginated = (
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
Option<String>,
|
||||||
|
String,
|
||||||
|
i64,
|
||||||
|
i64,
|
||||||
|
i64,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Type alias for folder rows with optional user_id.
|
||||||
|
type FolderRowOptUser = (
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
Option<String>,
|
||||||
|
Option<String>,
|
||||||
|
i64,
|
||||||
|
i64,
|
||||||
|
);
|
||||||
|
|
||||||
/// PostgreSQL-backed folder repository.
|
/// PostgreSQL-backed folder repository.
|
||||||
///
|
///
|
||||||
/// All folder metadata lives in the `storage.folders` table. The physical
|
/// All folder metadata lives in the `storage.folders` table. The physical
|
||||||
@@ -173,8 +199,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn list_folders(&self, parent_id: Option<&str>) -> Result<Vec<Folder>, DomainError> {
|
async fn list_folders(&self, parent_id: Option<&str>) -> Result<Vec<Folder>, DomainError> {
|
||||||
let rows: Vec<(String, String, String, Option<String>, String, i64, i64)> =
|
let rows: Vec<FolderRow> = if let Some(pid) = parent_id {
|
||||||
if let Some(pid) = parent_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id::text, name, path, parent_id::text, user_id,
|
SELECT id::text, name, path, parent_id::text, user_id,
|
||||||
@@ -216,8 +241,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
parent_id: Option<&str>,
|
parent_id: Option<&str>,
|
||||||
owner_id: &str,
|
owner_id: &str,
|
||||||
) -> Result<Vec<Folder>, DomainError> {
|
) -> Result<Vec<Folder>, DomainError> {
|
||||||
let rows: Vec<(String, String, String, Option<String>, String, i64, i64)> =
|
let rows: Vec<FolderRow> = if let Some(pid) = parent_id {
|
||||||
if let Some(pid) = parent_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id::text, name, path, parent_id::text, user_id,
|
SELECT id::text, name, path, parent_id::text, user_id,
|
||||||
@@ -266,16 +290,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
limit: usize,
|
limit: usize,
|
||||||
include_total: bool,
|
include_total: bool,
|
||||||
) -> Result<(Vec<Folder>, Option<usize>), DomainError> {
|
) -> Result<(Vec<Folder>, Option<usize>), DomainError> {
|
||||||
let rows: Vec<(
|
let rows: Vec<FolderRowPaginated> = if let Some(pid) = parent_id {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
String,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
)> = if let Some(pid) = parent_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id::text, name, path, parent_id::text, user_id,
|
SELECT id::text, name, path, parent_id::text, user_id,
|
||||||
@@ -339,16 +354,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
limit: usize,
|
limit: usize,
|
||||||
include_total: bool,
|
include_total: bool,
|
||||||
) -> Result<(Vec<Folder>, Option<usize>), DomainError> {
|
) -> Result<(Vec<Folder>, Option<usize>), DomainError> {
|
||||||
let rows: Vec<(
|
let rows: Vec<FolderRowPaginated> = if let Some(pid) = parent_id {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
String,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
)> = if let Some(pid) = parent_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id::text, name, path, parent_id::text, user_id,
|
SELECT id::text, name, path, parent_id::text, user_id,
|
||||||
@@ -689,15 +695,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
AND fo.lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid) \
|
AND fo.lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid) \
|
||||||
ORDER BY fo.path";
|
ORDER BY fo.path";
|
||||||
|
|
||||||
let rows: Vec<(
|
let rows: Vec<FolderRowOptUser> = sqlx::query_as(sql)
|
||||||
String,
|
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
Option<String>,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
)> = sqlx::query_as(sql)
|
|
||||||
.bind(folder_id)
|
.bind(folder_id)
|
||||||
.fetch_all(self.pool())
|
.fetch_all(self.pool())
|
||||||
.await
|
.await
|
||||||
@@ -760,15 +758,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
ORDER BY fo.name"
|
ORDER BY fo.name"
|
||||||
);
|
);
|
||||||
|
|
||||||
let rows: Vec<(
|
let rows: Vec<FolderRowOptUser> = if let Some(ref pattern) = name_pattern {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
Option<String>,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
)> = if let Some(ref pattern) = name_pattern {
|
|
||||||
sqlx::query_as(&sql)
|
sqlx::query_as(&sql)
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.bind(pattern)
|
.bind(pattern)
|
||||||
@@ -824,15 +814,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let rows: Vec<(
|
let rows: Vec<FolderRowOptUser> = if let Some(pid) = parent_id {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
Option<String>,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
)> = if let Some(pid) = parent_id {
|
|
||||||
if let Some(ref pattern) = name_pattern {
|
if let Some(ref pattern) = name_pattern {
|
||||||
sqlx::query_as(&sql)
|
sqlx::query_as(&sql)
|
||||||
.bind(pid)
|
.bind(pid)
|
||||||
@@ -897,15 +879,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
ORDER BY fo.name"
|
ORDER BY fo.name"
|
||||||
);
|
);
|
||||||
|
|
||||||
let rows: Vec<(
|
let rows: Vec<FolderRowOptUser> = if let Some(ref pattern) = name_pattern {
|
||||||
String,
|
|
||||||
String,
|
|
||||||
String,
|
|
||||||
Option<String>,
|
|
||||||
Option<String>,
|
|
||||||
i64,
|
|
||||||
i64,
|
|
||||||
)> = if let Some(ref pattern) = name_pattern {
|
|
||||||
sqlx::query_as(&sql)
|
sqlx::query_as(&sql)
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.bind(folder_id)
|
.bind(folder_id)
|
||||||
@@ -937,8 +911,7 @@ impl FolderRepository for FolderDbRepository {
|
|||||||
let pattern = format!("%{}%", query);
|
let pattern = format!("%{}%", query);
|
||||||
let limit_i64 = limit as i64;
|
let limit_i64 = limit as i64;
|
||||||
|
|
||||||
let rows: Vec<(String, String, String, Option<String>, String, i64, i64)> =
|
let rows: Vec<FolderRow> = if let Some(pid) = parent_id {
|
||||||
if let Some(pid) = parent_id {
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id::text, name, path, parent_id::text, user_id,
|
SELECT id::text, name, path, parent_id::text, user_id,
|
||||||
|
|||||||
@@ -495,9 +495,8 @@ impl ChunkedUploadService {
|
|||||||
// worker free for other connections.
|
// worker free for other connections.
|
||||||
if let Some(ref expected_checksum) = checksum {
|
if let Some(ref expected_checksum) = checksum {
|
||||||
let data_clone = data.clone(); // Bytes::clone is O(1) — just an Arc increment
|
let data_clone = data.clone(); // Bytes::clone is O(1) — just an Arc increment
|
||||||
let actual_checksum =
|
let actual_checksum = tokio::task::spawn_blocking(move || {
|
||||||
tokio::task::spawn_blocking(move || {
|
use md5::{Digest, Md5};
|
||||||
use md5::{Md5, Digest};
|
|
||||||
format!("{:x}", Md5::digest(&data_clone))
|
format!("{:x}", Md5::digest(&data_clone))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ impl LoginLockoutService {
|
|||||||
/// Returns `Ok(())` if the user may attempt login, or
|
/// Returns `Ok(())` if the user may attempt login, or
|
||||||
/// `Err(remaining_secs)` with the *approximate* remaining lockout time.
|
/// `Err(remaining_secs)` with the *approximate* remaining lockout time.
|
||||||
pub fn check(&self, username: &str) -> Result<(), u64> {
|
pub fn check(&self, username: &str) -> Result<(), u64> {
|
||||||
if let Some(rec) = self.cache.get(&username.to_lowercase()) {
|
if let Some(rec) = self.cache.get(&username.to_lowercase())
|
||||||
if rec.count >= self.max_failures {
|
&& rec.count >= self.max_failures
|
||||||
|
{
|
||||||
// The entry exists and is over the threshold. Because moka
|
// The entry exists and is over the threshold. Because moka
|
||||||
// evicts at TTL we know the lockout window has not yet elapsed.
|
// evicts at TTL we know the lockout window has not yet elapsed.
|
||||||
return Err(self.lockout_secs);
|
return Err(self.lockout_secs);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,7 +312,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_validate_path_rejects_dangerous_chars() {
|
fn test_validate_path_rejects_dangerous_chars() {
|
||||||
let service = PathService::new(PathBuf::from("/storage"));
|
let service = PathService::new(PathBuf::from("/storage"));
|
||||||
for dangerous in &["file:name", "file*name", "file?name", "file<name", "file>name", "file|name", "file\"name"] {
|
for dangerous in &[
|
||||||
|
"file:name",
|
||||||
|
"file*name",
|
||||||
|
"file?name",
|
||||||
|
"file<name",
|
||||||
|
"file>name",
|
||||||
|
"file|name",
|
||||||
|
"file\"name",
|
||||||
|
] {
|
||||||
let path = StoragePath::new(vec![dangerous.to_string()]);
|
let path = StoragePath::new(vec![dangerous.to_string()]);
|
||||||
assert!(
|
assert!(
|
||||||
service.validate_path(&path).is_err(),
|
service.validate_path(&path).is_err(),
|
||||||
|
|||||||
@@ -63,16 +63,12 @@ impl WebDavLockStore {
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(LockEntry)` on success, or `Err(existing)` if the resource
|
/// Returns `Ok(LockEntry)` on success, or `Err(existing)` if the resource
|
||||||
/// is already exclusively locked by a different token.
|
/// is already exclusively locked by a different token.
|
||||||
pub fn acquire(
|
pub fn acquire(&self, path: &str, info: LockInfo) -> Result<LockEntry, Box<LockEntry>> {
|
||||||
&self,
|
|
||||||
path: &str,
|
|
||||||
info: LockInfo,
|
|
||||||
) -> Result<LockEntry, LockEntry> {
|
|
||||||
// Check for existing conflicting lock
|
// Check for existing conflicting lock
|
||||||
if let Some(existing) = self.by_path.get(path) {
|
if let Some(existing) = self.by_path.get(path)
|
||||||
if existing.info.scope == LockScope::Exclusive {
|
&& existing.info.scope == LockScope::Exclusive
|
||||||
return Err(existing);
|
{
|
||||||
}
|
return Err(Box::new(existing));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ttl = Self::parse_timeout(info.timeout.as_deref());
|
let ttl = Self::parse_timeout(info.timeout.as_deref());
|
||||||
@@ -81,8 +77,7 @@ impl WebDavLockStore {
|
|||||||
path: path.to_owned(),
|
path: path.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.by_path
|
self.by_path.insert(path.to_owned(), entry.clone());
|
||||||
.insert(path.to_owned(), entry.clone());
|
|
||||||
self.by_token
|
self.by_token
|
||||||
.insert(entry.info.token.clone(), path.to_owned());
|
.insert(entry.info.token.clone(), path.to_owned());
|
||||||
|
|
||||||
@@ -99,12 +94,12 @@ impl WebDavLockStore {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::time::sleep(ttl).await;
|
tokio::time::sleep(ttl).await;
|
||||||
// Only remove if the entry still matches (wasn't refreshed/replaced)
|
// Only remove if the entry still matches (wasn't refreshed/replaced)
|
||||||
if let Some(e) = by_path.get(&path_owned) {
|
if let Some(e) = by_path.get(&path_owned)
|
||||||
if e.info.token == token {
|
&& e.info.token == token
|
||||||
|
{
|
||||||
by_path.invalidate(&path_owned);
|
by_path.invalidate(&path_owned);
|
||||||
by_token.invalidate(&token);
|
by_token.invalidate(&token);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +133,12 @@ impl WebDavLockStore {
|
|||||||
let path_owned = path.clone();
|
let path_owned = path.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::time::sleep(ttl).await;
|
tokio::time::sleep(ttl).await;
|
||||||
if let Some(e) = by_path.get(&path_owned) {
|
if let Some(e) = by_path.get(&path_owned)
|
||||||
if e.info.token == token_owned {
|
&& e.info.token == token_owned
|
||||||
|
{
|
||||||
by_path.invalidate(&path_owned);
|
by_path.invalidate(&path_owned);
|
||||||
by_token.invalidate(&token_owned);
|
by_token.invalidate(&token_owned);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,11 +151,11 @@ impl WebDavLockStore {
|
|||||||
pub fn release(&self, token: &str) -> bool {
|
pub fn release(&self, token: &str) -> bool {
|
||||||
if let Some(path) = self.by_token.get(token) {
|
if let Some(path) = self.by_token.get(token) {
|
||||||
// Only remove from by_path if the token still matches
|
// Only remove from by_path if the token still matches
|
||||||
if let Some(entry) = self.by_path.get(&path) {
|
if let Some(entry) = self.by_path.get(&path)
|
||||||
if entry.info.token == token {
|
&& entry.info.token == token
|
||||||
|
{
|
||||||
self.by_path.invalidate(&path);
|
self.by_path.invalidate(&path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.by_token.invalidate(token);
|
self.by_token.invalidate(token);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -200,11 +195,11 @@ impl WebDavLockStore {
|
|||||||
return Duration::from_secs(MAX_LOCK_TIMEOUT_SECS);
|
return Duration::from_secs(MAX_LOCK_TIMEOUT_SECS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(secs_str) = first.strip_prefix("Second-") {
|
if let Some(secs_str) = first.strip_prefix("Second-")
|
||||||
if let Ok(secs) = secs_str.trim().parse::<u64>() {
|
&& let Ok(secs) = secs_str.trim().parse::<u64>()
|
||||||
|
{
|
||||||
return Duration::from_secs(secs.min(MAX_LOCK_TIMEOUT_SECS));
|
return Duration::from_secs(secs.min(MAX_LOCK_TIMEOUT_SECS));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Duration::from_secs(DEFAULT_LOCK_TIMEOUT_SECS)
|
Duration::from_secs(DEFAULT_LOCK_TIMEOUT_SECS)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
||||||
|
use crate::application::services::folder_service::FolderService;
|
||||||
use crate::{
|
use crate::{
|
||||||
application::dtos::file_dto::FileDto,
|
application::dtos::file_dto::FileDto,
|
||||||
application::ports::file_ports::FileRetrievalUseCase,
|
application::ports::file_ports::FileRetrievalUseCase,
|
||||||
@@ -16,8 +18,6 @@ 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)]
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ use crate::application::dtos::settings_dto::{
|
|||||||
SaveOidcSettingsDto, TestOidcConnectionDto, UpdateUserActiveDto, UpdateUserQuotaDto,
|
SaveOidcSettingsDto, TestOidcConnectionDto, UpdateUserActiveDto, UpdateUserQuotaDto,
|
||||||
UpdateUserRoleDto,
|
UpdateUserRoleDto,
|
||||||
};
|
};
|
||||||
|
use crate::application::ports::auth_ports::TokenServicePort;
|
||||||
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>> {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ async fn create_app_password(
|
|||||||
let response = service
|
let response = service
|
||||||
.create(&user.id, request)
|
.create(&user.id, request)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::from(e))?;
|
.map_err(AppError::from)?;
|
||||||
|
|
||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
}
|
}
|
||||||
@@ -55,10 +55,7 @@ async fn list_app_passwords(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
|
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
|
||||||
|
|
||||||
let response = service
|
let response = service.list(&user.id).await.map_err(AppError::from)?;
|
||||||
.list(&user.id)
|
|
||||||
.await
|
|
||||||
.map_err(|e| AppError::from(e))?;
|
|
||||||
|
|
||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
}
|
}
|
||||||
@@ -78,7 +75,7 @@ async fn revoke_app_password(
|
|||||||
let response = service
|
let response = service
|
||||||
.revoke(&user.id, &id)
|
.revoke(&user.id, &id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AppError::from(e))?;
|
.map_err(AppError::from)?;
|
||||||
|
|
||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ async fn login(
|
|||||||
);
|
);
|
||||||
return Err(AppError::new(
|
return Err(AppError::new(
|
||||||
StatusCode::TOO_MANY_REQUESTS,
|
StatusCode::TOO_MANY_REQUESTS,
|
||||||
&format!(
|
format!(
|
||||||
"Account temporarily locked due to too many failed attempts. Try again in {} seconds.",
|
"Account temporarily locked due to too many failed attempts. Try again in {} seconds.",
|
||||||
lockout_secs
|
lockout_secs
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ use crate::application::dtos::calendar_dto::{
|
|||||||
CreateCalendarDto, CreateEventICalDto, UpdateCalendarDto,
|
CreateCalendarDto, CreateEventICalDto, UpdateCalendarDto,
|
||||||
};
|
};
|
||||||
use crate::application::ports::calendar_ports::CalendarUseCase;
|
use crate::application::ports::calendar_ports::CalendarUseCase;
|
||||||
|
use crate::application::services::calendar_service::CalendarService;
|
||||||
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");
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ use crate::application::dtos::address_book_dto::{CreateAddressBookDto, UpdateAdd
|
|||||||
use crate::application::dtos::contact_dto::CreateContactVCardDto;
|
use crate::application::dtos::contact_dto::CreateContactVCardDto;
|
||||||
use crate::application::ports::carddav_ports::{AddressBookUseCase, ContactUseCase};
|
use crate::application::ports::carddav_ports::{AddressBookUseCase, ContactUseCase};
|
||||||
use crate::common::di::AppState;
|
use crate::common::di::AppState;
|
||||||
|
use crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter;
|
||||||
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");
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ use bytes::Bytes;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::application::ports::chunked_upload_ports::ChunkedUploadPort;
|
||||||
use crate::application::ports::chunked_upload_ports::DEFAULT_CHUNK_SIZE;
|
use crate::application::ports::chunked_upload_ports::DEFAULT_CHUNK_SIZE;
|
||||||
|
use crate::application::ports::file_ports::FileUploadUseCase;
|
||||||
|
use crate::application::ports::storage_ports::StorageUsagePort;
|
||||||
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)]
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ use std::sync::Arc;
|
|||||||
use tracing::{error, info};
|
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::application::services::favorites_service::FavoritesService;
|
use crate::application::services::favorites_service::FavoritesService;
|
||||||
|
use crate::interfaces::middleware::auth::AuthUser;
|
||||||
|
|
||||||
/// Single item in a batch-add-favorites request.
|
/// Single item in a batch-add-favorites request.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ use serde::Deserialize;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::application::ports::file_ports::OptimizedFileContent;
|
use crate::application::ports::file_ports::OptimizedFileContent;
|
||||||
|
use crate::application::ports::file_ports::{
|
||||||
|
FileManagementUseCase, FileRetrievalUseCase, FileUploadUseCase,
|
||||||
|
};
|
||||||
|
use crate::application::ports::storage_ports::StorageUsagePort;
|
||||||
|
use crate::application::ports::thumbnail_ports::ThumbnailPort;
|
||||||
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::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.
|
||||||
@@ -283,7 +285,10 @@ impl FileHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let file = match file_retrieval_service.get_file_owned(&id, &auth_user.id).await {
|
let file = match file_retrieval_service
|
||||||
|
.get_file_owned(&id, &auth_user.id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return (
|
return (
|
||||||
@@ -678,7 +683,8 @@ impl FileHandler {
|
|||||||
let mgmt = &state.applications.file_management_service;
|
let mgmt = &state.applications.file_management_service;
|
||||||
|
|
||||||
// Auth required: trash-first with dedup cleanup + ownership verification
|
// Auth required: trash-first with dedup cleanup + ownership verification
|
||||||
let result = mgmt.delete_with_cleanup(&id, &auth_user.id)
|
let result = mgmt
|
||||||
|
.delete_with_cleanup(&id, &auth_user.id)
|
||||||
.await
|
.await
|
||||||
.map(|was_trashed| {
|
.map(|was_trashed| {
|
||||||
if was_trashed {
|
if was_trashed {
|
||||||
@@ -771,7 +777,10 @@ impl FileHandler {
|
|||||||
|
|
||||||
let mgmt = &state.applications.file_management_service;
|
let mgmt = &state.applications.file_management_service;
|
||||||
|
|
||||||
match mgmt.move_file_owned(&id, &auth_user.id, payload.folder_id).await {
|
match mgmt
|
||||||
|
.move_file_owned(&id, &auth_user.id, payload.folder_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(file) => (StatusCode::OK, Json(file)).into_response(),
|
Ok(file) => (StatusCode::OK, Json(file)).into_response(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("Error moving file: {}", err);
|
tracing::error!("Error moving file: {}", err);
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ use tokio_util::io::ReaderStream;
|
|||||||
use crate::application::dtos::folder_dto::{CreateFolderDto, MoveFolderDto, RenameFolderDto};
|
use crate::application::dtos::folder_dto::{CreateFolderDto, MoveFolderDto, RenameFolderDto};
|
||||||
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
||||||
use crate::application::dtos::pagination::PaginationRequestDto;
|
use crate::application::dtos::pagination::PaginationRequestDto;
|
||||||
|
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::inbound::FolderUseCase;
|
||||||
|
use crate::application::ports::trash_ports::TrashUseCase;
|
||||||
use crate::application::services::folder_service::FolderService;
|
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>;
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ use std::sync::Arc;
|
|||||||
use tracing::{error, info};
|
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::application::services::recent_service::RecentService;
|
use crate::application::services::recent_service::RecentService;
|
||||||
|
use crate::interfaces::middleware::auth::AuthUser;
|
||||||
|
|
||||||
/// Query parameters for getting recent items
|
/// Query parameters for getting recent items
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ use serde_json::json;
|
|||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::application::dtos::search_dto::SearchCriteriaDto;
|
use crate::application::dtos::search_dto::SearchCriteriaDto;
|
||||||
|
use crate::application::ports::inbound::SearchUseCase;
|
||||||
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.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use axum::{
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::application::services::share_service::ShareService;
|
||||||
use crate::{
|
use crate::{
|
||||||
application::{
|
application::{
|
||||||
dtos::share_dto::{CreateShareDto, UpdateShareDto},
|
dtos::share_dto::{CreateShareDto, UpdateShareDto},
|
||||||
@@ -18,7 +19,6 @@ 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 {
|
||||||
|
|||||||
@@ -17,22 +17,20 @@ use chrono::Utc;
|
|||||||
use quick_xml::Writer;
|
use quick_xml::Writer;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::application::adapters::webdav_adapter::{
|
use crate::application::adapters::webdav_adapter::{LockInfo, PropFindRequest, WebDavAdapter};
|
||||||
LockInfo, PropFindRequest, WebDavAdapter,
|
|
||||||
};
|
|
||||||
use crate::application::dtos::file_dto::FileDto;
|
use crate::application::dtos::file_dto::FileDto;
|
||||||
use crate::application::dtos::folder_dto::FolderDto;
|
use crate::application::dtos::folder_dto::FolderDto;
|
||||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||||
|
use crate::application::ports::file_ports::{FileManagementUseCase, FileUploadUseCase};
|
||||||
use crate::application::ports::inbound::FolderUseCase;
|
use crate::application::ports::inbound::FolderUseCase;
|
||||||
|
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
||||||
|
use crate::application::services::folder_service::FolderService;
|
||||||
use crate::common::di::AppState;
|
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 / ":" / "@"
|
||||||
@@ -65,7 +63,7 @@ fn encode_path_segment(segment: &str) -> String {
|
|||||||
/// Percent-encode a full slash-separated path, encoding each segment individually.
|
/// Percent-encode a full slash-separated path, encoding each segment individually.
|
||||||
pub(crate) fn encode_uri_path(path: &str) -> String {
|
pub(crate) fn encode_uri_path(path: &str) -> String {
|
||||||
path.split('/')
|
path.split('/')
|
||||||
.map(|seg| encode_path_segment(seg))
|
.map(encode_path_segment)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("/")
|
.join("/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ use axum::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::application::ports::file_ports::{FileRetrievalUseCase, FileUploadUseCase};
|
||||||
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,3 +1,4 @@
|
|||||||
|
use crate::application::services::batch_operations::BatchOperationService;
|
||||||
use crate::common::di::AppState;
|
use crate::common::di::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
@@ -8,7 +9,6 @@ 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> {
|
||||||
@@ -18,7 +18,6 @@ async fn get_version() -> AxumJson<serde_json::Value> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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};
|
||||||
use crate::interfaces::api::handlers::chunked_upload_handler::ChunkedUploadHandler;
|
use crate::interfaces::api::handlers::chunked_upload_handler::ChunkedUploadHandler;
|
||||||
|
|||||||
@@ -273,8 +273,8 @@ pub async fn auth_middleware(
|
|||||||
|
|
||||||
if let Some(token_str) =
|
if let Some(token_str) =
|
||||||
cookie_auth::extract_cookie_value(&headers, cookie_auth::ACCESS_COOKIE)
|
cookie_auth::extract_cookie_value(&headers, cookie_auth::ACCESS_COOKIE)
|
||||||
|
&& !token_str.is_empty()
|
||||||
{
|
{
|
||||||
if !token_str.is_empty() {
|
|
||||||
tracing::debug!("Processing cookie-based authentication");
|
tracing::debug!("Processing cookie-based authentication");
|
||||||
|
|
||||||
if let Some(auth_service) = state.auth_service.as_ref() {
|
if let Some(auth_service) = state.auth_service.as_ref() {
|
||||||
@@ -301,7 +301,6 @@ pub async fn auth_middleware(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No valid credentials found via any method
|
// No valid credentials found via any method
|
||||||
if state.auth_service.is_none() {
|
if state.auth_service.is_none() {
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ impl RateLimiter {
|
|||||||
|
|
||||||
/// Check whether the IP is allowed. Returns `Ok(current_count)` or
|
/// Check whether the IP is allowed. Returns `Ok(current_count)` or
|
||||||
/// `Err(StatusCode::TOO_MANY_REQUESTS)`.
|
/// `Err(StatusCode::TOO_MANY_REQUESTS)`.
|
||||||
|
#[allow(clippy::result_unit_err)]
|
||||||
pub fn check_and_increment(&self, ip: &str) -> Result<u32, ()> {
|
pub fn check_and_increment(&self, ip: &str) -> Result<u32, ()> {
|
||||||
let key = ip.to_string();
|
let key = ip.to_string();
|
||||||
// moka's entry API lets us atomically read-modify-write.
|
// moka's entry API lets us atomically read-modify-write.
|
||||||
@@ -92,14 +93,14 @@ pub fn extract_client_ip<B>(req: &Request<B>) -> String {
|
|||||||
let headers = req.headers();
|
let headers = req.headers();
|
||||||
|
|
||||||
// 1. X-Forwarded-For (first entry — closest to the client)
|
// 1. X-Forwarded-For (first entry — closest to the client)
|
||||||
if let Some(xff) = headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
|
if let Some(xff) = headers.get("x-forwarded-for").and_then(|v| v.to_str().ok())
|
||||||
if let Some(first) = xff.split(',').next() {
|
&& let Some(first) = xff.split(',').next()
|
||||||
|
{
|
||||||
let ip = first.trim();
|
let ip = first.trim();
|
||||||
if !ip.is_empty() {
|
if !ip.is_empty() {
|
||||||
return ip.to_string();
|
return ip.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 2. X-Real-Ip
|
// 2. X-Real-Ip
|
||||||
if let Some(xri) = headers.get("x-real-ip").and_then(|v| v.to_str().ok()) {
|
if let Some(xri) = headers.get("x-real-ip").and_then(|v| v.to_str().ok()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user