From 29bcf48eb7078e6b4e64f75ba1da1ed21a575e97 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Fri, 3 Jul 2026 00:31:36 +0200 Subject: [PATCH] feat(drive): move UI to {created,updated}_by --- frontend/src/lib/api/types.ts | 21 +++++++++++++++++++ .../src/lib/components/FileViewer.test.ts | 2 ++ .../src/lib/components/MoveDialog.test.ts | 2 ++ .../src/lib/components/PhotoLightbox.test.ts | 2 ++ frontend/src/lib/utils/media.ts | 2 ++ frontend/src/routes/favorites/+page.svelte | 8 +++++-- frontend/src/routes/favorites/page.test.ts | 2 ++ .../src/routes/files/[...path]/+page.svelte | 8 +++++-- frontend/src/routes/files/page.test.ts | 4 ++++ frontend/src/routes/photos/page.test.ts | 2 ++ frontend/src/routes/recent/+page.svelte | 8 +++++-- frontend/src/routes/recent/page.test.ts | 2 ++ frontend/src/routes/shared/page.test.ts | 2 ++ 13 files changed, 59 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index 83f7b481..4dbc1a39 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -29,7 +29,22 @@ export interface FolderItem { // hierarchy fields (including owner_id) for non-owner callers. // Backend serialises `Option` (folder_dto.rs:53); this // type just tells the truth about the wire. + // + // Deprecated post-D7 (frontend cutover): callers should prefer + // `created_by` / `updated_by` instead — they carry §14 provenance + // and survive the drop of `storage.folders.user_id`. The + // `owner_id` field will be removed from the wire once every + // callsite has moved. owner_id: string | null; + // §14 provenance — who originally created the folder. `null` when + // the creating user has since been deleted (backend FK is + // `ON DELETE SET NULL`). Preferred over `owner_id` on the Files + // browser owner column and the Favorites / Shared surfaces. + created_by: string | null; + // §14 provenance — who last touched the folder (rename / move / + // metadata change). Preferred over `owner_id` on the Recent + // surface, where "who touched this recently" is the intent. + updated_by: string | null; parent_id: string | null; path: string; etag: string; @@ -46,7 +61,13 @@ export interface FileItem { name: string; // `null` on share-recipient responses (same as FolderItem above). // Backend serialises `Option` at file_dto.rs:59. + // + // Deprecated post-D7 (frontend cutover): prefer `created_by` / + // `updated_by` as documented on `FolderItem`. owner_id: string | null; + // §14 provenance — see FolderItem for semantics. + created_by: string | null; + updated_by: string | null; folder_id: string; path: string; size: number; diff --git a/frontend/src/lib/components/FileViewer.test.ts b/frontend/src/lib/components/FileViewer.test.ts index 1fc3974a..61e0c8ef 100644 --- a/frontend/src/lib/components/FileViewer.test.ts +++ b/frontend/src/lib/components/FileViewer.test.ts @@ -29,6 +29,8 @@ function file(over: Record = {}) { category: 'Image', folder_id: '', owner_id: '', + created_by: null, + updated_by: null, path: '', size: 1, modified_at: 0, diff --git a/frontend/src/lib/components/MoveDialog.test.ts b/frontend/src/lib/components/MoveDialog.test.ts index 05fc087a..a0dc053d 100644 --- a/frontend/src/lib/components/MoveDialog.test.ts +++ b/frontend/src/lib/components/MoveDialog.test.ts @@ -58,6 +58,8 @@ function folder(id: string, name: string) { modified_at: 0, name, owner_id: 'me', + created_by: 'me', + updated_by: 'me', parent_id: 'home', path: '/' + name, etag: 'e' diff --git a/frontend/src/lib/components/PhotoLightbox.test.ts b/frontend/src/lib/components/PhotoLightbox.test.ts index 95fd1ef1..4294afe5 100644 --- a/frontend/src/lib/components/PhotoLightbox.test.ts +++ b/frontend/src/lib/components/PhotoLightbox.test.ts @@ -21,6 +21,8 @@ function item(id: string) { category: 'Image', folder_id: '', owner_id: '', + created_by: null, + updated_by: null, path: '', size: 1, modified_at: 0, diff --git a/frontend/src/lib/utils/media.ts b/frontend/src/lib/utils/media.ts index 1b7dd066..622f3d2c 100644 --- a/frontend/src/lib/utils/media.ts +++ b/frontend/src/lib/utils/media.ts @@ -31,6 +31,8 @@ export function minimalPhotoItem(id: string): FileItem { modified_at: 0, name: '', owner_id: '', + created_by: null, + updated_by: null, folder_id: '', path: '', size: 0, diff --git a/frontend/src/routes/favorites/+page.svelte b/frontend/src/routes/favorites/+page.svelte index 0f94e397..5d0df162 100644 --- a/frontend/src/routes/favorites/+page.svelte +++ b/frontend/src/routes/favorites/+page.svelte @@ -40,7 +40,11 @@ const entries = $derived( raw.map((it): ResourceEntry => { const isFile = it.resource_type === 'file'; - const ownerId = it.resource.owner_id ?? null; + // §14 provenance — prefer `created_by` (who put the item + // into the system) over the deprecated `owner_id`. Fall + // back to `owner_id` for pre-D7 rows whose `created_by` + // column wasn't backfilled. + const ownerId = it.resource.created_by ?? it.resource.owner_id ?? null; return { id: it.resource.id, name: it.resource.name, @@ -106,7 +110,7 @@ }); raw = reset ? page.items : [...raw, ...page.items]; cursor = page.next_cursor; - void owners.resolve(page.items.map((i) => i.resource.owner_id)); + void owners.resolve(page.items.map((i) => i.resource.created_by ?? i.resource.owner_id)); } catch (e) { console.error('favorites: load error', e); error = t('errors_loadFailed', 'Failed to load items'); diff --git a/frontend/src/routes/favorites/page.test.ts b/frontend/src/routes/favorites/page.test.ts index 9246be85..4c583339 100644 --- a/frontend/src/routes/favorites/page.test.ts +++ b/frontend/src/routes/favorites/page.test.ts @@ -42,6 +42,8 @@ function withOneFile() { modified_at: 0, name: 'photo.png', owner_id: 'me', + created_by: 'me', + updated_by: 'me', folder_id: 'root', path: '/photo.png', size: 10, diff --git a/frontend/src/routes/files/[...path]/+page.svelte b/frontend/src/routes/files/[...path]/+page.svelte index cb4a2633..33601e10 100644 --- a/frontend/src/routes/files/[...path]/+page.svelte +++ b/frontend/src/routes/files/[...path]/+page.svelte @@ -1797,7 +1797,9 @@
{relativeTimeAgo(folder.modified_at)}
-
{ownerLabel(folder.owner_id, session.user?.id ?? null)}
+
+ {ownerLabel(folder.created_by ?? folder.owner_id, session.user?.id ?? null)} +
{t('files.file_types.folder', 'Folder')}
—
{formatDate(folder.modified_at)}
@@ -1927,7 +1929,9 @@ {relativeTimeAgo(file.modified_at)} {#if file.size != null}{formatBytes(file.size)}{/if} -
{ownerLabel(file.owner_id, session.user?.id ?? null)}
+
+ {ownerLabel(file.created_by ?? file.owner_id, session.user?.id ?? null)} +
{typeLabel(file.category)}
{file.size != null ? formatBytes(file.size) : ''}
{formatDate(file.modified_at)}
diff --git a/frontend/src/routes/files/page.test.ts b/frontend/src/routes/files/page.test.ts index fcf237dd..92abbf67 100644 --- a/frontend/src/routes/files/page.test.ts +++ b/frontend/src/routes/files/page.test.ts @@ -91,6 +91,8 @@ function fileItem(id: string, name: string) { modified_at: 0, name, owner_id: 'me', + created_by: 'me', + updated_by: 'me', folder_id: 'home', path: '/' + name, size: 4, @@ -111,6 +113,8 @@ function folderItem(id: string, name: string) { modified_at: 0, name, owner_id: 'me', + created_by: 'me', + updated_by: 'me', parent_id: 'home', path: '/' + name, etag: 'e' diff --git a/frontend/src/routes/photos/page.test.ts b/frontend/src/routes/photos/page.test.ts index eaa55665..486a48b9 100644 --- a/frontend/src/routes/photos/page.test.ts +++ b/frontend/src/routes/photos/page.test.ts @@ -35,6 +35,8 @@ function photo(id: string) { modified_at: 0, name: id + '.jpg', owner_id: 'me', + created_by: 'me', + updated_by: 'me', folder_id: 'home', path: '/' + id + '.jpg', size: 100, diff --git a/frontend/src/routes/recent/+page.svelte b/frontend/src/routes/recent/+page.svelte index fdf5bfca..9f662368 100644 --- a/frontend/src/routes/recent/+page.svelte +++ b/frontend/src/routes/recent/+page.svelte @@ -42,7 +42,11 @@ const entries = $derived( raw.map((it): ResourceEntry => { const isFile = it.resource_type === 'file'; - const ownerId = it.resource.owner_id ?? null; + // §14 provenance — Recent's mental model is "who touched + // this recently", so `updated_by` (the last mutator) is + // preferred over `created_by` (who put it in). Fall back + // to `owner_id` for pre-D7 rows with no provenance. + const ownerId = it.resource.updated_by ?? it.resource.owner_id ?? null; return { id: it.resource.id, name: it.resource.name, @@ -118,7 +122,7 @@ }); raw = reset ? page.items : [...raw, ...page.items]; cursor = page.next_cursor; - void owners.resolve(page.items.map((i) => i.resource.owner_id)); + void owners.resolve(page.items.map((i) => i.resource.updated_by ?? i.resource.owner_id)); } catch (e) { console.error('recent: load error', e); error = t('errors_loadFailed', 'Failed to load items'); diff --git a/frontend/src/routes/recent/page.test.ts b/frontend/src/routes/recent/page.test.ts index 9c7f393f..1759aee3 100644 --- a/frontend/src/routes/recent/page.test.ts +++ b/frontend/src/routes/recent/page.test.ts @@ -46,6 +46,8 @@ function withOneFile() { modified_at: 0, name: 'notes.txt', owner_id: 'me', + created_by: 'me', + updated_by: 'me', folder_id: 'root', path: '/notes.txt', size: 4, diff --git a/frontend/src/routes/shared/page.test.ts b/frontend/src/routes/shared/page.test.ts index fd6b49c9..6e16edef 100644 --- a/frontend/src/routes/shared/page.test.ts +++ b/frontend/src/routes/shared/page.test.ts @@ -47,6 +47,8 @@ function grantItem() { modified_at: 0, name: 'Docs', owner_id: 'me', + created_by: 'me', + updated_by: 'me', parent_id: null, path: '/Docs', etag: 'e'