refactor: apply clippy auto-fixes (162 warnings resolved)

- Fix needless borrows and references
- Collapse nested if statements
- Replace manual strip_prefix with str::strip_prefix()
- Remove redundant closures in map/unwrap_or_else
- Use Iterator::next_back() instead of rev().next()
- Simplify map_or patterns
- Use std::io::Error::other() instead of new(ErrorKind::Other, ..)
- Use div_ceil() instead of manual ceiling division
- Consolidate format! string arguments
- Various other idiomatic Rust improvements

39 files changed, 220 insertions(+), 320 deletions(-)
This commit is contained in:
Dionisio
2026-02-14 01:26:02 +01:00
parent 516b8727d2
commit 67137a3ef2
39 changed files with 220 additions and 320 deletions
+2 -2
View File
@@ -360,9 +360,9 @@ impl CalDavAdapter {
for (name, value) in &calendar.custom_properties {
// Skip properties that start with _ - they're internal
if !name.starts_with('_') {
xml_writer.write_event(Event::Start(BytesStart::new(&format!("CS:{}", name))))?;
xml_writer.write_event(Event::Start(BytesStart::new(format!("CS:{}", name))))?;
xml_writer.write_event(Event::Text(BytesText::new(value)))?;
xml_writer.write_event(Event::End(BytesEnd::new(&format!("CS:{}", name))))?;
xml_writer.write_event(Event::End(BytesEnd::new(format!("CS:{}", name))))?;
}
}
+17 -19
View File
@@ -222,7 +222,7 @@ impl WebDavAdapter {
// Add response for current folder if provided
if let Some(folder) = folder {
Self::write_folder_response(&mut xml_writer, folder, request, &format!("{}", base_href))?;
Self::write_folder_response(&mut xml_writer, folder, request, base_href)?;
}
// If depth allows, add responses for files and subfolders
@@ -396,7 +396,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let created_at = chrono::DateTime::<Utc>::from_timestamp(folder.created_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
@@ -406,7 +406,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let modified_at = chrono::DateTime::<Utc>::from_timestamp(folder.modified_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
@@ -457,7 +457,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let created_at = chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
@@ -467,7 +467,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let modified_at = chrono::DateTime::<Utc>::from_timestamp(file.modified_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
@@ -536,7 +536,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let created_at = chrono::DateTime::<Utc>::from_timestamp(folder.created_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
@@ -546,7 +546,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let modified_at = chrono::DateTime::<Utc>::from_timestamp(folder.modified_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
@@ -568,12 +568,12 @@ impl WebDavAdapter {
},
_ => {
// Property not supported - write empty element
xml_writer.write_event(Event::Empty(BytesStart::new(&format!("D:{}", prop.name))))?;
xml_writer.write_event(Event::Empty(BytesStart::new(format!("D:{}", prop.name))))?;
}
}
} else {
// Non-DAV namespace, not supported
xml_writer.write_event(Event::Empty(BytesStart::new(&format!("{}:{}", prop.namespace, prop.name))))?;
xml_writer.write_event(Event::Empty(BytesStart::new(format!("{}:{}", prop.namespace, prop.name))))?;
}
}
@@ -612,7 +612,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let created_at = chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&created_at.to_rfc3339())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:creationdate")))?;
@@ -622,7 +622,7 @@ impl WebDavAdapter {
// Convert u64 timestamp to DateTime
let modified_at = chrono::DateTime::<Utc>::from_timestamp(file.modified_at as i64, 0)
.unwrap_or_else(|| Utc::now());
.unwrap_or_else(Utc::now);
xml_writer.write_event(Event::Text(BytesText::new(&modified_at.to_rfc2822())))?;
xml_writer.write_event(Event::End(BytesEnd::new("D:getlastmodified")))?;
@@ -634,12 +634,12 @@ impl WebDavAdapter {
},
_ => {
// Property not supported - write empty element
xml_writer.write_event(Event::Empty(BytesStart::new(&format!("D:{}", prop.name))))?;
xml_writer.write_event(Event::Empty(BytesStart::new(format!("D:{}", prop.name))))?;
}
}
} else {
// Non-DAV namespace, not supported
xml_writer.write_event(Event::Empty(BytesStart::new(&format!("{}:{}", prop.namespace, prop.name))))?;
xml_writer.write_event(Event::Empty(BytesStart::new(format!("{}:{}", prop.namespace, prop.name))))?;
}
}
@@ -997,22 +997,20 @@ impl WebDavAdapter {
/// Helper method to extract namespace from tag name
pub fn extract_namespace(name: &str) -> String {
if let Some(idx) = name.rfind(':') {
if idx > 0 {
if let Some(idx) = name.rfind(':')
&& idx > 0 {
return name[..idx].to_string();
}
}
// Default namespace for WebDAV
"DAV:".to_string()
}
/// Helper method to extract local name from tag name
pub fn extract_local_name(name: &str) -> String {
if let Some(idx) = name.rfind(':') {
if idx > 0 && idx < name.len() - 1 {
if let Some(idx) = name.rfind(':')
&& idx > 0 && idx < name.len() - 1 {
return name[idx+1..].to_string();
}
}
name.to_string()
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ impl<T> PaginatedResponseDto<T> {
let total_pages = if total_items == 0 {
0
} else {
(total_items + page_size - 1) / page_size
total_items.div_ceil(page_size)
};
let pagination = PaginationDto {
+1 -1
View File
@@ -75,7 +75,7 @@ pub trait IdMappingPort: Send + Sync + 'static {
/// Updates a file's path
async fn update_file_path(&self, file_id: &str, new_path: &PathBuf) -> Result<(), DomainError> {
let storage_path = StoragePath::from_string(&new_path.to_string_lossy().to_string());
let storage_path = StoragePath::from_string(new_path.to_string_lossy().as_ref());
self.update_path(file_id, &storage_path).await
}
}
@@ -139,11 +139,10 @@ impl AdminSettingsService {
self.settings_repo.set("oidc.issuer_url", &dto.issuer_url, cat, false, by).await?;
self.settings_repo.set("oidc.client_id", &dto.client_id, cat, false, by).await?;
if let Some(ref secret) = dto.client_secret {
if !secret.is_empty() {
if let Some(ref secret) = dto.client_secret
&& !secret.is_empty() {
self.settings_repo.set("oidc.client_secret", secret, cat, true, by).await?;
}
}
if let Some(ref v) = dto.scopes {
self.settings_repo.set("oidc.scopes", v, cat, false, by).await?;
}
@@ -152,13 +152,13 @@ impl AuthApplicationService {
/// Returns whether OIDC is configured and enabled
pub fn oidc_enabled(&self) -> bool {
let state = self.oidc.read().unwrap();
state.service.is_some() && state.config.as_ref().map_or(false, |c| c.enabled)
state.service.is_some() && state.config.as_ref().is_some_and(|c| c.enabled)
}
/// Returns whether password login is disabled (OIDC-only mode)
pub fn password_login_disabled(&self) -> bool {
let state = self.oidc.read().unwrap();
state.config.as_ref().map_or(false, |c| c.disable_password_login)
state.config.as_ref().is_some_and(|c| c.disable_password_login)
}
/// Returns a clone of the OIDC config if available
@@ -145,12 +145,12 @@ impl FileRetrievalUseCase for FileRetrievalService {
let mime_type = dto.mime_type.clone();
let file_size = dto.size;
let file_name = dto.name.clone();
let modified_at = dto.modified_at.clone();
let modified_at = dto.modified_at;
let do_transcode = accept_webp && !prefer_original;
// ── Tier 0: Write-behind cache ───────────────────────
if let Some(wb) = &self.write_behind {
if let Some(pending) = wb.get_pending(id).await {
if let Some(wb) = &self.write_behind
&& let Some(pending) = wb.get_pending(id).await {
debug!("⚡ TIER 0 Write-Behind HIT: {} ({} bytes)", file_name, pending.len());
let (data, mime) = if do_transcode {
if let Some((t, m)) = self.try_transcode(id, &pending, &mime_type, file_size, true).await {
@@ -167,30 +167,27 @@ impl FileRetrievalUseCase for FileRetrievalService {
was_transcoded: do_transcode,
}));
}
}
// ── Tier 1: Hot cache + transcode (<10 MB) ──────────
if file_size < CACHE_THRESHOLD {
// Check content cache first
if let Some(cache) = &self.content_cache {
if let Some((cached, _etag, _ct)) = cache.get(id).await {
if let Some(cache) = &self.content_cache
&& let Some((cached, _etag, _ct)) = cache.get(id).await {
debug!("🔥 TIER 1 Cache HIT: {} ({} bytes)", file_name, cached.len());
if do_transcode {
if let Some((t, m)) = self.try_transcode(id, &cached, &mime_type, file_size, true).await {
if do_transcode
&& let Some((t, m)) = self.try_transcode(id, &cached, &mime_type, file_size, true).await {
return Ok((dto, OptimizedFileContent::Bytes {
data: t,
mime_type: m,
was_transcoded: true,
}));
}
}
return Ok((dto, OptimizedFileContent::Bytes {
data: cached,
mime_type: mime_type.clone(),
was_transcoded: false,
}));
}
}
// Cache miss – load from disk
debug!("💾 TIER 1 Cache MISS: {} – loading from disk", file_name);
@@ -203,15 +200,14 @@ impl FileRetrievalUseCase for FileRetrievalService {
cache.put(id.to_string(), content_bytes.clone(), etag, mime_type.clone()).await;
}
if do_transcode {
if let Some((t, m)) = self.try_transcode(id, &content_bytes, &mime_type, file_size, true).await {
if do_transcode
&& let Some((t, m)) = self.try_transcode(id, &content_bytes, &mime_type, file_size, true).await {
return Ok((dto, OptimizedFileContent::Bytes {
data: t,
mime_type: m,
was_transcoded: true,
}));
}
}
return Ok((dto, OptimizedFileContent::Bytes {
data: content_bytes,
mime_type: mime_type.clone(),
@@ -13,7 +13,7 @@ use crate::common::errors::DomainError;
use tracing::{debug, info, warn};
/// Threshold for using streaming upload (files >= 1MB use streaming)
const STREAMING_UPLOAD_THRESHOLD: usize = 1 * 1024 * 1024;
const STREAMING_UPLOAD_THRESHOLD: usize = 1024 * 1024;
/// Threshold for write-behind cache (files < 256KB get instant response)
const WRITE_BEHIND_THRESHOLD: usize = 256 * 1024;
@@ -168,9 +168,9 @@ impl FileUploadUseCase for FileUploadService {
}
// ─── TIER 1: Write-Behind (<256 KB) ──────────────────
if total_size < WRITE_BEHIND_THRESHOLD {
if let Some(wb) = &self.write_behind {
if wb.is_eligible_size(total_size) {
if total_size < WRITE_BEHIND_THRESHOLD
&& let Some(wb) = &self.write_behind
&& wb.is_eligible_size(total_size) {
let data: Bytes = if chunks.len() == 1 {
chunks.into_iter().next().unwrap()
} else {
@@ -198,12 +198,10 @@ impl FileUploadUseCase for FileUploadService {
self.maybe_update_storage_usage(&dto);
return Ok((dto, UploadStrategy::WriteBehind));
}
}
}
// ─── TIER 2: Streaming (≥1 MB) ──────────────────────
if total_size >= STREAMING_UPLOAD_THRESHOLD {
let chunk_stream = stream::iter(chunks.into_iter().map(|c| Ok::<_, std::io::Error>(c)));
let chunk_stream = stream::iter(chunks.into_iter().map(Ok::<_, std::io::Error>));
let pinned_stream: Pin<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>> =
Box::pin(chunk_stream);
+31 -47
View File
@@ -154,8 +154,8 @@ impl SearchService {
return None;
}
if let Ok(cache) = self.search_cache.lock() {
if let Some(cached_result) = cache.get(key) {
if let Ok(cache) = self.search_cache.lock()
&& let Some(cached_result) = cache.get(key) {
let now = Instant::now();
let ttl = Duration::from_secs(self.cache_ttl);
@@ -164,7 +164,6 @@ impl SearchService {
return Some(cached_result.results.clone());
}
}
}
None
}
@@ -183,14 +182,13 @@ impl SearchService {
if let Ok(mut cache) = self.search_cache.lock() {
// If the cache is full, remove the oldest entry
if cache.len() >= self.max_cache_size {
if let Some((oldest_key, _)) = cache
if cache.len() >= self.max_cache_size
&& let Some((oldest_key, _)) = cache
.iter()
.min_by_key(|(_, result)| result.timestamp) {
let key_to_remove = oldest_key.clone();
cache.remove(&key_to_remove);
}
}
// Store the new result
cache.insert(key, CachedSearchResult {
@@ -211,15 +209,14 @@ impl SearchService {
files.into_iter()
.filter(|file| {
// Filter by name
if let Some(name_query) = &criteria.name_contains {
if !file.name.to_lowercase().contains(&name_query.to_lowercase()) {
if let Some(name_query) = &criteria.name_contains
&& !file.name.to_lowercase().contains(&name_query.to_lowercase()) {
return false;
}
}
// Filter by file type (extension)
if let Some(file_types) = &criteria.file_types {
if let Some(extension) = file.name.split('.').last() {
if let Some(extension) = file.name.split('.').next_back() {
if !file_types.iter().any(|ext| ext.eq_ignore_ascii_case(extension)) {
return false;
}
@@ -230,43 +227,37 @@ impl SearchService {
}
// Filter by creation date
if let Some(created_after) = criteria.created_after {
if file.created_at < created_after {
if let Some(created_after) = criteria.created_after
&& file.created_at < created_after {
return false;
}
}
if let Some(created_before) = criteria.created_before {
if file.created_at > created_before {
if let Some(created_before) = criteria.created_before
&& file.created_at > created_before {
return false;
}
}
// Filter by modification date
if let Some(modified_after) = criteria.modified_after {
if file.modified_at < modified_after {
if let Some(modified_after) = criteria.modified_after
&& file.modified_at < modified_after {
return false;
}
}
if let Some(modified_before) = criteria.modified_before {
if file.modified_at > modified_before {
if let Some(modified_before) = criteria.modified_before
&& file.modified_at > modified_before {
return false;
}
}
// Filter by size
if let Some(min_size) = criteria.min_size {
if file.size < min_size {
if let Some(min_size) = criteria.min_size
&& file.size < min_size {
return false;
}
}
if let Some(max_size) = criteria.max_size {
if file.size > max_size {
if let Some(max_size) = criteria.max_size
&& file.size > max_size {
return false;
}
}
true
})
@@ -284,37 +275,32 @@ impl SearchService {
folders.into_iter()
.filter(|folder| {
// Filter by name
if let Some(name_query) = &criteria.name_contains {
if !folder.name.to_lowercase().contains(&name_query.to_lowercase()) {
if let Some(name_query) = &criteria.name_contains
&& !folder.name.to_lowercase().contains(&name_query.to_lowercase()) {
return false;
}
}
// Filter by creation date
if let Some(created_after) = criteria.created_after {
if folder.created_at < created_after {
if let Some(created_after) = criteria.created_after
&& folder.created_at < created_after {
return false;
}
}
if let Some(created_before) = criteria.created_before {
if folder.created_at > created_before {
if let Some(created_before) = criteria.created_before
&& folder.created_at > created_before {
return false;
}
}
// Filter by modification date
if let Some(modified_after) = criteria.modified_after {
if folder.modified_at < modified_after {
if let Some(modified_after) = criteria.modified_after
&& folder.modified_at < modified_after {
return false;
}
}
if let Some(modified_before) = criteria.modified_before {
if folder.modified_at > modified_before {
if let Some(modified_before) = criteria.modified_before
&& folder.modified_at > modified_before {
return false;
}
}
true
})
@@ -438,10 +424,8 @@ impl SearchUseCase for SearchService {
if idx < found_folders.len() {
paginated_folders.push(found_folders[idx].clone());
}
} else {
if idx < found_files.len() {
paginated_files.push(found_files[idx].clone());
}
} else if idx < found_files.len() {
paginated_files.push(found_files[idx].clone());
}
}
@@ -57,21 +57,19 @@ impl TrashRepository for MockTrashRepository {
async fn restore_from_trash(&self, id: &Uuid, user_id: &Uuid) -> Result<()> {
let mut items = self.trash_items.lock().unwrap();
if let Some(item) = items.get(id) {
if item.user_id() == *user_id {
if let Some(item) = items.get(id)
&& item.user_id() == *user_id {
items.remove(id);
}
}
Ok(())
}
async fn delete_permanently(&self, id: &Uuid, user_id: &Uuid) -> Result<()> {
let mut items = self.trash_items.lock().unwrap();
if let Some(item) = items.get(id) {
if item.user_id() == *user_id {
if let Some(item) = items.get(id)
&& item.user_id() == *user_id {
items.remove(id);
}
}
Ok(())
}
+24 -36
View File
@@ -142,8 +142,7 @@ impl ResourceConfig {
}
// Calculate the number of chunks based on size
let chunk_count = (size_bytes as usize + config.parallel_chunk_size_bytes - 1)
/ config.parallel_chunk_size_bytes;
let chunk_count = (size_bytes as usize).div_ceil(config.parallel_chunk_size_bytes);
// Limit to the maximum number of parallel chunks
chunk_count.min(config.max_parallel_chunks)
@@ -156,7 +155,7 @@ impl ResourceConfig {
}
// Distribute the size evenly among the chunks
((file_size as usize) + chunk_count - 1) / chunk_count
(file_size as usize).div_ceil(chunk_count)
}
}
@@ -421,11 +420,10 @@ impl AppConfig {
config.static_path = PathBuf::from(static_path);
}
if let Ok(server_port) = env::var("OXICLOUD_SERVER_PORT") {
if let Ok(port) = server_port.parse::<u16>() {
if let Ok(server_port) = env::var("OXICLOUD_SERVER_PORT")
&& let Ok(port) = server_port.parse::<u16>() {
config.server_port = port;
}
}
if let Ok(server_host) = env::var("OXICLOUD_SERVER_HOST") {
config.server_host = server_host;
@@ -437,18 +435,16 @@ impl AppConfig {
}
if let Ok(max_connections) = env::var("OXICLOUD_DB_MAX_CONNECTIONS")
.map(|v| v.parse::<u32>()) {
if let Ok(val) = max_connections {
.map(|v| v.parse::<u32>())
&& let Ok(val) = max_connections {
config.database.max_connections = val;
}
}
if let Ok(min_connections) = env::var("OXICLOUD_DB_MIN_CONNECTIONS")
.map(|v| v.parse::<u32>()) {
if let Ok(val) = min_connections {
.map(|v| v.parse::<u32>())
&& let Ok(val) = min_connections {
config.database.min_connections = val;
}
}
// Auth configuration
if let Ok(jwt_secret) = env::var("OXICLOUD_JWT_SECRET") {
@@ -473,54 +469,47 @@ impl AppConfig {
}
if let Ok(access_token_expiry) = env::var("OXICLOUD_ACCESS_TOKEN_EXPIRY_SECS")
.map(|v| v.parse::<i64>()) {
if let Ok(val) = access_token_expiry {
.map(|v| v.parse::<i64>())
&& let Ok(val) = access_token_expiry {
config.auth.access_token_expiry_secs = val;
}
}
if let Ok(refresh_token_expiry) = env::var("OXICLOUD_REFRESH_TOKEN_EXPIRY_SECS")
.map(|v| v.parse::<i64>()) {
if let Ok(val) = refresh_token_expiry {
.map(|v| v.parse::<i64>())
&& let Ok(val) = refresh_token_expiry {
config.auth.refresh_token_expiry_secs = val;
}
}
// Feature flags
if let Ok(enable_auth) = env::var("OXICLOUD_ENABLE_AUTH")
.map(|v| v.parse::<bool>()) {
if let Ok(val) = enable_auth {
.map(|v| v.parse::<bool>())
&& let Ok(val) = enable_auth {
config.features.enable_auth = val;
}
}
if let Ok(enable_user_storage_quotas) = env::var("OXICLOUD_ENABLE_USER_STORAGE_QUOTAS")
.map(|v| v.parse::<bool>()) {
if let Ok(val) = enable_user_storage_quotas {
.map(|v| v.parse::<bool>())
&& let Ok(val) = enable_user_storage_quotas {
config.features.enable_user_storage_quotas = val;
}
}
if let Ok(enable_file_sharing) = env::var("OXICLOUD_ENABLE_FILE_SHARING")
.map(|v| v.parse::<bool>()) {
if let Ok(val) = enable_file_sharing {
.map(|v| v.parse::<bool>())
&& let Ok(val) = enable_file_sharing {
config.features.enable_file_sharing = val;
}
}
if let Ok(enable_trash) = env::var("OXICLOUD_ENABLE_TRASH")
.map(|v| v.parse::<bool>()) {
if let Ok(val) = enable_trash {
.map(|v| v.parse::<bool>())
&& let Ok(val) = enable_trash {
config.features.enable_trash = val;
}
}
if let Ok(enable_search) = env::var("OXICLOUD_ENABLE_SEARCH")
.map(|v| v.parse::<bool>()) {
if let Ok(val) = enable_search {
.map(|v| v.parse::<bool>())
&& let Ok(val) = enable_search {
config.features.enable_search = val;
}
}
// OIDC configuration
if let Ok(v) = env::var("OXICLOUD_OIDC_ENABLED") {
@@ -558,12 +547,11 @@ impl AppConfig {
}
// Validate OIDC config when enabled
if config.oidc.enabled {
if config.oidc.issuer_url.is_empty() || config.oidc.client_id.is_empty() || config.oidc.client_secret.is_empty() {
if config.oidc.enabled
&& (config.oidc.issuer_url.is_empty() || config.oidc.client_id.is_empty() || config.oidc.client_secret.is_empty()) {
tracing::error!("OIDC is enabled but OXICLOUD_OIDC_ISSUER_URL, OXICLOUD_OIDC_CLIENT_ID, or OXICLOUD_OIDC_CLIENT_SECRET are not set");
config.oidc.enabled = false;
}
}
config
}
+12 -16
View File
@@ -109,15 +109,14 @@ impl CalendarEvent {
}
// Validate RRULE if provided (basic validation)
if let Some(ref rule) = rrule {
if !rule.starts_with("FREQ=") {
if let Some(ref rule) = rrule
&& !rule.starts_with("FREQ=") {
return Err(DomainError::new(
ErrorKind::InvalidInput,
"CalendarEvent",
"Recurrence rule must start with FREQ=",
));
}
}
// Validate iCalendar data (basic validation)
if !ical_data.contains("BEGIN:VEVENT") || !ical_data.contains("END:VEVENT") {
@@ -500,15 +499,14 @@ impl CalendarEvent {
*/
pub fn update_rrule(&mut self, rrule: Option<String>) -> Result<()> {
// Validate RRULE if provided (basic validation)
if let Some(ref rule) = rrule {
if !rule.starts_with("FREQ=") {
if let Some(ref rule) = rrule
&& !rule.starts_with("FREQ=") {
return Err(DomainError::new(
ErrorKind::InvalidInput,
"CalendarEvent",
"Recurrence rule must start with FREQ=",
));
}
}
self.rrule = rrule.clone();
self.updated_at = Utc::now();
@@ -547,17 +545,15 @@ impl CalendarEvent {
self.description = Self::extract_ical_property(&ical_data, "DESCRIPTION");
self.location = Self::extract_ical_property(&ical_data, "LOCATION");
if let Some(dtstart) = Self::extract_ical_property(&ical_data, "DTSTART") {
if let Ok(start_time) = Self::parse_ical_datetime(&dtstart) {
if let Some(dtstart) = Self::extract_ical_property(&ical_data, "DTSTART")
&& let Ok(start_time) = Self::parse_ical_datetime(&dtstart) {
self.start_time = start_time;
}
}
if let Some(dtend) = Self::extract_ical_property(&ical_data, "DTEND") {
if let Ok(end_time) = Self::parse_ical_datetime(&dtend) {
if let Some(dtend) = Self::extract_ical_property(&ical_data, "DTEND")
&& let Ok(end_time) = Self::parse_ical_datetime(&dtend) {
self.end_time = end_time;
}
}
// Update all-day status based on DTSTART
if let Some(dtstart) = Self::extract_ical_property(&ical_data, "DTSTART") {
@@ -612,13 +608,13 @@ impl CalendarEvent {
let until_start = until_pos + 6; // "UNTIL=" is 6 chars
if let Some(until_end) = rrule[until_start..].find(';') {
let until_str = &rrule[until_start..until_start+until_end];
if let Ok(until_date) = Self::parse_ical_datetime(&until_str) {
if let Ok(until_date) = Self::parse_ical_datetime(until_str) {
return until_date >= *start;
}
} else {
// UNTIL is the last part of the rule
let until_str = &rrule[until_start..];
if let Ok(until_date) = Self::parse_ical_datetime(&until_str) {
if let Ok(until_date) = Self::parse_ical_datetime(until_str) {
return until_date >= *start;
}
}
@@ -677,7 +673,7 @@ impl CalendarEvent {
fn parse_ical_datetime(datetime: &str) -> std::result::Result<DateTime<Utc>, String> {
// Handle VALUE=DATE format
if datetime.contains("VALUE=DATE") {
let date_str = datetime.split(':').last().unwrap_or("");
let date_str = datetime.split(':').next_back().unwrap_or("");
if date_str.len() != 8 {
return Err("Invalid date format".to_string());
}
@@ -696,7 +692,7 @@ impl CalendarEvent {
}
// Handle standard UTC format (20230101T120000Z)
let datetime_str = datetime.split(':').last().unwrap_or(datetime);
let datetime_str = datetime.split(':').next_back().unwrap_or(datetime);
if datetime_str.len() < 15 || !datetime_str.ends_with('Z') {
return Err("Invalid datetime format".to_string());
}
+3 -3
View File
@@ -262,9 +262,9 @@ mod tests {
assert_eq!(share.item_id(), "test_file_id");
assert_eq!(*share.item_type(), ShareItemType::File);
assert_eq!(share.created_by(), "user123");
assert_eq!(share.permissions().read(), true);
assert_eq!(share.permissions().write(), false);
assert_eq!(share.permissions().reshare(), false);
assert!(share.permissions().read());
assert!(!share.permissions().write());
assert!(!share.permissions().reshare());
assert!(!share.has_password());
assert!(share.expires_at().is_none());
assert_eq!(share.access_count(), 0);
+3 -9
View File
@@ -59,21 +59,15 @@ impl User {
) -> UserResult<Self> {
// Validations
if username.is_empty() || username.len() < 3 || username.len() > 32 {
return Err(UserError::InvalidUsername(format!(
"Username must be between 3 and 32 characters"
)));
return Err(UserError::InvalidUsername("Username must be between 3 and 32 characters".to_string()));
}
if !email.contains('@') || email.len() < 5 {
return Err(UserError::ValidationError(format!(
"Invalid email"
)));
return Err(UserError::ValidationError("Invalid email".to_string()));
}
if password_hash.is_empty() {
return Err(UserError::InvalidPassword(format!(
"Password hash cannot be empty"
)));
return Err(UserError::InvalidPassword("Password hash cannot be empty".to_string()));
}
let now = Utc::now();
@@ -380,8 +380,8 @@ impl ContactUseCase for ContactStorageAdapter {
} else if trimmed.starts_with("NOTE:") {
notes = Some(trimmed[5..].trim().to_string());
} else if trimmed.starts_with("EMAIL") {
if let Some(value) = trimmed.split(':').nth(1) {
if !value.is_empty() {
if let Some(value) = trimmed.split(':').nth(1)
&& !value.is_empty() {
let email_type = if trimmed.contains("TYPE=HOME") { "home" }
else if trimmed.contains("TYPE=WORK") { "work" }
else { "other" };
@@ -391,10 +391,9 @@ impl ContactUseCase for ContactStorageAdapter {
is_primary: emails.is_empty(),
});
}
}
} else if trimmed.starts_with("TEL") {
if let Some(value) = trimmed.split(':').nth(1) {
if !value.is_empty() {
} else if trimmed.starts_with("TEL")
&& let Some(value) = trimmed.split(':').nth(1)
&& !value.is_empty() {
let phone_type = if trimmed.contains("TYPE=CELL") || trimmed.contains("TYPE=MOBILE") { "mobile" }
else if trimmed.contains("TYPE=HOME") { "home" }
else if trimmed.contains("TYPE=WORK") { "work" }
@@ -405,8 +404,6 @@ impl ContactUseCase for ContactStorageAdapter {
is_primary: phones.is_empty(),
});
}
}
}
}
let contact_uid = uid.unwrap_or_else(|| format!("{}@oxicloud", Uuid::new_v4()));
@@ -24,10 +24,10 @@ use crate::domain::errors::{DomainError, ErrorKind};
#[macro_export]
macro_rules! impl_infra_error_to_domain {
($error_type:ty, $entity_type:expr) => {
impl From<$error_type> for crate::domain::errors::DomainError {
impl From<$error_type> for $crate::domain::errors::DomainError {
fn from(err: $error_type) -> Self {
crate::domain::errors::DomainError {
kind: crate::domain::errors::ErrorKind::InternalError,
$crate::domain::errors::DomainError {
kind: $crate::domain::errors::ErrorKind::InternalError,
entity_type: $entity_type,
entity_id: None,
message: format!("{}", err),
@@ -81,7 +81,7 @@ impl IntoDomainError for sqlx::Error {
}
sqlx::Error::Database(db_err) => {
// Handle specific PostgreSQL error codes
if db_err.code().map_or(false, |c| c == "23505") {
if db_err.code().is_some_and(|c| c == "23505") {
DomainError::already_exists(entity_type, "Record already exists")
} else {
DomainError::new(
+1 -2
View File
@@ -73,8 +73,7 @@ pub async fn create_database_pool(config: &AppConfig) -> Result<PgPool> {
async fn tables_exist(pool: &PgPool) -> bool {
sqlx::query("SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'auth' AND tablename = 'users')")
.fetch_one(pool)
.await
.and_then(|row| Ok(row.get::<bool, _>(0)))
.await.map(|row| row.get::<bool, _>(0))
.unwrap_or(false)
}
@@ -83,11 +83,10 @@ impl FileFsReadRepository {
async fn get_file_metadata_raw(&self, abs_path: &PathBuf) -> FileRepositoryResult<(u64, u64, u64)> {
// Cache first
if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await {
if let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) {
if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await
&& let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) {
return Ok((s, c, m));
}
}
let metadata = time::timeout(self.config.timeouts.file_timeout(), fs::metadata(abs_path))
.await
.map_err(|_| FileRepositoryError::StorageError(format!("Timeout metadata: {}", abs_path.display())))?
@@ -101,11 +101,10 @@ impl FileFsWriteRepository {
}
async fn get_file_metadata_raw(&self, abs_path: &PathBuf) -> FileRepositoryResult<(u64, u64, u64)> {
if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await {
if let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) {
if let Some(cached) = self.metadata_cache.get_metadata(abs_path).await
&& let (Some(s), Some(c), Some(m)) = (cached.size, cached.created_at, cached.modified_at) {
return Ok((s, c, m));
}
}
let meta = time::timeout(self.config.timeouts.file_timeout(), fs::metadata(abs_path))
.await
.map_err(|_| FileRepositoryError::StorageError(format!("Timeout: {}", abs_path.display())))?
@@ -177,11 +176,10 @@ impl FileFsWriteRepository {
for attempt in 1..=3 {
match self.id_mapping_service.save_changes().await {
Ok(_) => {
if let Ok(verified) = self.id_mapping_service.get_path_by_id(id).await {
if verified.to_string() == expected_path {
if let Ok(verified) = self.id_mapping_service.get_path_by_id(id).await
&& verified.to_string() == expected_path {
return Ok(());
}
}
if attempt == 3 {
return Err(FileRepositoryError::Other("Failed to verify ID mapping after 3 attempts".into()));
}
@@ -135,7 +135,7 @@ impl FolderFsRepository {
/// Updates a folder path in the ID mapping service
pub async fn update_mapped_folder_path(&self, folder_id: &str, new_path: &PathBuf) -> FolderRepositoryResult<()> {
let storage_path = StoragePath::from_string(&new_path.to_string_lossy().to_string());
let storage_path = StoragePath::from_string(new_path.to_string_lossy().as_ref());
self.id_mapping_service.update_path(folder_id, &storage_path).await
.map_err(|e| FolderRepositoryError::StorageError(format!("Failed to update folder path: {}", e)))
}
@@ -338,10 +338,7 @@ impl FolderRepository for FolderFsRepository {
let parent_id: Option<String> = if parent.is_none() || parent.as_ref().unwrap().is_empty() {
None
} else {
match self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await {
Ok(pid) => Some(pid),
Err(_) => None,
}
self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await.ok()
};
// Create folder entity
@@ -377,10 +374,7 @@ impl FolderRepository for FolderFsRepository {
let parent_id: Option<String> = if parent.is_none() || parent.as_ref().unwrap().is_empty() {
None
} else {
match self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await {
Ok(pid) => Some(pid),
Err(_) => None,
}
self.id_mapping_service.get_or_create_id(parent.as_ref().unwrap()).await.ok()
};
// Get folder metadata
@@ -651,11 +645,10 @@ impl FolderRepository for FolderFsRepository {
current_idx += 1;
}
if !folders.is_empty() {
if let Err(e) = self.id_mapping_service.save_changes().await {
if !folders.is_empty()
&& let Err(e) = self.id_mapping_service.save_changes().await {
tracing::error!("Error saving ID mappings: {}", e);
}
}
Ok((folders, total_count))
}
@@ -771,15 +764,13 @@ impl FolderRepository for FolderFsRepository {
tokio::task::spawn_blocking(move || {
if let Err(e) = std::fs::remove_dir_all(&path_for_large_removal) {
tracing::error!("Error removing large directory: {}", e);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
return Err(std::io::Error::other(
format!("Failed to remove large directory: {}", e)
));
}
Ok(())
}).await.unwrap_or_else(|e| {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
Err(std::io::Error::other(
format!("Task panicked during directory removal: {}", e)
))
})
@@ -94,15 +94,14 @@ impl FolderFsRepository {
let original_path_buf = PathBuf::from(original_path);
// Ensure the destination parent directory exists
if let Some(parent) = original_path_buf.parent() {
if !parent.exists() {
if let Some(parent) = original_path_buf.parent()
&& !parent.exists() {
fs::create_dir_all(parent).await
.map_err(|e| {
error!("Error creating parent directory for restoration: {}", e);
FolderRepositoryError::StorageError(e.to_string())
})?;
}
}
// Move the folder from the trash to its original location
match fs::rename(&current_path, &original_path_buf).await {
@@ -32,7 +32,7 @@ impl UserPgRepository {
UserRepositoryError::NotFound("User not found".to_string())
},
sqlx::Error::Database(db_err) => {
if db_err.code().map_or(false, |code| code == "23505") {
if db_err.code().is_some_and(|code| code == "23505") {
// PostgreSQL uniqueness violation code
UserRepositoryError::AlreadyExists(
"User or email already exists".to_string()
@@ -268,7 +268,7 @@ impl UserRepository for UserPgRepository {
.bind(user_clone.username())
.bind(user_clone.email())
.bind(user_clone.password_hash())
.bind(&user_clone.role().to_string())
.bind(user_clone.role().to_string())
.bind(user_clone.storage_quota_bytes())
.bind(user_clone.storage_used_bytes())
.bind(user_clone.updated_at())
@@ -80,7 +80,7 @@ pub struct UploadSession {
impl UploadSession {
/// Calculate number of chunks needed
pub fn calculate_chunk_count(total_size: u64, chunk_size: usize) -> usize {
((total_size as usize + chunk_size - 1) / chunk_size).max(1)
(total_size as usize).div_ceil(chunk_size).max(1)
}
/// Get upload progress (0.0 - 1.0)
@@ -212,14 +212,12 @@ impl ChunkedUploadService {
let sessions = sessions.read().await;
if !sessions.contains_key(dir_name) {
// Check if directory is old (>24h)
if let Ok(metadata) = fs::metadata(&path).await {
if let Ok(modified) = metadata.modified() {
if modified.elapsed().unwrap_or_default() > SESSION_EXPIRATION {
if let Ok(metadata) = fs::metadata(&path).await
&& let Ok(modified) = metadata.modified()
&& modified.elapsed().unwrap_or_default() > SESSION_EXPIRATION {
let _ = fs::remove_dir_all(&path).await;
tracing::info!("🧹 Cleaned orphaned upload dir: {:?}", path);
}
}
}
}
}
}
@@ -283,7 +281,7 @@ impl ChunkedUploadService {
bytes_received: 0,
};
let expires_at = (SESSION_EXPIRATION.as_secs()) as u64;
let expires_at = SESSION_EXPIRATION.as_secs();
{
let mut sessions = self.sessions.write().await;
@@ -159,7 +159,7 @@ impl CompressionService for GzipCompressionService {
Ok(compressed)
}).await.unwrap_or_else(|e| {
error!("Compression task error: {}", e);
Err(io::Error::new(io::ErrorKind::Other, e.to_string()))
Err(io::Error::other(e.to_string()))
})
}
@@ -226,7 +226,7 @@ impl CompressionService for GzipCompressionService {
Ok(decompressed)
}).await.unwrap_or_else(|e| {
error!("Decompression task error: {}", e);
Err(io::Error::new(io::ErrorKind::Other, e.to_string()))
Err(io::Error::other(e.to_string()))
})
}
+2 -3
View File
@@ -622,14 +622,13 @@ impl DedupService {
}
// Check size
if let Ok(file_meta) = fs::metadata(&blob_path).await {
if file_meta.len() != metadata.size {
if let Ok(file_meta) = fs::metadata(&blob_path).await
&& file_meta.len() != metadata.size {
corrupted.push(format!(
"{}: size mismatch (expected: {}, actual: {})",
hash, metadata.size, file_meta.len()
));
}
}
}
if corrupted.is_empty() {
@@ -51,13 +51,11 @@ impl FileSystemI18nService {
}
}
if let Some(last_part) = parts.last() {
if let Some(value) = current.get(last_part) {
if value.is_string() {
if let Some(last_part) = parts.last()
&& let Some(value) = current.get(last_part)
&& value.is_string() {
return value.as_str().map(|s| s.to_string());
}
}
}
None
}
@@ -75,13 +73,11 @@ impl I18nService for FileSystemI18nService {
}
// Try to use English as fallback if we couldn't find the key
if locale != Locale::English {
if let Some(english_translations) = cache.get(&Locale::English) {
if let Some(value) = self.get_nested_value(english_translations, key) {
if locale != Locale::English
&& let Some(english_translations) = cache.get(&Locale::English)
&& let Some(value) = self.get_nested_value(english_translations, key) {
return Ok(value);
}
}
}
return Err(I18nError::KeyNotFound(key.to_string()));
}
@@ -98,13 +94,11 @@ impl I18nService for FileSystemI18nService {
}
// Try to use English as fallback
if locale != Locale::English {
if let Some(english_translations) = cache.get(&Locale::English) {
if let Some(value) = self.get_nested_value(english_translations, key) {
if locale != Locale::English
&& let Some(english_translations) = cache.get(&Locale::English)
&& let Some(value) = self.get_nested_value(english_translations, key) {
return Ok(value);
}
}
}
}
}
@@ -25,8 +25,7 @@ impl FileSystemUtils {
Ok(file) => file,
Err(e) => {
error!("Failed to create temporary file in {}: {}", dir.display(), e);
return Err(IoError::new(std::io::ErrorKind::Other,
format!("Failed to create temporary file: {}", e)));
return Err(IoError::other(format!("Failed to create temporary file: {}", e)));
}
};
@@ -60,6 +60,7 @@ pub struct OptimizerStats {
}
/// Queue for batch operations
#[derive(Default)]
struct BatchQueue {
/// Pending paths to get/create ID
path_to_id_requests: HashSet<String>,
@@ -67,14 +68,6 @@ struct BatchQueue {
id_to_path_requests: HashSet<String>,
}
impl Default for BatchQueue {
fn default() -> Self {
Self {
path_to_id_requests: HashSet::new(),
id_to_path_requests: HashSet::new(),
}
}
}
/// Result of a batch operation
struct BatchResult {
@@ -173,13 +173,11 @@ impl IdMappingService {
};
// Ensure directory exists
if let Some(parent) = map_path.parent() {
if !parent.exists() {
if let Err(e) = fs::create_dir_all(parent).await {
if let Some(parent) = map_path.parent()
&& !parent.exists()
&& let Err(e) = fs::create_dir_all(parent).await {
tracing::error!("Failed to create directory for ID map: {}", e);
}
}
}
// Write empty map to file (best-effort: the in-memory map is valid even if disk write fails)
match serde_json::to_string_pretty(&empty_map) {
@@ -413,8 +411,7 @@ impl IdMappingService {
// Try a second save if verification fails
if let Err(retry_err) = self.save_id_map().await {
tracing::error!("Second save attempt also failed: {}", retry_err);
return Err(IdMappingError::IoError(std::io::Error::new(
std::io::ErrorKind::Other,
return Err(IdMappingError::IoError(std::io::Error::other(
format!("Failed to verify and retry save: {}", retry_err)
)));
}
@@ -435,8 +432,7 @@ impl IdMappingService {
},
Err(retry_e) => {
tracing::error!("Second save attempt also failed: {}", retry_e);
Err(IdMappingError::IoError(std::io::Error::new(
std::io::ErrorKind::Other,
Err(IdMappingError::IoError(std::io::Error::other(
format!("Failed to save ID mappings after retry: {}", retry_e)
)))
}
@@ -200,11 +200,7 @@ impl ImageTranscodeService {
// Calculate savings
let original_size = original_content.len();
let transcoded_size = transcoded_bytes.len();
let saved = if transcoded_size < original_size {
original_size - transcoded_size
} else {
0
};
let saved = original_size.saturating_sub(transcoded_size);
// Only use transcoded if it's actually smaller
if transcoded_size >= original_size {
+1 -1
View File
@@ -65,7 +65,7 @@ impl PathService {
/// Checks if a path is at the root
pub fn is_in_root(&self, path: &StoragePath) -> bool {
path.parent().map_or(true, |p| p.is_empty())
path.parent().is_none_or(|p| p.is_empty())
}
/// Gets the root path used by this service
@@ -256,13 +256,12 @@ impl WriteBehindCache {
pending.get(&file_id).cloned()
};
if let Some(entry) = entry {
if let Err(e) = self.flush_single(&file_id, &entry).await {
if let Some(entry) = entry
&& let Err(e) = self.flush_single(&file_id, &entry).await {
tracing::error!("Failed to flush {}: {}", file_id, e);
// Keep in cache for retry
continue;
}
}
}
FlushCommand::FlushAll => {
let entries: Vec<_> = {
+14 -14
View File
@@ -51,7 +51,7 @@ async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(String, S
.ok_or_else(|| AppError::unauthorized("Authorization token required"))?;
let claims = auth.token_service.validate_token(token)
.map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?;
.map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?;
if claims.role != "admin" {
return Err(AppError::new(
@@ -75,7 +75,7 @@ async fn get_oidc_settings(
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
let settings = svc.get_oidc_settings().await
.map_err(|e| AppError::internal_error(&format!("Failed to load settings: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to load settings: {}", e)))?;
Ok(Json(settings))
}
@@ -92,7 +92,7 @@ async fn save_oidc_settings(
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
svc.save_oidc_settings(dto, &user_id).await
.map_err(|e| AppError::internal_error(&format!("Failed to save settings: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to save settings: {}", e)))?;
Ok((StatusCode::OK, Json(serde_json::json!({
"message": "OIDC settings saved and applied successfully"
@@ -111,7 +111,7 @@ async fn test_oidc_connection(
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
let result = svc.test_oidc_connection(dto).await
.map_err(|e| AppError::internal_error(&format!("Connection test failed: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Connection test failed: {}", e)))?;
Ok(Json(result))
}
@@ -173,7 +173,7 @@ async fn get_dashboard_stats(
)
.fetch_one(db_pool.as_ref())
.await
.map_err(|e| AppError::internal_error(&format!("Database query failed: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Database query failed: {}", e)))?;
use sqlx::Row;
let total_quota: i64 = stats_row.get("total_quota_bytes");
@@ -228,7 +228,7 @@ async fn list_users(
let offset = query.offset.unwrap_or(0);
let users = auth.auth_application_service.list_users(limit, offset).await
.map_err(|e| AppError::internal_error(&format!("Failed to list users: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to list users: {}", e)))?;
let total = auth.auth_application_service.count_users_efficient().await.unwrap_or(0);
@@ -252,7 +252,7 @@ async fn get_user(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
let user = auth.auth_application_service.get_user_admin(&id).await
.map_err(|e| AppError::not_found(&format!("User not found: {}", e)))?;
.map_err(|e| AppError::not_found(format!("User not found: {}", e)))?;
Ok(Json(user))
}
@@ -278,7 +278,7 @@ async fn delete_user(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service.delete_user_admin(&id).await
.map_err(|e| AppError::internal_error(&format!("Failed to delete user: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to delete user: {}", e)))?;
Ok((StatusCode::OK, Json(serde_json::json!({
"message": "User deleted successfully"
@@ -307,7 +307,7 @@ async fn update_user_role(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service.change_user_role(&id, &dto.role).await
.map_err(|e| AppError::internal_error(&format!("Failed to change role: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to change role: {}", e)))?;
Ok((StatusCode::OK, Json(serde_json::json!({
"message": format!("User role updated to '{}'", dto.role)
@@ -336,7 +336,7 @@ async fn update_user_active(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service.set_user_active(&id, dto.active).await
.map_err(|e| AppError::internal_error(&format!("Failed to update user status: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to update user status: {}", e)))?;
let status = if dto.active { "activated" } else { "deactivated" };
Ok((StatusCode::OK, Json(serde_json::json!({
@@ -357,7 +357,7 @@ async fn update_user_quota(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service.update_user_quota(&id, dto.quota_bytes).await
.map_err(|e| AppError::internal_error(&format!("Failed to update quota: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to update quota: {}", e)))?;
Ok((StatusCode::OK, Json(serde_json::json!({
"message": "User quota updated",
@@ -383,7 +383,7 @@ async fn create_user(
let user = auth.auth_application_service.admin_create_user(dto).await
.map_err(|e| AppError::new(
StatusCode::BAD_REQUEST,
&format!("Failed to create user: {}", e),
format!("Failed to create user: {}", e),
"CreateUserFailed",
))?;
@@ -405,7 +405,7 @@ async fn reset_user_password(
auth.auth_application_service.admin_reset_password(&id, &dto.new_password).await
.map_err(|e| AppError::new(
StatusCode::BAD_REQUEST,
&format!("Failed to reset password: {}", e),
format!("Failed to reset password: {}", e),
"ResetPasswordFailed",
))?;
@@ -455,7 +455,7 @@ async fn set_registration_setting(
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
svc.set_registration_enabled(enabled, &admin_id).await
.map_err(|e| AppError::internal_error(&format!("Failed to save setting: {}", e)))?;
.map_err(|e| AppError::internal_error(format!("Failed to save setting: {}", e)))?;
Ok((StatusCode::OK, Json(serde_json::json!({
"message": format!("Public registration {}", if enabled { "enabled" } else { "disabled" }),
+5 -6
View File
@@ -67,15 +67,14 @@ async fn register(
}
// Check if public registration has been disabled by the admin
if let Some(admin_svc) = state.admin_settings_service.as_ref() {
if !admin_svc.get_registration_enabled().await {
if let Some(admin_svc) = state.admin_settings_service.as_ref()
&& !admin_svc.get_registration_enabled().await {
return Err(AppError::new(
StatusCode::FORBIDDEN,
"Public registration has been disabled by the administrator.",
"RegistrationDisabled",
));
}
}
// Registration logic (admin detection, fresh-install handling, duplicate
// checks) is all inside the service layer. Call it directly.
@@ -178,7 +177,7 @@ async fn get_current_user(
// Validate the token and get claims
let claims = auth_service.token_service.validate_token(token)
.map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?;
.map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?;
let user_id = claims.sub;
@@ -220,7 +219,7 @@ async fn change_password(
// Validate the token and get claims
let claims = auth_service.token_service.validate_token(token)
.map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?;
.map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?;
auth_service.auth_application_service.change_password(&claims.sub, dto).await?;
@@ -243,7 +242,7 @@ async fn logout(
// Validate the token and get claims
let claims = auth_service.token_service.validate_token(token)
.map_err(|e| AppError::unauthorized(&format!("Invalid token: {}", e)))?;
.map_err(|e| AppError::unauthorized(format!("Invalid token: {}", e)))?;
// Use access token for logout (we don't have refresh token in headers)
auth_service.auth_application_service.logout(&claims.sub, token).await?;
@@ -320,7 +320,7 @@ async fn handle_mkcalendar(
.map_err(|e| AppError::bad_request(format!("Failed to read request body: {}", e)))?;
let (name, description, color) = if body_bytes.is_empty() {
let name = path.split('/').last().unwrap_or("New Calendar").to_string();
let name = path.split('/').next_back().unwrap_or("New Calendar").to_string();
(name, None, None)
} else {
CalDavAdapter::parse_mkcalendar(body_bytes.reader())
@@ -335,7 +335,7 @@ async fn handle_mkcol(
.map_err(|e| AppError::bad_request(format!("Failed to read request body: {}", e)))?;
let (name, description, color) = if body_bytes.is_empty() {
let name = path.split('/').last().unwrap_or("New Address Book").to_string();
let name = path.split('/').next_back().unwrap_or("New Address Book").to_string();
(name, None, None)
} else {
CardDavAdapter::parse_mkaddressbook(body_bytes.reader())
+14 -20
View File
@@ -271,7 +271,7 @@ impl FileHandler {
};
// ── Metadata-only request ────────────────────────────────────
if params.get("metadata").map_or(false, |v| v == "true" || v == "1") {
if params.get("metadata").is_some_and(|v| v == "true" || v == "1") {
return (StatusCode::OK, Json(serde_json::json!({
"id": file_dto.id,
"name": file_dto.name,
@@ -287,9 +287,9 @@ impl FileHandler {
let etag = format!("\"{}-{}\"", id, file_dto.modified_at);
// ── ETag (304 Not Modified) ──────────────────────────────────
if let Some(inm) = headers.get(header::IF_NONE_MATCH) {
if let Ok(client_etag) = inm.to_str() {
if client_etag == etag || client_etag == "*" {
if let Some(inm) = headers.get(header::IF_NONE_MATCH)
&& let Ok(client_etag) = inm.to_str()
&& (client_etag == etag || client_etag == "*") {
return Response::builder()
.status(StatusCode::NOT_MODIFIED)
.header(header::ETAG, &etag)
@@ -297,13 +297,11 @@ impl FileHandler {
.unwrap()
.into_response();
}
}
}
// ── Range Requests ───────────────────────────────────────────
if let Some(range_header) = headers.get(header::RANGE) {
if let Ok(range_str) = range_header.to_str() {
if let Ok(ranges) = parse_range_header(range_str) {
if let Some(range_header) = headers.get(header::RANGE)
&& let Ok(range_str) = range_header.to_str()
&& let Ok(ranges) = parse_range_header(range_str) {
let validated = ranges.validate(file_dto.size);
if let Ok(valid_ranges) = validated {
if let Some(range) = valid_ranges.first() {
@@ -342,16 +340,14 @@ impl FileHandler {
.into_response();
}
}
}
}
// ── Normal download (delegated to service) ───────────────────
let disposition = Self::content_disposition(&file_dto.name, &file_dto.mime_type, &params);
let accept_webp = headers.get(header::ACCEPT)
.and_then(|v| v.to_str().ok())
.map_or(false, |a| a.contains("image/webp"));
let prefer_original = params.get("original").map_or(false, |v| v == "true" || v == "1");
.is_some_and(|a| a.contains("image/webp"));
let prefer_original = params.get("original").is_some_and(|v| v == "true" || v == "1");
match retrieval.get_file_optimized(&id, accept_webp, prefer_original).await {
Ok((_file, content)) => match content {
@@ -447,9 +443,9 @@ impl FileHandler {
if let Ok(body_bytes) = axum::body::to_bytes(
response.into_response().into_body(),
10 * 1024,
).await {
if let Ok(file_info) = serde_json::from_slice::<serde_json::Value>(&body_bytes) {
if let (Some(file_id), Some(mime_type), Some(file_path_str)) = (
).await
&& let Ok(file_info) = serde_json::from_slice::<serde_json::Value>(&body_bytes)
&& let (Some(file_id), Some(mime_type), Some(file_path_str)) = (
file_info.get("id").and_then(|v| v.as_str()),
file_info.get("mime_type").and_then(|v| v.as_str()),
file_info.get("path").and_then(|v| v.as_str()),
@@ -477,8 +473,6 @@ impl FileHandler {
.unwrap()
.into_response();
}
}
}
// Fallback for errors
(StatusCode::INTERNAL_SERVER_ERROR, "Upload processing error").into_response()
@@ -676,7 +670,7 @@ impl FileHandler {
/// Build a Content-Disposition header value.
fn content_disposition(name: &str, mime: &str, params: &HashMap<String, String>) -> String {
let force_inline = params.get("inline").map_or(false, |v| v == "true" || v == "1");
let force_inline = params.get("inline").is_some_and(|v| v == "true" || v == "1");
if force_inline
|| mime.starts_with("image/")
|| mime == "application/pdf"
@@ -750,7 +744,7 @@ impl FileHandler {
.header(header::VARY, "Accept-Encoding");
if should_compress {
match compression_service.compress_data(&content.to_vec(), compression_level).await {
match compression_service.compress_data(&content, compression_level).await {
Ok(compressed) => {
builder
.header(header::CONTENT_TYPE, mime_type)
@@ -409,7 +409,7 @@ async fn handle_get(
.header(header::CONTENT_LENGTH, content.len())
.header(header::ETAG, format!("\"{}\"", file.id))
.header(header::LAST_MODIFIED, chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
.unwrap_or_else(|| Utc::now())
.unwrap_or_else(Utc::now)
.to_rfc2822())
.body(Body::from(content))
.unwrap())
@@ -463,7 +463,7 @@ async fn handle_head(
.header(header::CONTENT_LENGTH, content.len())
.header(header::ETAG, format!("\"{}\"", file.id))
.header(header::LAST_MODIFIED, chrono::DateTime::<Utc>::from_timestamp(file.created_at as i64, 0)
.unwrap_or_else(|| Utc::now())
.unwrap_or_else(Utc::now)
.to_rfc2822())
.body(Body::empty())
.unwrap())
@@ -573,7 +573,7 @@ async fn handle_mkcol(
}
// Extract folder name from path
let folder_name = path.split('/').last().unwrap_or("unnamed");
let folder_name = path.split('/').next_back().unwrap_or("unnamed");
// Get parent folder path
let parent_path = if let Some(idx) = path.rfind('/') {
@@ -715,7 +715,7 @@ async fn handle_move(
if let Ok(folder) = folder_result {
// Move folder
let dest_folder_name = destination_path.split('/').last().unwrap_or(&destination_path);
let dest_folder_name = destination_path.split('/').next_back().unwrap_or(&destination_path);
let dest_parent_path = if let Some(idx) = destination_path.rfind('/') {
&destination_path[..idx]
} else {
@@ -753,7 +753,7 @@ async fn handle_move(
AppError::not_found(format!("Resource not found: {}", source_path))
})?;
let dest_filename = destination_path.split('/').last().unwrap_or(&destination_path);
let dest_filename = destination_path.split('/').next_back().unwrap_or(&destination_path);
let dest_parent_path = if let Some(idx) = destination_path.rfind('/') {
&destination_path[..idx]
} else {
@@ -854,7 +854,7 @@ async fn handle_copy(
// Copy folder
let recursive = depth != "0";
let dest_folder_name = destination_path.split('/').last().unwrap_or(&destination_path);
let dest_folder_name = destination_path.split('/').next_back().unwrap_or(&destination_path);
let dest_parent_path = if let Some(idx) = destination_path.rfind('/') {
&destination_path[..idx]
} else {
@@ -908,7 +908,7 @@ async fn handle_copy(
})?;
// Get destination parent path and filename
let dest_filename = destination_path.split('/').last().unwrap_or(&destination_path);
let dest_filename = destination_path.split('/').next_back().unwrap_or(&destination_path);
let dest_parent_path = if let Some(idx) = destination_path.rfind('/') {
&destination_path[..idx]
} else {
+6 -7
View File
@@ -197,13 +197,12 @@ where
// If there is a cache entry
if let Some(cache_entry) = cache.get(cache_key) {
// Check if the client already has the updated version
if let Some(client_etag) = if_none_match {
if client_etag == cache_entry.etag {
if let Some(client_etag) = if_none_match
&& client_etag == cache_entry.etag {
// The client has the most recent version, send 304 Not Modified
debug!("Cache hit (304) for key: {}", cache_key);
return Ok(create_not_modified_response(&cache_entry));
}
}
// The client needs the updated version
if let Some(data) = &cache_entry.data {
@@ -392,7 +391,7 @@ where
// The client has the correct version, send 304
debug!("Cache HIT (304): {}", cache_key);
let response = create_not_modified_response(&cache_entry);
return Box::pin(async move { Ok(response) });
Box::pin(async move { Ok(response) })
},
Some(cache_entry) if cache_entry.data.is_some() => {
// The client needs the updated version
@@ -409,7 +408,7 @@ where
// Add cache headers
set_cache_headers(&mut response, &cache_entry.etag, max_age.unwrap_or(cache_entry.max_age));
return Box::pin(async move { Ok(response) });
Box::pin(async move { Ok(response) })
},
_ => {
// Not cached or expired
@@ -419,7 +418,7 @@ where
let max_age = self.max_age;
let cache_key = cache_key.clone();
return Box::pin(async move {
Box::pin(async move {
let response = future.await.map_err(|e| e.into())?;
let response = response_map_body(response).await;
@@ -449,7 +448,7 @@ where
set_cache_headers(&mut response, &etag, max_age.unwrap_or(cache_clone.default_max_age));
Ok(response)
});
})
}
}
}