diff --git a/web/src/lib/util/runMutation.ts b/web/src/lib/util/runMutation.ts new file mode 100644 index 00000000..82afccac --- /dev/null +++ b/web/src/lib/util/runMutation.ts @@ -0,0 +1,28 @@ +// Wraps the dominant admin-handler mutation pattern: +// try { await fn(); pushToast(success?); } +// catch (e) { pushToast(errMessage(e), 'error'); } +// +// Errors are SWALLOWED — caller does not need a try/catch. Any +// `saving = true / saving = false` state flag can sit around the +// `await runMutation(...)` call without try/finally because the +// helper never rethrows. +// +// Use ONLY for handlers that surface errMessage(e) directly. +// Handlers that branch on specific error codes (last_admin, +// password_too_short) or build custom copy with errCode keep +// their inline catch — wrapping those would obscure intent. + +import { errMessage } from '$lib/api/errors'; +import { pushToast } from '$lib/stores/toast.svelte'; + +export async function runMutation( + fn: () => Promise, + opts: { successMessage?: string } = {} +): Promise { + try { + await fn(); + if (opts.successMessage) pushToast(opts.successMessage); + } catch (e) { + pushToast(errMessage(e), 'error'); + } +} diff --git a/web/src/routes/admin/+page.svelte b/web/src/routes/admin/+page.svelte index 82328ab9..5f570a37 100644 --- a/web/src/routes/admin/+page.svelte +++ b/web/src/routes/admin/+page.svelte @@ -24,8 +24,9 @@ type ScanScheduleMode } from '$lib/api/admin'; import { qk } from '$lib/api/queries'; - import { errCode, errMessage } from '$lib/api/errors'; + import { errCode } from '$lib/api/errors'; import { pushToast } from '$lib/stores/toast.svelte'; + import { runMutation } from '$lib/util/runMutation'; import type { AdminQuarantineRow, LidarrRequest, @@ -119,20 +120,16 @@ } async function onApprove(r: LidarrRequest) { - try { + await runMutation(async () => { await approveRequest(r.id); await invalidateRequests(); - } catch (e) { - pushToast(errMessage(e), 'error'); - } + }); } async function onReject(r: LidarrRequest) { - try { + await runMutation(async () => { await rejectRequest(r.id); await invalidateRequests(); - } catch (e) { - pushToast(errMessage(e), 'error'); - } + }); } async function invalidateQuarantine() { @@ -143,12 +140,10 @@ } async function onResolve(row: AdminQuarantineRow) { - try { + await runMutation(async () => { await resolveQuarantine(row.track_id); await invalidateQuarantine(); - } catch (e) { - pushToast(errMessage(e), 'error'); - } + }); } async function onDeleteFile(row: AdminQuarantineRow) { const key = confirmKey(row.track_id, 'delete-file'); @@ -157,12 +152,10 @@ return; } clearConfirm(); - try { + await runMutation(async () => { await deleteQuarantineFile(row.track_id); await invalidateQuarantine(); - } catch (e) { - pushToast(errMessage(e), 'error'); - } + }); } async function onDeleteLidarr(row: AdminQuarantineRow) { const key = confirmKey(row.track_id, 'delete-lidarr'); @@ -171,12 +164,10 @@ return; } clearConfirm(); - try { + await runMutation(async () => { await deleteQuarantineViaLidarr(row.track_id); await invalidateQuarantine(); - } catch (e) { - pushToast(errMessage(e), 'error'); - } + }); } // ---- Library scan ----