fix: resolve file management operations not working (#83)

- Fix rename: context menu was nullifying target reference before rename dialog could use it
- Fix delete files/folders: auth extractors were mandatory, causing 401 when auth not configured
- Fix view-file: async fetch race condition with context menu cleanup
- Fix orphaned ID mappings on file deletion
- Fix Authorization: Bearer null headers sent without token
- Add OptionalUserId and OptionalAuthUser infallible extractors
This commit is contained in:
Dionisio
2026-02-13 08:54:51 +01:00
parent 915d9a4353
commit fb65d1976b
9 changed files with 294 additions and 63 deletions
+19 -6
View File
@@ -13,7 +13,7 @@ use http_range_header::parse_range_header;
use crate::application::ports::compression_ports::{CompressionPort, CompressionLevel};
use crate::application::ports::file_ports::OptimizedFileContent;
use crate::common::di::AppState;
use crate::interfaces::middleware::auth::CurrentUserId;
use crate::interfaces::middleware::auth::OptionalUserId;
/**
* Type aliases for dependency injection state.
@@ -506,22 +506,35 @@ impl FileHandler {
///
/// All logic (trash fallback, dedup ref-count, hash computation) is handled
/// by `FileManagementUseCase::delete_with_cleanup`.
///
/// When auth is available, uses trash-first deletion; otherwise falls back
/// to permanent delete so the endpoint works with or without auth.
pub async fn delete_file(
State(state): State<GlobalState>,
CurrentUserId(user_id): CurrentUserId,
OptionalUserId(user_id): OptionalUserId,
Path(id): Path<String>,
) -> impl IntoResponse {
let mgmt = &state.applications.file_management_service;
match mgmt.delete_with_cleanup(&id, &user_id).await {
Ok(was_trashed) => {
let result = if let Some(uid) = user_id {
// Auth available: trash-first with dedup cleanup
mgmt.delete_with_cleanup(&id, &uid).await.map(|was_trashed| {
if was_trashed {
tracing::info!("File moved to trash: {}", id);
} else {
tracing::info!("File permanently deleted: {}", id);
}
StatusCode::NO_CONTENT.into_response()
}
})
} else {
// No auth: permanent delete
tracing::warn!("No auth context – permanently deleting file: {}", id);
mgmt.delete_file(&id).await.map(|_| {
tracing::info!("File permanently deleted (no auth): {}", id);
})
};
match result {
Ok(_) => StatusCode::NO_CONTENT.into_response(),
Err(err) => {
tracing::error!("Error deleting file: {}", err);
let status = if err.to_string().contains("not found") || err.to_string().contains("NotFound") {
@@ -13,7 +13,7 @@ use crate::application::dtos::pagination::PaginationRequestDto;
use crate::common::errors::ErrorKind;
use crate::application::ports::inbound::FolderUseCase;
use crate::common::di::AppState as GlobalAppState;
use crate::interfaces::middleware::auth::AuthUser;
use crate::interfaces::middleware::auth::OptionalAuthUser;
type AppState = Arc<FolderService>;
@@ -205,15 +205,16 @@ impl FolderHandler {
/// Deletes a folder with trash functionality
pub async fn delete_folder_with_trash(
State(state): State<GlobalAppState>,
auth_user: AuthUser,
OptionalAuthUser(auth_user): OptionalAuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
let user_id = auth_user.as_ref().map(|u| u.id.as_str()).unwrap_or("anonymous");
// Check if trash service is available
if let Some(trash_service) = &state.trash_service {
tracing::info!("Moving folder to trash: {}", id);
// Try to move to trash first
match trash_service.move_to_trash(&id, "folder", &auth_user.id).await {
match trash_service.move_to_trash(&id, "folder", user_id).await {
Ok(_) => {
tracing::info!("Folder successfully moved to trash: {}", id);
return StatusCode::NO_CONTENT.into_response();
+13 -10
View File
@@ -6,7 +6,7 @@ use tracing::{debug, error, warn, instrument};
// use crate::application::ports::trash_ports::TrashUseCase;
use crate::common::di::AppState;
use crate::interfaces::middleware::auth::AuthUser;
use crate::interfaces::middleware::auth::{AuthUser, OptionalAuthUser};
/// Gets all items in the trash for the current user
#[instrument(skip_all)]
@@ -50,11 +50,12 @@ pub async fn get_trash_items(
#[instrument(skip_all)]
pub async fn move_to_trash(
State(state): State<AppState>,
auth_user: AuthUser,
OptionalAuthUser(auth_user): OptionalAuthUser,
Path((item_type, item_id)): Path<(String, String)>,
) -> (StatusCode, Json<serde_json::Value>) {
let user_id = auth_user.as_ref().map(|u| u.id.as_str()).unwrap_or("anonymous");
debug!("Request to move to trash: type={}, id={}, user={}",
item_type, item_id, auth_user.id);
item_type, item_id, user_id);
let trash_service = match state.trash_service.as_ref() {
Some(service) => service,
@@ -64,7 +65,7 @@ pub async fn move_to_trash(
})));
}
};
let result = trash_service.move_to_trash(&item_id, &item_type, &auth_user.id).await;
let result = trash_service.move_to_trash(&item_id, &item_type, user_id).await;
match result {
Ok(_) => {
@@ -87,11 +88,12 @@ pub async fn move_to_trash(
#[instrument(skip_all)]
pub async fn move_file_to_trash(
State(state): State<AppState>,
auth_user: AuthUser,
OptionalAuthUser(auth_user): OptionalAuthUser,
Path(item_id): Path<String>,
) -> (StatusCode, Json<serde_json::Value>) {
let user_id = auth_user.as_ref().map(|u| u.id.as_str()).unwrap_or("anonymous");
debug!("Request to move file to trash: id={}, user={}",
item_id, auth_user.id);
item_id, user_id);
let trash_service = match state.trash_service.as_ref() {
Some(service) => service,
@@ -103,7 +105,7 @@ pub async fn move_file_to_trash(
};
// Specify that it is a file
let result = trash_service.move_to_trash(&item_id, "file", &auth_user.id).await;
let result = trash_service.move_to_trash(&item_id, "file", user_id).await;
match result {
Ok(_) => {
@@ -126,11 +128,12 @@ pub async fn move_file_to_trash(
#[instrument(skip_all)]
pub async fn move_folder_to_trash(
State(state): State<AppState>,
auth_user: AuthUser,
OptionalAuthUser(auth_user): OptionalAuthUser,
Path(item_id): Path<String>,
) -> (StatusCode, Json<serde_json::Value>) {
let user_id = auth_user.as_ref().map(|u| u.id.as_str()).unwrap_or("anonymous");
debug!("Request to move folder to trash: id={}, user={}",
item_id, auth_user.id);
item_id, user_id);
let trash_service = match state.trash_service.as_ref() {
Some(service) => service,
@@ -142,7 +145,7 @@ pub async fn move_folder_to_trash(
};
// Specify that it is a folder
let result = trash_service.move_to_trash(&item_id, "folder", &auth_user.id).await;
let result = trash_service.move_to_trash(&item_id, "folder", user_id).await;
match result {
Ok(_) => {