refactor(web/api): errCode + errMessage helpers; 41 sites migrated (W1)
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { errCode, errMessage } from './errors';
|
||||||
|
import { ERROR_COPY } from './error-copy';
|
||||||
|
|
||||||
|
describe('errCode', () => {
|
||||||
|
test('returns code from object', () => {
|
||||||
|
expect(errCode({ code: 'foo' })).toBe('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns 'unknown' for null", () => {
|
||||||
|
expect(errCode(null)).toBe('unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns 'unknown' for undefined", () => {
|
||||||
|
expect(errCode(undefined)).toBe('unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns 'unknown' for non-object values", () => {
|
||||||
|
expect(errCode('boom')).toBe('unknown');
|
||||||
|
expect(errCode(42)).toBe('unknown');
|
||||||
|
expect(errCode(true)).toBe('unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles missing code field', () => {
|
||||||
|
expect(errCode({})).toBe('unknown');
|
||||||
|
expect(errCode({ message: 'hi' })).toBe('unknown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('errMessage', () => {
|
||||||
|
test('returns mapped copy when code is known', () => {
|
||||||
|
const expected = ERROR_COPY.track_not_found;
|
||||||
|
expect(errMessage({ code: 'track_not_found' })).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns the unknown-fallback copy when code is missing', () => {
|
||||||
|
const result = errMessage({ code: 'definitely_not_a_real_code_xyz' });
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
expect(typeof result).toBe('string');
|
||||||
|
expect(result.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('accepts custom fallback parameter without throwing', () => {
|
||||||
|
// copyForCode itself returns a non-empty string for any input, so the
|
||||||
|
// explicit fallback only matters in narrow cases. Just exercise the
|
||||||
|
// signature.
|
||||||
|
const result = errMessage(null, 'custom fallback');
|
||||||
|
expect(typeof result).toBe('string');
|
||||||
|
expect(result.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { copyForCode } from './error-copy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the `code` field from an unknown error value, or 'unknown'
|
||||||
|
* when the value isn't shaped like { code: string }.
|
||||||
|
*/
|
||||||
|
export function errCode(err: unknown): string {
|
||||||
|
return (err as { code?: string })?.code ?? 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns user-facing copy for an unknown error value. Looks up the
|
||||||
|
* error's code in the error-copy map; falls back to the supplied
|
||||||
|
* fallback (default: "Something went wrong.") when the code is unknown.
|
||||||
|
*/
|
||||||
|
export function errMessage(err: unknown, fallback = 'Something went wrong.'): string {
|
||||||
|
return copyForCode(errCode(err)) ?? fallback;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useQueryClient } from '@tanstack/svelte-query';
|
import { useQueryClient } from '@tanstack/svelte-query';
|
||||||
import { createPlaylistsQuery, appendTracks, createPlaylist } from '$lib/api/playlists';
|
import { createPlaylistsQuery, appendTracks, createPlaylist } from '$lib/api/playlists';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
import { copyForCode } from '$lib/api/error-copy';
|
import { errMessage } from '$lib/api/errors';
|
||||||
import TrackMenuItem from './TrackMenuItem.svelte';
|
import TrackMenuItem from './TrackMenuItem.svelte';
|
||||||
import TrackMenuDivider from './TrackMenuDivider.svelte';
|
import TrackMenuDivider from './TrackMenuDivider.svelte';
|
||||||
import type { TrackRef } from '$lib/api/types';
|
import type { TrackRef } from '$lib/api/types';
|
||||||
@@ -37,8 +37,7 @@
|
|||||||
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
||||||
onClose();
|
onClose();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code ?? 'unknown';
|
error = errMessage(e);
|
||||||
error = copyForCode(code);
|
|
||||||
} finally {
|
} finally {
|
||||||
busy = false;
|
busy = false;
|
||||||
}
|
}
|
||||||
@@ -57,8 +56,7 @@
|
|||||||
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
||||||
onClose();
|
onClose();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code ?? 'unknown';
|
error = errMessage(e);
|
||||||
error = copyForCode(code);
|
|
||||||
} finally {
|
} finally {
|
||||||
busy = false;
|
busy = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { Flag } from 'lucide-svelte';
|
import { Flag } from 'lucide-svelte';
|
||||||
import { useQueryClient } from '@tanstack/svelte-query';
|
import { useQueryClient } from '@tanstack/svelte-query';
|
||||||
import { flagTrack } from '$lib/api/quarantine';
|
import { flagTrack } from '$lib/api/quarantine';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
import type { TrackRef, LidarrQuarantineReason } from '$lib/api/types';
|
import type { TrackRef, LidarrQuarantineReason } from '$lib/api/types';
|
||||||
|
|
||||||
@@ -34,7 +35,8 @@
|
|||||||
await client.invalidateQueries({ queryKey: qk.myQuarantine() });
|
await client.invalidateQueries({ queryKey: qk.myQuarantine() });
|
||||||
onClose();
|
onClose();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as { code?: string }).code ?? 'flag_failed';
|
const code = errCode(e);
|
||||||
|
error = code === 'unknown' ? 'flag_failed' : code;
|
||||||
} finally {
|
} finally {
|
||||||
submitting = false;
|
submitting = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import type { Playlist, PlaylistTrack, TrackRef } from '$lib/api/types';
|
import type { Playlist, PlaylistTrack, TrackRef } from '$lib/api/types';
|
||||||
import { user } from '$lib/auth/store.svelte';
|
import { user } from '$lib/auth/store.svelte';
|
||||||
import { getPlaylist, refreshDiscover, refreshForYou } from '$lib/api/playlists';
|
import { getPlaylist, refreshDiscover, refreshForYou } from '$lib/api/playlists';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
import { playQueue } from '$lib/player/store.svelte';
|
import { playQueue } from '$lib/player/store.svelte';
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
await queryClient.invalidateQueries({ queryKey: qk.playlist(playlist.id) });
|
await queryClient.invalidateQueries({ queryKey: qk.playlist(playlist.id) });
|
||||||
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
showToast(`Refresh failed: ${(err as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Refresh failed: ${errCode(err)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { Trash2 } from 'lucide-svelte';
|
import { Trash2 } from 'lucide-svelte';
|
||||||
import { useQueryClient } from '@tanstack/svelte-query';
|
import { useQueryClient } from '@tanstack/svelte-query';
|
||||||
import { removeTrack } from '$lib/api/admin/tracks';
|
import { removeTrack } from '$lib/api/admin/tracks';
|
||||||
import { copyForCode } from '$lib/api/error-copy';
|
import { errMessage } from '$lib/api/errors';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
import type { TrackRef } from '$lib/api/types';
|
import type { TrackRef } from '$lib/api/types';
|
||||||
|
|
||||||
@@ -45,8 +45,7 @@
|
|||||||
// surface (if any) can pick up the flag.
|
// surface (if any) can pick up the flag.
|
||||||
onClose();
|
onClose();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code ?? 'unknown';
|
error = errMessage(e);
|
||||||
error = copyForCode(code);
|
|
||||||
} finally {
|
} finally {
|
||||||
busy = false;
|
busy = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
type ScanScheduleMode
|
type ScanScheduleMode
|
||||||
} from '$lib/api/admin';
|
} from '$lib/api/admin';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
import { copyForCode } from '$lib/api/error-copy';
|
import { errCode, errMessage } from '$lib/api/errors';
|
||||||
import type {
|
import type {
|
||||||
AdminQuarantineRow,
|
AdminQuarantineRow,
|
||||||
LidarrRequest,
|
LidarrRequest,
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
await approveRequest(r.id);
|
await approveRequest(r.id);
|
||||||
await invalidateRequests();
|
await invalidateRequests();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(copyForCode((e as { code?: string }).code));
|
showToast(errMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onReject(r: LidarrRequest) {
|
async function onReject(r: LidarrRequest) {
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
await rejectRequest(r.id);
|
await rejectRequest(r.id);
|
||||||
await invalidateRequests();
|
await invalidateRequests();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(copyForCode((e as { code?: string }).code));
|
showToast(errMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
await resolveQuarantine(row.track_id);
|
await resolveQuarantine(row.track_id);
|
||||||
await invalidateQuarantine();
|
await invalidateQuarantine();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(copyForCode((e as { code?: string }).code));
|
showToast(errMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onDeleteFile(row: AdminQuarantineRow) {
|
async function onDeleteFile(row: AdminQuarantineRow) {
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
await deleteQuarantineFile(row.track_id);
|
await deleteQuarantineFile(row.track_id);
|
||||||
await invalidateQuarantine();
|
await invalidateQuarantine();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(copyForCode((e as { code?: string }).code));
|
showToast(errMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onDeleteLidarr(row: AdminQuarantineRow) {
|
async function onDeleteLidarr(row: AdminQuarantineRow) {
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
await deleteQuarantineViaLidarr(row.track_id);
|
await deleteQuarantineViaLidarr(row.track_id);
|
||||||
await invalidateQuarantine();
|
await invalidateQuarantine();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(copyForCode((e as { code?: string }).code));
|
showToast(errMessage(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
await client.invalidateQueries({ queryKey: qk.scanStatus() });
|
await client.invalidateQueries({ queryKey: qk.scanStatus() });
|
||||||
await client.invalidateQueries({ queryKey: qk.coverage() });
|
await client.invalidateQueries({ queryKey: qk.coverage() });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const code = (e as { code?: string; status?: number })?.code ?? 'unknown';
|
const code = errCode(e);
|
||||||
const status = (e as { status?: number })?.status;
|
const status = (e as { status?: number })?.status;
|
||||||
if (status === 409) {
|
if (status === 409) {
|
||||||
triggerResult = 'A scan is already running.';
|
triggerResult = 'A scan is already running.';
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
await updateScanSchedule(patch);
|
await updateScanSchedule(patch);
|
||||||
await client.invalidateQueries({ queryKey: qk.scanSchedule() });
|
await client.invalidateQueries({ queryKey: qk.scanSchedule() });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(`Schedule save failed: ${(e as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Schedule save failed: ${errCode(e)}`);
|
||||||
} finally {
|
} finally {
|
||||||
scheduleSaving = false;
|
scheduleSaving = false;
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@
|
|||||||
await researchMissingArt();
|
await researchMissingArt();
|
||||||
showToast('All previously-failed art will be re-attempted on the next scan.');
|
showToast('All previously-failed art will be re-attempted on the next scan.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(`Re-search failed: ${(e as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Re-search failed: ${errCode(e)}`);
|
||||||
} finally {
|
} finally {
|
||||||
researchSaving = false;
|
researchSaving = false;
|
||||||
}
|
}
|
||||||
@@ -340,7 +340,7 @@
|
|||||||
bulkResult = `Queued ${queued} albums for cover refetch.`;
|
bulkResult = `Queued ${queued} albums for cover refetch.`;
|
||||||
await client.invalidateQueries({ queryKey: qk.coverage() });
|
await client.invalidateQueries({ queryKey: qk.coverage() });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
bulkResult = `Failed: ${(e as { code?: string })?.code ?? 'unknown'}`;
|
bulkResult = `Failed: ${errCode(e)}`;
|
||||||
} finally {
|
} finally {
|
||||||
bulkBusy = false;
|
bulkBusy = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
type SMTPConfig
|
type SMTPConfig
|
||||||
} from '$lib/api/admin';
|
} from '$lib/api/admin';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
import type { LidarrConfig, LidarrTestResult } from '$lib/api/types';
|
import type { LidarrConfig, LidarrTestResult } from '$lib/api/types';
|
||||||
|
|
||||||
// Lidarr connection panel. The "saved api key" is masked as "***" on GET —
|
// Lidarr connection panel. The "saved api key" is masked as "***" on GET —
|
||||||
@@ -112,7 +113,8 @@
|
|||||||
]);
|
]);
|
||||||
apiKeyInput = '';
|
apiKeyInput = '';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
saveError = (e as { code?: string }).code ?? 'save_failed';
|
const code = errCode(e);
|
||||||
|
saveError = code === 'unknown' ? 'save_failed' : code;
|
||||||
} finally {
|
} finally {
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
}
|
}
|
||||||
@@ -160,7 +162,8 @@
|
|||||||
modalOpen = false;
|
modalOpen = false;
|
||||||
disconnectInput = '';
|
disconnectInput = '';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
disconnectError = (e as { code?: string }).code ?? 'disconnect_failed';
|
const code = errCode(e);
|
||||||
|
disconnectError = code === 'unknown' ? 'disconnect_failed' : code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,9 +273,9 @@
|
|||||||
smtpPasswordInput = '';
|
smtpPasswordInput = '';
|
||||||
showToast('SMTP config saved.');
|
showToast('SMTP config saved.');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
if (code === 'missing_fields') showToast('Host and from address are required when enabled.');
|
if (code === 'missing_fields') showToast('Host and from address are required when enabled.');
|
||||||
else showToast(`Save failed: ${code ?? 'unknown'}`);
|
else showToast(`Save failed: ${code}`);
|
||||||
} finally {
|
} finally {
|
||||||
smtpSaving = false;
|
smtpSaving = false;
|
||||||
}
|
}
|
||||||
@@ -284,12 +287,12 @@
|
|||||||
await testSMTPConfig();
|
await testSMTPConfig();
|
||||||
showToast('Test email sent. Check your inbox.');
|
showToast('Test email sent. Check your inbox.');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
const message = (e as { message?: string })?.message;
|
const message = (e as { message?: string })?.message;
|
||||||
if (code === 'no_email_on_file') showToast('Set your email in /settings before testing.');
|
if (code === 'no_email_on_file') showToast('Set your email in /settings before testing.');
|
||||||
else if (code === 'not_configured') showToast('Save the SMTP config first.');
|
else if (code === 'not_configured') showToast('Save the SMTP config first.');
|
||||||
else if (code === 'send_failed') showToast(`Send failed: ${message || 'see server logs'}`);
|
else if (code === 'send_failed') showToast(`Send failed: ${message || 'see server logs'}`);
|
||||||
else showToast(`Test failed: ${code ?? 'unknown'}`);
|
else showToast(`Test failed: ${code}`);
|
||||||
} finally {
|
} finally {
|
||||||
smtpTesting = false;
|
smtpTesting = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
deleteQuarantineViaLidarr
|
deleteQuarantineViaLidarr
|
||||||
} from '$lib/api/admin';
|
} from '$lib/api/admin';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
import { copyForCode } from '$lib/api/error-copy';
|
import { errMessage } from '$lib/api/errors';
|
||||||
import { playRadio } from '$lib/player/store.svelte';
|
import { playRadio } from '$lib/player/store.svelte';
|
||||||
import type { AdminQuarantineRow, LidarrQuarantineReason } from '$lib/api/types';
|
import type { AdminQuarantineRow, LidarrQuarantineReason } from '$lib/api/types';
|
||||||
|
|
||||||
@@ -89,8 +89,7 @@
|
|||||||
await resolveQuarantine(r.track_id);
|
await resolveQuarantine(r.track_id);
|
||||||
await invalidate();
|
await invalidate();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const code = (e as { code?: string }).code ?? 'unknown';
|
showToast(errMessage(e));
|
||||||
showToast(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +107,7 @@
|
|||||||
await deleteQuarantineFile(r.track_id);
|
await deleteQuarantineFile(r.track_id);
|
||||||
await invalidate();
|
await invalidate();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const code = (e as { code?: string }).code ?? 'unknown';
|
showToast(errMessage(e));
|
||||||
showToast(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,12 +133,12 @@
|
|||||||
deleteLidarrError = null;
|
deleteLidarrError = null;
|
||||||
await invalidate();
|
await invalidate();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const code = (e as { code?: string }).code ?? 'unknown';
|
const msg = errMessage(e);
|
||||||
// Inline error keeps the modal open so the operator sees the failure
|
// Inline error keeps the modal open so the operator sees the failure
|
||||||
// alongside the album they were about to remove.
|
// alongside the album they were about to remove.
|
||||||
deleteLidarrError = copyForCode(code);
|
deleteLidarrError = msg;
|
||||||
// Also surface as toast so it's visible after dismissing the modal.
|
// Also surface as toast so it's visible after dismissing the modal.
|
||||||
showToast(copyForCode(code));
|
showToast(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
approveRequest,
|
approveRequest,
|
||||||
rejectRequest
|
rejectRequest
|
||||||
} from '$lib/api/admin';
|
} from '$lib/api/admin';
|
||||||
import { copyForCode } from '$lib/api/error-copy';
|
import { errMessage } from '$lib/api/errors';
|
||||||
import StatusPill from '$lib/components/StatusPill.svelte';
|
import StatusPill from '$lib/components/StatusPill.svelte';
|
||||||
import type { LidarrRequest, LidarrRequestKind, LidarrRequestStatus } from '$lib/api/types';
|
import type { LidarrRequest, LidarrRequestKind, LidarrRequestStatus } from '$lib/api/types';
|
||||||
|
|
||||||
@@ -114,8 +114,7 @@
|
|||||||
}
|
}
|
||||||
await invalidate();
|
await invalidate();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const code = (e as { code?: string }).code ?? 'unknown';
|
showToast(errMessage(e));
|
||||||
showToast(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,8 +154,7 @@
|
|||||||
await rejectRequest(r.id, notes ? notes : undefined);
|
await rejectRequest(r.id, notes ? notes : undefined);
|
||||||
await invalidate();
|
await invalidate();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const code = (e as { code?: string }).code ?? 'unknown';
|
showToast(errMessage(e));
|
||||||
showToast(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
type AdminInvite,
|
type AdminInvite,
|
||||||
type CreateUserInput
|
type CreateUserInput
|
||||||
} from '$lib/api/admin';
|
} from '$lib/api/admin';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
|
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
|
|
||||||
@@ -62,11 +63,11 @@
|
|||||||
await client.invalidateQueries({ queryKey: qk.adminUsers() });
|
await client.invalidateQueries({ queryKey: qk.adminUsers() });
|
||||||
showToast(u.is_admin ? `${u.username} is no longer an admin.` : `${u.username} is now an admin.`);
|
showToast(u.is_admin ? `${u.username} is no longer an admin.` : `${u.username} is now an admin.`);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
if (code === 'last_admin') {
|
if (code === 'last_admin') {
|
||||||
showToast(`Can't remove the last admin — promote someone else first.`);
|
showToast(`Can't remove the last admin — promote someone else first.`);
|
||||||
} else {
|
} else {
|
||||||
showToast(`Action failed: ${code ?? 'unknown'}`);
|
showToast(`Action failed: ${code}`);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
saving = false;
|
saving = false;
|
||||||
@@ -92,8 +93,7 @@
|
|||||||
createForm = { username: '', password: '', confirmPassword: '', display_name: '', is_admin: false };
|
createForm = { username: '', password: '', confirmPassword: '', display_name: '', is_admin: false };
|
||||||
showToast('User created.');
|
showToast('User created.');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
showToast(createUserErrorMessage(errCode(e)));
|
||||||
showToast(createUserErrorMessage(code));
|
|
||||||
} finally {
|
} finally {
|
||||||
saving = false;
|
saving = false;
|
||||||
}
|
}
|
||||||
@@ -117,11 +117,11 @@
|
|||||||
showToast(`Deleted ${confirmDeleteTarget.username}.`);
|
showToast(`Deleted ${confirmDeleteTarget.username}.`);
|
||||||
confirmDeleteTarget = null;
|
confirmDeleteTarget = null;
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
if (code === 'last_admin') {
|
if (code === 'last_admin') {
|
||||||
showToast(`Can't delete the last admin — promote someone else first.`);
|
showToast(`Can't delete the last admin — promote someone else first.`);
|
||||||
} else {
|
} else {
|
||||||
showToast(`Delete failed: ${code ?? 'unknown'}`);
|
showToast(`Delete failed: ${code}`);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
saving = false;
|
saving = false;
|
||||||
@@ -143,11 +143,11 @@
|
|||||||
resetPasswordValue = '';
|
resetPasswordValue = '';
|
||||||
resetPasswordConfirm = '';
|
resetPasswordConfirm = '';
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
if (code === 'password_too_short') {
|
if (code === 'password_too_short') {
|
||||||
showToast('Password must be at least 8 characters.');
|
showToast('Password must be at least 8 characters.');
|
||||||
} else {
|
} else {
|
||||||
showToast(`Reset failed: ${code ?? 'unknown'}`);
|
showToast(`Reset failed: ${code}`);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
saving = false;
|
saving = false;
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
? `Auto-approve disabled for ${u.username}.`
|
? `Auto-approve disabled for ${u.username}.`
|
||||||
: `Auto-approve enabled for ${u.username}.`);
|
: `Auto-approve enabled for ${u.username}.`);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
showToast(`Toggle failed: ${(e as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Toggle failed: ${errCode(e)}`);
|
||||||
} finally {
|
} finally {
|
||||||
saving = false;
|
saving = false;
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
await client.invalidateQueries({ queryKey: qk.adminInvites() });
|
await client.invalidateQueries({ queryKey: qk.adminInvites() });
|
||||||
showToast('Invite generated. Copy the token from the list below.');
|
showToast('Invite generated. Copy the token from the list below.');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
showToast(`Generate failed: ${(e as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Generate failed: ${errCode(e)}`);
|
||||||
} finally {
|
} finally {
|
||||||
saving = false;
|
saving = false;
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
await client.invalidateQueries({ queryKey: qk.adminInvites() });
|
await client.invalidateQueries({ queryKey: qk.adminInvites() });
|
||||||
showToast('Invite revoked.');
|
showToast('Invite revoked.');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
showToast(`Revoke failed: ${(e as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Revoke failed: ${errCode(e)}`);
|
||||||
} finally {
|
} finally {
|
||||||
saving = false;
|
saving = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
import { playQueue } from '$lib/player/store.svelte';
|
import { playQueue } from '$lib/player/store.svelte';
|
||||||
import { user } from '$lib/auth/store.svelte';
|
import { user } from '$lib/auth/store.svelte';
|
||||||
import { refetchAlbumCover } from '$lib/api/admin';
|
import { refetchAlbumCover } from '$lib/api/admin';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
import { useQueryClient } from '@tanstack/svelte-query';
|
import { useQueryClient } from '@tanstack/svelte-query';
|
||||||
|
|
||||||
const id = $derived(page.params.id ?? '');
|
const id = $derived(page.params.id ?? '');
|
||||||
@@ -78,7 +79,8 @@
|
|||||||
await refetchAlbumCover(data.id);
|
await refetchAlbumCover(data.id);
|
||||||
await queryClient.invalidateQueries({ queryKey: qk.album(data.id) });
|
await queryClient.invalidateQueries({ queryKey: qk.album(data.id) });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
refetchError = (e as { code?: string })?.code ?? 'refetch failed';
|
const code = errCode(e);
|
||||||
|
refetchError = code === 'unknown' ? 'refetch failed' : code;
|
||||||
} finally {
|
} finally {
|
||||||
refetching = false;
|
refetching = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
} from '$lib/api/playlists';
|
} from '$lib/api/playlists';
|
||||||
import { qk } from '$lib/api/queries';
|
import { qk } from '$lib/api/queries';
|
||||||
import { user } from '$lib/auth/store.svelte';
|
import { user } from '$lib/auth/store.svelte';
|
||||||
import { copyForCode } from '$lib/api/error-copy';
|
import { errCode, errMessage } from '$lib/api/errors';
|
||||||
import { playQueue } from '$lib/player/store.svelte';
|
import { playQueue } from '$lib/player/store.svelte';
|
||||||
import type { TrackRef } from '$lib/api/types';
|
import type { TrackRef } from '$lib/api/types';
|
||||||
|
|
||||||
@@ -48,8 +48,7 @@
|
|||||||
await reorderPlaylist(id, cur);
|
await reorderPlaylist(id, cur);
|
||||||
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
|
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code ?? 'unknown';
|
alert(errMessage(e));
|
||||||
alert(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,8 +58,7 @@
|
|||||||
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
|
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
|
||||||
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code ?? 'unknown';
|
alert(errMessage(e));
|
||||||
alert(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +118,7 @@
|
|||||||
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
||||||
editing = false;
|
editing = false;
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code ?? 'unknown';
|
alert(errMessage(e));
|
||||||
alert(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +129,7 @@
|
|||||||
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
||||||
goto('/playlists');
|
goto('/playlists');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code ?? 'unknown';
|
alert(errMessage(e));
|
||||||
alert(copyForCode(code));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +154,7 @@
|
|||||||
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
|
await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
|
||||||
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
await queryClient.invalidateQueries({ queryKey: qk.playlists() });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
showToast(`Refresh failed: ${(e as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Refresh failed: ${errCode(e)}`);
|
||||||
} finally {
|
} finally {
|
||||||
refreshingDiscover = false;
|
refreshingDiscover = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { pageTitle } from '$lib/branding';
|
import { pageTitle } from '$lib/branding';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { register } from '$lib/auth/store.svelte';
|
import { register } from '$lib/auth/store.svelte';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
|
|
||||||
let username = $state('');
|
let username = $state('');
|
||||||
let password = $state('');
|
let password = $state('');
|
||||||
@@ -46,8 +47,7 @@
|
|||||||
});
|
});
|
||||||
await goto('/', { replaceState: true });
|
await goto('/', { replaceState: true });
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const code = (err as { code?: string })?.code ?? 'unknown';
|
error = errorMessageFor(errCode(err));
|
||||||
error = errorMessageFor(code);
|
|
||||||
} finally {
|
} finally {
|
||||||
submitting = false;
|
submitting = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { pageTitle } from '$lib/branding';
|
import { pageTitle } from '$lib/branding';
|
||||||
import { resetPassword } from '$lib/auth/store.svelte';
|
import { resetPassword } from '$lib/auth/store.svelte';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
|
|
||||||
let newPassword = $state('');
|
let newPassword = $state('');
|
||||||
let confirmPassword = $state('');
|
let confirmPassword = $state('');
|
||||||
@@ -27,13 +28,13 @@
|
|||||||
await resetPassword(token, newPassword);
|
await resetPassword(token, newPassword);
|
||||||
await goto('/login?reset=ok', { replaceState: true });
|
await goto('/login?reset=ok', { replaceState: true });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
if (code === 'invalid_token') {
|
if (code === 'invalid_token') {
|
||||||
error = 'This reset link is invalid, expired, or already used. Request a new one.';
|
error = 'This reset link is invalid, expired, or already used. Request a new one.';
|
||||||
} else if (code === 'password_too_short') {
|
} else if (code === 'password_too_short') {
|
||||||
error = 'Password must be at least 8 characters.';
|
error = 'Password must be at least 8 characters.';
|
||||||
} else {
|
} else {
|
||||||
error = `Reset failed: ${code ?? 'unknown'}`;
|
error = `Reset failed: ${code}`;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
submitting = false;
|
submitting = false;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
getAPIToken,
|
getAPIToken,
|
||||||
regenerateAPIToken
|
regenerateAPIToken
|
||||||
} from '$lib/api/me';
|
} from '$lib/api/me';
|
||||||
|
import { errCode } from '$lib/api/errors';
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@@ -67,10 +68,10 @@
|
|||||||
});
|
});
|
||||||
showToast('Profile saved.');
|
showToast('Profile saved.');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
if (code === 'email_taken') showToast('That email is already in use.');
|
if (code === 'email_taken') showToast('That email is already in use.');
|
||||||
else if (code === 'email_invalid') showToast('Email format is invalid.');
|
else if (code === 'email_invalid') showToast('Email format is invalid.');
|
||||||
else showToast(`Save failed: ${code ?? 'unknown'}`);
|
else showToast(`Save failed: ${code}`);
|
||||||
} finally {
|
} finally {
|
||||||
profileSaving = false;
|
profileSaving = false;
|
||||||
}
|
}
|
||||||
@@ -95,10 +96,10 @@
|
|||||||
showToast('Password changed.');
|
showToast('Password changed.');
|
||||||
passwordForm = { current: '', new: '', confirm: '' };
|
passwordForm = { current: '', new: '', confirm: '' };
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const code = (e as { code?: string })?.code;
|
const code = errCode(e);
|
||||||
if (code === 'wrong_password') showToast('Current password is incorrect.');
|
if (code === 'wrong_password') showToast('Current password is incorrect.');
|
||||||
else if (code === 'password_too_short') showToast('Password must be at least 8 characters.');
|
else if (code === 'password_too_short') showToast('Password must be at least 8 characters.');
|
||||||
else showToast(`Change failed: ${code ?? 'unknown'}`);
|
else showToast(`Change failed: ${code}`);
|
||||||
} finally {
|
} finally {
|
||||||
passwordSaving = false;
|
passwordSaving = false;
|
||||||
}
|
}
|
||||||
@@ -140,7 +141,7 @@
|
|||||||
apiToken = r.api_token;
|
apiToken = r.api_token;
|
||||||
showToast('API token regenerated.');
|
showToast('API token regenerated.');
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
showToast(`Regenerate failed: ${(e as { code?: string })?.code ?? 'unknown'}`);
|
showToast(`Regenerate failed: ${errCode(e)}`);
|
||||||
} finally {
|
} finally {
|
||||||
tokenSaving = false;
|
tokenSaving = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user