diff --git a/src/lib/ae_events/ae_events__event_file.ts b/src/lib/ae_events/ae_events__event_file.ts index d2b9e1be..9f684118 100644 --- a/src/lib/ae_events/ae_events__event_file.ts +++ b/src/lib/ae_events/ae_events__event_file.ts @@ -365,70 +365,22 @@ export async function create_event_file_obj_from_hosted_file_async({ export async function delete_ae_obj_id__event_file({ api_cfg, event_file_id, - hosted_file_id, params = {}, try_cache = true, log_lvl = 0 }: { api_cfg: any; event_file_id: string; - // Providing hosted_file_id enables full cleanup of the physical file and - // hosted_file record. The /links fetch is done first because it calls - // get_id_random() on each linked object, which populates Redis. Without - // this pre-fetch, redis_lookup_id_random('event_file', id) raises 404 - // in the delete handler — the cleanup is silently skipped. - hosted_file_id?: string; params?: key_val; try_cache?: boolean; log_lvl?: number; }) { - if (hosted_file_id) { - // Step 1: Fetch links — populates Redis for all linked object IDs. - let file_links: { link_to_type: string; link_to_id_random: string | null }[] = []; - try { - const links_result = await api.get_object({ - api_cfg, - endpoint: `/v3/action/hosted_file/${hosted_file_id}/links`, - log_lvl: 0 - }); - file_links = links_result?.data ?? links_result ?? []; - } catch (e) { - if (log_lvl) console.warn('[delete_event_file] /links fetch failed:', e); - } - - // Step 2: Remove each link. rm_orphan=true on the last one triggers - // physical file + hosted_file record cleanup if nothing else uses it. - if (file_links.length > 0) { - for (let i = 0; i < file_links.length; i++) { - const lnk = file_links[i]; - const is_last = i === file_links.length - 1; - await api.delete_hosted_file({ - api_cfg, - hosted_file_id, - link_to_type: lnk.link_to_type, - link_to_id: lnk.link_to_id_random ?? undefined, - rm_orphan: is_last, - params: { method: 'delete' }, - log_lvl - }); - } - } else { - // No links recorded — attempt direct orphan cleanup. - await api.delete_hosted_file({ - api_cfg, - hosted_file_id, - rm_orphan: true, - params: { method: 'delete' }, - log_lvl - }); - } - } - - // Step 3: Delete the event_file record after the hosted_file cleanup. - const result = await api.delete_ae_obj({ + // Single atomic call: removes hosted_file_link, cleans up physical file + + // hosted_file record if no other links remain (rm_orphan=true is the default), + // then deletes the event_file row. Replaces the old multi-step workaround. + const result = await api.delete_object({ api_cfg, - obj_type: 'event_file', - obj_id: event_file_id, + endpoint: `/v3/action/event_file/${event_file_id}`, params, log_lvl }); diff --git a/src/lib/elements/element_manage_event_file_li.svelte b/src/lib/elements/element_manage_event_file_li.svelte index e6f673a5..f4c2b8ff 100644 --- a/src/lib/elements/element_manage_event_file_li.svelte +++ b/src/lib/elements/element_manage_event_file_li.svelte @@ -570,8 +570,6 @@ async function handle_convert_pdf_to_image(event_file_obj: key_val) { api_cfg: $ae_api, event_file_id: event_file_obj.event_file_id, - hosted_file_id: - event_file_obj.hosted_file_id, log_lvl: 1 } ); diff --git a/src/routes/core/files/+page.svelte b/src/routes/core/files/+page.svelte index 101bfcb7..74ebb425 100644 --- a/src/routes/core/files/+page.svelte +++ b/src/routes/core/files/+page.svelte @@ -359,30 +359,29 @@ let displayed_results = $derived( async function check_all_for_orphans() { orphan_checking = true; orphan_filter = false; - // Fetch links for all visible results in parallel; skip already-cached entries. - await Promise.all( - results.map(async (file) => { - const id = file.hosted_file_id; - if (links_map.has(id)) return; - links_loading.set(id, true); - try { - const result = await api.get_object({ - api_cfg: $ae_api, - endpoint: `/v3/action/hosted_file/${id}/links`, - log_lvl: 0 - }); - links_map.set(id, result?.data ?? result ?? []); - // Also resolve nav URLs for any links found - await Promise.all((result?.data ?? result ?? []).map((lnk: any) => resolve_link_url(lnk.link_to_type, lnk.link_to_id_random))); - } catch { - links_map.set(id, []); - } finally { - links_loading.delete(id); + try { + // Single backend query instead of N+1 per-file link fetches. + const result = await api.get_object({ + api_cfg: $ae_api, + endpoint: `/v3/action/hosted_file/orphan_scan?limit=500`, + log_lvl: 0 + }); + const db_orphans: { hosted_file_id: string }[] = result?.db_orphans ?? result?.data?.db_orphans ?? []; + const orphan_id_set = new Set(db_orphans.map((o) => o.hosted_file_id)); + // Only mark confirmed orphans — empty array means no links found. + // Non-orphans are left out of links_map entirely so the link toggle + // still works normally and orphan_ids only captures confirmed orphans. + for (const file of results) { + if (orphan_id_set.has(file.hosted_file_id)) { + links_map.set(file.hosted_file_id, []); } - }) - ); - orphan_filter = true; - orphan_checking = false; + } + } catch (e) { + console.error('[orphan_scan]', e); + } finally { + orphan_filter = true; + orphan_checking = false; + } }