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
@@ -22,11 +22,12 @@ use crate::domain::services::path_service::StoragePath;
/// Type alias for folder metadata rows from SQL queries.
/// Tuple order: id, name, path, parent_id, user_id, drive_id,
/// created_at, modified_at, tree_modified_at. The trailing
/// `tree_modified_at` feeds [`Folder::etag`] — every SELECT here
/// must include `EXTRACT(EPOCH FROM tree_modified_at)::bigint`.
/// created_at, modified_at, tree_modified_at, created_by, updated_by.
/// The trailing `tree_modified_at` feeds [`Folder::etag`] — every
/// SELECT here must include `EXTRACT(EPOCH FROM tree_modified_at)::bigint`.
/// `drive_id` is the post-D0 `NOT NULL` scope axis for path-based
/// lookups.
/// lookups. `created_by` / `updated_by` are the §14 provenance
/// columns, nullable because the FK is `ON DELETE SET NULL`.
type FolderRow = (
String,
String,
@@ -37,10 +38,12 @@ type FolderRow = (
i64,
i64,
i64,
Option<Uuid>,
Option<Uuid>,
);
/// Type alias for paginated folder rows (includes total_count as
/// the last element after `tree_modified_at`).
/// the last element after the §14 provenance columns).
type FolderRowPaginated = (
String,
String,
@@ -51,10 +54,13 @@ type FolderRowPaginated = (
i64,
i64,
i64,
Option<Uuid>,
Option<Uuid>,
i64,
);
/// Type alias for folder rows with optional user_id.
/// Includes the §14 provenance columns `created_by` / `updated_by`.
type FolderRowOptUser = (
String,
String,
@@ -65,6 +71,8 @@ type FolderRowOptUser = (
i64,
i64,
i64,
Option<Uuid>,
Option<Uuid>,
);
/// PostgreSQL-backed folder repository.
@@ -98,7 +106,9 @@ impl FolderDbRepository {
/// Convert a database row into a `Folder` domain entity.
///
/// The `path` comes directly from the materialized `path` column — no
/// extra queries needed.
/// extra queries needed. `created_by` / `updated_by` carry the
/// §14 provenance signal through the entity layer; both are
/// `Option<Uuid>` because the FK is `ON DELETE SET NULL`.
#[allow(clippy::too_many_arguments)]
fn row_to_folder(
id: String,
@@ -110,9 +120,11 @@ impl FolderDbRepository {
created_at: i64,
modified_at: i64,
tree_modified_at: i64,
created_by: Option<Uuid>,
updated_by: Option<Uuid>,
) -> Result<Folder, DomainError> {
let storage_path = StoragePath::from_string(&path);
Folder::with_timestamps_and_tree(
Folder::with_timestamps_tree_and_provenance(
id,
name,
storage_path,
@@ -122,6 +134,8 @@ impl FolderDbRepository {
created_at as u64,
modified_at as u64,
tree_modified_at as u64,
created_by,
updated_by,
)
.map_err(|e| DomainError::internal_error("FolderDb", format!("entity: {e}")))
}
@@ -139,10 +153,11 @@ impl FolderDbRepository {
let rows = sqlx::query_as::<_, FolderRow>(
r#"
SELECT id::text, name, path, parent_id::text, user_id,
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE id = ANY($1) AND NOT is_trashed
"#,
@@ -153,7 +168,9 @@ impl FolderDbRepository {
.map_err(|e| DomainError::internal_error("FolderDb", format!("get_folders_by_ids: {e}")))?;
rows.into_iter()
.map(|r| Self::row_to_folder(r.0, r.1, r.2, r.3, Some(r.4), r.5, r.6, r.7))
.map(|r| {
Self::row_to_folder(r.0, r.1, r.2, r.3, Some(r.4), r.5, r.6, r.7, r.8, r.9, r.10)
})
.collect()
}
}
@@ -163,6 +180,7 @@ impl FolderRepository for FolderDbRepository {
&self,
name: String,
parent_id: Option<String>,
caller_id: Uuid,
) -> Result<Folder, DomainError> {
// Derive (user_id, drive_id) from parent folder in one round-trip.
// Root-level folders require the caller to have set up the home
@@ -184,28 +202,34 @@ impl FolderRepository for FolderDbRepository {
));
};
// D0 dual-write: drive_id alongside user_id (drops in D7), plus
// provenance columns created_by/updated_by. The repo derives
// created_by from user_id because the parent's owner is the
// creator on personal drives (the only kind that exists in D0).
// D2 plumbs the real caller_id when shared drives let other
// members write into a drive they don't own.
let row = sqlx::query_as::<_, (String, String, i64, i64, i64)>(
// D0 dual-write: drive_id alongside user_id (drops in D7); plus
// §14 provenance — `created_by` / `updated_by` bind to the caller
// ($5), NOT to the parent folder's `user_id`. Pre-D2 they're
// silently equivalent (only the parent's owner can write); the
// distinction matters once shared drives let an Editor mutate
// a folder owned by someone else.
//
// RETURNING also surfaces the two provenance columns so the
// built entity / DTO carries fresh values without a re-read.
let row = sqlx::query_as::<_, (String, String, i64, i64, i64, Option<Uuid>, Option<Uuid>)>(
r#"
INSERT INTO storage.folders
(name, parent_id, user_id, drive_id, created_by, updated_by)
VALUES ($1, $2::uuid, $3, $4, $3, $3)
VALUES ($1, $2::uuid, $3, $4, $5, $5)
RETURNING id::text,
path,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by,
updated_by
"#,
)
.bind(&name)
.bind(&parent_id)
.bind(user_id)
.bind(drive_id)
.bind(caller_id)
.fetch_one(self.pool())
.await
.map_err(|e| {
@@ -230,6 +254,9 @@ impl FolderRepository for FolderDbRepository {
row.2,
row.3,
row.4,
// Fresh from RETURNING — caller_id was bound to both columns.
row.5,
row.6,
)
}
@@ -239,7 +266,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE id = $1::uuid AND NOT is_trashed
"#,
@@ -260,6 +288,8 @@ impl FolderRepository for FolderDbRepository {
row.6,
row.7,
row.8,
row.9,
row.10,
)
}
@@ -288,7 +318,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE path = $1 AND drive_id = $2 AND NOT is_trashed
"#,
@@ -310,6 +341,8 @@ impl FolderRepository for FolderDbRepository {
row.6,
row.7,
row.8,
row.9,
row.10,
)
}
@@ -321,7 +354,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE parent_id = $1::uuid AND NOT is_trashed
ORDER BY name
@@ -336,7 +370,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE parent_id IS NULL AND NOT is_trashed
ORDER BY name
@@ -348,8 +383,8 @@ impl FolderRepository for FolderDbRepository {
.map_err(|e| DomainError::internal_error("FolderDb", format!("list: {e}")))?;
rows.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma)
.map(|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma, cb, ub)
})
.collect()
}
@@ -366,7 +401,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE parent_id = $1::uuid AND user_id = $2 AND NOT is_trashed
ORDER BY name
@@ -382,7 +418,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE parent_id IS NULL AND user_id = $1 AND NOT is_trashed
ORDER BY name
@@ -395,8 +432,8 @@ impl FolderRepository for FolderDbRepository {
.map_err(|e| DomainError::internal_error("FolderDb", format!("list_by_owner: {e}")))?;
rows.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma)
.map(|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma, cb, ub)
})
.collect()
}
@@ -419,6 +456,7 @@ impl FolderRepository for FolderDbRepository {
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by,
COUNT(*) OVER() AS total_count
FROM storage.folders
WHERE parent_id = $1::uuid AND NOT is_trashed
@@ -438,6 +476,7 @@ impl FolderRepository for FolderDbRepository {
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by,
COUNT(*) OVER() AS total_count
FROM storage.folders
WHERE parent_id IS NULL AND NOT is_trashed
@@ -454,16 +493,18 @@ impl FolderRepository for FolderDbRepository {
// total_count is identical in every row; 0 when the result set is empty.
let total = if include_total {
Some(rows.first().map_or(0, |r| r.9) as usize)
Some(rows.first().map_or(0, |r| r.11) as usize)
} else {
None
};
let folders: Result<Vec<Folder>, DomainError> = rows
.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma, _total)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma)
})
.map(
|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub, _total)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma, cb, ub)
},
)
.collect();
Ok((folders?, total))
}
@@ -486,6 +527,7 @@ impl FolderRepository for FolderDbRepository {
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by,
COUNT(*) OVER() AS total_count
FROM storage.folders
WHERE parent_id = $1::uuid AND user_id = $2 AND NOT is_trashed
@@ -506,6 +548,7 @@ impl FolderRepository for FolderDbRepository {
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by,
COUNT(*) OVER() AS total_count
FROM storage.folders
WHERE parent_id IS NULL AND user_id = $1 AND NOT is_trashed
@@ -522,41 +565,55 @@ impl FolderRepository for FolderDbRepository {
.map_err(|e| DomainError::internal_error("FolderDb", format!("paginate_by_owner: {e}")))?;
let total = if include_total {
Some(rows.first().map_or(0, |r| r.9) as usize)
Some(rows.first().map_or(0, |r| r.11) as usize)
} else {
None
};
let folders: Result<Vec<Folder>, DomainError> = rows
.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma, _total)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma)
})
.map(
|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub, _total)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma, cb, ub)
},
)
.collect();
Ok((folders?, total))
}
async fn rename_folder(&self, id: &str, new_name: String) -> Result<Folder, DomainError> {
async fn rename_folder(
&self,
id: &str,
new_name: String,
caller_id: Uuid,
) -> Result<Folder, DomainError> {
// The BEFORE UPDATE trigger recomputes path/lpath for this row;
// the AFTER UPDATE cascade trigger then batch-updates all
// descendants in a single UPDATE using the GiST lpath index.
// That multi-row rewrite can deadlock against the tree-ETag
// flusher's id-ordered ancestor bump — retry instead of failing
// the user's operation (40P01 only; 23505 still maps below).
//
// §14: `updated_by = $3` (caller_id) — the caller mutated this
// row, not the row's owner. In D2 a shared-drive member can
// rename a row they don't own; the previous `updated_by = user_id`
// would have silently recorded the wrong principal.
let row = retry_on_deadlock("folders.rename", || {
sqlx::query_as::<_, FolderRow>(
r#"
UPDATE storage.folders
SET name = $1, updated_at = NOW(), updated_by = user_id
SET name = $1, updated_at = NOW(), updated_by = $3
WHERE id = $2::uuid AND NOT is_trashed
RETURNING id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
"#,
)
.bind(&new_name)
.bind(id)
.bind(caller_id)
.fetch_optional(self.pool())
})
.await
@@ -580,6 +637,8 @@ impl FolderRepository for FolderDbRepository {
row.6,
row.7,
row.8,
row.9,
row.10,
)
}
@@ -587,25 +646,30 @@ impl FolderRepository for FolderDbRepository {
&self,
id: &str,
new_parent_id: Option<&str>,
caller_id: Uuid,
) -> Result<Folder, DomainError> {
// The BEFORE UPDATE trigger recomputes path/lpath for this row;
// the AFTER UPDATE cascade trigger then batch-updates all
// descendants in a single UPDATE using the GiST lpath index.
// Retried on deadlock vs the tree-ETag flusher (see rename_folder).
//
// §14: `updated_by = $3` (caller_id), see rename_folder.
let row = retry_on_deadlock("folders.move", || {
sqlx::query_as::<_, FolderRow>(
r#"
UPDATE storage.folders
SET parent_id = $1::uuid, updated_at = NOW(), updated_by = user_id
SET parent_id = $1::uuid, updated_at = NOW(), updated_by = $3
WHERE id = $2::uuid AND NOT is_trashed
RETURNING id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
"#,
)
.bind(new_parent_id)
.bind(id)
.bind(caller_id)
.fetch_optional(self.pool())
})
.await
@@ -622,6 +686,8 @@ impl FolderRepository for FolderDbRepository {
row.6,
row.7,
row.8,
row.9,
row.10,
)
}
@@ -697,7 +763,7 @@ impl FolderRepository for FolderDbRepository {
// ── Trash operations ──
async fn move_to_trash(&self, folder_id: &str) -> Result<(), DomainError> {
async fn move_to_trash(&self, folder_id: &str, caller_id: Uuid) -> Result<(), DomainError> {
// Soft-delete the whole subtree in one statement: the root flips
// `is_trashed` and records `original_parent_id` so restore knows
// where to put it back; every descendant (folder or file) that
@@ -712,6 +778,10 @@ impl FolderRepository for FolderDbRepository {
// `/g9-tree/file.txt` still resolved 207 even though the parent
// collection was gone) — a class of data-integrity drift that
// confused desktop-sync tree walks.
//
// §14: all three CTE branches stamp `updated_by = $2`
// (caller_id). The cascade is "the caller trashed this
// subtree", not "each owner trashed their own row".
let result = retry_on_deadlock("folders.trash", || {
sqlx::query_scalar::<_, i64>(
r#"
@@ -721,7 +791,7 @@ impl FolderRepository for FolderDbRepository {
trashed_at = NOW(),
original_parent_id = parent_id,
updated_at = NOW(),
updated_by = user_id
updated_by = $2
WHERE id = $1::uuid AND NOT is_trashed
RETURNING id, lpath
),
@@ -730,7 +800,7 @@ impl FolderRepository for FolderDbRepository {
SET is_trashed = TRUE,
trashed_at = NOW(),
updated_at = NOW(),
updated_by = f.user_id
updated_by = $2
FROM trash_root tr
WHERE f.lpath <@ tr.lpath
AND f.id != tr.id
@@ -742,7 +812,7 @@ impl FolderRepository for FolderDbRepository {
SET is_trashed = TRUE,
trashed_at = NOW(),
updated_at = NOW(),
updated_by = fi.user_id
updated_by = $2
FROM trash_root tr
JOIN storage.folders f ON f.lpath <@ tr.lpath
WHERE fi.folder_id = f.id
@@ -753,6 +823,7 @@ impl FolderRepository for FolderDbRepository {
"#,
)
.bind(folder_id)
.bind(caller_id)
.fetch_one(self.pool())
})
.await
@@ -769,6 +840,7 @@ impl FolderRepository for FolderDbRepository {
&self,
folder_id: &str,
_original_path: &str,
caller_id: Uuid,
) -> Result<(), DomainError> {
// Inverse of the cascade in `move_to_trash`: restore the root
// (BEFORE UPDATE trigger recomputes path/lpath via the parent_id
@@ -778,6 +850,10 @@ impl FolderRepository for FolderDbRepository {
// *before* this folder went to trash have `original_*` set, so
// they correctly stay in trash and continue to show up as
// top-level trash entries via `storage.trash_items`.
//
// §14: all three CTE branches stamp `updated_by = $2`
// (caller_id). Restoration is "the caller restored this
// subtree", regardless of who originally owned each row.
let result = retry_on_deadlock("folders.restore", || {
sqlx::query_scalar::<_, i64>(
r#"
@@ -788,7 +864,7 @@ impl FolderRepository for FolderDbRepository {
parent_id = COALESCE(original_parent_id, parent_id),
original_parent_id = NULL,
updated_at = NOW(),
updated_by = user_id
updated_by = $2
WHERE id = $1::uuid AND is_trashed
RETURNING id, lpath
),
@@ -797,7 +873,7 @@ impl FolderRepository for FolderDbRepository {
SET is_trashed = FALSE,
trashed_at = NULL,
updated_at = NOW(),
updated_by = f.user_id
updated_by = $2
FROM restore_root rr
WHERE f.lpath <@ rr.lpath
AND f.id != rr.id
@@ -810,7 +886,7 @@ impl FolderRepository for FolderDbRepository {
SET is_trashed = FALSE,
trashed_at = NULL,
updated_at = NOW(),
updated_by = fi.user_id
updated_by = $2
FROM restore_root rr
JOIN storage.folders f ON f.lpath <@ rr.lpath
WHERE fi.folder_id = f.id
@@ -822,6 +898,7 @@ impl FolderRepository for FolderDbRepository {
"#,
)
.bind(folder_id)
.bind(caller_id)
.fetch_one(self.pool())
})
.await
@@ -911,16 +988,25 @@ impl FolderRepository for FolderDbRepository {
ca,
ma,
tma,
// INSERT stamped both provenance columns from user_id
// (D0 dual-write); D2 will plumb the real caller_id.
Some(user_id),
Some(user_id),
),
None => {
// Already exists — fetch it
let existing = sqlx::query_as::<_, (String, String, i64, i64, i64)>(
// Already exists — fetch it. SELECT also pulls the §14
// provenance columns so the entity layer reflects DB truth.
let existing = sqlx::query_as::<
_,
(String, String, i64, i64, i64, Option<Uuid>, Option<Uuid>),
>(
r#"
SELECT id::text,
path,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE name = $1 AND user_id = $2 AND parent_id IS NULL
"#,
@@ -940,6 +1026,8 @@ impl FolderRepository for FolderDbRepository {
existing.2,
existing.3,
existing.4,
existing.5,
existing.6,
)
}
}
@@ -955,7 +1043,8 @@ impl FolderRepository for FolderDbRepository {
fo.user_id, fo.drive_id, \
EXTRACT(EPOCH FROM fo.created_at)::bigint, \
EXTRACT(EPOCH FROM fo.updated_at)::bigint, \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint, \
fo.created_by, fo.updated_by \
FROM storage.folders fo \
WHERE fo.is_trashed = false \
AND fo.lpath <@ (SELECT lpath FROM storage.folders WHERE id = $1::uuid) \
@@ -970,8 +1059,8 @@ impl FolderRepository for FolderDbRepository {
})?;
rows.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma)
.map(|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)
})
.collect()
}
@@ -1018,7 +1107,8 @@ impl FolderRepository for FolderDbRepository {
fo.user_id, fo.drive_id, \
EXTRACT(EPOCH FROM fo.created_at)::bigint, \
EXTRACT(EPOCH FROM fo.updated_at)::bigint, \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint, \
fo.created_by, fo.updated_by \
FROM storage.folders fo \
WHERE fo.user_id = $1 \
AND fo.is_trashed = false \
@@ -1042,8 +1132,8 @@ impl FolderRepository for FolderDbRepository {
return rows
.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma)
.map(|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)
})
.collect();
}
@@ -1055,7 +1145,8 @@ impl FolderRepository for FolderDbRepository {
fo.user_id, fo.drive_id, \
EXTRACT(EPOCH FROM fo.created_at)::bigint, \
EXTRACT(EPOCH FROM fo.updated_at)::bigint, \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint, \
fo.created_by, fo.updated_by \
FROM storage.folders fo \
WHERE fo.parent_id = $1::uuid \
AND fo.user_id = $2 \
@@ -1074,7 +1165,8 @@ impl FolderRepository for FolderDbRepository {
fo.user_id, fo.drive_id, \
EXTRACT(EPOCH FROM fo.created_at)::bigint, \
EXTRACT(EPOCH FROM fo.updated_at)::bigint, \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint, \
fo.created_by, fo.updated_by \
FROM storage.folders fo \
WHERE fo.parent_id IS NULL \
AND fo.user_id = $1 \
@@ -1114,8 +1206,8 @@ impl FolderRepository for FolderDbRepository {
.map_err(|e| DomainError::internal_error("FolderDb", format!("search_folders: {e}")))?;
rows.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma)
.map(|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)
})
.collect()
}
@@ -1143,7 +1235,8 @@ impl FolderRepository for FolderDbRepository {
fo.user_id, fo.drive_id, \
EXTRACT(EPOCH FROM fo.created_at)::bigint, \
EXTRACT(EPOCH FROM fo.updated_at)::bigint, \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint \
EXTRACT(EPOCH FROM fo.tree_modified_at)::bigint, \
fo.created_by, fo.updated_by \
FROM storage.folders fo \
WHERE fo.user_id = $1 \
AND fo.is_trashed = false \
@@ -1170,8 +1263,8 @@ impl FolderRepository for FolderDbRepository {
.map_err(|e| DomainError::internal_error("FolderDb", format!("descendant search: {e}")))?;
rows.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma)
.map(|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)| {
Self::row_to_folder(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)
})
.collect()
}
@@ -1192,7 +1285,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE parent_id = $1::uuid
AND NOT is_trashed
@@ -1218,7 +1312,8 @@ impl FolderRepository for FolderDbRepository {
SELECT id::text, name, path, parent_id::text, user_id, drive_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
EXTRACT(EPOCH FROM tree_modified_at)::bigint
EXTRACT(EPOCH FROM tree_modified_at)::bigint,
created_by, updated_by
FROM storage.folders
WHERE parent_id IS NULL
AND NOT is_trashed
@@ -1241,8 +1336,8 @@ impl FolderRepository for FolderDbRepository {
.map_err(|e| DomainError::internal_error("FolderDb", format!("suggest: {e}")))?;
rows.into_iter()
.map(|(id, name, path, pid, uid, did, ca, ma, tma)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma)
.map(|(id, name, path, pid, uid, did, ca, ma, tma, cb, ub)| {
Self::row_to_folder(id, name, path, pid, Some(uid), did, ca, ma, tma, cb, ub)
})
.collect()
}