perf: migrate all user/session/auth IDs from VARCHAR(36) to native UUID
- Schema: all ~15 VARCHAR(36) columns → UUID with DEFAULT gen_random_uuid() - Domain entities: User, Session, DeviceCode, AppPassword, Share → id: Uuid - DTOs: CurrentUser.id → Uuid (API boundary DTOs keep String for JSON) - Auth middleware: parse JWT claims.sub (String) → Uuid at boundary - All repository traits, port traits, service impls updated end-to-end - Handlers: pass Uuid by value (Copy, 16 bytes) instead of String refs - Settings chain: updated_by column → Uuid (was text, caused setup crash) - Removed ~650 lines of String↔Uuid conversion boilerplate - Eliminates per-request heap allocations for ID cloning - 16-byte binary comparison vs 36-byte string comparison in all queries - Native UUID indexing in PostgreSQL (btree on 16 bytes vs 36-char text) 85 files changed, 1090 insertions(+), 1739 deletions(-)
This commit is contained in:
@@ -15,6 +15,7 @@ use crate::application::ports::auth_ports::TokenServicePort;
|
||||
use crate::common::di::AppState;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Admin API routes — all require admin role.
|
||||
pub fn admin_routes() -> Router<Arc<AppState>> {
|
||||
@@ -41,7 +42,7 @@ pub fn admin_routes() -> Router<Arc<AppState>> {
|
||||
}
|
||||
|
||||
/// Validate JWT and require admin role. Returns (user_id, role).
|
||||
async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(String, String), AppError> {
|
||||
async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(Uuid, String), AppError> {
|
||||
let auth = state
|
||||
.auth_service
|
||||
.as_ref()
|
||||
@@ -72,7 +73,7 @@ async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(String, S
|
||||
));
|
||||
}
|
||||
|
||||
Ok((claims.sub, claims.role))
|
||||
Ok((Uuid::parse_str(&claims.sub).map_err(|_| AppError::internal_error("Invalid user ID in token"))?, claims.role))
|
||||
}
|
||||
|
||||
/// GET /api/admin/settings/oidc — get OIDC settings for the admin panel
|
||||
@@ -108,7 +109,7 @@ async fn save_oidc_settings(
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
|
||||
|
||||
svc.save_oidc_settings(dto, &user_id)
|
||||
svc.save_oidc_settings(dto, user_id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to save settings: {}", e)))?;
|
||||
|
||||
@@ -292,6 +293,8 @@ async fn get_user(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
admin_guard(&state, &headers).await?;
|
||||
|
||||
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
|
||||
|
||||
let auth = state
|
||||
.auth_service
|
||||
.as_ref()
|
||||
@@ -299,7 +302,7 @@ async fn get_user(
|
||||
|
||||
let user = auth
|
||||
.auth_application_service
|
||||
.get_user_admin(&id)
|
||||
.get_user_admin(id)
|
||||
.await
|
||||
.map_err(|e| AppError::not_found(format!("User not found: {}", e)))?;
|
||||
|
||||
@@ -314,6 +317,8 @@ async fn delete_user(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let (admin_id, _) = admin_guard(&state, &headers).await?;
|
||||
|
||||
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
|
||||
|
||||
// Prevent self-deletion
|
||||
if admin_id == id {
|
||||
return Err(AppError::new(
|
||||
@@ -329,7 +334,7 @@ async fn delete_user(
|
||||
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
|
||||
|
||||
auth.auth_application_service
|
||||
.delete_user_admin(&id)
|
||||
.delete_user_admin(id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to delete user: {}", e)))?;
|
||||
|
||||
@@ -350,6 +355,8 @@ async fn update_user_role(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let (admin_id, _) = admin_guard(&state, &headers).await?;
|
||||
|
||||
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
|
||||
|
||||
// Prevent changing own role
|
||||
if admin_id == id {
|
||||
return Err(AppError::new(
|
||||
@@ -365,7 +372,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)
|
||||
.change_user_role(id, &dto.role)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to change role: {}", e)))?;
|
||||
|
||||
@@ -386,6 +393,8 @@ async fn update_user_active(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let (admin_id, _) = admin_guard(&state, &headers).await?;
|
||||
|
||||
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
|
||||
|
||||
// Prevent deactivating yourself
|
||||
if admin_id == id && !dto.active {
|
||||
return Err(AppError::new(
|
||||
@@ -401,7 +410,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)
|
||||
.set_user_active(id, dto.active)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to update user status: {}", e)))?;
|
||||
|
||||
@@ -427,13 +436,15 @@ async fn update_user_quota(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
admin_guard(&state, &headers).await?;
|
||||
|
||||
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
|
||||
|
||||
let auth = state
|
||||
.auth_service
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
|
||||
|
||||
auth.auth_application_service
|
||||
.update_user_quota(&id, dto.quota_bytes)
|
||||
.update_user_quota(id, dto.quota_bytes)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to update quota: {}", e)))?;
|
||||
|
||||
@@ -487,13 +498,15 @@ async fn reset_user_password(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
admin_guard(&state, &headers).await?;
|
||||
|
||||
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
|
||||
|
||||
let auth = state
|
||||
.auth_service
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
|
||||
|
||||
auth.auth_application_service
|
||||
.admin_reset_password(&id, &dto.new_password)
|
||||
.admin_reset_password(id, &dto.new_password)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::new(
|
||||
@@ -558,7 +571,7 @@ async fn set_registration_setting(
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
|
||||
|
||||
svc.set_registration_enabled(enabled, &admin_id)
|
||||
svc.set_registration_enabled(enabled, admin_id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to save setting: {}", e)))?;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use axum::extract::State;
|
||||
use axum::routing::{delete, get, post};
|
||||
use axum::{Json, Router};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Protected routes — require JWT auth middleware.
|
||||
pub fn app_password_routes() -> Router<Arc<AppState>> {
|
||||
@@ -35,7 +36,7 @@ async fn create_app_password(
|
||||
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
|
||||
|
||||
let response = service
|
||||
.create(&user.id, request)
|
||||
.create(user.id, request)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
@@ -55,7 +56,7 @@ async fn list_app_passwords(
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
|
||||
|
||||
let response = service.list(&user.id).await.map_err(AppError::from)?;
|
||||
let response = service.list(user.id).await.map_err(AppError::from)?;
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
@@ -72,8 +73,10 @@ async fn revoke_app_password(
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
|
||||
|
||||
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
|
||||
|
||||
let response = service
|
||||
.revoke(&user.id, &id)
|
||||
.revoke(user.id, id)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use axum::{
|
||||
routing::{get, post, put},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::application::dtos::user_dto::{
|
||||
ChangePasswordDto, LoginDto, OidcCallbackQueryDto, OidcExchangeDto, OidcProviderInfoDto,
|
||||
@@ -273,7 +274,7 @@ async fn get_current_user(
|
||||
if let Some(storage_usage_service) = state.storage_usage_service.as_ref() {
|
||||
// Calculate storage synchronously (we await the result)
|
||||
match storage_usage_service
|
||||
.update_user_storage_usage(&user_id)
|
||||
.update_user_storage_usage(user_id)
|
||||
.await
|
||||
{
|
||||
Ok(usage) => {
|
||||
@@ -293,7 +294,7 @@ async fn get_current_user(
|
||||
// Now get the user data WITH the updated storage
|
||||
let user = auth_service
|
||||
.auth_application_service
|
||||
.get_user_by_id(&user_id)
|
||||
.get_user_by_id(user_id)
|
||||
.await?;
|
||||
|
||||
Ok((StatusCode::OK, Json(user)))
|
||||
@@ -311,7 +312,7 @@ async fn change_password(
|
||||
|
||||
auth_service
|
||||
.auth_application_service
|
||||
.change_password(&user_id, dto)
|
||||
.change_password(user_id, dto)
|
||||
.await?;
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
@@ -341,7 +342,7 @@ async fn logout(
|
||||
|
||||
auth_service
|
||||
.auth_application_service
|
||||
.logout(&user_id, &refresh_token)
|
||||
.logout(user_id, &refresh_token)
|
||||
.await?;
|
||||
|
||||
// Clear HttpOnly + CSRF cookies so the browser forgets the session
|
||||
@@ -393,10 +394,10 @@ async fn setup_admin(
|
||||
}
|
||||
|
||||
// 4. ATOMIC: claim initialization — only one concurrent request can win.
|
||||
// We use a placeholder user_id ("pending") because the admin user
|
||||
// We use Uuid::nil() as a placeholder because the admin user
|
||||
// doesn't exist yet. It will be updated to the real id below.
|
||||
let claimed = admin_svc
|
||||
.try_claim_initialization("pending")
|
||||
.try_claim_initialization(Uuid::nil())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to claim system initialization: {}", e);
|
||||
@@ -426,7 +427,8 @@ async fn setup_admin(
|
||||
})?;
|
||||
|
||||
// 5. Update the initialization record with the real admin user_id
|
||||
if let Err(e) = admin_svc.mark_system_initialized(&user.id).await {
|
||||
let real_user_id = Uuid::parse_str(&user.id).unwrap_or_default();
|
||||
if let Err(e) = admin_svc.mark_system_initialized(real_user_id).await {
|
||||
// Not fatal — the claim already prevents concurrent re-initialization,
|
||||
// and the "pending" marker is still "true" so the system stays locked.
|
||||
tracing::error!(
|
||||
@@ -603,7 +605,7 @@ async fn oidc_callback(
|
||||
|
||||
let (_id, app_password) = nextcloud
|
||||
.app_passwords
|
||||
.create_nc(&user_id, "Nextcloud (OIDC)")
|
||||
.create_nc(user_id, "Nextcloud (OIDC)")
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, user = %username, "OIDC+NC: failed to create app password");
|
||||
|
||||
@@ -160,7 +160,7 @@ pub async fn move_files_batch(
|
||||
// Execute batch operation
|
||||
let result = state
|
||||
.batch_service
|
||||
.move_files(request.file_ids, request.target_folder_id, &auth_user.id)
|
||||
.move_files(request.file_ids, request.target_folder_id, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch move_files failed: {}", e);
|
||||
@@ -216,7 +216,7 @@ pub async fn copy_files_batch(
|
||||
// Execute batch operation
|
||||
let result = state
|
||||
.batch_service
|
||||
.copy_files(request.file_ids, request.target_folder_id, &auth_user.id)
|
||||
.copy_files(request.file_ids, request.target_folder_id, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch copy_files failed: {}", e);
|
||||
@@ -272,7 +272,7 @@ pub async fn delete_files_batch(
|
||||
// Execute batch operation
|
||||
let result = state
|
||||
.batch_service
|
||||
.delete_files(request.file_ids, &auth_user.id)
|
||||
.delete_files(request.file_ids, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch delete_files failed: {}", e);
|
||||
@@ -336,7 +336,7 @@ pub async fn delete_folders_batch(
|
||||
// Execute batch operation
|
||||
let result = state
|
||||
.batch_service
|
||||
.delete_folders(request.folder_ids, request.recursive, &auth_user.id)
|
||||
.delete_folders(request.folder_ids, request.recursive, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch delete_folders failed: {}", e);
|
||||
@@ -407,7 +407,7 @@ pub async fn create_folders_batch(
|
||||
// Execute batch operation
|
||||
let result = state
|
||||
.batch_service
|
||||
.create_folders(folders, &auth_user.id)
|
||||
.create_folders(folders, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch create_folders failed: {}", e);
|
||||
@@ -463,7 +463,7 @@ pub async fn get_files_batch(
|
||||
// Execute batch operation
|
||||
let result = state
|
||||
.batch_service
|
||||
.get_multiple_files(request.file_ids, &auth_user.id)
|
||||
.get_multiple_files(request.file_ids, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch get_files failed: {}", e);
|
||||
@@ -519,7 +519,7 @@ pub async fn get_folders_batch(
|
||||
// Execute batch operation
|
||||
let result = state
|
||||
.batch_service
|
||||
.get_multiple_folders(request.folder_ids, &auth_user.id)
|
||||
.get_multiple_folders(request.folder_ids, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch get_folders failed: {}", e);
|
||||
@@ -603,7 +603,7 @@ pub async fn trash_batch(
|
||||
if !request.file_ids.is_empty() {
|
||||
match state
|
||||
.batch_service
|
||||
.trash_files(request.file_ids, &auth_user.id)
|
||||
.trash_files(request.file_ids, auth_user.id)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
@@ -630,7 +630,7 @@ pub async fn trash_batch(
|
||||
if !request.folder_ids.is_empty() {
|
||||
match state
|
||||
.batch_service
|
||||
.trash_folders(request.folder_ids, &auth_user.id)
|
||||
.trash_folders(request.folder_ids, auth_user.id)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
@@ -707,7 +707,7 @@ pub async fn move_folders_batch(
|
||||
|
||||
let result = state
|
||||
.batch_service
|
||||
.move_folders(request.folder_ids, request.target_folder_id, &auth_user.id)
|
||||
.move_folders(request.folder_ids, request.target_folder_id, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch move_folders failed: {}", e);
|
||||
@@ -760,7 +760,7 @@ pub async fn download_batch(
|
||||
|
||||
let temp_file = state
|
||||
.batch_service
|
||||
.download_zip(request.file_ids, request.folder_ids, &auth_user.id)
|
||||
.download_zip(request.file_ids, request.folder_ids, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Batch download ZIP failed: {}", e);
|
||||
|
||||
@@ -209,7 +209,7 @@ async fn handle_propfind(
|
||||
vec![]
|
||||
} else {
|
||||
calendar_service
|
||||
.list_my_calendars(&user.id)
|
||||
.list_my_calendars(user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list calendars: {}", e)))?
|
||||
};
|
||||
@@ -264,13 +264,13 @@ async fn handle_propfind(
|
||||
|
||||
if parts.len() == 1 {
|
||||
// Single path segment: try as calendar ID first, fall back to user home
|
||||
let calendar_result = calendar_service.get_calendar(first_segment, &user.id).await;
|
||||
let calendar_result = calendar_service.get_calendar(first_segment, user.id).await;
|
||||
|
||||
if let Ok(calendar) = calendar_result {
|
||||
// Valid calendar ID — return calendar collection
|
||||
let events = if depth != "0" {
|
||||
calendar_service
|
||||
.list_events(first_segment, None, None, &user.id)
|
||||
.list_events(first_segment, None, None, user.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -300,7 +300,7 @@ async fn handle_propfind(
|
||||
// List all calendars for this user
|
||||
let calendars =
|
||||
calendar_service
|
||||
.list_my_calendars(&user.id)
|
||||
.list_my_calendars(user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to list calendars: {}", e))
|
||||
@@ -328,7 +328,7 @@ async fn handle_propfind(
|
||||
let rest = parts[1];
|
||||
|
||||
// Check if first_segment is a valid calendar ID
|
||||
let calendar_result = calendar_service.get_calendar(first_segment, &user.id).await;
|
||||
let calendar_result = calendar_service.get_calendar(first_segment, user.id).await;
|
||||
|
||||
let (calendar_id, event_path) = if calendar_result.is_ok() {
|
||||
// first_segment is a calendar ID, rest is event path
|
||||
@@ -341,13 +341,13 @@ async fn handle_propfind(
|
||||
// /caldav/{username}/{calendar_id}
|
||||
// Try to get this as a calendar collection
|
||||
let cal = calendar_service
|
||||
.get_calendar(sub_parts[0], &user.id)
|
||||
.get_calendar(sub_parts[0], user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::not_found(format!("Calendar not found: {}", e)))?;
|
||||
|
||||
let events = if depth != "0" {
|
||||
calendar_service
|
||||
.list_events(sub_parts[0], None, None, &user.id)
|
||||
.list_events(sub_parts[0], None, None, user.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -384,7 +384,7 @@ async fn handle_propfind(
|
||||
let ical_uid = event_path.trim_end_matches(".ics");
|
||||
|
||||
let events = calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
|
||||
|
||||
@@ -444,14 +444,14 @@ async fn handle_report(
|
||||
CalDavReportType::CalendarQuery { time_range, .. } => {
|
||||
if let Some((start, end)) = time_range {
|
||||
calendar_service
|
||||
.get_events_in_range(calendar_id, *start, *end, &user.id)
|
||||
.get_events_in_range(calendar_id, *start, *end, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to query events: {}", e))
|
||||
})?
|
||||
} else {
|
||||
calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to list events: {}", e))
|
||||
@@ -460,7 +460,7 @@ async fn handle_report(
|
||||
}
|
||||
CalDavReportType::CalendarMultiget { hrefs, .. } => {
|
||||
let all_events = calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
|
||||
|
||||
@@ -470,7 +470,7 @@ async fn handle_report(
|
||||
.collect()
|
||||
}
|
||||
CalDavReportType::SyncCollection { .. } => calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?,
|
||||
};
|
||||
@@ -526,7 +526,7 @@ async fn handle_mkcalendar(
|
||||
};
|
||||
|
||||
calendar_service
|
||||
.create_calendar(create_dto, &user.id)
|
||||
.create_calendar(create_dto, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to create calendar: {}", e)))?;
|
||||
|
||||
@@ -566,7 +566,7 @@ async fn handle_put(
|
||||
|
||||
let existing = if let Some(ref uid) = ical_uid {
|
||||
let events = calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
events.into_iter().find(|e| e.ical_uid == *uid)
|
||||
@@ -577,7 +577,7 @@ async fn handle_put(
|
||||
if let Some(existing_event) = existing {
|
||||
// Update existing event — re-create from iCal for full fidelity
|
||||
calendar_service
|
||||
.delete_event(&existing_event.id, &user.id)
|
||||
.delete_event(&existing_event.id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to update event: {}", e)))?;
|
||||
|
||||
@@ -586,7 +586,7 @@ async fn handle_put(
|
||||
ical_data,
|
||||
};
|
||||
let event = calendar_service
|
||||
.create_event_from_ical(create_dto, &user.id)
|
||||
.create_event_from_ical(create_dto, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to recreate event: {}", e)))?;
|
||||
|
||||
@@ -602,7 +602,7 @@ async fn handle_put(
|
||||
};
|
||||
|
||||
let event = calendar_service
|
||||
.create_event_from_ical(create_dto, &user.id)
|
||||
.create_event_from_ical(create_dto, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to create event: {}", e)))?;
|
||||
|
||||
@@ -641,12 +641,12 @@ async fn handle_get(
|
||||
if parts.len() < 2 {
|
||||
// GET on calendar collection
|
||||
let events = calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
|
||||
|
||||
let calendar = calendar_service
|
||||
.get_calendar(calendar_id, &user.id)
|
||||
.get_calendar(calendar_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::not_found(format!("Calendar not found: {}", e)))?;
|
||||
|
||||
@@ -664,7 +664,7 @@ async fn handle_get(
|
||||
let ical_uid = event_file.trim_end_matches(".ics");
|
||||
|
||||
let events = calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
|
||||
|
||||
@@ -760,7 +760,7 @@ async fn handle_delete(
|
||||
|
||||
if parts.len() < 2 {
|
||||
calendar_service
|
||||
.delete_calendar(calendar_id, &user.id)
|
||||
.delete_calendar(calendar_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to delete calendar: {}", e)))?;
|
||||
} else {
|
||||
@@ -768,7 +768,7 @@ async fn handle_delete(
|
||||
let ical_uid = event_file.trim_end_matches(".ics");
|
||||
|
||||
let events = calendar_service
|
||||
.list_events(calendar_id, None, None, &user.id)
|
||||
.list_events(calendar_id, None, None, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
|
||||
|
||||
@@ -778,7 +778,7 @@ async fn handle_delete(
|
||||
.ok_or_else(|| AppError::not_found(format!("Event not found: {}", ical_uid)))?;
|
||||
|
||||
calendar_service
|
||||
.delete_event(&event.id, &user.id)
|
||||
.delete_event(&event.id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to delete event: {}", e)))?;
|
||||
}
|
||||
@@ -833,7 +833,7 @@ async fn handle_proppatch(
|
||||
|
||||
if update.name.is_some() || update.description.is_some() || update.color.is_some() {
|
||||
calendar_service
|
||||
.update_calendar(calendar_id, update, &user.id)
|
||||
.update_calendar(calendar_id, update, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to update calendar: {}", e)))?;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ async fn handle_propfind(
|
||||
if path.is_empty() {
|
||||
// Root CardDAV path — list user's address books
|
||||
let address_books = addressbook_service
|
||||
.list_user_address_books(&user.id)
|
||||
.list_user_address_books(user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to list address books: {}", e))
|
||||
@@ -231,13 +231,13 @@ async fn handle_propfind(
|
||||
if parts.len() == 1 {
|
||||
// Address book collection
|
||||
let address_book = addressbook_service
|
||||
.get_address_book(address_book_id, &user.id)
|
||||
.get_address_book(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::not_found(format!("Address book not found: {}", e)))?;
|
||||
|
||||
let contacts = if depth != "0" {
|
||||
contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -269,7 +269,7 @@ async fn handle_propfind(
|
||||
|
||||
// Look up by UID across all contacts in this address book
|
||||
let contacts = contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
|
||||
|
||||
@@ -331,12 +331,12 @@ async fn handle_report(
|
||||
|
||||
let contacts = match &report {
|
||||
CardDavReportType::AddressbookQuery { .. } => contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?,
|
||||
CardDavReportType::AddressbookMultiget { hrefs, .. } => {
|
||||
let all_contacts = contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
|
||||
|
||||
@@ -346,7 +346,7 @@ async fn handle_report(
|
||||
.collect()
|
||||
}
|
||||
CardDavReportType::SyncCollection { .. } => contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?,
|
||||
};
|
||||
@@ -403,7 +403,7 @@ async fn handle_mkcol(
|
||||
|
||||
let create_dto = CreateAddressBookDto {
|
||||
name,
|
||||
owner_id: user.id.clone(),
|
||||
owner_id: user.id.to_string(),
|
||||
description,
|
||||
color,
|
||||
is_public: Some(false),
|
||||
@@ -452,7 +452,7 @@ async fn handle_put(
|
||||
// Check if contact already exists
|
||||
let existing = if let Some(ref uid) = vcard_uid {
|
||||
let contacts = contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
contacts.into_iter().find(|c| c.uid == *uid)
|
||||
@@ -463,14 +463,14 @@ async fn handle_put(
|
||||
if let Some(existing_contact) = existing {
|
||||
// Update: delete + recreate from vCard
|
||||
contact_svc
|
||||
.delete_contact(&existing_contact.id, &user.id)
|
||||
.delete_contact(&existing_contact.id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to update contact: {}", e)))?;
|
||||
|
||||
let create_dto = CreateContactVCardDto {
|
||||
address_book_id: address_book_id.to_string(),
|
||||
vcard: vcard_data,
|
||||
user_id: user.id.clone(),
|
||||
user_id: user.id.to_string(),
|
||||
};
|
||||
let contact = contact_svc
|
||||
.create_contact_from_vcard(create_dto)
|
||||
@@ -486,7 +486,7 @@ async fn handle_put(
|
||||
let create_dto = CreateContactVCardDto {
|
||||
address_book_id: address_book_id.to_string(),
|
||||
vcard: vcard_data,
|
||||
user_id: user.id.clone(),
|
||||
user_id: user.id.to_string(),
|
||||
};
|
||||
|
||||
let contact = contact_svc
|
||||
@@ -529,7 +529,7 @@ async fn handle_get(
|
||||
if parts.len() < 2 {
|
||||
// GET on address book collection — return all contacts as vcf
|
||||
let contacts = contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
|
||||
|
||||
@@ -549,7 +549,7 @@ async fn handle_get(
|
||||
let contact_uid = contact_file.trim_end_matches(".vcf");
|
||||
|
||||
let contacts = contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
|
||||
|
||||
@@ -590,7 +590,7 @@ async fn handle_delete(
|
||||
if parts.len() < 2 {
|
||||
// Delete address book
|
||||
addressbook_service
|
||||
.delete_address_book(address_book_id, &user.id)
|
||||
.delete_address_book(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to delete address book: {}", e))
|
||||
@@ -601,7 +601,7 @@ async fn handle_delete(
|
||||
let contact_uid = contact_file.trim_end_matches(".vcf");
|
||||
|
||||
let contacts = contact_svc
|
||||
.list_contacts(address_book_id, &user.id)
|
||||
.list_contacts(address_book_id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
|
||||
|
||||
@@ -611,7 +611,7 @@ async fn handle_delete(
|
||||
.ok_or_else(|| AppError::not_found(format!("Contact not found: {}", contact_uid)))?;
|
||||
|
||||
contact_svc
|
||||
.delete_contact(&contact.id, &user.id)
|
||||
.delete_contact(&contact.id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to delete contact: {}", e)))?;
|
||||
}
|
||||
@@ -653,7 +653,7 @@ async fn handle_proppatch(
|
||||
description: None,
|
||||
color: None,
|
||||
is_public: None,
|
||||
user_id: user.id.clone(),
|
||||
user_id: user.id.to_string(),
|
||||
};
|
||||
|
||||
for prop in &props_to_set {
|
||||
|
||||
@@ -108,7 +108,7 @@ impl ChunkedUploadHandler {
|
||||
// ── Quota enforcement ────────────────────────────────────
|
||||
if let Some(storage_svc) = state.storage_usage_service.as_ref()
|
||||
&& let Err(err) = storage_svc
|
||||
.check_storage_quota(&auth_user.id, request.total_size)
|
||||
.check_storage_quota(auth_user.id, request.total_size)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
@@ -146,7 +146,7 @@ impl ChunkedUploadHandler {
|
||||
|
||||
match chunked_service
|
||||
.create_session(
|
||||
&auth_user.id,
|
||||
auth_user.id,
|
||||
request.filename,
|
||||
request.folder_id,
|
||||
content_type,
|
||||
@@ -192,7 +192,7 @@ impl ChunkedUploadHandler {
|
||||
match chunked_service
|
||||
.upload_chunk(
|
||||
&upload_id,
|
||||
&auth_user.id,
|
||||
auth_user.id,
|
||||
params.chunk_index,
|
||||
body,
|
||||
checksum,
|
||||
@@ -233,7 +233,7 @@ impl ChunkedUploadHandler {
|
||||
) -> impl IntoResponse {
|
||||
let chunked_service = &state.core.chunked_upload_service;
|
||||
|
||||
match chunked_service.get_status(&upload_id, &auth_user.id).await {
|
||||
match chunked_service.get_status(&upload_id, auth_user.id).await {
|
||||
Ok(status) => Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
@@ -268,7 +268,7 @@ impl ChunkedUploadHandler {
|
||||
// Assemble chunks (hash-on-write: SHA-256 computed during assembly)
|
||||
let (assembled_path, filename, folder_id, content_type, total_size, hash) =
|
||||
match chunked_service
|
||||
.complete_upload(&upload_id, &auth_user.id)
|
||||
.complete_upload(&upload_id, auth_user.id)
|
||||
.await
|
||||
{
|
||||
Ok(result) => result,
|
||||
@@ -299,7 +299,7 @@ impl ChunkedUploadHandler {
|
||||
Ok(file) => {
|
||||
// Cleanup session
|
||||
let _ = chunked_service
|
||||
.finalize_upload(&upload_id, &auth_user.id)
|
||||
.finalize_upload(&upload_id, auth_user.id)
|
||||
.await;
|
||||
|
||||
tracing::info!(
|
||||
@@ -338,7 +338,7 @@ impl ChunkedUploadHandler {
|
||||
let chunked_service = &state.core.chunked_upload_service;
|
||||
|
||||
match chunked_service
|
||||
.cancel_upload(&upload_id, &auth_user.id)
|
||||
.cancel_upload(&upload_id, auth_user.id)
|
||||
.await
|
||||
{
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
|
||||
@@ -99,7 +99,7 @@ impl DedupHandler {
|
||||
}
|
||||
|
||||
// Only reveal whether THIS user has the blob — no global oracle
|
||||
let user_has_it = dedup.user_owns_blob_reference(&hash, &auth_user.id).await;
|
||||
let user_has_it = dedup.user_owns_blob_reference(&hash, &auth_user.id.to_string()).await;
|
||||
|
||||
if user_has_it {
|
||||
// Fetch size from metadata (safe — user owns a reference)
|
||||
@@ -346,7 +346,7 @@ impl DedupHandler {
|
||||
}
|
||||
|
||||
// Verify the user owns at least one file referencing this blob
|
||||
if !dedup.user_owns_blob_reference(&hash, &auth_user.id).await {
|
||||
if !dedup.user_owns_blob_reference(&hash, &auth_user.id.to_string()).await {
|
||||
return Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
|
||||
@@ -16,6 +16,7 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::application::dtos::device_auth_dto::*;
|
||||
use crate::application::services::device_auth_service::DeviceAuthService;
|
||||
@@ -145,7 +146,7 @@ async fn device_verify_action(
|
||||
match body.action.to_lowercase().as_str() {
|
||||
"approve" | "allow" | "accept" => {
|
||||
device_service
|
||||
.approve(&body.user_code, &auth_user.id)
|
||||
.approve(&body.user_code, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Device approve failed: {}", e);
|
||||
@@ -181,7 +182,7 @@ async fn list_devices(
|
||||
let device_service = get_device_service(&state)?;
|
||||
|
||||
let devices = device_service
|
||||
.list_user_devices(&auth_user.id)
|
||||
.list_user_devices(auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("List devices failed: {}", e);
|
||||
@@ -202,8 +203,10 @@ async fn revoke_device(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let device_service = get_device_service(&state)?;
|
||||
|
||||
let device_id = Uuid::parse_str(&device_id).map_err(|_| AppError::bad_request("Invalid device ID"))?;
|
||||
|
||||
device_service
|
||||
.revoke_device(&device_id, &auth_user.id)
|
||||
.revoke_device(device_id, auth_user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Revoke device failed: {}", e);
|
||||
|
||||
@@ -30,7 +30,7 @@ pub async fn get_favorites(
|
||||
State(favorites_service): State<Arc<FavoritesService>>,
|
||||
auth_user: AuthUser,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
match favorites_service.get_favorites(user_id).await {
|
||||
Ok(favorites) => {
|
||||
@@ -56,7 +56,7 @@ pub async fn add_favorite(
|
||||
auth_user: AuthUser,
|
||||
Path((item_type, item_id)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
// Validate item_type
|
||||
if item_type != "file" && item_type != "folder" {
|
||||
@@ -99,7 +99,7 @@ pub async fn remove_favorite(
|
||||
auth_user: AuthUser,
|
||||
Path((item_type, item_id)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
match favorites_service
|
||||
.remove_from_favorites(user_id, &item_id, &item_type)
|
||||
@@ -143,7 +143,7 @@ pub async fn batch_add_favorites(
|
||||
auth_user: AuthUser,
|
||||
Json(body): Json<BatchFavoritesRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
if body.items.is_empty() {
|
||||
return (
|
||||
|
||||
@@ -109,7 +109,7 @@ impl FileHandler {
|
||||
if let Some(ref fid) = folder_id {
|
||||
use crate::application::ports::inbound::FolderUseCase;
|
||||
let folder_service = &state.applications.folder_service;
|
||||
if folder_service.get_folder_owned(fid, &auth_user.id).await.is_err() {
|
||||
if folder_service.get_folder_owned(fid, auth_user.id).await.is_err() {
|
||||
tracing::warn!(
|
||||
"⛔ UPLOAD REJECTED (IDOR): user='{}' attempted upload to folder '{}' owned by another user",
|
||||
auth_user.username,
|
||||
@@ -130,7 +130,7 @@ impl FileHandler {
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
if let Err(err) = storage_svc
|
||||
.check_storage_quota(&auth_user.id, estimated_size)
|
||||
.check_storage_quota(auth_user.id, estimated_size)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
@@ -235,7 +235,7 @@ impl FileHandler {
|
||||
// ── Quota enforcement ────────────────────────────────
|
||||
if let Some(storage_svc) = state.storage_usage_service.as_ref()
|
||||
&& let Err(err) = storage_svc
|
||||
.check_storage_quota(&auth_user.id, total_size)
|
||||
.check_storage_quota(auth_user.id, total_size)
|
||||
.await
|
||||
{
|
||||
let _ = tokio::fs::remove_file(&temp_path).await;
|
||||
@@ -321,7 +321,7 @@ impl FileHandler {
|
||||
};
|
||||
|
||||
let file = match file_retrieval_service
|
||||
.get_file_owned(&id, &auth_user.id)
|
||||
.get_file_owned(&id, auth_user.id)
|
||||
.await
|
||||
{
|
||||
Ok(f) => f,
|
||||
@@ -397,7 +397,7 @@ impl FileHandler {
|
||||
let retrieval = &state.applications.file_retrieval_service;
|
||||
|
||||
// ── Get file metadata (ownership-scoped) ────────────────────────
|
||||
let file_dto = match retrieval.get_file_owned(&id, &auth_user.id).await {
|
||||
let file_dto = match retrieval.get_file_owned(&id, auth_user.id).await {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
return AppError::from(err).into_response();
|
||||
@@ -455,7 +455,7 @@ impl FileHandler {
|
||||
Self::content_disposition(&file_dto.name, &file_dto.mime_type, ¶ms);
|
||||
|
||||
match retrieval
|
||||
.get_file_range_stream_owned(&id, &auth_user.id, start, Some(end + 1))
|
||||
.get_file_range_stream_owned(&id, auth_user.id, start, Some(end + 1))
|
||||
.await
|
||||
{
|
||||
Ok(stream) => {
|
||||
@@ -569,7 +569,7 @@ impl FileHandler {
|
||||
tracing::info!("API: Listing files with folder_id: {:?}", folder_id);
|
||||
|
||||
let retrieval = &state.applications.file_retrieval_service;
|
||||
match retrieval.list_files_owned(folder_id, &auth_user.id).await {
|
||||
match retrieval.list_files_owned(folder_id, auth_user.id).await {
|
||||
Ok(files) => {
|
||||
// Compute lightweight ETag from max modified_at + count
|
||||
let max_mod = files.iter().map(|f| f.modified_at).max().unwrap_or(0);
|
||||
@@ -655,7 +655,7 @@ impl FileHandler {
|
||||
) -> impl IntoResponse {
|
||||
// Verify ownership
|
||||
let file_read = &state.repositories.file_read_repository;
|
||||
if let Err(e) = file_read.verify_file_owner(&file_id, &auth_user.id).await {
|
||||
if let Err(e) = file_read.verify_file_owner(&file_id, auth_user.id).await {
|
||||
let msg = e.to_string();
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
@@ -703,7 +703,7 @@ impl FileHandler {
|
||||
|
||||
// Auth required: trash-first with dedup cleanup + ownership verification
|
||||
let result = mgmt
|
||||
.delete_with_cleanup(&id, &auth_user.id)
|
||||
.delete_with_cleanup(&id, auth_user.id)
|
||||
.await
|
||||
.map(|was_trashed| {
|
||||
if was_trashed {
|
||||
@@ -745,7 +745,7 @@ impl FileHandler {
|
||||
|
||||
tracing::info!("Renaming file {} to \"{}\"", id, new_name);
|
||||
let mgmt = &state.applications.file_management_service;
|
||||
match mgmt.rename_file_owned(&id, &auth_user.id, &new_name).await {
|
||||
match mgmt.rename_file_owned(&id, auth_user.id, &new_name).await {
|
||||
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
|
||||
Err(err) => AppError::from(err).into_response()
|
||||
}
|
||||
@@ -763,7 +763,7 @@ impl FileHandler {
|
||||
let mgmt = &state.applications.file_management_service;
|
||||
|
||||
match mgmt
|
||||
.move_file_owned(&id, &auth_user.id, payload.folder_id)
|
||||
.move_file_owned(&id, auth_user.id, payload.folder_id)
|
||||
.await
|
||||
{
|
||||
Ok(file) => (StatusCode::OK, Json(file)).into_response(),
|
||||
@@ -784,7 +784,7 @@ impl FileHandler {
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let mgmt = &state.applications.file_management_service;
|
||||
match mgmt.move_file_owned(&id, &auth_user.id, folder_id).await {
|
||||
match mgmt.move_file_owned(&id, auth_user.id, folder_id).await {
|
||||
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
|
||||
Err(err) => AppError::from(err).into_response()
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ impl FolderHandler {
|
||||
"create_folder: parent_id is None for user '{}', resolving home folder",
|
||||
auth_user.username
|
||||
);
|
||||
match service.list_folders_for_owner(None, &auth_user.id).await {
|
||||
match service.list_folders_for_owner(None, auth_user.id).await {
|
||||
Ok(folders) => {
|
||||
if let Some(home) = folders.first() {
|
||||
tracing::info!(
|
||||
@@ -71,7 +71,7 @@ impl FolderHandler {
|
||||
if let Some(ref parent_id) = dto.parent_id {
|
||||
use crate::application::ports::inbound::FolderUseCase;
|
||||
if service
|
||||
.get_folder_owned(parent_id, &auth_user.id)
|
||||
.get_folder_owned(parent_id, auth_user.id)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -102,7 +102,7 @@ impl FolderHandler {
|
||||
Ok(folder) => {
|
||||
// Access check: folder must belong to the requesting user
|
||||
if let Some(ref owner) = folder.owner_id
|
||||
&& owner != &auth_user.id
|
||||
&& owner != &auth_user.id.to_string()
|
||||
{
|
||||
tracing::warn!(
|
||||
"get_folder: user '{}' attempted to access folder '{}' owned by '{}'",
|
||||
@@ -155,7 +155,7 @@ impl FolderHandler {
|
||||
pagination: Query<PaginationRequestDto>,
|
||||
) -> axum::response::Response {
|
||||
match service
|
||||
.list_folders_for_owner_paginated(Some(&id), &auth_user.id, &pagination)
|
||||
.list_folders_for_owner_paginated(Some(&id), auth_user.id, &pagination)
|
||||
.await
|
||||
{
|
||||
Ok(paginated_result) => (StatusCode::OK, Json(paginated_result)).into_response(),
|
||||
@@ -172,7 +172,7 @@ impl FolderHandler {
|
||||
auth_user: &AuthUser,
|
||||
) -> axum::response::Response {
|
||||
match service
|
||||
.list_folders_for_owner(parent_id, &auth_user.id)
|
||||
.list_folders_for_owner(parent_id, auth_user.id)
|
||||
.await
|
||||
{
|
||||
Ok(folders) => (StatusCode::OK, Json(folders)).into_response(),
|
||||
@@ -215,8 +215,8 @@ impl FolderHandler {
|
||||
|
||||
// Run both queries concurrently — no sequential wait.
|
||||
let (folders_result, files_result) = tokio::join!(
|
||||
folder_service.list_folders_for_owner(Some(&id), &auth_user.id),
|
||||
file_service.list_files_owned(Some(&id), &auth_user.id)
|
||||
folder_service.list_folders_for_owner(Some(&id), auth_user.id),
|
||||
file_service.list_files_owned(Some(&id), auth_user.id)
|
||||
);
|
||||
|
||||
match (folders_result, files_result) {
|
||||
@@ -253,7 +253,7 @@ impl FolderHandler {
|
||||
Path(id): Path<String>,
|
||||
Json(dto): Json<RenameFolderDto>,
|
||||
) -> impl IntoResponse {
|
||||
match service.rename_folder(&id, dto, &auth_user.id).await {
|
||||
match service.rename_folder(&id, dto, auth_user.id).await {
|
||||
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
}
|
||||
@@ -266,7 +266,7 @@ impl FolderHandler {
|
||||
Path(id): Path<String>,
|
||||
Json(dto): Json<MoveFolderDto>,
|
||||
) -> impl IntoResponse {
|
||||
match service.move_folder(&id, dto, &auth_user.id).await {
|
||||
match service.move_folder(&id, dto, auth_user.id).await {
|
||||
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
}
|
||||
@@ -278,7 +278,7 @@ impl FolderHandler {
|
||||
auth_user: AuthUser,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
match service.delete_folder(&id, &auth_user.id).await {
|
||||
match service.delete_folder(&id, auth_user.id).await {
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
}
|
||||
@@ -290,7 +290,7 @@ impl FolderHandler {
|
||||
auth_user: AuthUser,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
// Check if trash service is available
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
tracing::info!("Moving folder to trash: {}", id);
|
||||
@@ -337,7 +337,7 @@ impl FolderHandler {
|
||||
match folder_service.get_folder(&id).await {
|
||||
Ok(folder) => {
|
||||
// Access check: folder must belong to the requesting user
|
||||
if folder.owner_id.as_deref() != Some(&auth_user.id) {
|
||||
if folder.owner_id.as_deref() != Some(&auth_user.id.to_string()) {
|
||||
tracing::warn!(
|
||||
"download_folder_zip: user '{}' attempted to download folder '{}' owned by '{:?}'",
|
||||
auth_user.id,
|
||||
|
||||
@@ -31,7 +31,7 @@ pub async fn list_photos(
|
||||
auth_user: AuthUser,
|
||||
Query(params): Query<PhotosQueryParams>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
let limit = params.limit.unwrap_or(200).clamp(1, 500);
|
||||
|
||||
let file_read = &state.repositories.file_read_repository;
|
||||
|
||||
@@ -25,7 +25,7 @@ pub async fn get_recent_items(
|
||||
auth_user: AuthUser,
|
||||
Query(params): Query<GetRecentParams>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
match recent_service.get_recent_items(user_id, params.limit).await {
|
||||
Ok(items) => {
|
||||
@@ -51,7 +51,7 @@ pub async fn record_item_access(
|
||||
auth_user: AuthUser,
|
||||
Path((item_type, item_id)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
// Validate item type
|
||||
if item_type != "file" && item_type != "folder" {
|
||||
@@ -97,7 +97,7 @@ pub async fn remove_from_recent(
|
||||
auth_user: AuthUser,
|
||||
Path((item_type, item_id)): Path<(String, String)>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
match recent_service
|
||||
.remove_from_recent(user_id, &item_id, &item_type)
|
||||
@@ -142,7 +142,7 @@ pub async fn clear_recent_items(
|
||||
State(recent_service): State<Arc<RecentService>>,
|
||||
auth_user: AuthUser,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
match recent_service.clear_recent_items(user_id).await {
|
||||
Ok(_) => {
|
||||
|
||||
@@ -60,7 +60,7 @@ impl SearchHandler {
|
||||
sort_by: params.sort_by.unwrap_or_else(|| "relevance".to_string()),
|
||||
};
|
||||
|
||||
match search_service.search(search_criteria, &auth_user.id).await {
|
||||
match search_service.search(search_criteria, auth_user.id).await {
|
||||
Ok(results) => {
|
||||
info!(
|
||||
"Search completed in {}ms — {} files, {} folders",
|
||||
@@ -101,7 +101,7 @@ impl SearchHandler {
|
||||
}
|
||||
};
|
||||
|
||||
match search_service.search(criteria, &auth_user.id).await {
|
||||
match search_service.search(criteria, auth_user.id).await {
|
||||
Ok(results) => {
|
||||
info!(
|
||||
"Advanced search completed in {}ms — {} files, {} folders",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
@@ -40,7 +41,7 @@ pub async fn create_shared_link(
|
||||
auth_user: AuthUser,
|
||||
Json(dto): Json<CreateShareDto>,
|
||||
) -> impl IntoResponse {
|
||||
match share_use_case.create_shared_link(&auth_user.id, dto).await {
|
||||
match share_use_case.create_shared_link(auth_user.id, dto).await {
|
||||
Ok(share) => (StatusCode::CREATED, Json(share)).into_response(),
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
}
|
||||
@@ -52,7 +53,11 @@ pub async fn get_shared_link(
|
||||
auth_user: AuthUser,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
match share_use_case.get_shared_link(&id, &auth_user.id).await {
|
||||
let id = match Uuid::parse_str(&id) {
|
||||
Ok(id) => id,
|
||||
Err(_) => return AppError::bad_request("Invalid UUID").into_response(),
|
||||
};
|
||||
match share_use_case.get_shared_link(id, auth_user.id).await {
|
||||
Ok(share) => (StatusCode::OK, Json(share)).into_response(),
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
}
|
||||
@@ -65,7 +70,7 @@ pub async fn get_user_shares(
|
||||
auth_user: AuthUser,
|
||||
Query(query): Query<GetSharesQuery>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
|
||||
// If both item_id and item_type are provided, return shares for that specific item
|
||||
if let (Some(item_id), Some(item_type_str)) = (&query.item_id, &query.item_type) {
|
||||
@@ -108,8 +113,12 @@ pub async fn update_shared_link(
|
||||
Path(id): Path<String>,
|
||||
Json(dto): Json<UpdateShareDto>,
|
||||
) -> impl IntoResponse {
|
||||
let id = match Uuid::parse_str(&id) {
|
||||
Ok(id) => id,
|
||||
Err(_) => return AppError::bad_request("Invalid UUID").into_response(),
|
||||
};
|
||||
match share_use_case
|
||||
.update_shared_link(&id, &auth_user.id, dto)
|
||||
.update_shared_link(id, auth_user.id, dto)
|
||||
.await
|
||||
{
|
||||
Ok(share) => (StatusCode::OK, Json(share)).into_response(),
|
||||
@@ -123,7 +132,11 @@ pub async fn delete_shared_link(
|
||||
auth_user: AuthUser,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
match share_use_case.delete_shared_link(&id, &auth_user.id).await {
|
||||
let id = match Uuid::parse_str(&id) {
|
||||
Ok(id) => id,
|
||||
Err(_) => return AppError::bad_request("Invalid UUID").into_response(),
|
||||
};
|
||||
match share_use_case.delete_shared_link(id, auth_user.id).await {
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub async fn get_trash_items(
|
||||
// SECURITY: Always use the authenticated user's ID from the JWT token.
|
||||
// Never allow user ID override via query parameters to prevent
|
||||
// privilege escalation attacks.
|
||||
let effective_user = auth_user.id.clone();
|
||||
let effective_user = auth_user.id;
|
||||
|
||||
debug!("Request to list trash items for user {}", effective_user);
|
||||
|
||||
@@ -34,7 +34,7 @@ pub async fn get_trash_items(
|
||||
}
|
||||
};
|
||||
|
||||
let result = trash_service.get_trash_items(&effective_user).await;
|
||||
let result = trash_service.get_trash_items(effective_user).await;
|
||||
|
||||
match result {
|
||||
Ok(items) => {
|
||||
@@ -60,7 +60,7 @@ pub async fn move_file_to_trash(
|
||||
auth_user: AuthUser,
|
||||
Path(item_id): Path<String>,
|
||||
) -> (StatusCode, Json<serde_json::Value>) {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
debug!(
|
||||
"Request to move file to trash: id={}, user={}",
|
||||
item_id, user_id
|
||||
@@ -111,7 +111,7 @@ pub async fn move_folder_to_trash(
|
||||
auth_user: AuthUser,
|
||||
Path(item_id): Path<String>,
|
||||
) -> (StatusCode, Json<serde_json::Value>) {
|
||||
let user_id = &auth_user.id;
|
||||
let user_id = auth_user.id;
|
||||
debug!(
|
||||
"Request to move folder to trash: id={}, user={}",
|
||||
item_id, user_id
|
||||
@@ -177,7 +177,7 @@ pub async fn restore_from_trash(
|
||||
);
|
||||
}
|
||||
};
|
||||
let result = trash_service.restore_item(&trash_id, &auth_user.id).await;
|
||||
let result = trash_service.restore_item(&trash_id, auth_user.id).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
@@ -239,7 +239,7 @@ pub async fn delete_permanently(
|
||||
}
|
||||
};
|
||||
let result = trash_service
|
||||
.delete_permanently(&trash_id, &auth_user.id)
|
||||
.delete_permanently(&trash_id, auth_user.id)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
@@ -300,7 +300,7 @@ pub async fn empty_trash(
|
||||
);
|
||||
}
|
||||
};
|
||||
let result = trash_service.empty_trash(&auth_user.id).await;
|
||||
let result = trash_service.empty_trash(auth_user.id).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
|
||||
@@ -183,7 +183,7 @@ async fn handle_webdav_methods(
|
||||
/// If `path` doesn't already start with the user's home folder name, prepend
|
||||
/// the home folder path so downstream services can find the resource in the DB.
|
||||
/// Returns `None` when the path already includes the prefix or resolution fails.
|
||||
async fn resolve_webdav_path(state: &Arc<AppState>, user_id: &str, path: &str) -> Option<String> {
|
||||
async fn resolve_webdav_path(state: &Arc<AppState>, user_id: Uuid, path: &str) -> Option<String> {
|
||||
let folder_service = &state.applications.folder_service;
|
||||
let home_folders = folder_service
|
||||
.list_folders_for_owner(None, user_id)
|
||||
@@ -209,9 +209,9 @@ async fn handle_webdav_dispatch(
|
||||
// prefix when the path doesn't already include it.
|
||||
// Extract user_id before any async call to keep the future Send.
|
||||
let path = if !path.is_empty() && method.as_str() != "OPTIONS" {
|
||||
let user_id = req.extensions().get::<Arc<CurrentUser>>().map(|u| u.id.clone());
|
||||
let user_id = req.extensions().get::<Arc<CurrentUser>>().map(|u| u.id);
|
||||
if let Some(uid) = user_id {
|
||||
resolve_webdav_path(&state, &uid, &path)
|
||||
resolve_webdav_path(&state, uid, &path)
|
||||
.await
|
||||
.unwrap_or(path)
|
||||
} else {
|
||||
@@ -371,14 +371,14 @@ async fn handle_propfind(
|
||||
propfind_request,
|
||||
folder_service,
|
||||
file_retrieval_service,
|
||||
&user.id,
|
||||
user.id,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Single-query path resolution: folder OR file in one DB round-trip
|
||||
if let Some(resolver) = &state.path_resolver {
|
||||
match resolver.resolve_path_for_user(&path, &user.id).await {
|
||||
match resolver.resolve_path_for_user(&path, user.id).await {
|
||||
Ok(ResolvedResource::Folder(folder)) => {
|
||||
let folder_id = folder.id.clone();
|
||||
return build_streaming_propfind_response(
|
||||
@@ -389,7 +389,7 @@ async fn handle_propfind(
|
||||
propfind_request,
|
||||
folder_service,
|
||||
file_retrieval_service,
|
||||
&user.id,
|
||||
user.id,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -420,7 +420,7 @@ async fn handle_propfind(
|
||||
} else {
|
||||
// Fallback: legacy double-query path when PathResolver is unavailable
|
||||
if let Ok(folder) = folder_service.get_folder_by_path(&path).await {
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id, &path)?;
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||
let folder_id = folder.id.clone();
|
||||
return build_streaming_propfind_response(
|
||||
folder,
|
||||
@@ -430,12 +430,12 @@ async fn handle_propfind(
|
||||
propfind_request,
|
||||
folder_service,
|
||||
file_retrieval_service,
|
||||
&user.id,
|
||||
user.id,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if let Ok(file) = file_retrieval_service.get_file_by_path(&path).await {
|
||||
assert_owner(file.owner_id.as_deref(), &user.id, &path)?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||
let mut buf = Vec::with_capacity(1024);
|
||||
{
|
||||
let mut xml_writer = Writer::new(&mut buf);
|
||||
@@ -477,12 +477,11 @@ async fn build_streaming_propfind_response(
|
||||
propfind_request: PropFindRequest,
|
||||
folder_service: std::sync::Arc<FolderService>,
|
||||
file_retrieval_service: std::sync::Arc<FileRetrievalService>,
|
||||
user_id: &str,
|
||||
user_id: Uuid,
|
||||
) -> Result<Response<Body>, AppError> {
|
||||
let depth = depth.to_string();
|
||||
let base_href = base_href.to_string();
|
||||
let propfind_request = Arc::new(propfind_request);
|
||||
let user_id = user_id.to_string();
|
||||
|
||||
let stream = async_stream::try_stream! {
|
||||
// ── XML header + <D:multistatus> + folder entry ──────────
|
||||
@@ -512,7 +511,7 @@ async fn build_streaming_propfind_response(
|
||||
page_size: pagination.page_size,
|
||||
};
|
||||
let result = folder_service
|
||||
.list_folders_for_owner_paginated(fid_ref, &user_id, &pag)
|
||||
.list_folders_for_owner_paginated(fid_ref, user_id, &pag)
|
||||
.await
|
||||
.map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
|
||||
@@ -542,7 +541,7 @@ async fn build_streaming_propfind_response(
|
||||
let mut offset: i64 = 0;
|
||||
loop {
|
||||
let batch: Vec<FileDto> = file_retrieval_service
|
||||
.list_files_batch_for_owner(fid_ref, &user_id, offset, PROPFIND_BATCH_SIZE)
|
||||
.list_files_batch_for_owner(fid_ref, user_id, offset, PROPFIND_BATCH_SIZE)
|
||||
.await
|
||||
.map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
|
||||
@@ -675,7 +674,7 @@ async fn handle_get(
|
||||
|
||||
// Resolve file — user-scoped when PathResolver is available
|
||||
let file = if let Some(resolver) = &state.path_resolver {
|
||||
match resolver.resolve_path_for_user(&path, &user.id).await {
|
||||
match resolver.resolve_path_for_user(&path, user.id).await {
|
||||
Ok(ResolvedResource::File(f)) => f,
|
||||
Ok(ResolvedResource::Folder(_)) => {
|
||||
return Err(AppError::bad_request("Cannot GET a directory"));
|
||||
@@ -690,7 +689,7 @@ async fn handle_get(
|
||||
.get_file_by_path(&path)
|
||||
.await
|
||||
.map_err(|_e| AppError::not_found(format!("File not found: {}", path)))?;
|
||||
assert_owner(f.owner_id.as_deref(), &user.id, &path)?;
|
||||
assert_owner(f.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||
f
|
||||
};
|
||||
|
||||
@@ -740,7 +739,7 @@ async fn handle_head(
|
||||
|
||||
// Single-query path resolution (user-scoped)
|
||||
if let Some(resolver) = &state.path_resolver {
|
||||
match resolver.resolve_path_for_user(&path, &user.id).await {
|
||||
match resolver.resolve_path_for_user(&path, user.id).await {
|
||||
Ok(ResolvedResource::Folder(folder)) => {
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
@@ -771,7 +770,7 @@ async fn handle_head(
|
||||
|
||||
// Fallback: legacy double-query path (with ownership check)
|
||||
if let Ok(folder) = folder_service.get_folder_by_path(&path).await {
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id, &path)?;
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "httpd/unix-directory")
|
||||
@@ -786,7 +785,7 @@ async fn handle_head(
|
||||
.get_file_by_path(&path)
|
||||
.await
|
||||
.map_err(|_e| AppError::not_found(format!("Resource not found: {}", path)))?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id, &path)?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
@@ -840,7 +839,7 @@ async fn handle_put(
|
||||
// parent folder (create). Without this check a user could
|
||||
// overwrite another user's file via a crafted PUT path.
|
||||
if let Some(resolver) = &state.path_resolver {
|
||||
match resolver.resolve_path_for_user(&path, &user.id).await {
|
||||
match resolver.resolve_path_for_user(&path, user.id).await {
|
||||
Ok(ResolvedResource::File(_)) => { /* existing file owned by user — OK */ }
|
||||
Ok(ResolvedResource::Folder(_)) => {
|
||||
return Err(AppError::bad_request("Cannot PUT to a directory"));
|
||||
@@ -854,7 +853,7 @@ async fn handle_put(
|
||||
};
|
||||
if !parent_path.is_empty() {
|
||||
resolver
|
||||
.resolve_path_for_user(parent_path, &user.id)
|
||||
.resolve_path_for_user(parent_path, user.id)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AppError::not_found(format!("Parent folder not found: {}", parent_path))
|
||||
@@ -1052,10 +1051,10 @@ async fn handle_delete(
|
||||
|
||||
// Single-query path resolution (user-scoped)
|
||||
if let Some(resolver) = &state.path_resolver {
|
||||
match resolver.resolve_path_for_user(&path, &user.id).await {
|
||||
match resolver.resolve_path_for_user(&path, user.id).await {
|
||||
Ok(ResolvedResource::Folder(folder)) => {
|
||||
folder_service
|
||||
.delete_folder(&folder.id, &user.id)
|
||||
.delete_folder(&folder.id, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to delete folder: {}", e))
|
||||
@@ -1076,9 +1075,9 @@ async fn handle_delete(
|
||||
let folder_result = folder_service.get_folder_by_path(&path).await;
|
||||
|
||||
if let Ok(folder) = folder_result {
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id, &path)?;
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||
folder_service
|
||||
.delete_folder(&folder.id, &user.id)
|
||||
.delete_folder(&folder.id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
|
||||
} else {
|
||||
@@ -1086,7 +1085,7 @@ async fn handle_delete(
|
||||
.get_file_by_path(&path)
|
||||
.await
|
||||
.map_err(|_e| AppError::not_found(format!("Resource not found: {}", path)))?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id, &path)?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
|
||||
|
||||
file_management_service
|
||||
.delete_file(&file.id)
|
||||
@@ -1157,7 +1156,7 @@ async fn handle_move(
|
||||
if !overwrite {
|
||||
let dest_exists = if let Some(resolver) = &state.path_resolver {
|
||||
resolver
|
||||
.exists_for_user(&destination_path, &user.id)
|
||||
.exists_for_user(&destination_path, user.id)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
@@ -1179,7 +1178,7 @@ async fn handle_move(
|
||||
|
||||
// Resolve source: single-query when PathResolver is available (user-scoped)
|
||||
if let Some(resolver) = &state.path_resolver {
|
||||
match resolver.resolve_path_for_user(&source_path, &user.id).await {
|
||||
match resolver.resolve_path_for_user(&source_path, user.id).await {
|
||||
Ok(ResolvedResource::Folder(folder)) => {
|
||||
let dest_folder_name = destination_path
|
||||
.split('/')
|
||||
@@ -1200,7 +1199,7 @@ async fn handle_move(
|
||||
// SECURITY: verify destination parent belongs to caller (V-08)
|
||||
assert_owner(
|
||||
parent.owner_id.as_deref(),
|
||||
&user.id,
|
||||
&user.id.to_string(),
|
||||
dest_parent_path,
|
||||
)?;
|
||||
Some(parent.id)
|
||||
@@ -1211,7 +1210,7 @@ async fn handle_move(
|
||||
};
|
||||
|
||||
folder_service
|
||||
.move_folder(&folder.id, move_dto, &user.id)
|
||||
.move_folder(&folder.id, move_dto, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to move folder: {}", e))
|
||||
@@ -1222,7 +1221,7 @@ async fn handle_move(
|
||||
name: dest_folder_name.to_string(),
|
||||
};
|
||||
folder_service
|
||||
.rename_folder(&folder.id, rename_dto, &user.id)
|
||||
.rename_folder(&folder.id, rename_dto, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to rename folder: {}", e))
|
||||
@@ -1251,7 +1250,7 @@ async fn handle_move(
|
||||
&& let Ok(parent) =
|
||||
folder_service.get_folder_by_path(dest_parent_path).await
|
||||
{
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
|
||||
}
|
||||
file_management_service
|
||||
.move_file(&file.id, Some(dest_parent_path.to_string()))
|
||||
@@ -1281,7 +1280,7 @@ async fn handle_move(
|
||||
let folder_result = folder_service.get_folder_by_path(&source_path).await;
|
||||
|
||||
if let Ok(folder) = folder_result {
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id, &source_path)?;
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
|
||||
let dest_folder_name = destination_path
|
||||
.split('/')
|
||||
.next_back()
|
||||
@@ -1299,7 +1298,7 @@ async fn handle_move(
|
||||
match folder_service.get_folder_by_path(dest_parent_path).await {
|
||||
Ok(parent) => {
|
||||
// SECURITY: verify destination parent belongs to caller (V-08)
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
|
||||
Some(parent.id)
|
||||
}
|
||||
Err(_) => None,
|
||||
@@ -1308,7 +1307,7 @@ async fn handle_move(
|
||||
};
|
||||
|
||||
folder_service
|
||||
.move_folder(&folder.id, move_dto, &user.id)
|
||||
.move_folder(&folder.id, move_dto, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to move folder: {}", e)))?;
|
||||
|
||||
@@ -1317,7 +1316,7 @@ async fn handle_move(
|
||||
name: dest_folder_name.to_string(),
|
||||
};
|
||||
folder_service
|
||||
.rename_folder(&folder.id, rename_dto, &user.id)
|
||||
.rename_folder(&folder.id, rename_dto, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to rename folder: {}", e))
|
||||
@@ -1330,7 +1329,7 @@ async fn handle_move(
|
||||
.map_err(|_e| {
|
||||
AppError::not_found(format!("Resource not found: {}", source_path))
|
||||
})?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id, &source_path)?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
|
||||
|
||||
let dest_filename = destination_path
|
||||
.split('/')
|
||||
@@ -1352,7 +1351,7 @@ async fn handle_move(
|
||||
if !dest_parent_path.is_empty()
|
||||
&& let Ok(parent) = folder_service.get_folder_by_path(dest_parent_path).await
|
||||
{
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
|
||||
}
|
||||
file_management_service
|
||||
.move_file(&file.id, Some(dest_parent_path.to_string()))
|
||||
@@ -1438,7 +1437,7 @@ async fn handle_copy(
|
||||
if !overwrite {
|
||||
let dest_exists = if let Some(resolver) = &state.path_resolver {
|
||||
resolver
|
||||
.exists_for_user(&destination_path, &user.id)
|
||||
.exists_for_user(&destination_path, user.id)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
@@ -1460,7 +1459,7 @@ async fn handle_copy(
|
||||
|
||||
// Resolve source: single-query when PathResolver is available (user-scoped)
|
||||
if let Some(resolver) = &state.path_resolver {
|
||||
match resolver.resolve_path_for_user(&source_path, &user.id).await {
|
||||
match resolver.resolve_path_for_user(&source_path, user.id).await {
|
||||
Ok(ResolvedResource::Folder(folder)) => {
|
||||
let recursive = depth != "0";
|
||||
|
||||
@@ -1480,7 +1479,7 @@ async fn handle_copy(
|
||||
match folder_service.get_folder_by_path(dest_parent_path).await {
|
||||
Ok(parent) => {
|
||||
// SECURITY: verify destination parent belongs to caller (V-08)
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
|
||||
Some(parent.id)
|
||||
}
|
||||
Err(_) => None,
|
||||
@@ -1528,7 +1527,7 @@ async fn handle_copy(
|
||||
match folder_service.get_folder_by_path(dest_parent_path).await {
|
||||
Ok(parent) => {
|
||||
// SECURITY: verify destination parent belongs to caller (V-08)
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
|
||||
Some(parent.id)
|
||||
}
|
||||
Err(_) => None,
|
||||
@@ -1553,7 +1552,7 @@ async fn handle_copy(
|
||||
let folder_result = folder_service.get_folder_by_path(&source_path).await;
|
||||
|
||||
if let Ok(folder) = folder_result {
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id, &source_path)?;
|
||||
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
|
||||
let recursive = depth != "0";
|
||||
|
||||
let dest_folder_name = destination_path
|
||||
@@ -1572,7 +1571,7 @@ async fn handle_copy(
|
||||
match folder_service.get_folder_by_path(dest_parent_path).await {
|
||||
Ok(parent) => {
|
||||
// SECURITY: verify destination parent belongs to caller (V-08)
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
|
||||
Some(parent.id)
|
||||
}
|
||||
Err(_) => None,
|
||||
@@ -1613,7 +1612,7 @@ async fn handle_copy(
|
||||
.map_err(|_e| {
|
||||
AppError::not_found(format!("Resource not found: {}", source_path))
|
||||
})?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id, &source_path)?;
|
||||
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
|
||||
|
||||
let dest_parent_path = if let Some(idx) = destination_path.rfind('/') {
|
||||
&destination_path[..idx]
|
||||
@@ -1627,7 +1626,7 @@ async fn handle_copy(
|
||||
match folder_service.get_folder_by_path(dest_parent_path).await {
|
||||
Ok(parent) => {
|
||||
// SECURITY: verify destination parent belongs to caller (V-08)
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
|
||||
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
|
||||
Some(parent.id)
|
||||
}
|
||||
Err(_) => None,
|
||||
@@ -1737,7 +1736,7 @@ async fn handle_lock(
|
||||
let token = format!("opaquelocktoken:{}", Uuid::new_v4());
|
||||
let lock_info = LockInfo {
|
||||
token,
|
||||
owner: owner.or(Some(user.id.clone())),
|
||||
owner: owner.or(Some(user.id.to_string())),
|
||||
depth: depth.to_string(),
|
||||
timeout,
|
||||
scope,
|
||||
|
||||
@@ -396,7 +396,7 @@ pub struct EditorUrlResponse {
|
||||
async fn authorize_wopi_access<S: FileRetrievalUseCase>(
|
||||
file_retrieval: &S,
|
||||
file_id: &str,
|
||||
caller_id: &str,
|
||||
caller_id: uuid::Uuid,
|
||||
requested_action: &str,
|
||||
) -> Result<(crate::application::dtos::file_dto::FileDto, bool), StatusCode> {
|
||||
let file = file_retrieval
|
||||
@@ -425,7 +425,7 @@ pub async fn get_editor_url(
|
||||
let (file, can_write) = match authorize_wopi_access(
|
||||
state.app_state.applications.file_retrieval_service.as_ref(),
|
||||
¶ms.file_id,
|
||||
&user_id,
|
||||
user_id,
|
||||
¶ms.action,
|
||||
)
|
||||
.await
|
||||
@@ -464,7 +464,7 @@ pub async fn get_editor_url(
|
||||
let (access_token, access_token_ttl) =
|
||||
match state
|
||||
.token_service
|
||||
.generate_token(¶ms.file_id, &user_id, &username, can_write)
|
||||
.generate_token(¶ms.file_id, &user_id.to_string(), &username, can_write)
|
||||
{
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
@@ -503,10 +503,14 @@ async fn host_page(
|
||||
|
||||
// Re-verify ownership even though the token was valid — defence in depth.
|
||||
let requested_action = if claims.can_write { "edit" } else { "view" };
|
||||
let caller_uuid = match uuid::Uuid::parse_str(&claims.sub) {
|
||||
Ok(u) => u,
|
||||
Err(_) => return StatusCode::UNAUTHORIZED.into_response(),
|
||||
};
|
||||
let file = match authorize_wopi_access(
|
||||
state.app_state.applications.file_retrieval_service.as_ref(),
|
||||
&file_id,
|
||||
&claims.sub,
|
||||
caller_uuid,
|
||||
requested_action,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -6,6 +6,7 @@ use axum::{
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::common::di::AppState;
|
||||
|
||||
@@ -22,7 +23,7 @@ pub struct CookieAuthenticated;
|
||||
// Structure for use in Axum extractors
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AuthUser {
|
||||
pub id: String,
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub role: String,
|
||||
}
|
||||
@@ -35,7 +36,7 @@ pub struct AuthUser {
|
||||
/// async fn my_handler(CurrentUserId(user_id): CurrentUserId) -> impl IntoResponse { ... }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CurrentUserId(pub String);
|
||||
pub struct CurrentUserId(pub Uuid);
|
||||
|
||||
// Implement FromRequestParts for AuthUser — allows using `auth_user: AuthUser` in handlers
|
||||
impl<S> FromRequestParts<S> for AuthUser
|
||||
@@ -49,7 +50,7 @@ where
|
||||
.extensions
|
||||
.get::<Arc<CurrentUser>>()
|
||||
.map(|cu| AuthUser {
|
||||
id: cu.id.clone(),
|
||||
id: cu.id,
|
||||
username: cu.username.clone(),
|
||||
role: cu.role.clone(),
|
||||
})
|
||||
@@ -86,7 +87,7 @@ where
|
||||
parts
|
||||
.extensions
|
||||
.get::<Arc<CurrentUser>>()
|
||||
.map(|cu| CurrentUserId(cu.id.clone()))
|
||||
.map(|cu| CurrentUserId(cu.id))
|
||||
.ok_or(AuthError::UserNotFound)
|
||||
}
|
||||
}
|
||||
@@ -94,7 +95,7 @@ where
|
||||
/// Optional user ID extractor – never fails.
|
||||
/// Yields `Some(id)` when auth middleware ran, `None` otherwise.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OptionalUserId(pub Option<String>);
|
||||
pub struct OptionalUserId(pub Option<Uuid>);
|
||||
|
||||
impl<S> FromRequestParts<S> for OptionalUserId
|
||||
where
|
||||
@@ -107,7 +108,7 @@ where
|
||||
parts
|
||||
.extensions
|
||||
.get::<Arc<CurrentUser>>()
|
||||
.map(|cu| cu.id.clone()),
|
||||
.map(|cu| cu.id),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -126,7 +127,7 @@ where
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
Ok(OptionalAuthUser(parts.extensions.get::<Arc<CurrentUser>>().map(
|
||||
|cu| AuthUser {
|
||||
id: cu.id.clone(),
|
||||
id: cu.id,
|
||||
username: cu.username.clone(),
|
||||
role: cu.role.clone(),
|
||||
},
|
||||
@@ -216,8 +217,11 @@ pub async fn auth_middleware(
|
||||
"Token validated successfully for user: {}",
|
||||
claims.username
|
||||
);
|
||||
let user_id = Uuid::parse_str(&claims.sub).map_err(|_| {
|
||||
AuthError::InvalidToken("Invalid user ID in token".to_string())
|
||||
})?;
|
||||
let current_user = Arc::new(CurrentUser {
|
||||
id: claims.sub,
|
||||
id: user_id,
|
||||
username: claims.username,
|
||||
email: claims.email,
|
||||
role: claims.role,
|
||||
@@ -303,8 +307,11 @@ pub async fn auth_middleware(
|
||||
match token_service.validate_token(&token_str) {
|
||||
Ok(claims) => {
|
||||
tracing::debug!("Cookie token validated for user: {}", claims.username);
|
||||
let user_id = Uuid::parse_str(&claims.sub).map_err(|_| {
|
||||
AuthError::InvalidToken("Invalid user ID in token".to_string())
|
||||
})?;
|
||||
let current_user = Arc::new(CurrentUser {
|
||||
id: claims.sub,
|
||||
id: user_id,
|
||||
username: claims.username,
|
||||
email: claims.email,
|
||||
role: claims.role,
|
||||
|
||||
@@ -174,7 +174,7 @@ pub async fn handle_login_submit(
|
||||
|
||||
let app_password = match nextcloud
|
||||
.app_passwords
|
||||
.create_nc(¤t_user.id, "Nextcloud")
|
||||
.create_nc(current_user.id, "Nextcloud")
|
||||
.await
|
||||
{
|
||||
Ok((_id, password)) => password,
|
||||
|
||||
@@ -47,7 +47,7 @@ pub async fn handle_capabilities_v2(State(state): State<Arc<AppState>>) -> Respo
|
||||
|
||||
pub async fn handle_user_info(State(state): State<Arc<AppState>>, user: CurrentUser) -> Response {
|
||||
let quota: (i64, i64) = match state.storage_usage_service.as_ref() {
|
||||
Some(service) => match service.get_user_storage_info(&user.id).await {
|
||||
Some(service) => match service.get_user_storage_info(user.id).await {
|
||||
Ok((used, total)) => (used, total),
|
||||
Err(_) => (0, 0),
|
||||
},
|
||||
@@ -151,7 +151,7 @@ async fn user_provisioning_response(
|
||||
|
||||
// Fetch quota from storage usage service
|
||||
let quota: (i64, i64) = match state.storage_usage_service.as_ref() {
|
||||
Some(service) => match service.get_user_storage_info(&user_dto.id).await {
|
||||
Some(service) => match service.get_user_storage_info(uuid::Uuid::parse_str(&user_dto.id).unwrap_or_default()).await {
|
||||
Ok((used, total)) => (used, total),
|
||||
Err(_) => (0, 0),
|
||||
},
|
||||
@@ -212,7 +212,7 @@ pub async fn handle_revoke_apppassword(
|
||||
|
||||
if let Err(e) = nextcloud
|
||||
.app_passwords
|
||||
.revoke_by_password(&user.id, &app_password)
|
||||
.revoke_by_password(user.id, &app_password)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("Failed to revoke app password for {}: {}", user.id, e);
|
||||
@@ -367,7 +367,7 @@ pub async fn handle_search(
|
||||
..SearchCriteriaDto::default()
|
||||
};
|
||||
|
||||
let results = match search_service.search(criteria, &user.id).await {
|
||||
let results = match search_service.search(criteria, user.id).await {
|
||||
Ok(r) => r,
|
||||
Err(_) => return empty_search_response().into_response(),
|
||||
};
|
||||
|
||||
@@ -90,7 +90,8 @@ pub async fn handle_preview(
|
||||
};
|
||||
|
||||
// Verify the authenticated user owns this file
|
||||
if file.owner_id.as_deref() != Some(&user.id) {
|
||||
let user_id_str = user.id.to_string();
|
||||
if file.owner_id.as_deref() != Some(user_id_str.as_str()) {
|
||||
return Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::from("File not found"))
|
||||
|
||||
@@ -67,7 +67,7 @@ async fn handle_filter_files(
|
||||
};
|
||||
|
||||
let favorites = fav_svc
|
||||
.get_favorites(&user.id)
|
||||
.get_favorites(user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to get favorites: {}", e)))?;
|
||||
|
||||
@@ -179,7 +179,7 @@ async fn handle_search(
|
||||
};
|
||||
|
||||
let results = search_svc
|
||||
.search(criteria, &user.id)
|
||||
.search(criteria, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Search failed: {}", e)))?;
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ async fn handle_propfind(
|
||||
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
|
||||
|
||||
let items = trash_svc
|
||||
.get_trash_items(&user.id)
|
||||
.get_trash_items(user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to list trash: {}", e)))?;
|
||||
|
||||
@@ -109,7 +109,7 @@ async fn handle_restore(
|
||||
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
|
||||
|
||||
trash_svc
|
||||
.restore_item(&id, &user.id)
|
||||
.restore_item(&id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to restore item: {}", e)))?;
|
||||
|
||||
@@ -131,7 +131,7 @@ async fn handle_empty_trash(
|
||||
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
|
||||
|
||||
trash_svc
|
||||
.empty_trash(&user.id)
|
||||
.empty_trash(user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to empty trash: {}", e)))?;
|
||||
|
||||
@@ -156,7 +156,7 @@ async fn handle_delete_permanent(
|
||||
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
|
||||
|
||||
trash_svc
|
||||
.delete_permanently(&id, &user.id)
|
||||
.delete_permanently(&id, user.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to permanently delete item: {}", e))
|
||||
|
||||
@@ -193,7 +193,7 @@ async fn handle_propfind(
|
||||
items.push((&sf.id, "folder"));
|
||||
}
|
||||
fav_svc
|
||||
.batch_check_favorites(&user.id, &items)
|
||||
.batch_check_favorites(user.id, &items)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -234,7 +234,7 @@ async fn handle_propfind(
|
||||
let favorite_ids = if let Some(fav_svc) = state.favorites_service.as_ref() {
|
||||
let items: Vec<(&str, &str)> = vec![(&file.id, "file")];
|
||||
fav_svc
|
||||
.batch_check_favorites(&user.id, &items)
|
||||
.batch_check_favorites(user.id, &items)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -414,14 +414,14 @@ async fn handle_proppatch(
|
||||
if let Some(fav_svc) = state.favorites_service.as_ref() {
|
||||
if value == 1 {
|
||||
fav_svc
|
||||
.add_to_favorites(&user.id, &item_id, item_type)
|
||||
.add_to_favorites(user.id, &item_id, item_type)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to add favorite: {}", e))
|
||||
})?;
|
||||
} else {
|
||||
fav_svc
|
||||
.remove_from_favorites(&user.id, &item_id, item_type)
|
||||
.remove_from_favorites(user.id, &item_id, item_type)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
AppError::internal_error(format!("Failed to remove favorite: {}", e))
|
||||
@@ -685,7 +685,7 @@ async fn handle_delete(
|
||||
if let Some(trash_svc) = state.trash_service.as_ref() {
|
||||
if let Ok(folder) = folder_service.get_folder_by_path(&internal_path).await {
|
||||
trash_svc
|
||||
.move_to_trash(&folder.id, "folder", &user.id)
|
||||
.move_to_trash(&folder.id, "folder", user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to trash folder: {}", e)))?;
|
||||
return Ok(Response::builder()
|
||||
@@ -695,7 +695,7 @@ async fn handle_delete(
|
||||
}
|
||||
if let Ok(file) = file_service.get_file_by_path(&internal_path).await {
|
||||
trash_svc
|
||||
.move_to_trash(&file.id, "file", &user.id)
|
||||
.move_to_trash(&file.id, "file", user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to trash file: {}", e)))?;
|
||||
return Ok(Response::builder()
|
||||
@@ -711,7 +711,7 @@ async fn handle_delete(
|
||||
|
||||
if let Ok(folder) = folder_service.get_folder_by_path(&internal_path).await {
|
||||
folder_service
|
||||
.delete_folder(&folder.id, &user.id)
|
||||
.delete_folder(&folder.id, user.id)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
|
||||
|
||||
@@ -835,7 +835,7 @@ async fn handle_move(
|
||||
RenameFolderDto {
|
||||
name: dest_name.to_string(),
|
||||
},
|
||||
&user.id,
|
||||
user.id,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;
|
||||
@@ -853,7 +853,7 @@ async fn handle_move(
|
||||
MoveFolderDto {
|
||||
parent_id: Some(dest_parent.id.clone()),
|
||||
},
|
||||
&user.id,
|
||||
user.id,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Move failed: {}", e)))?;
|
||||
@@ -867,7 +867,7 @@ async fn handle_move(
|
||||
RenameFolderDto {
|
||||
name: dest_name.to_string(),
|
||||
},
|
||||
&user.id,
|
||||
user.id,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;
|
||||
|
||||
Reference in New Issue
Block a user