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:
@@ -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))))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(¤t_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()))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<_> = {
|
||||
|
||||
@@ -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" }),
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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, ¶ms);
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user