diff --git a/documentation/GUIDE__AE_API_V3_for_Frontend.md b/documentation/GUIDE__AE_API_V3_for_Frontend.md index 8dee8b93..dbaa8fa2 100644 --- a/documentation/GUIDE__AE_API_V3_for_Frontend.md +++ b/documentation/GUIDE__AE_API_V3_for_Frontend.md @@ -370,6 +370,20 @@ These helper endpoints let the frontend request small server-side transformation - Add `?background=true` to schedule the clip asynchronously — returns `202 Accepted` immediately; poll the `hosted_file` record for completion. - Returns 400 on synchronous failure; 202 when scheduled successfully. +- **Get Links** + - Method: `GET` + - Path: `/v3/action/hosted_file/{hosted_file_id}/links` + - Auth: standard V3 headers + - Returns: array of `{ link_to_type, link_to_id, link_to_id_random }` for every record in `hosted_file_link`. Empty array if no links exist (file is an orphan). + - Use this to assess what objects are using a file before deleting it. + +- **Delete** + - Method: `DELETE` + - Path: `/v3/action/hosted_file/{hosted_file_id}` + - Query params: `link_to_type`, `link_to_id` (random string), `method` (`hide` | `disable` | `delete`, default `hide`), `rm_orphan` (bool, default `false`) + - Behavior: removes the specified link record, then if `rm_orphan=true` and no links remain, applies `method` to the file. Use `method=delete` to hard-delete the physical file and DB record. Without `link_to_type`/`link_to_id`, no link is removed; `rm_orphan` only fires if the file already has zero links. + - Use the `/links` endpoint first to get `link_to_id_random` — the delete endpoint resolves `link_to_id` as a random string, not an integer. + Frontend guidance: - Call these routes with the same `link_to_type` / `link_to_id` you plan to associate the resulting hosted_file with — the server resolves random IDs for you. diff --git a/src/routes/core/+layout.svelte b/src/routes/core/+layout.svelte index 019dfc3e..4cc89eda 100644 --- a/src/routes/core/+layout.svelte +++ b/src/routes/core/+layout.svelte @@ -5,6 +5,7 @@ import { ae_loc, ae_sess, ae_api, slct } from '$lib/stores/ae_stores'; import { Building, Database, + File, Globe, History, LayoutDashboard, @@ -92,6 +93,11 @@ onMount(() => { class="btn btn-sm preset-tonal-surface"> Contacts + + Files + diff --git a/src/routes/core/files/+page.svelte b/src/routes/core/files/+page.svelte new file mode 100644 index 00000000..e5c78ecc --- /dev/null +++ b/src/routes/core/files/+page.svelte @@ -0,0 +1,624 @@ + + +
+ + +
+
+
+ +
+
+

Hosted Files

+

File Storage Management

+
+
+
+ + +
+
+ Filters +
+ +
+ + + + + + + +
+ +
+ + + +
+ + +
+
+
+ + + {#if results.length > 0} +
+ +
+ + {results.length} file{results.length !== 1 ? 's' : ''} + {#if results.length === page_limit} + (may be more — increase per-page or paginate) + {/if} + · {fmt_size(total_size)} total + +
+ {#if page_offset > 0} + + {/if} + {#if results.length === page_limit} + + {/if} + +
+
+ +
+ + + + + + + + + + + + + {#each results as file (file.hosted_file_id)} + + + + + + + + + + + {#if links_map.has(file.hosted_file_id)} + {@const file_links = links_map.get(file.hosted_file_id) ?? []} + + + + {/if} + {/each} + +
+ + + +
+
+ + + {(file as any).filename_w_ext ?? file.filename ?? '—'} + + {#if !file.enable} + off + {/if} + {#if file.hide} + hidden + {/if} +
+
{fmt_size(file.size)} +
+ + + +
+
+ {#if file_links.length === 0} + No links found — file may be an orphan. + {:else} +
+ Linked to: + {#each file_links as lnk} + + {lnk.link_to_type} + + / {lnk.link_to_id_random ? lnk.link_to_id_random.slice(0, 8) + '…' : lnk.link_to_id} + + + {/each} +
+ {/if} +
+
+
+ + {:else if searched && !loading} +
+ +

No files found

+

Try different filters.

+
+ + {:else if !searched && !loading} +
+ +

Use the filters above to search for files.

+
+ {/if} + +
diff --git a/src/routes/core/files/+page.ts b/src/routes/core/files/+page.ts new file mode 100644 index 00000000..6dea6ed4 --- /dev/null +++ b/src/routes/core/files/+page.ts @@ -0,0 +1,5 @@ +import type { PageLoad } from './$types'; + +export const load: PageLoad = async () => { + return {}; +};