From 190527edfb7136420a75628089bce1b2dd0df973 Mon Sep 17 00:00:00 2001 From: zjean Date: Thu, 5 Mar 2026 21:28:51 +0100 Subject: [PATCH] style: apply rustfmt formatting to fix CI Co-Authored-By: Claude Opus 4.6 --- src/application/ports/chunked_upload_ports.rs | 6 ++- src/application/ports/share_ports.rs | 6 +-- .../services/admin_settings_service.rs | 5 +- src/application/services/batch_operations.rs | 10 +++- .../services/file_retrieval_service.rs | 5 +- src/application/services/folder_service.rs | 6 ++- src/application/services/share_service.rs | 29 ++++++----- src/application/services/trash_service.rs | 6 ++- src/common/stubs.rs | 6 ++- src/domain/entities/user.rs | 14 ++++-- .../repositories/settings_repository.rs | 5 +- .../pg/file_blob_read_repository.rs | 4 +- .../repositories/pg/folder_db_repository.rs | 4 +- src/infrastructure/repositories/pg/mod.rs | 5 +- .../repositories/pg/settings_pg_repository.rs | 5 +- .../repositories/pg/share_pg_repository.rs | 10 +--- .../services/chunked_upload_service.rs | 47 +++++++++++------ .../services/file_system_i18n_service.rs | 2 +- src/infrastructure/services/oidc_service.rs | 2 +- .../services/path_resolver_service.rs | 12 +++-- .../services/thumbnail_service_test.rs | 7 +-- src/interfaces/api/handlers/auth_handler.rs | 4 +- src/interfaces/api/handlers/batch_handler.rs | 50 +++++++++++++++---- .../api/handlers/chunked_upload_handler.rs | 32 ++++++++---- src/interfaces/api/handlers/dedup_handler.rs | 8 +-- src/interfaces/api/handlers/file_handler.rs | 39 ++++++++------- src/interfaces/api/handlers/folder_handler.rs | 29 ++++++----- src/interfaces/api/handlers/share_handler.rs | 24 +++------ src/interfaces/api/handlers/webdav_handler.rs | 25 ++++++---- 29 files changed, 242 insertions(+), 165 deletions(-) diff --git a/src/application/ports/chunked_upload_ports.rs b/src/application/ports/chunked_upload_ports.rs index f2b3c4f1..1f3082ce 100644 --- a/src/application/ports/chunked_upload_ports.rs +++ b/src/application/ports/chunked_upload_ports.rs @@ -80,7 +80,11 @@ pub trait ChunkedUploadPort: Send + Sync + 'static { ) -> Result; /// Get the current status of an upload session. - async fn get_status(&self, upload_id: &str, user_id: &str) -> Result; + async fn get_status( + &self, + upload_id: &str, + user_id: &str, + ) -> Result; /// Assemble all chunks into the final file. /// diff --git a/src/application/ports/share_ports.rs b/src/application/ports/share_ports.rs index eb7302ba..0bcc1592 100644 --- a/src/application/ports/share_ports.rs +++ b/src/application/ports/share_ports.rs @@ -16,11 +16,7 @@ pub trait ShareUseCase: Send + Sync + 'static { ) -> Result; /// Get a shared link by its ID (ownership-verified) - async fn get_shared_link( - &self, - id: &str, - requester_id: &str, - ) -> Result; + async fn get_shared_link(&self, id: &str, requester_id: &str) -> Result; /// Get a shared link by its token (for access by non-users) async fn get_shared_link_by_token(&self, token: &str) -> Result; diff --git a/src/application/services/admin_settings_service.rs b/src/application/services/admin_settings_service.rs index 9604af49..c2accbad 100644 --- a/src/application/services/admin_settings_service.rs +++ b/src/application/services/admin_settings_service.rs @@ -384,10 +384,7 @@ impl AdminSettingsService { /// initialized (the caller "won" the race), or `Ok(false)` if another /// request already did it. This eliminates the race-condition window /// between `is_system_initialized()` and `mark_system_initialized()`. - pub async fn try_claim_initialization( - &self, - admin_user_id: &str, - ) -> Result { + pub async fn try_claim_initialization(&self, admin_user_id: &str) -> Result { self.settings_repo .try_claim_initialization(admin_user_id) .await diff --git a/src/application/services/batch_operations.rs b/src/application/services/batch_operations.rs index ea8efdcf..b4199b1c 100644 --- a/src/application/services/batch_operations.rs +++ b/src/application/services/batch_operations.rs @@ -682,7 +682,11 @@ impl BatchOperationService { // ── Add folders as sub-trees (bulk subtree queries, not N+1) ───── for folder_id in &folder_ids { - match self.folder_service.get_folder_owned(folder_id, caller_id).await { + match self + .folder_service + .get_folder_owned(folder_id, caller_id) + .await + { Ok(root_folder) => { if let Err(e) = self .add_folder_subtree_to_zip(&mut zip, folder_id, &root_folder, caller_id) @@ -928,7 +932,9 @@ impl BatchOperationService { async move { // If a parent is specified, verify the caller owns it - if let Some(ref pid) = parent_id && let Err(e) = folder_service.get_folder_owned(pid, &caller).await { + if let Some(ref pid) = parent_id + && let Err(e) = folder_service.get_folder_owned(pid, &caller).await + { let id = format!("{}:{}", name, pid); return (id, Err(e)); } diff --git a/src/application/services/file_retrieval_service.rs b/src/application/services/file_retrieval_service.rs index d94592fc..7e1fb9a9 100644 --- a/src/application/services/file_retrieval_service.rs +++ b/src/application/services/file_retrieval_service.rs @@ -228,7 +228,10 @@ impl FileRetrievalUseCase for FileRetrievalService { folder_id: Option<&str>, owner_id: &str, ) -> Result, DomainError> { - let files = self.file_read.list_files_for_owner(folder_id, owner_id).await?; + let files = self + .file_read + .list_files_for_owner(folder_id, owner_id) + .await?; Ok(files.into_iter().map(FileDto::from).collect()) } diff --git a/src/application/services/folder_service.rs b/src/application/services/folder_service.rs index 76ae21a4..e547dd2e 100644 --- a/src/application/services/folder_service.rs +++ b/src/application/services/folder_service.rs @@ -32,7 +32,11 @@ impl FolderService { Ok(FolderDto::empty()) } - async fn get_folder_owned(&self, _id: &str, _caller_id: &str) -> Result { + async fn get_folder_owned( + &self, + _id: &str, + _caller_id: &str, + ) -> Result { Ok(FolderDto::empty()) } diff --git a/src/application/services/share_service.rs b/src/application/services/share_service.rs index e26d5ffb..2089d3fe 100644 --- a/src/application/services/share_service.rs +++ b/src/application/services/share_service.rs @@ -20,7 +20,10 @@ use crate::{ storage_ports::FileReadPort, }, }, - common::{config::AppConfig, errors::{DomainError, ErrorKind}}, + common::{ + config::AppConfig, + errors::{DomainError, ErrorKind}, + }, domain::entities::share::{Share, ShareItemType, SharePermissions}, }; @@ -147,11 +150,7 @@ impl ShareService { /// but belongs to a different user — this prevents share-ID enumeration /// attacks where an attacker probes IDs and uses 403-vs-404 to learn /// which ones are valid. - async fn fetch_owned_share( - &self, - id: &str, - requester_id: &str, - ) -> Result { + async fn fetch_owned_share(&self, id: &str, requester_id: &str) -> Result { let share = self .share_repository .find_share_by_id_for_user(id, requester_id) @@ -206,11 +205,7 @@ impl ShareUseCase for ShareService { Ok(ShareDto::from_entity(&saved_share, &self.config.base_url())) } - async fn get_shared_link( - &self, - id: &str, - requester_id: &str, - ) -> Result { + async fn get_shared_link(&self, id: &str, requester_id: &str) -> Result { // SECURITY: ownership-verified lookup — returns 404 if the share // doesn't exist OR belongs to another user. let share = self.fetch_owned_share(id, requester_id).await?; @@ -553,7 +548,11 @@ mod tests { Ok(ShareDto::from_entity(&saved_share, &self.config.base_url())) } - async fn get_shared_link(&self, id: &str, requester_id: &str) -> Result { + async fn get_shared_link( + &self, + id: &str, + requester_id: &str, + ) -> Result { let share = self .share_repository .find_share_by_id_for_user(id, requester_id) @@ -634,7 +633,11 @@ mod tests { Ok(ShareDto::from_entity(&updated, &self.config.base_url())) } - async fn delete_shared_link(&self, id: &str, requester_id: &str) -> Result<(), DomainError> { + async fn delete_shared_link( + &self, + id: &str, + requester_id: &str, + ) -> Result<(), DomainError> { self.share_repository .delete_share_for_user(id, requester_id) .await diff --git a/src/application/services/trash_service.rs b/src/application/services/trash_service.rs index 90699e09..5697e1fa 100644 --- a/src/application/services/trash_service.rs +++ b/src/application/services/trash_service.rs @@ -186,7 +186,11 @@ impl TrashUseCase for TrashService { // Returns NotFound if the file does not exist OR belongs to // another user, preventing cross-user trash operations. debug!("Getting file data (owner-scoped): {}", item_id); - let file = match self.file_read_port.get_file_for_owner(item_id, user_id).await { + let file = match self + .file_read_port + .get_file_for_owner(item_id, user_id) + .await + { Ok(file) => { debug!("File found: {} ({})", file.name(), item_id); file diff --git a/src/common/stubs.rs b/src/common/stubs.rs index 2b3fe0eb..08cde4ea 100644 --- a/src/common/stubs.rs +++ b/src/common/stubs.rs @@ -359,7 +359,11 @@ impl FolderUseCase for StubFolderUseCase { Ok(FolderDto::default()) } - async fn get_folder_owned(&self, _id: &str, _caller_id: &str) -> Result { + async fn get_folder_owned( + &self, + _id: &str, + _caller_id: &str, + ) -> Result { Ok(FolderDto::default()) } diff --git a/src/domain/entities/user.rs b/src/domain/entities/user.rs index baaf86a6..519f7724 100644 --- a/src/domain/entities/user.rs +++ b/src/domain/entities/user.rs @@ -292,8 +292,10 @@ impl User { )); } // Disallow leading/trailing dots or hyphens - if username.starts_with('.') || username.starts_with('-') - || username.ends_with('.') || username.ends_with('-') + if username.starts_with('.') + || username.starts_with('-') + || username.ends_with('.') + || username.ends_with('-') { return Err(UserError::InvalidUsername( "Username must not start or end with a dot or hyphen".to_string(), @@ -310,7 +312,9 @@ impl User { fn validate_email(email: &str) -> UserResult<()> { let parts: Vec<&str> = email.splitn(2, '@').collect(); if parts.len() != 2 { - return Err(UserError::ValidationError("Invalid email: missing @".to_string())); + return Err(UserError::ValidationError( + "Invalid email: missing @".to_string(), + )); } let (local, domain) = (parts[0], parts[1]); if local.is_empty() || domain.is_empty() { @@ -324,7 +328,9 @@ impl User { )); } // Reject characters commonly used in XSS / header injection - let forbidden = ['<', '>', '"', '\'', '\\', ' ', '\t', '\n', '\r', '(', ')', ',', ';']; + let forbidden = [ + '<', '>', '"', '\'', '\\', ' ', '\t', '\n', '\r', '(', ')', ',', ';', + ]; if email.chars().any(|c| forbidden.contains(&c)) { return Err(UserError::ValidationError( "Invalid email: contains forbidden characters".to_string(), diff --git a/src/domain/repositories/settings_repository.rs b/src/domain/repositories/settings_repository.rs index 536c9cf7..e921d3d9 100644 --- a/src/domain/repositories/settings_repository.rs +++ b/src/domain/repositories/settings_repository.rs @@ -34,10 +34,7 @@ pub trait SettingsRepository: Send + Sync + 'static { /// The default implementation falls back to the non-atomic /// get-then-set pattern for repositories that don't support a native /// atomic upsert. - async fn try_claim_initialization( - &self, - admin_user_id: &str, - ) -> Result { + async fn try_claim_initialization(&self, admin_user_id: &str) -> Result { // Default: non-atomic fallback (overridden by PG implementation) match self.get("system_initialized").await? { Some(v) if v == "true" => Ok(false), diff --git a/src/infrastructure/repositories/pg/file_blob_read_repository.rs b/src/infrastructure/repositories/pg/file_blob_read_repository.rs index 3a4e27d8..6207d6fb 100644 --- a/src/infrastructure/repositories/pg/file_blob_read_repository.rs +++ b/src/infrastructure/repositories/pg/file_blob_read_repository.rs @@ -325,9 +325,7 @@ impl FileReadPort for FileBlobReadRepository { .fetch_all(self.pool.as_ref()) .await } - .map_err(|e| { - DomainError::internal_error("FileBlobRead", format!("list_for_owner: {e}")) - })?; + .map_err(|e| DomainError::internal_error("FileBlobRead", format!("list_for_owner: {e}")))?; rows.into_iter() .map(|(id, name, fid, fpath, size, mime, ca, ma, uid)| { diff --git a/src/infrastructure/repositories/pg/folder_db_repository.rs b/src/infrastructure/repositories/pg/folder_db_repository.rs index 3ca8393c..3096a18e 100644 --- a/src/infrastructure/repositories/pg/folder_db_repository.rs +++ b/src/infrastructure/repositories/pg/folder_db_repository.rs @@ -868,7 +868,9 @@ impl FolderRepository for FolderDbRepository { user_id: &str, ) -> Result, DomainError> { let (where_extra, name_pattern) = match name_contains { - Some(name) if name.len() >= 3 => (" AND fo.name ILIKE $3", Some(super::like_escape(name))), + Some(name) if name.len() >= 3 => { + (" AND fo.name ILIKE $3", Some(super::like_escape(name))) + } _ => ("", None), }; diff --git a/src/infrastructure/repositories/pg/mod.rs b/src/infrastructure/repositories/pg/mod.rs index b5d753b5..8a245867 100644 --- a/src/infrastructure/repositories/pg/mod.rs +++ b/src/infrastructure/repositories/pg/mod.rs @@ -50,6 +50,9 @@ pub use user_pg_repository::UserPgRepository; /// `%` is a wildcard in LIKE patterns. #[inline] pub fn like_escape(raw: &str) -> String { - let escaped = raw.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_"); + let escaped = raw + .replace('\\', "\\\\") + .replace('%', "\\%") + .replace('_', "\\_"); format!("%{escaped}%") } diff --git a/src/infrastructure/repositories/pg/settings_pg_repository.rs b/src/infrastructure/repositories/pg/settings_pg_repository.rs index 30499af8..c98af3b9 100644 --- a/src/infrastructure/repositories/pg/settings_pg_repository.rs +++ b/src/infrastructure/repositories/pg/settings_pg_repository.rs @@ -102,10 +102,7 @@ impl SettingsRepository for SettingsPgRepository { /// /// Only the first caller that inserts the row gets `rows_affected == 1`; /// concurrent callers see 0 rows affected and receive `false`. - async fn try_claim_initialization( - &self, - admin_user_id: &str, - ) -> Result { + async fn try_claim_initialization(&self, admin_user_id: &str) -> Result { let result = sqlx::query( "INSERT INTO auth.admin_settings (key, value, category, is_secret, updated_by, updated_at) VALUES ('system_initialized', 'true', 'system', false, $1, NOW()) diff --git a/src/infrastructure/repositories/pg/share_pg_repository.rs b/src/infrastructure/repositories/pg/share_pg_repository.rs index c60cb89c..62779069 100644 --- a/src/infrastructure/repositories/pg/share_pg_repository.rs +++ b/src/infrastructure/repositories/pg/share_pg_repository.rs @@ -202,10 +202,7 @@ impl ShareStoragePort for SharePgRepository { .await .map_err(|e| { tracing::error!("Database error deleting share for user: {}", e); - DomainError::internal_error( - "Share", - format!("Failed to delete share: {e}"), - ) + DomainError::internal_error("Share", format!("Failed to delete share: {e}")) })?; if result.rows_affected() == 0 { @@ -242,10 +239,7 @@ impl ShareStoragePort for SharePgRepository { .await .map_err(|e| { tracing::error!("Database error finding shares by item for user: {}", e); - DomainError::internal_error( - "Share", - format!("Failed to find shares by item: {e}"), - ) + DomainError::internal_error("Share", format!("Failed to find shares by item: {e}")) })?; rows.iter().map(Self::row_to_entity).collect() diff --git a/src/infrastructure/services/chunked_upload_service.rs b/src/infrastructure/services/chunked_upload_service.rs index 6e0723fb..25938350 100644 --- a/src/infrastructure/services/chunked_upload_service.rs +++ b/src/infrastructure/services/chunked_upload_service.rs @@ -371,11 +371,7 @@ impl ChunkedUploadService { /// Verify that the given session belongs to the given user. /// Returns 404 (not 403) to avoid revealing the existence of other users' sessions. - fn verify_session_owner( - &self, - upload_id: &str, - user_id: &str, - ) -> Result<(), String> { + fn verify_session_owner(&self, upload_id: &str, user_id: &str) -> Result<(), String> { let session = self .sessions .get(upload_id) @@ -592,7 +588,11 @@ impl ChunkedUploadService { } /// Get upload status - async fn get_status_inner(&self, upload_id: &str, user_id: &str) -> Result { + async fn get_status_inner( + &self, + upload_id: &str, + user_id: &str, + ) -> Result { self.verify_session_owner(upload_id, user_id)?; let session = self @@ -808,9 +808,16 @@ impl ChunkedUploadPort for ChunkedUploadService { total_size: u64, chunk_size: Option, ) -> Result { - self.create_session_inner(user_id.to_owned(), filename, folder_id, content_type, total_size, chunk_size) - .await - .map_err(|e| DomainError::new(ErrorKind::InternalError, "ChunkedUpload", e)) + self.create_session_inner( + user_id.to_owned(), + filename, + folder_id, + content_type, + total_size, + chunk_size, + ) + .await + .map_err(|e| DomainError::new(ErrorKind::InternalError, "ChunkedUpload", e)) } async fn upload_chunk( @@ -826,7 +833,11 @@ impl ChunkedUploadPort for ChunkedUploadService { .map_err(|e| DomainError::new(ErrorKind::InternalError, "ChunkedUpload", e)) } - async fn get_status(&self, upload_id: &str, user_id: &str) -> Result { + async fn get_status( + &self, + upload_id: &str, + user_id: &str, + ) -> Result { self.get_status_inner(upload_id, user_id) .await .map_err(|e| DomainError::new(ErrorKind::NotFound, "ChunkedUpload", e)) @@ -1097,14 +1108,19 @@ mod tests { assert_eq!(r1.bytes_received, 1024); // 3. Status check - let status = service.get_status_inner(&id, "test-user").await.expect("status"); + let status = service + .get_status_inner(&id, "test-user") + .await + .expect("status"); assert!(status.is_complete); assert_eq!(status.completed_chunks, 2); assert!(status.pending_chunks.is_empty()); // 4. Complete (assemble) - let (path, filename, _folder, _ct, size, hash) = - service.complete_upload_inner(&id, "test-user").await.expect("complete"); + let (path, filename, _folder, _ct, size, hash) = service + .complete_upload_inner(&id, "test-user") + .await + .expect("complete"); assert_eq!(filename, "test.txt"); assert_eq!(size, 1024); assert!(!hash.is_empty()); @@ -1116,7 +1132,10 @@ mod tests { assert_eq!(&content[512..], &[b'B'; 512]); // 6. Finalize - service.finalize_upload_inner(&id, "test-user").await.expect("finalize"); + service + .finalize_upload_inner(&id, "test-user") + .await + .expect("finalize"); assert_eq!(service.active_sessions().await, 0); let _ = fs::remove_dir_all(&base).await; diff --git a/src/infrastructure/services/file_system_i18n_service.rs b/src/infrastructure/services/file_system_i18n_service.rs index 29c9f6ae..b85a778d 100644 --- a/src/infrastructure/services/file_system_i18n_service.rs +++ b/src/infrastructure/services/file_system_i18n_service.rs @@ -1,8 +1,8 @@ use serde_json::Value; use std::collections::HashMap; use std::path::PathBuf; -use tokio::sync::RwLock; use tokio::fs; +use tokio::sync::RwLock; use crate::domain::services::i18n_service::{I18nError, I18nResult, I18nService, Locale}; diff --git a/src/infrastructure/services/oidc_service.rs b/src/infrastructure/services/oidc_service.rs index 97d01135..92503be1 100644 --- a/src/infrastructure/services/oidc_service.rs +++ b/src/infrastructure/services/oidc_service.rs @@ -6,8 +6,8 @@ //! Compatible with Authentik, Keycloak, and any standard OIDC provider. use serde::Deserialize; -use tokio::sync::RwLock; use std::time::{Duration, Instant}; +use tokio::sync::RwLock; use crate::application::ports::auth_ports::{OidcIdClaims, OidcServicePort, OidcTokenSet}; use crate::common::config::OidcConfig; diff --git a/src/infrastructure/services/path_resolver_service.rs b/src/infrastructure/services/path_resolver_service.rs index 1eabc457..f237afe6 100644 --- a/src/infrastructure/services/path_resolver_service.rs +++ b/src/infrastructure/services/path_resolver_service.rs @@ -119,10 +119,10 @@ impl PathResolverService { LIMIT 1 "#, ) - .bind(path) // $1 - .bind(filename) // $2 - .bind(&folder_path) // $3 - .bind(user_id) // $4 + .bind(path) // $1 + .bind(filename) // $2 + .bind(&folder_path) // $3 + .bind(user_id) // $4 .fetch_optional(self.pool.as_ref()) .await .map_err(|e| DomainError::internal_error("PathResolver", format!("resolve_for_user: {e}")))? @@ -215,7 +215,9 @@ impl PathResolverService { .bind(user_id) .fetch_one(self.pool.as_ref()) .await - .map_err(|e| DomainError::internal_error("PathResolver", format!("exists_for_user: {e}")))?; + .map_err(|e| { + DomainError::internal_error("PathResolver", format!("exists_for_user: {e}")) + })?; Ok(exists) } diff --git a/src/infrastructure/services/thumbnail_service_test.rs b/src/infrastructure/services/thumbnail_service_test.rs index b9e01eec..003e8154 100644 --- a/src/infrastructure/services/thumbnail_service_test.rs +++ b/src/infrastructure/services/thumbnail_service_test.rs @@ -8,11 +8,8 @@ fn tiny_png() -> Vec { let mut img = image::RgbaImage::new(1, 1); img.put_pixel(0, 0, image::Rgba([255, 0, 0, 255])); let mut buf = Vec::new(); - img.write_to( - &mut std::io::Cursor::new(&mut buf), - image::ImageFormat::Png, - ) - .expect("encode test PNG"); + img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png) + .expect("encode test PNG"); buf } diff --git a/src/interfaces/api/handlers/auth_handler.rs b/src/interfaces/api/handlers/auth_handler.rs index 717b2617..1d012c8c 100644 --- a/src/interfaces/api/handlers/auth_handler.rs +++ b/src/interfaces/api/handlers/auth_handler.rs @@ -342,7 +342,9 @@ async fn logout( .ok() .map(|dto| dto.refresh_token) .or_else(|| cookie_auth::extract_cookie_value(&headers, cookie_auth::REFRESH_COOKIE)) - .ok_or_else(|| AppError::unauthorized("Refresh token required for logout (JSON body or cookie)"))?; + .ok_or_else(|| { + AppError::unauthorized("Refresh token required for logout (JSON body or cookie)") + })?; auth_service .auth_application_service diff --git a/src/interfaces/api/handlers/batch_handler.rs b/src/interfaces/api/handlers/batch_handler.rs index 36a3826b..3bfc24be 100644 --- a/src/interfaces/api/handlers/batch_handler.rs +++ b/src/interfaces/api/handlers/batch_handler.rs @@ -164,7 +164,10 @@ pub async fn move_files_batch( .await .map_err(|e| { tracing::error!("Batch move_files failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; // Convert result to DTO @@ -217,7 +220,10 @@ pub async fn copy_files_batch( .await .map_err(|e| { tracing::error!("Batch copy_files failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; // Convert result to DTO @@ -270,7 +276,10 @@ pub async fn delete_files_batch( .await .map_err(|e| { tracing::error!("Batch delete_files failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; // Create custom response for string IDs @@ -331,7 +340,10 @@ pub async fn delete_folders_batch( .await .map_err(|e| { tracing::error!("Batch delete_folders failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; // Create custom response for string IDs @@ -399,7 +411,10 @@ pub async fn create_folders_batch( .await .map_err(|e| { tracing::error!("Batch create_folders failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; // Convert result to DTO @@ -452,7 +467,10 @@ pub async fn get_files_batch( .await .map_err(|e| { tracing::error!("Batch get_files failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; // Convert result to DTO @@ -505,7 +523,10 @@ pub async fn get_folders_batch( .await .map_err(|e| { tracing::error!("Batch get_folders failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; // Convert result to DTO @@ -690,7 +711,10 @@ pub async fn move_folders_batch( .await .map_err(|e| { tracing::error!("Batch move_folders failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch operation failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch operation failed".to_string(), + ) })?; let response: BatchOperationResponse = result.into(); @@ -727,7 +751,10 @@ pub async fn download_batch( if combined_size > MAX_BATCH_SIZE { return Err(( StatusCode::BAD_REQUEST, - format!("Batch size {} exceeds maximum of {}", combined_size, MAX_BATCH_SIZE), + format!( + "Batch size {} exceeds maximum of {}", + combined_size, MAX_BATCH_SIZE + ), )); } @@ -737,7 +764,10 @@ pub async fn download_batch( .await .map_err(|e| { tracing::error!("Batch download ZIP failed: {}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Batch download failed".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Batch download failed".to_string(), + ) })?; // Read file size for Content-Length before splitting ownership diff --git a/src/interfaces/api/handlers/chunked_upload_handler.rs b/src/interfaces/api/handlers/chunked_upload_handler.rs index 2145a58b..b1e31233 100644 --- a/src/interfaces/api/handlers/chunked_upload_handler.rs +++ b/src/interfaces/api/handlers/chunked_upload_handler.rs @@ -190,7 +190,13 @@ impl ChunkedUploadHandler { }); match chunked_service - .upload_chunk(&upload_id, &auth_user.id, params.chunk_index, body, checksum) + .upload_chunk( + &upload_id, + &auth_user.id, + params.chunk_index, + body, + checksum, + ) .await { Ok(response) => { @@ -213,7 +219,7 @@ impl ChunkedUploadHandler { .unwrap() .into_response() } - Err(e) => AppError::from(e).into_response() + Err(e) => AppError::from(e).into_response(), } } @@ -261,7 +267,10 @@ impl ChunkedUploadHandler { // Assemble chunks (hash-on-write: SHA-256 computed during assembly) let (assembled_path, filename, folder_id, content_type, total_size, hash) = - match chunked_service.complete_upload(&upload_id, &auth_user.id).await { + match chunked_service + .complete_upload(&upload_id, &auth_user.id) + .await + { Ok(result) => result, Err(e) => { return AppError::from(e).into_response(); @@ -289,7 +298,9 @@ impl ChunkedUploadHandler { { Ok(file) => { // Cleanup session - let _ = chunked_service.finalize_upload(&upload_id, &auth_user.id).await; + let _ = chunked_service + .finalize_upload(&upload_id, &auth_user.id) + .await; tracing::info!( "✅ CHUNKED UPLOAD COMPLETE: {} (ID: {}, {} bytes)", @@ -311,8 +322,7 @@ impl ChunkedUploadHandler { } Err(e) => { tracing::error!("Failed to create file from assembled upload: {:?}", e); - AppError::internal_error(format!("Failed to create file: {}", e)) - .into_response() + AppError::internal_error(format!("Failed to create file: {}", e)).into_response() } } } @@ -327,10 +337,14 @@ impl ChunkedUploadHandler { ) -> impl IntoResponse { let chunked_service = &state.core.chunked_upload_service; - match chunked_service.cancel_upload(&upload_id, &auth_user.id).await { + match chunked_service + .cancel_upload(&upload_id, &auth_user.id) + .await + { Ok(_) => StatusCode::NO_CONTENT.into_response(), - Err(e) => AppError::internal_error(format!("Failed to cancel upload: {}", e)) - .into_response(), + Err(e) => { + AppError::internal_error(format!("Failed to cancel upload: {}", e)).into_response() + } } } } diff --git a/src/interfaces/api/handlers/dedup_handler.rs b/src/interfaces/api/handlers/dedup_handler.rs index 2c3de0ea..381577b9 100644 --- a/src/interfaces/api/handlers/dedup_handler.rs +++ b/src/interfaces/api/handlers/dedup_handler.rs @@ -231,9 +231,7 @@ impl DedupHandler { return Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .header(header::CONTENT_TYPE, "application/json") - .body(Body::from( - r#"{"error": "Upload failed"}"#, - )) + .body(Body::from(r#"{"error": "Upload failed"}"#)) .unwrap() .into_response(); } @@ -408,9 +406,7 @@ impl DedupHandler { return Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .header(header::CONTENT_TYPE, "application/json") - .body(Body::from( - r#"{"error": "Verification failed"}"#, - )) + .body(Body::from(r#"{"error": "Verification failed"}"#)) .unwrap() .into_response(); } diff --git a/src/interfaces/api/handlers/file_handler.rs b/src/interfaces/api/handlers/file_handler.rs index 4951dd6a..de5f0859 100644 --- a/src/interfaces/api/handlers/file_handler.rs +++ b/src/interfaces/api/handlers/file_handler.rs @@ -106,7 +106,11 @@ impl FileHandler { if let Some(ref fid) = folder_id { use crate::application::ports::inbound::FolderUseCase; let folder_service = &state.applications.folder_service; - if folder_service.get_folder_owned(fid, &auth_user.id).await.is_err() { + if folder_service + .get_folder_owned(fid, &auth_user.id) + .await + .is_err() + { tracing::warn!( "⛔ UPLOAD REJECTED (IDOR): user='{}' attempted upload to folder '{}' owned by another user", auth_user.username, @@ -323,7 +327,12 @@ impl FileHandler { } // Resolve the actual blob path on disk (not the logical file path). - let blob_hash = match state.repositories.file_read_repository.get_blob_hash(&id).await { + let blob_hash = match state + .repositories + .file_read_repository + .get_blob_hash(&id) + .await + { Ok(h) => h, Err(err) => { return ( @@ -353,10 +362,8 @@ impl FileHandler { .unwrap() .into_response() } - Err(err) => { - AppError::internal_error(format!("Thumbnail generation failed: {}", err)) - .into_response() - } + Err(err) => AppError::internal_error(format!("Thumbnail generation failed: {}", err)) + .into_response(), } } @@ -529,9 +536,7 @@ impl FileHandler { .unwrap() .into_response(), }, - Err(err) => { - AppError::from(err).into_response() - } + Err(err) => AppError::from(err).into_response(), } } @@ -581,9 +586,7 @@ impl FileHandler { .insert(header::ETAG, header::HeaderValue::from_str(&etag).unwrap()); resp } - Err(err) => { - AppError::from(err).into_response() - } + Err(err) => AppError::from(err).into_response(), } } @@ -664,7 +667,7 @@ impl FileHandler { match result { Ok(_) => StatusCode::NO_CONTENT.into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -696,7 +699,7 @@ impl FileHandler { let mgmt = &state.applications.file_management_service; match mgmt.rename_file_owned(&id, &auth_user.id, &new_name).await { Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -716,7 +719,7 @@ impl FileHandler { .await { Ok(file) => (StatusCode::OK, Json(file)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -735,7 +738,7 @@ impl FileHandler { let mgmt = &state.applications.file_management_service; match mgmt.move_file_owned(&id, &auth_user.id, folder_id).await { Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -794,9 +797,7 @@ impl FileHandler { }) .collect(); - format!( - "{disposition}; filename=\"{ascii_safe}\"; filename*=UTF-8''{encoded}" - ) + format!("{disposition}; filename=\"{ascii_safe}\"; filename*=UTF-8''{encoded}") } /// Build a 201 Created JSON response. diff --git a/src/interfaces/api/handlers/folder_handler.rs b/src/interfaces/api/handlers/folder_handler.rs index 9edb4c60..e7d830f4 100644 --- a/src/interfaces/api/handlers/folder_handler.rs +++ b/src/interfaces/api/handlers/folder_handler.rs @@ -70,19 +70,24 @@ impl FolderHandler { // ── SECURITY: Verify parent folder ownership (IDOR V-04 fix) ── if let Some(ref parent_id) = dto.parent_id { use crate::application::ports::inbound::FolderUseCase; - if service.get_folder_owned(parent_id, &auth_user.id).await.is_err() { + if service + .get_folder_owned(parent_id, &auth_user.id) + .await + .is_err() + { tracing::warn!( "create_folder: user '{}' attempted to create folder in parent '{}' owned by another user", auth_user.username, parent_id, ); - return AppError::not_found(format!("Parent folder not found: {}", parent_id)).into_response(); + return AppError::not_found(format!("Parent folder not found: {}", parent_id)) + .into_response(); } } match service.create_folder(dto).await { Ok(folder) => (StatusCode::CREATED, Json(folder)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -109,7 +114,7 @@ impl FolderHandler { } (StatusCode::OK, Json(folder)).into_response() } - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -154,7 +159,7 @@ impl FolderHandler { .await { Ok(paginated_result) => (StatusCode::OK, Json(paginated_result)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -171,7 +176,7 @@ impl FolderHandler { .await { Ok(folders) => (StatusCode::OK, Json(folders)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -237,7 +242,7 @@ impl FolderHandler { .insert(header::ETAG, header::HeaderValue::from_str(&etag).unwrap()); resp } - (Err(err), _) | (_, Err(err)) => AppError::from(err).into_response() + (Err(err), _) | (_, Err(err)) => AppError::from(err).into_response(), } } @@ -250,7 +255,7 @@ impl FolderHandler { ) -> impl IntoResponse { match service.rename_folder(&id, dto, &auth_user.id).await { Ok(folder) => (StatusCode::OK, Json(folder)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -263,7 +268,7 @@ impl FolderHandler { ) -> impl IntoResponse { match service.move_folder(&id, dto, &auth_user.id).await { Ok(folder) => (StatusCode::OK, Json(folder)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -275,7 +280,7 @@ impl FolderHandler { ) -> impl IntoResponse { match service.delete_folder(&id, &auth_user.id).await { Ok(_) => StatusCode::NO_CONTENT.into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -313,9 +318,7 @@ impl FolderHandler { tracing::info!("Folder permanently deleted: {}", id); StatusCode::NO_CONTENT.into_response() } - Err(err) => { - AppError::from(err).into_response() - } + Err(err) => AppError::from(err).into_response(), } } diff --git a/src/interfaces/api/handlers/share_handler.rs b/src/interfaces/api/handlers/share_handler.rs index 5c66b600..e92db963 100644 --- a/src/interfaces/api/handlers/share_handler.rs +++ b/src/interfaces/api/handlers/share_handler.rs @@ -40,12 +40,9 @@ pub async fn create_shared_link( auth_user: AuthUser, Json(dto): Json, ) -> impl IntoResponse { - match share_use_case - .create_shared_link(&auth_user.id, dto) - .await - { + match share_use_case.create_shared_link(&auth_user.id, dto).await { Ok(share) => (StatusCode::CREATED, Json(share)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -57,7 +54,7 @@ pub async fn get_shared_link( ) -> impl IntoResponse { match share_use_case.get_shared_link(&id, &auth_user.id).await { Ok(share) => (StatusCode::OK, Json(share)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -116,7 +113,7 @@ pub async fn update_shared_link( .await { Ok(share) => (StatusCode::OK, Json(share)).into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -126,12 +123,9 @@ pub async fn delete_shared_link( auth_user: AuthUser, Path(id): Path, ) -> impl IntoResponse { - match share_use_case - .delete_shared_link(&id, &auth_user.id) - .await - { + match share_use_case.delete_shared_link(&id, &auth_user.id).await { Ok(_) => StatusCode::NO_CONTENT.into_response(), - Err(err) => AppError::from(err).into_response() + Err(err) => AppError::from(err).into_response(), } } @@ -160,8 +154,7 @@ pub async fn access_shared_item( .into_response(); } if err.message.contains("expired") { - return AppError::new(StatusCode::GONE, err.message, "Expired") - .into_response(); + return AppError::new(StatusCode::GONE, err.message, "Expired").into_response(); } } AppError::from(err).into_response() @@ -183,8 +176,7 @@ pub async fn verify_shared_item_password( Err(err) => { if err.kind == ErrorKind::AccessDenied { if err.message.contains("expired") { - return AppError::new(StatusCode::GONE, err.message, "Expired") - .into_response(); + return AppError::new(StatusCode::GONE, err.message, "Expired").into_response(); } if err.message.contains("password") { return AppError::unauthorized("Invalid password").into_response(); diff --git a/src/interfaces/api/handlers/webdav_handler.rs b/src/interfaces/api/handlers/webdav_handler.rs index 2cf0d0eb..de29d6af 100644 --- a/src/interfaces/api/handlers/webdav_handler.rs +++ b/src/interfaces/api/handlers/webdav_handler.rs @@ -1161,10 +1161,7 @@ async fn handle_move( // Resolve source: single-query when PathResolver is available (user-scoped) if let Some(resolver) = &state.path_resolver { - match resolver - .resolve_path_for_user(&source_path, &user.id) - .await - { + match resolver.resolve_path_for_user(&source_path, &user.id).await { Ok(ResolvedResource::Folder(folder)) => { let dest_folder_name = destination_path .split('/') @@ -1183,7 +1180,11 @@ async fn handle_move( match folder_service.get_folder_by_path(dest_parent_path).await { Ok(parent) => { // SECURITY: verify destination parent belongs to caller (V-08) - assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?; + assert_owner( + parent.owner_id.as_deref(), + &user.id, + dest_parent_path, + )?; Some(parent.id) } Err(_) => None, @@ -1228,7 +1229,10 @@ async fn handle_move( if source_parent_path != dest_parent_path { // SECURITY: verify destination parent belongs to caller (V-08) - if !dest_parent_path.is_empty() && let Ok(parent) = folder_service.get_folder_by_path(dest_parent_path).await { + if !dest_parent_path.is_empty() + && let Ok(parent) = + folder_service.get_folder_by_path(dest_parent_path).await + { assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?; } file_management_service @@ -1327,7 +1331,9 @@ async fn handle_move( if source_parent_path != dest_parent_path { // SECURITY: verify destination parent belongs to caller (V-08) - if !dest_parent_path.is_empty() && let Ok(parent) = folder_service.get_folder_by_path(dest_parent_path).await { + if !dest_parent_path.is_empty() + && let Ok(parent) = folder_service.get_folder_by_path(dest_parent_path).await + { assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?; } file_management_service @@ -1436,10 +1442,7 @@ async fn handle_copy( // Resolve source: single-query when PathResolver is available (user-scoped) if let Some(resolver) = &state.path_resolver { - match resolver - .resolve_path_for_user(&source_path, &user.id) - .await - { + match resolver.resolve_path_for_user(&source_path, &user.id).await { Ok(ResolvedResource::Folder(folder)) => { let recursive = depth != "0";