feat(drive): limit NC request by disk

This commit is contained in:
Edouard Vanbelle
2026-06-19 07:49:33 +02:00
parent aa155dffa0
commit 50fb34659e
28 changed files with 591 additions and 179 deletions
+13 -2
View File
@@ -56,9 +56,15 @@ pub trait FileUploadUseCase: Send + Sync + 'static {
/// blob, or create the file when it doesn't exist (WebDAV/WOPI PUT).
///
/// Takes ownership of the blob's reference (released on failure).
///
/// `drive_id` scopes both the existence probe (`find_file_by_path`)
/// and the parent-folder resolution (`get_parent_folder_id`) — the
/// handler is responsible for deriving it from its protocol context
/// (NC chroot, native default-drive lookup, WOPI default-drive).
async fn update_file_streaming(
&self,
path: &str,
drive_id: Uuid,
blob: StoredBlob,
content_type: &str,
modified_at: Option<i64>,
@@ -104,8 +110,13 @@ pub trait FileRetrievalUseCase: Send + Sync + 'static {
caller_id: Uuid,
) -> Result<FileDto, DomainError>;
/// Gets a file by its path (for WebDAV)
async fn get_file_by_path(&self, path: &str) -> Result<FileDto, DomainError>;
/// Gets a file by its path (for WebDAV), scoped to a drive.
///
/// Post-D0, `storage.files.path` is unique only within a single
/// drive. The `drive_id` filter scopes the lookup to a specific
/// drive (caller derives it from its protocol context: NC chroot,
/// native default-drive lookup, WOPI default-drive lookup).
async fn get_file_by_path(&self, path: &str, drive_id: Uuid) -> Result<FileDto, DomainError>;
/// Lists files in a folder
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<FileDto>, DomainError>;
+12 -8
View File
@@ -38,14 +38,18 @@ pub trait FolderUseCase: Send + Sync + 'static {
/// Gets a folder by its path within the caller's tree.
///
/// Scoped by `user_id` because `storage.folders.path` is unique
/// only within a single user's drive after D0 — multiple users
/// share names like `"Personal"` for their default-drive root
/// 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<FolderDto, DomainError>;
/// Scoped by `drive_id` because `storage.folders.path` is unique
/// only within a single drive after D0 — multiple drives (whether
/// owned by the same user or different users) share names like
/// `"Personal"` for their root folder (docs/plan/drive.md §10).
/// Pre-D0 the wrapper name embedded the username; post-D0 the
/// caller derives a `drive_id` from its protocol context (NC
/// chroot, native default-drive lookup, WOPI default-drive).
async fn get_folder_by_path(
&self,
path: &str,
drive_id: Uuid,
) -> Result<FolderDto, DomainError>;
/// Lists folders within a parent folder
async fn list_folders(&self, parent_id: Option<&str>) -> Result<Vec<FolderDto>, DomainError>;
+32 -7
View File
@@ -82,11 +82,25 @@ pub trait FileReadPort: Send + Sync + 'static {
/// Gets the logical storage path of a file.
async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError>;
/// Gets the parent folder ID from a path (WebDAV).
async fn get_parent_folder_id(&self, path: &str) -> Result<String, DomainError>;
/// Gets the parent folder ID from a path (WebDAV), scoped to a drive.
///
/// Post-D0, `storage.folders.path` is unique only within a single
/// drive. The `drive_id` filter scopes the lookup to a specific
/// drive (caller derives it from its protocol context: NC chroot,
/// native default-drive lookup, WOPI default-drive lookup).
async fn get_parent_folder_id(&self, path: &str, drive_id: Uuid)
-> Result<String, DomainError>;
/// Gets a folder ID by its path.
async fn get_folder_id_by_path(&self, folder_path: &str) -> Result<String, DomainError>;
/// Gets a folder ID by its path, scoped to a drive.
///
/// Post-D0 same scoping rule as `get_parent_folder_id` — names like
/// `"Personal"` repeat across drives, so the `drive_id` filter is
/// required to disambiguate.
async fn get_folder_id_by_path(
&self,
folder_path: &str,
drive_id: Uuid,
) -> Result<String, DomainError>;
/// Gets the content-addressable blob hash for a file (O(1) DB lookup).
///
@@ -94,11 +108,22 @@ pub trait FileReadPort: Send + Sync + 'static {
/// Used for dedup reference tracking without loading file content.
async fn get_blob_hash(&self, file_id: &str) -> Result<String, DomainError>;
/// Find a file by its logical path (folder_name/.../file_name).
/// Find a file by its logical path (folder_name/.../file_name),
/// scoped to a drive.
///
/// Post-D0 `storage.files.path` is unique only within a single
/// drive. The `drive_id` filter prevents non-deterministic
/// resolution when the same path exists in multiple drives.
///
/// The default implementation falls back to `list_files(None)` + linear
/// scan (O(N)). Repositories should override with a direct SQL query.
async fn find_file_by_path(&self, path: &str) -> Result<Option<File>, DomainError> {
/// scan (O(N)) and ignores the drive filter — only used by stubs.
/// Repositories should override with a direct SQL query that applies
/// the filter.
async fn find_file_by_path(
&self,
path: &str,
_drive_id: Uuid,
) -> Result<Option<File>, DomainError> {
let path = path.trim_start_matches('/').trim_end_matches('/');
let all_files = self.list_files(None).await?;
for file in all_files {