feat(drive): complete updated_by created_by

This commit is contained in:
Edouard Vanbelle
2026-06-19 10:51:13 +02:00
parent 06116dc6e7
commit e7f4826778
34 changed files with 987 additions and 230 deletions
+15
View File
@@ -44,12 +44,20 @@ pub trait FileUploadUseCase: Send + Sync + 'static {
/// Register a new file row pointing at an already-ingested blob.
///
/// Takes ownership of the blob's reference (released on failure).
///
/// `caller_id` is plumbed down into
/// `FileWritePort::save_file_with_blob` so the §14 `created_by` /
/// `updated_by` columns record the principal performing the upload —
/// not the parent folder's owner. D2 shared drives surface this
/// most clearly: Adam upload into Alice's folder must record
/// `created_by = adam.id`.
async fn upload_file_streaming(
&self,
name: String,
folder_id: Option<String>,
content_type: String,
blob: StoredBlob,
caller_id: Uuid,
) -> Result<FileDto, DomainError>;
/// Replace the content of the file at `path` with an already-ingested
@@ -61,6 +69,12 @@ pub trait FileUploadUseCase: Send + Sync + 'static {
/// 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).
///
/// `caller_id` is plumbed down into
/// `FileWritePort::update_file_content_with_blob` so the §14
/// `updated_by` column reflects the principal that performed the
/// PUT — not the file's existing owner (D2 shared drives let
/// non-owners overwrite content).
async fn update_file_streaming(
&self,
path: &str,
@@ -68,6 +82,7 @@ pub trait FileUploadUseCase: Send + Sync + 'static {
blob: StoredBlob,
content_type: &str,
modified_at: Option<i64>,
caller_id: Uuid,
) -> Result<FileDto, DomainError>;
}
+41 -6
View File
@@ -284,6 +284,13 @@ pub trait FileWritePort: Send + Sync + 'static {
///
/// Takes ownership of one blob reference: on any failure the reference
/// is released before the error is returned.
///
/// `caller_id` is stamped into both `created_by` and `updated_by`
/// (§14 provenance — authorship belongs to the caller, not to the
/// parent folder's owner). In D2 shared drives, a non-owner member
/// can upload into a folder owned by someone else; the previous
/// `created_by = parent.user_id` would have silently recorded the
/// wrong principal.
async fn save_file_with_blob(
&self,
name: String,
@@ -291,17 +298,29 @@ pub trait FileWritePort: Send + Sync + 'static {
content_type: String,
blob_hash: &str,
size: u64,
caller_id: Uuid,
) -> Result<File, DomainError>;
/// Moves a file to another folder.
/// Moves a file to another folder. `caller_id` is stamped into
/// `updated_by` alongside the `updated_at = NOW()` bump
/// (§14 provenance — authorship belongs to the caller, not to
/// the destination folder's owner).
async fn move_file(
&self,
file_id: &str,
target_folder_id: Option<String>,
caller_id: Uuid,
) -> Result<File, DomainError>;
/// Renames a file (same folder, different name).
async fn rename_file(&self, file_id: &str, new_name: &str) -> Result<File, DomainError>;
/// Renames a file (same folder, different name). `caller_id` is
/// stamped into `updated_by` alongside the `updated_at = NOW()`
/// bump (§14 provenance).
async fn rename_file(
&self,
file_id: &str,
new_name: &str,
caller_id: Uuid,
) -> Result<File, DomainError>;
/// Deletes a file.
async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
@@ -315,24 +334,32 @@ pub trait FileWritePort: Send + Sync + 'static {
/// Returns `(new_blob_hash, updated_at_epoch)` — everything a caller
/// needs to rebuild the fresh entity/ETag from a `File` it already
/// holds, without re-reading the row it just updated.
///
/// `caller_id` is stamped into `updated_by` alongside the
/// `updated_at` bump (§14 provenance).
async fn update_file_content_with_blob(
&self,
file_id: &str,
blob_hash: &str,
size: u64,
modified_at: Option<i64>,
caller_id: Uuid,
) -> Result<(String, i64), DomainError>;
/// Registers file metadata WITHOUT writing content to disk (write-behind).
///
/// Returns `(File, PathBuf)` where `PathBuf` is the destination path for the
/// deferred write that the `WriteBehindCache` will perform.
///
/// `caller_id` is stamped into both `created_by` and `updated_by`
/// (§14 provenance — see `save_file_with_blob`).
async fn register_file_deferred(
&self,
name: String,
folder_id: Option<String>,
content_type: String,
size: u64,
caller_id: Uuid,
) -> Result<(File, PathBuf), DomainError>;
/// Copies a file to a (possibly different) folder.
@@ -344,11 +371,16 @@ pub trait FileWritePort: Send + Sync + 'static {
/// the same folder always collides on the source's filename. WebDAV
/// COPY uses this for the "same folder, different name" case (the
/// classic `COPY /a.txt → /b.txt` pattern).
///
/// `caller_id` is stamped into both `created_by` and `updated_by`
/// on the new row (§14 provenance — the caller authored this copy,
/// not the destination folder's owner).
async fn copy_file(
&self,
file_id: &str,
target_folder_id: Option<String>,
new_name: Option<&str>,
caller_id: Uuid,
) -> Result<File, DomainError>;
/// Copies an entire folder subtree atomically using ltree.
@@ -375,14 +407,17 @@ pub trait FileWritePort: Send + Sync + 'static {
// ── Trash operations ──
/// Moves a file to the trash
async fn move_to_trash(&self, file_id: &str) -> Result<(), DomainError>;
/// Moves a file to the trash. `caller_id` is stamped into
/// `updated_by` (§14 provenance).
async fn move_to_trash(&self, file_id: &str, caller_id: Uuid) -> Result<(), DomainError>;
/// Restores a file from the trash to its original location
/// Restores a file from the trash to its original location.
/// `caller_id` is stamped into `updated_by` (§14 provenance).
async fn restore_from_trash(
&self,
file_id: &str,
original_path: &str,
caller_id: Uuid,
) -> Result<(), DomainError>;
/// Permanently deletes a file (used by the trash)