feat(drive): remove all owner_id from {File,Folder}Dto

This commit is contained in:
Edouard Vanbelle
2026-07-03 00:48:14 +02:00
parent 29bcf48eb7
commit 37467ed9d3
33 changed files with 116 additions and 149 deletions
@@ -41,8 +41,7 @@ impl FavoritesRepositoryPort for FavoritesPgRepository {
WHEN uf.item_type = 'folder' THEN fld.path
WHEN uf.item_type = 'file' THEN COALESCE(pfld.path || '/' || f.name, f.name)
ELSE NULL
END AS "item_path",
COALESCE(f.user_id, fld.user_id)::TEXT AS "owner_id"
END AS "item_path"
FROM auth.user_favorites uf
LEFT JOIN storage.files f ON uf.item_type = 'file'
AND f.id = uf.item_id::UUID
@@ -82,7 +81,6 @@ impl FavoritesRepositoryPort for FavoritesPgRepository {
parent_id: row.try_get("parent_id").ok(),
modified_at: row.try_get("modified_at").ok(),
item_path: row.try_get("item_path").ok(),
owner_id: row.try_get("owner_id").ok(),
// Temporary defaults; with_display_fields() computes the real values
icon_class: String::new(),
icon_special_class: String::new(),
@@ -309,10 +307,18 @@ impl FavoritesRepositoryPort for FavoritesPgRepository {
-1::bigint AS size,
fld.created_at AS resource_created_at,
fld.updated_at AS modified_at,
fld.user_id AS owner_id,
fld.drive_id AS drive_id,
NULL::text AS blob_hash,
(fld.user_id = $1::uuid) AS is_owner,
fld.created_by AS created_by,
EXISTS (
SELECT 1 FROM storage.role_grants g
WHERE g.resource_type = 'drive'
AND g.resource_id = fld.drive_id
AND g.role = 'owner'
AND g.subject_type = 'user'
AND g.subject_id = $1::uuid
AND (g.expires_at IS NULL OR g.expires_at > NOW())
) AS is_owner,
uf.created_at AS favorited_at,
fld.path::text AS resource_path,
LOWER(fld.name) AS sort_str,
@@ -333,10 +339,18 @@ impl FavoritesRepositoryPort for FavoritesPgRepository {
f.size::bigint,
f.created_at AS resource_created_at,
f.updated_at AS modified_at,
f.user_id AS owner_id,
f.drive_id AS drive_id,
f.blob_hash,
(f.user_id = $1::uuid) AS is_owner,
f.created_by AS created_by,
EXISTS (
SELECT 1 FROM storage.role_grants g
WHERE g.resource_type = 'drive'
AND g.resource_id = f.drive_id
AND g.role = 'owner'
AND g.subject_type = 'user'
AND g.subject_id = $1::uuid
AND (g.expires_at IS NULL OR g.expires_at > NOW())
) AS is_owner,
uf.created_at AS favorited_at,
COALESCE(pfld.path::text || '/' || f.name, f.name) AS resource_path,
LOWER(f.name) AS sort_str,
@@ -489,7 +503,8 @@ impl FavoritesRepositoryPort for FavoritesPgRepository {
};
let user_join = if need_user_join {
"LEFT JOIN auth.users u ON u.id = r.owner_id"
// Post-D7: `owner_id` retired; join by `created_by`.
"LEFT JOIN auth.users u ON u.id = r.created_by"
} else {
""
};
@@ -506,7 +521,7 @@ impl FavoritesRepositoryPort for FavoritesPgRepository {
SELECT
r.resource_type, r.resource_id, r.name, r.parent_id,
r.mime_type, r.size, r.resource_created_at, r.modified_at,
r.owner_id, r.drive_id, r.is_owner, r.favorited_at, r.resource_path,
r.drive_id, r.is_owner, r.favorited_at, r.resource_path,
r.sort_str, r.type_order, r.folder_first{username_col}
FROM resources r
{user_join}
@@ -577,7 +592,6 @@ LIMIT $6"
size,
resource_created_at: row.get("resource_created_at"),
modified_at: row.get("modified_at"),
owner_id: row.try_get("owner_id").ok(),
drive_id: row.get("drive_id"),
blob_hash: row.try_get("blob_hash").ok(),
is_owner: row.try_get("is_owner").unwrap_or(false),
@@ -1390,7 +1390,6 @@ impl FolderDbRepository {
-1::bigint AS size,
f.created_at,
f.updated_at AS modified_at,
f.user_id,
f.drive_id,
NULL::text AS blob_hash,
LOWER(f.name) AS sort_str,
@@ -1410,7 +1409,6 @@ impl FolderDbRepository {
fm.size::bigint,
fm.created_at,
fm.updated_at AS modified_at,
fm.user_id,
fm.drive_id,
fm.blob_hash,
LOWER(fm.name) AS sort_str,
@@ -1536,7 +1534,7 @@ impl FolderDbRepository {
let sql = format!(
"WITH resources AS ({cte_inner}) \
SELECT resource_type, id, name, folder_id, mime_type, size, \
created_at, modified_at, user_id, drive_id, blob_hash, \
created_at, modified_at, drive_id, blob_hash, \
sort_str, type_order, folder_first \
FROM resources \
{where_clause} \
@@ -1545,7 +1543,7 @@ impl FolderDbRepository {
);
// Row: (resource_type, id, name, folder_id, mime_type, size,
// created_at, modified_at, user_id, drive_id, blob_hash,
// created_at, modified_at, drive_id, blob_hash,
// sort_str, type_order, folder_first)
type Row = (
String,
@@ -1556,8 +1554,7 @@ impl FolderDbRepository {
i64,
chrono::DateTime<chrono::Utc>,
chrono::DateTime<chrono::Utc>,
Option<Uuid>, // user_id (post-D7: nullable on new rows)
Uuid,
Uuid, // drive_id
Option<String>,
String,
i64,
@@ -1588,12 +1585,11 @@ impl FolderDbRepository {
size: r.5,
created_at: r.6,
modified_at: r.7,
owner_id: r.8,
drive_id: r.9,
blob_hash: r.10,
sort_str: r.11,
type_order: r.12,
folder_first: r.13,
drive_id: r.8,
blob_hash: r.9,
sort_str: r.10,
type_order: r.11,
folder_first: r.12,
})
.collect())
}
@@ -206,6 +206,20 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository {
// ── Build the UNION ALL CTE ─────────────────────────────────────────
let mut cte_branches: Vec<&str> = Vec::new();
// Post-D7: `is_owner` means "the caller holds an Owner
// role_grant on the drive owning this row". Personal drives:
// the single-owner invariant makes this trivially true for the
// owner and false for anyone else. Shared drives: multiple
// Owners possible; each of them gets `true`. Used only to gate
// whether the handler exposes the full path (path-hierarchy
// hiding for share recipients — see `recent_handler.rs`).
//
// The `created_by` projection is separate — §14 provenance,
// used for the "Owner" column and the owner sort's username
// JOIN. The two signals genuinely differ post-D2: e.g. Bob
// (Editor on Alice's shared drive) making a file has
// `created_by = Bob` but `is_owner = false` because Alice owns
// the drive.
let folder_branch = r#"
SELECT
'folder'::text AS resource_type,
@@ -216,10 +230,18 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository {
-1::bigint AS size,
fld.created_at AS resource_created_at,
fld.updated_at AS modified_at,
fld.user_id AS owner_id,
fld.drive_id AS drive_id,
NULL::text AS blob_hash,
(fld.user_id = $1::uuid) AS is_owner,
fld.created_by AS created_by,
EXISTS (
SELECT 1 FROM storage.role_grants g
WHERE g.resource_type = 'drive'
AND g.resource_id = fld.drive_id
AND g.role = 'owner'
AND g.subject_type = 'user'
AND g.subject_id = $1::uuid
AND (g.expires_at IS NULL OR g.expires_at > NOW())
) AS is_owner,
ur.accessed_at AS accessed_at,
fld.path::text AS resource_path,
LOWER(fld.name) AS sort_str,
@@ -240,10 +262,18 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository {
f.size::bigint,
f.created_at AS resource_created_at,
f.updated_at AS modified_at,
f.user_id AS owner_id,
f.drive_id AS drive_id,
f.blob_hash,
(f.user_id = $1::uuid) AS is_owner,
f.created_by AS created_by,
EXISTS (
SELECT 1 FROM storage.role_grants g
WHERE g.resource_type = 'drive'
AND g.resource_id = f.drive_id
AND g.role = 'owner'
AND g.subject_type = 'user'
AND g.subject_id = $1::uuid
AND (g.expires_at IS NULL OR g.expires_at > NOW())
) AS is_owner,
ur.accessed_at AS accessed_at,
COALESCE(pfld.path::text || '/' || f.name, f.name) AS resource_path,
LOWER(f.name) AS sort_str,
@@ -394,7 +424,9 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository {
};
let user_join = if need_user_join {
"LEFT JOIN auth.users u ON u.id = r.owner_id"
// Post-D7: `owner_id` column retired; use `created_by`
// (§14 provenance) as the "owner" identity for the sort.
"LEFT JOIN auth.users u ON u.id = r.created_by"
} else {
""
};
@@ -411,7 +443,7 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository {
SELECT
r.resource_type, r.resource_id, r.name, r.parent_id,
r.mime_type, r.size, r.resource_created_at, r.modified_at,
r.owner_id, r.drive_id, r.is_owner, r.accessed_at, r.resource_path,
r.drive_id, r.is_owner, r.accessed_at, r.resource_path,
r.sort_str, r.type_order, r.folder_first{username_col}
FROM resources r
{user_join}
@@ -486,7 +518,6 @@ LIMIT $6"
size,
resource_created_at: row.get("resource_created_at"),
modified_at: row.get("modified_at"),
owner_id: row.try_get("owner_id").ok(),
drive_id: row.get("drive_id"),
blob_hash: row.try_get("blob_hash").ok(),
is_owner: row.try_get("is_owner").unwrap_or(false),
@@ -374,7 +374,6 @@ impl TrashDbRepository {
-1::bigint AS size,
fld.created_at AS resource_created_at,
fld.updated_at AS modified_at,
fld.user_id AS owner_id,
fld.drive_id AS drive_id,
NULL::text AS blob_hash,
fld.trashed_at AS trashed_at,
@@ -401,7 +400,6 @@ impl TrashDbRepository {
f.size::bigint AS size,
f.created_at AS resource_created_at,
f.updated_at AS modified_at,
f.user_id AS owner_id,
f.drive_id AS drive_id,
f.blob_hash,
f.trashed_at AS trashed_at,
@@ -533,7 +531,7 @@ impl TrashDbRepository {
SELECT
r.resource_type, r.resource_id, r.name, r.parent_id,
r.mime_type, r.size, r.resource_created_at, r.modified_at,
r.owner_id, r.drive_id, r.trashed_at, r.deletion_date, r.resource_path,
r.drive_id, r.trashed_at, r.deletion_date, r.resource_path,
r.sort_str, r.type_order, r.folder_first
FROM resources r
{keyset}
@@ -590,7 +588,6 @@ LIMIT $6"
size,
resource_created_at: row.get("resource_created_at"),
modified_at: row.get("modified_at"),
owner_id: row.try_get("owner_id").ok(),
drive_id: row.get("drive_id"),
blob_hash: row.try_get("blob_hash").ok(),
trashed_at,
@@ -160,7 +160,7 @@ impl PathResolverService {
name,
res_path,
parent_id,
uid,
_uid, // Post-D7: `user_id` column no longer flowed into the DTO.
drive_id,
created_at,
modified_at,
@@ -180,7 +180,6 @@ impl PathResolverService {
name: name.clone(),
path: res_path,
parent_id,
owner_id: uid,
drive_id,
created_at: created_at as u64,
modified_at: modified_at as u64,
@@ -215,7 +214,6 @@ impl PathResolverService {
icon_special_class: Arc::from(icon_special_class_for(&name, &mime)),
category: Arc::from(category_for(&name, &mime)),
size_formatted: format_file_size(sz),
owner_id: uid,
sort_date: None,
content_hash: hash,
etag,