fix_next_cloud

This commit is contained in:
Edouard Vanbelle
2026-06-19 01:08:07 +02:00
parent 3f4b151f45
commit aa155dffa0
4 changed files with 37 additions and 25 deletions
+2 -5
View File
@@ -44,11 +44,8 @@ pub trait FolderUseCase: Send + Sync + 'static {
/// folder (docs/plan/drive.md §10). Pre-D0 the wrapper name /// folder (docs/plan/drive.md §10). Pre-D0 the wrapper name
/// embedded the username and made the path globally unique; /// embedded the username and made the path globally unique;
/// post-D0 the caller_id filter is required. /// post-D0 the caller_id filter is required.
async fn get_folder_by_path( async fn get_folder_by_path(&self, path: &str, user_id: Uuid)
&self, -> Result<FolderDto, DomainError>;
path: &str,
user_id: Uuid,
) -> Result<FolderDto, DomainError>;
/// Lists folders within a parent folder /// Lists folders within a parent folder
async fn list_folders(&self, parent_id: Option<&str>) -> Result<Vec<FolderDto>, DomainError>; async fn list_folders(&self, parent_id: Option<&str>) -> Result<Vec<FolderDto>, DomainError>;
@@ -127,14 +127,12 @@ impl DriveRepository for DrivePgRepository {
.map_err(|e| Self::map_sqlx_err("create_personal_drive_atomic.folder", e))?; .map_err(|e| Self::map_sqlx_err("create_personal_drive_atomic.folder", e))?;
// 3. Close the other side of the circular reference. // 3. Close the other side of the circular reference.
sqlx::query( sqlx::query(r#"UPDATE storage.drives SET root_folder_id = $1 WHERE id = $2"#)
r#"UPDATE storage.drives SET root_folder_id = $1 WHERE id = $2"#, .bind(folder_id)
) .bind(drive_id)
.bind(folder_id) .execute(&mut *tx)
.bind(drive_id) .await
.execute(&mut *tx) .map_err(|e| Self::map_sqlx_err("create_personal_drive_atomic.wire", e))?;
.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 // 4. Owner role_grant — the caller becomes the drive's sole
// owner (single-user invariant on personal drives, §2). // owner (single-user invariant on personal drives, §2).
+11 -3
View File
@@ -1208,7 +1208,10 @@ async fn handle_mkcol(
} }
accumulated_path.push_str(segment); 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) => { Ok(existing) => {
parent_id = Some(existing.id); parent_id = Some(existing.id);
} }
@@ -1443,7 +1446,9 @@ async fn handle_move(
let move_dto = crate::application::dtos::folder_dto::MoveFolderDto { let move_dto = crate::application::dtos::folder_dto::MoveFolderDto {
parent_id: if dest_parent_path.is_empty() { parent_id: if dest_parent_path.is_empty() {
None 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( assert_owner(
parent.owner_id.as_deref(), parent.owner_id.as_deref(),
@@ -1644,7 +1649,10 @@ async fn handle_copy(
let target_parent_id = if dest_parent_path.is_empty() { let target_parent_id = if dest_parent_path.is_empty() {
None 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( assert_owner(
parent.owner_id.as_deref(), parent.owner_id.as_deref(),
&user.id.to_string(), &user.id.to_string(),
@@ -168,22 +168,31 @@ pub async fn basic_auth_middleware(
}; };
// ── Resolve chroot from the Basic Auth drive marker ───── // ── 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 // `get_folder_with_perms` enforces per-folder access (404
// anti-enumeration on miss / no-read). Today this is the // anti-enumeration on miss / no-read). Today this is the
// sole chroot source; tomorrow it'll come from the // sole chroot source; tomorrow it'll come from the
// app-password row instead. // app-password row instead.
//
// Pre-D0 this lookup name-matched `"My Folder - <username>"`
// 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::application::ports::folder_ports::FolderUseCase;
use crate::domain::repositories::drive_repository::DriveRepository;
let chroot = match drive_marker.as_deref() { let chroot = match drive_marker.as_deref() {
None => { None => {
let expected = format!("My Folder - {}", current_user.username); match state.drive_repo.find_default_for_user(current_user.id).await {
match state Ok(drive_with_name) => state
.applications .applications
.folder_service .folder_service
.list_folders_with_perms(None, current_user.id) .get_folder(&drive_with_name.drive.root_folder_id.to_string())
.await .await
{ .ok(),
Ok(folders) => folders.into_iter().find(|f| f.name == expected),
Err(_) => None, Err(_) => None,
} }
} }