diff --git a/src/application/ports/folder_ports.rs b/src/application/ports/folder_ports.rs index dfae242e..76b2c6f0 100644 --- a/src/application/ports/folder_ports.rs +++ b/src/application/ports/folder_ports.rs @@ -44,11 +44,8 @@ pub trait FolderUseCase: Send + Sync + 'static { /// folder (docs/plan/drive.md §10). Pre-D0 the wrapper name /// embedded the username and made the path globally unique; /// post-D0 the caller_id filter is required. - async fn get_folder_by_path( - &self, - path: &str, - user_id: Uuid, - ) -> Result; + async fn get_folder_by_path(&self, path: &str, user_id: Uuid) + -> Result; /// Lists folders within a parent folder async fn list_folders(&self, parent_id: Option<&str>) -> Result, DomainError>; diff --git a/src/infrastructure/repositories/pg/drive_pg_repository.rs b/src/infrastructure/repositories/pg/drive_pg_repository.rs index 12a60424..f02079b4 100644 --- a/src/infrastructure/repositories/pg/drive_pg_repository.rs +++ b/src/infrastructure/repositories/pg/drive_pg_repository.rs @@ -127,14 +127,12 @@ impl DriveRepository for DrivePgRepository { .map_err(|e| Self::map_sqlx_err("create_personal_drive_atomic.folder", e))?; // 3. Close the other side of the circular reference. - sqlx::query( - r#"UPDATE storage.drives SET root_folder_id = $1 WHERE id = $2"#, - ) - .bind(folder_id) - .bind(drive_id) - .execute(&mut *tx) - .await - .map_err(|e| Self::map_sqlx_err("create_personal_drive_atomic.wire", e))?; + sqlx::query(r#"UPDATE storage.drives SET root_folder_id = $1 WHERE id = $2"#) + .bind(folder_id) + .bind(drive_id) + .execute(&mut *tx) + .await + .map_err(|e| Self::map_sqlx_err("create_personal_drive_atomic.wire", e))?; // 4. Owner role_grant — the caller becomes the drive's sole // owner (single-user invariant on personal drives, §2). diff --git a/src/interfaces/api/handlers/webdav_handler.rs b/src/interfaces/api/handlers/webdav_handler.rs index ea9f8343..e6b535fd 100644 --- a/src/interfaces/api/handlers/webdav_handler.rs +++ b/src/interfaces/api/handlers/webdav_handler.rs @@ -1208,7 +1208,10 @@ async fn handle_mkcol( } accumulated_path.push_str(segment); - match folder_service.get_folder_by_path(&accumulated_path, user.id).await { + match folder_service + .get_folder_by_path(&accumulated_path, user.id) + .await + { Ok(existing) => { parent_id = Some(existing.id); } @@ -1443,7 +1446,9 @@ async fn handle_move( let move_dto = crate::application::dtos::folder_dto::MoveFolderDto { parent_id: if dest_parent_path.is_empty() { None - } else if let Ok(parent) = folder_service.get_folder_by_path(dest_parent_path, user.id).await + } else if let Ok(parent) = folder_service + .get_folder_by_path(dest_parent_path, user.id) + .await { assert_owner( parent.owner_id.as_deref(), @@ -1644,7 +1649,10 @@ async fn handle_copy( let target_parent_id = if dest_parent_path.is_empty() { None - } else if let Ok(parent) = folder_service.get_folder_by_path(dest_parent_path, user.id).await { + } else if let Ok(parent) = folder_service + .get_folder_by_path(dest_parent_path, user.id) + .await + { assert_owner( parent.owner_id.as_deref(), &user.id.to_string(), diff --git a/src/interfaces/nextcloud/basic_auth_middleware.rs b/src/interfaces/nextcloud/basic_auth_middleware.rs index 726e786f..6fe930bb 100644 --- a/src/interfaces/nextcloud/basic_auth_middleware.rs +++ b/src/interfaces/nextcloud/basic_auth_middleware.rs @@ -168,22 +168,31 @@ pub async fn basic_auth_middleware( }; // ── Resolve chroot from the Basic Auth drive marker ───── - // No marker → user's home folder. With a marker → + // No marker → caller's default personal drive's root folder + // (post-D0 every internal user has one — provisioned by the + // lifecycle hook via the atomic four-write transaction in + // §3 of docs/plan/drive.md). With a marker → // `get_folder_with_perms` enforces per-folder access (404 // anti-enumeration on miss / no-read). Today this is the // sole chroot source; tomorrow it'll come from the // app-password row instead. + // + // Pre-D0 this lookup name-matched `"My Folder - "` + // against the user's root folders; that broke after the + // wrapper was renamed to `"Personal"` and shared across all + // users — name-matching was the wrong axis. The drive lookup + // is the right one: name-independent, secondary-drive-safe. use crate::application::ports::folder_ports::FolderUseCase; + use crate::domain::repositories::drive_repository::DriveRepository; let chroot = match drive_marker.as_deref() { None => { - let expected = format!("My Folder - {}", current_user.username); - match state - .applications - .folder_service - .list_folders_with_perms(None, current_user.id) - .await - { - Ok(folders) => folders.into_iter().find(|f| f.name == expected), + match state.drive_repo.find_default_for_user(current_user.id).await { + Ok(drive_with_name) => state + .applications + .folder_service + .get_folder(&drive_with_name.drive.root_folder_id.to_string()) + .await + .ok(), Err(_) => None, } }