From b73f1760246d933145a9791e3780c038acd368a5 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Wed, 24 Jun 2026 21:31:59 +0200 Subject: [PATCH] feat(drive): permanent deletion per drive --- frontend/src/lib/api/endpoints/trash.ts | 16 + .../src/lib/components/ResourceList.svelte | 28 +- frontend/src/routes/trash/+page.svelte | 72 ++++ src/application/ports/trash_ports.rs | 10 + src/application/services/trash_service.rs | 85 +++-- .../services/trash_service_test.rs | 8 + src/interfaces/api/handlers/trash_handler.rs | 58 +++ src/interfaces/api/routes.rs | 7 + tests/api/run.sh | 3 +- tests/api/trash_per_drive.hurl | 332 ++++++++++++++++++ 10 files changed, 596 insertions(+), 23 deletions(-) create mode 100644 tests/api/trash_per_drive.hurl diff --git a/frontend/src/lib/api/endpoints/trash.ts b/frontend/src/lib/api/endpoints/trash.ts index 8a49d5a1..8f20da22 100644 --- a/frontend/src/lib/api/endpoints/trash.ts +++ b/frontend/src/lib/api/endpoints/trash.ts @@ -111,3 +111,19 @@ export async function emptyTrash(): Promise { }); if (!res.ok) throw new Error(`empty trash failed: ${res.status}`); } + +/** + * `DELETE /api/trash/drive/{drive_id}` — empty the trash within a + * single drive. Used by the trash page's Drive group-by, where each + * bucket header carries a per-drive Empty button so multi-drive + * owners don't have to wipe everything at once. Refused 404 when the + * caller lacks Delete on the named drive (anti-enum). + */ +export async function emptyTrashForDrive(driveId: string): Promise { + const res = await apiFetch(`/api/trash/drive/${encodeURIComponent(driveId)}`, { + method: 'DELETE', + credentials: 'same-origin', + headers: getCsrfHeaders() + }); + if (!res.ok) throw new Error(`empty drive trash failed: ${res.status}`); +} diff --git a/frontend/src/lib/components/ResourceList.svelte b/frontend/src/lib/components/ResourceList.svelte index b734fc60..c0ca6ca9 100644 --- a/frontend/src/lib/components/ResourceList.svelte +++ b/frontend/src/lib/components/ResourceList.svelte @@ -87,6 +87,14 @@ dateLabel?: string; /** Custom renderer for the date cell (e.g. trash expiry chip). */ dateCell?: Snippet<[ResourceEntry]>; + /** + * Optional per-bucket action button rendered alongside the swimlane + * header label. Receives the bucket key (the value `bucketOf` + * returned for the active group-by). Used by the trash page to expose + * a per-drive "Empty" affordance — the page decides which group-bys + * the action is meaningful for and returns nothing otherwise. + */ + bucketAction?: Snippet<[string]>; /** Show the owner column + vignette (list view) and hover tooltip. */ showOwner?: boolean; /** Allow grid/list toggle (shares the app-wide view mode). */ @@ -131,6 +139,7 @@ showDate = true, dateLabel, dateCell, + bucketAction, showOwner = false, showViewToggle = true, selectable = false, @@ -434,7 +443,14 @@
{@render listHeader()} {#each sections as section (section.key)} -
{section.label}
+
+ {section.label} + {#if bucketAction} + + {@render bucketAction(section.key)} + + {/if} +
{#if filesStore.viewMode === 'list'}