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
+79
View File
@@ -25,6 +25,10 @@ pub struct FileParts {
pub owner_id: Option<Uuid>,
/// BLAKE3 content hash. See [`File::content_hash`] for semantics.
pub blob_hash: String,
/// §14 provenance: original creator. See [`File::created_by`].
pub created_by: Option<Uuid>,
/// §14 provenance: most recent mutator. See [`File::updated_by`].
pub updated_by: Option<Uuid>,
}
/**
@@ -77,6 +81,18 @@ pub struct File {
/// ETag (the ETag formula may grow to include `modified_at` etc.,
/// but `content_hash` remains the raw hash).
blob_hash: String,
/// User that originally created this file (§14 provenance).
/// Stamped at INSERT and never updated thereafter. `None` when
/// the referenced user has been deleted (FK is `ON DELETE SET
/// NULL`) or for stub/DTO-reconstructed files.
created_by: Option<Uuid>,
/// User that performed the most recent mutation that bumped
/// `updated_at` (rename, move, content overwrite, trash, restore).
/// Authorship signal — distinct from ownership. `None` when the
/// referenced user is deleted or for stub/DTO-reconstructed files.
updated_by: Option<Uuid>,
}
// We no longer need this module, now we use a String directly
@@ -95,6 +111,8 @@ impl Default for File {
modified_at: 0,
owner_id: None,
blob_hash: String::new(),
created_by: None,
updated_by: None,
}
}
}
@@ -134,6 +152,8 @@ impl File {
modified_at: now,
owner_id: None,
blob_hash: String::new(),
created_by: None,
updated_by: None,
})
}
@@ -166,6 +186,8 @@ impl File {
modified_at,
owner_id: None,
blob_hash: String::new(),
created_by: None,
updated_by: None,
})
}
@@ -207,6 +229,40 @@ impl File {
modified_at: u64,
owner_id: Option<Uuid>,
blob_hash: String,
) -> FileResult<Self> {
Self::with_timestamps_blob_hash_and_provenance(
id,
name,
storage_path,
size,
mime_type,
folder_id,
created_at,
modified_at,
owner_id,
blob_hash,
None,
None,
)
}
/// Full constructor including the §14 provenance columns
/// (`created_by` / `updated_by`). PG-row callers use this to
/// preserve authorship across reconstruction.
#[allow(clippy::too_many_arguments)]
pub fn with_timestamps_blob_hash_and_provenance(
id: String,
name: String,
storage_path: StoragePath,
size: u64,
mime_type: String,
folder_id: Option<String>,
created_at: u64,
modified_at: u64,
owner_id: Option<Uuid>,
blob_hash: String,
created_by: Option<Uuid>,
updated_by: Option<Uuid>,
) -> FileResult<Self> {
let name = normalize_storage_name(&name);
if let Err(reason) = validate_storage_name(&name) {
@@ -228,6 +284,8 @@ impl File {
modified_at,
owner_id,
blob_hash,
created_by,
updated_by,
})
}
@@ -248,6 +306,8 @@ impl File {
modified_at: self.modified_at,
owner_id: self.owner_id,
blob_hash: self.blob_hash,
created_by: self.created_by,
updated_by: self.updated_by,
}
}
@@ -351,6 +411,21 @@ impl File {
self.owner_id
}
/// User that originally created this file (§14 provenance).
/// `None` when the referenced user has been deleted
/// (FK is `ON DELETE SET NULL`) or for stub/DTO entities.
pub fn created_by(&self) -> Option<Uuid> {
self.created_by
}
/// User that performed the most recent mutation that bumped
/// `updated_at`. Authorship signal — distinct from ownership.
/// `None` when the referenced user is deleted or for
/// stub/DTO entities.
pub fn updated_by(&self) -> Option<Uuid> {
self.updated_by
}
#[allow(clippy::too_many_arguments)]
pub fn from_dto(
id: String,
@@ -382,6 +457,10 @@ impl File {
modified_at,
owner_id: None,
blob_hash: String::new(),
// DTO round-trips don't carry provenance; callers needing
// it must reload from the repository.
created_by: None,
updated_by: None,
}
}
+84 -1
View File
@@ -50,6 +50,20 @@ pub struct Folder {
/// HTTP ETag emitted in PROPFIND/GET/HEAD responses — see
/// [`Folder::etag`] for the formula and rationale.
tree_modified_at: u64,
/// User that originally created this folder. Stamped at INSERT
/// from the caller's id and never updated afterwards (provenance,
/// not ownership — see §14 of the Drive plan). `None` when the
/// referenced user is later deleted (FK is `ON DELETE SET NULL`)
/// or for stub/DTO-reconstructed folders that never touched the DB.
created_by: Option<Uuid>,
/// User that performed the most recent mutation that touched
/// `updated_at` (rename, move, trash, restore, content overwrite).
/// Authorship signal — does NOT propagate via the tree-ETag flush
/// trigger. `None` when the referenced user is deleted or for
/// stub/DTO-reconstructed folders.
updated_by: Option<Uuid>,
}
// We no longer need this module, now we use a String directly
@@ -67,6 +81,8 @@ impl Default for Folder {
created_at: 0,
modified_at: 0,
tree_modified_at: 0,
created_by: None,
updated_by: None,
}
}
}
@@ -119,6 +135,10 @@ impl Folder {
created_at: now,
modified_at: now,
tree_modified_at: now,
// Provenance is unknown for in-memory construction; the DB
// reconstruction path supplies real values.
created_by: None,
updated_by: None,
})
}
@@ -179,7 +199,10 @@ impl Folder {
/// `tree_modified_at` comes from the trigger-maintained column on
/// `storage.folders` and feeds [`Folder::etag`]. `drive_id` is the
/// post-D0 `storage.folders.drive_id NOT NULL` column — every
/// path-based lookup scopes by this axis.
/// path-based lookup scopes by this axis. `created_by` /
/// `updated_by` are the §14 provenance columns; both are nullable
/// because the M1 FK is `ON DELETE SET NULL` (a deleted user
/// leaves authored rows in place).
#[allow(clippy::too_many_arguments)]
pub fn with_timestamps_and_tree(
id: String,
@@ -191,6 +214,38 @@ impl Folder {
created_at: u64,
modified_at: u64,
tree_modified_at: u64,
) -> FolderResult<Self> {
Self::with_timestamps_tree_and_provenance(
id,
name,
storage_path,
parent_id,
owner_id,
drive_id,
created_at,
modified_at,
tree_modified_at,
None,
None,
)
}
/// Full constructor including the §14 provenance columns
/// (`created_by` / `updated_by`). Direct PG-row callers use this
/// to preserve authorship through the entity layer.
#[allow(clippy::too_many_arguments)]
pub fn with_timestamps_tree_and_provenance(
id: String,
name: String,
storage_path: StoragePath,
parent_id: Option<String>,
owner_id: Option<Uuid>,
drive_id: Uuid,
created_at: u64,
modified_at: u64,
tree_modified_at: u64,
created_by: Option<Uuid>,
updated_by: Option<Uuid>,
) -> FolderResult<Self> {
let name = normalize_storage_name(&name);
if let Err(reason) = validate_storage_name(&name) {
@@ -210,6 +265,8 @@ impl Folder {
created_at,
modified_at,
tree_modified_at,
created_by,
updated_by,
})
}
@@ -253,6 +310,22 @@ impl Folder {
self.drive_id
}
/// User that originally created this folder (§14 provenance).
/// `None` when the referenced user has been deleted
/// (FK is `ON DELETE SET NULL`) or for in-memory/DTO-reconstructed
/// entities.
pub fn created_by(&self) -> Option<Uuid> {
self.created_by
}
/// User that performed the most recent mutation that bumped
/// `updated_at`. Authorship signal — distinct from ownership.
/// `None` when the referenced user has been deleted or for
/// in-memory/DTO-reconstructed entities.
pub fn updated_by(&self) -> Option<Uuid> {
self.updated_by
}
/// Latest descendant-write timestamp. Statement-level Postgres
/// triggers enqueue every file/folder write into
/// `storage.tree_etag_dirty`; the background `TreeEtagFlushService`
@@ -348,6 +421,10 @@ impl Folder {
created_at,
modified_at,
tree_modified_at: modified_at,
// DTO round-trips through this constructor lose
// provenance; callers that need it reload through the repo.
created_by: None,
updated_by: None,
}
}
@@ -391,6 +468,10 @@ impl Folder {
// ancestors' listings now show a new name, so the
// collection has materially changed.
tree_modified_at: now,
// Provenance is preserved across the in-memory rebuild;
// real persisted updates re-read from the DB.
created_by: self.created_by,
updated_by: self.updated_by,
})
}
@@ -425,6 +506,8 @@ impl Folder {
created_at: self.created_at,
modified_at: now,
tree_modified_at: now,
created_by: self.created_by,
updated_by: self.updated_by,
})
}