feat(ui/trash): show original file path in tooltip on mouse over + display thumbnails

* fix: permission also check elements trashed elements
 * tested manually
 * all automated tests ok
This commit is contained in:
Edouard Vanbelle
2026-05-22 00:38:44 +02:00
parent 0cd2f24b7f
commit 191f725199
12 changed files with 144 additions and 29 deletions
+6
View File
@@ -122,6 +122,12 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
/// another user. All user-facing handlers should use this method.
async fn get_file_with_perms(&self, id: &str, caller_id: Uuid) -> Result<FileDto, DomainError>;
async fn get_file_or_trashed_with_perms(
&self,
id: &str,
caller_id: Uuid,
) -> Result<FileDto, DomainError>;
/// Gets a file by its path (for WebDAV)
async fn get_file_by_path(&self, path: &str) -> Result<FileDto, DomainError>;
+2
View File
@@ -29,6 +29,8 @@ pub trait FileReadPort: Send + Sync + 'static {
/// Gets a file by its ID.
async fn get_file(&self, id: &str) -> Result<File, DomainError>;
async fn get_file_or_trashed(&self, id: &str) -> Result<File, DomainError>;
/// Gets a file by its ID, scoped to a specific owner.
///
/// Returns `NotFound` if the file does not exist **or** belongs to a
@@ -255,6 +255,16 @@ impl FileRetrievalUseCase for FileRetrievalService {
Ok(FileDto::from(file))
}
async fn get_file_or_trashed_with_perms(
&self,
id: &str,
caller_id: Uuid,
) -> Result<FileDto, DomainError> {
self.require_file(id, Permission::Read, caller_id).await?;
let file = self.file_read.get_file_or_trashed(id).await?;
Ok(FileDto::from(file))
}
// FIXME no authorisation at all
async fn get_file_by_path(&self, path: &str) -> Result<FileDto, DomainError> {
// Direct SQL lookup — O(folder_depth) queries instead of O(total_files)
@@ -60,6 +60,14 @@ impl FileReadPort for MockFileReadPort {
.ok_or_else(|| DomainError::not_found("File", id.to_string()))
}
async fn get_file_or_trashed(&self, id: &str) -> Result<File, DomainError> {
let files = self.files.lock().unwrap();
files
.get(id)
.map(|(f, _)| f.clone())
.ok_or_else(|| DomainError::not_found("File", id.to_string()))
}
async fn get_file_for_owner(&self, id: &str, owner_id: Uuid) -> Result<File, DomainError> {
let files = self.files.lock().unwrap();
match files.get(id) {
@@ -782,6 +782,13 @@ mod tests {
}
}
async fn get_file_or_trashed(
&self,
_id: &str,
) -> Result<crate::domain::entities::file::File, DomainError> {
unimplemented!()
}
async fn list_files(
&self,
_folder_id: Option<&str>,
@@ -459,6 +459,14 @@ impl FileReadPort for MockFileRepository {
}
}
async fn get_file_or_trashed(&self, id: &str) -> std::prelude::v1::Result<File, DomainError> {
let files = self.files.lock().unwrap();
if let Some(file) = files.get(id) {
Ok(file.clone())
} else {
Err(DomainError::not_found("File", id.to_string()))
}
}
async fn list_files(
&self,
_folder_id: Option<&str>,