From 615fa1e92fcd7694c55d5a89909545a2229ac543 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Sun, 7 Jun 2026 18:37:52 -0500 Subject: [PATCH] Chore: GQL Cleanup P.3 --- .../settings/sections/DevToolsSettings.svelte | 8 +- .../settings/sections/LibrarySettings.svelte | 4 +- .../settings/sections/StorageSettings.svelte | 90 +++----------- .../settings/sections/TrackingSettings.svelte | 4 +- src/lib/request-manager/meta.ts | 9 ++ src/lib/server-adapters/suwayomi/index.ts | 117 ++++++++++++++---- src/lib/server-adapters/suwayomi/manga.ts | 15 +++ src/lib/server-adapters/suwayomi/meta.ts | 22 ++++ src/lib/server-adapters/suwayomi/tracking.ts | 8 ++ src/lib/server-adapters/types.ts | 24 ++++ src/lib/state/app.svelte.ts | 1 + src/routes/+layout.svelte | 5 + 12 files changed, 202 insertions(+), 105 deletions(-) diff --git a/src/lib/components/settings/sections/DevToolsSettings.svelte b/src/lib/components/settings/sections/DevToolsSettings.svelte index 71eb81b..260b50a 100644 --- a/src/lib/components/settings/sections/DevToolsSettings.svelte +++ b/src/lib/components/settings/sections/DevToolsSettings.svelte @@ -76,7 +76,7 @@ let entries = 0, oldest: number | null = null, newest: number | null = null const foundKeys: string[] = [] const checkKey = (k: string) => { - const age = cache.ageOf(k) + const age = cache?.ageOf?.(k) if (age !== undefined) { entries++ foundKeys.push(k) @@ -85,7 +85,7 @@ if (newest === null || ts > newest) newest = ts } } - ['library', 'sources', 'popular'].forEach(checkKey) + ['library', 'sources', 'popular'].forEach(checkKey); ['Action','Romance','Fantasy','Comedy','Drama','Horror','Sci-Fi','Adventure','Thriller', 'Isekai','Supernatural','Historical','Psychological','Sports','Mystery','Mecha', 'Slice of Life','School Life','Martial Arts','Magic','Military'].forEach(g => checkKey(`genre:${g}`)) @@ -104,7 +104,7 @@ function triggerSplash() { splashTriggered = true setTimeout(() => splashTriggered = false, 200) - ;(window as any).__mokuShowSplash?.() + appState.idleSplash = true } async function testWindowsHello() { @@ -192,7 +192,7 @@
Filter {appState.libraryFilter} Folders {appState.categories.filter(c => c.id !== 0).map(c => c.name).join(', ') || 'none'} - History {appState.history.length} entries + History {appState.history?.length ?? 0} entries Cache {perfSnapshot?.cacheEntries ?? '—'} entries Toasts {appState.toasts.length} queued Version {appVersion} · {import.meta.env.MODE} diff --git a/src/lib/components/settings/sections/LibrarySettings.svelte b/src/lib/components/settings/sections/LibrarySettings.svelte index 311419a..dc9c081 100644 --- a/src/lib/components/settings/sections/LibrarySettings.svelte +++ b/src/lib/components/settings/sections/LibrarySettings.svelte @@ -83,8 +83,8 @@
-
Reading history{homeState.history.length} entries
- +
Reading history{homeState.history?.length ?? 0} entries
+
diff --git a/src/lib/components/settings/sections/StorageSettings.svelte b/src/lib/components/settings/sections/StorageSettings.svelte index ecac1fe..1956a9a 100644 --- a/src/lib/components/settings/sections/StorageSettings.svelte +++ b/src/lib/components/settings/sections/StorageSettings.svelte @@ -12,6 +12,9 @@ import { clearBlobCache } from '$lib/core/cache/imageCache' import { clearPageCache } from '$lib/request-manager' import { cache as queryCache } from '$lib/core/cache/queryCache' + import { getAdapter } from '$lib/request-manager' + import { requestManager } from '$lib/request-manager' + import type { ValidateBackupResult, RestoreStatus } from '$lib/server-adapters/types' const supportsFilesystem = platformService.isSupported('filesystem') @@ -79,7 +82,7 @@ await Promise.all([ platformService.clearMokuCache(), platformService.clearSuwayomiCache(), - gql(`mutation { clearCachedImages(input: { cachedPages: true, cachedThumbnails: true, downloadedThumbnails: false }) { cachedPages cachedThumbnails } }`), + getAdapter().clearCachedImages({ cachedPages: true, cachedThumbnails: true, downloadedThumbnails: false }), ]) } @@ -168,11 +171,7 @@ if (!supportsFilesystem) return storageLoading = true; storageError = null try { - const pathData = await gql<{ settings: { downloadsPath: string | null; localSourcePath: string | null } }>( - `{ settings { downloadsPath localSourcePath } }` - ) - const dl = pathData.settings.downloadsPath ?? '' - const loc = pathData.settings.localSourcePath ?? '' + const { downloadsPath: dl, localSourcePath: loc } = await getAdapter().getDownloadsPath() downloadsPathInput = dl; localSourcePathInput = loc confirmedDownloadsPath = dl; confirmedLocalSourcePath = loc updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc }) @@ -218,8 +217,8 @@ if (dlErr || locErr) { pathsFieldError = { ...(dlErr ? { dl: dlErr } : {}), ...(locErr ? { loc: locErr } : {}) }; return } pathsSaving = true try { - await gql(`mutation($path: String!) { setSettings(input: { settings: { downloadsPath: $path } }) { settings { downloadsPath } } }`, { path: dl }) - if (loc) await gql(`mutation($path: String!) { setSettings(input: { settings: { localSourcePath: $path } }) { settings { localSourcePath } } }`, { path: loc }) + await getAdapter().setDownloadsPath(dl) + if (loc) await getAdapter().setLocalSourcePath(loc) updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc }) if (supportsFilesystem && !isExternalServer) { const oldDl = confirmedDownloadsPath || defaultDownloadsPath @@ -301,8 +300,7 @@ async function createBackup() { backupLoading = true; backupError = null try { - const data = await gql<{ createBackup: { url: string } }>(`mutation { createBackup { url } }`) - const { url } = data.createBackup + const { url } = await getAdapter().createBackup() const name = url.split('/').pop() ?? url backupList = [{ url, name }, ...backupList] await saveBackupList() @@ -313,7 +311,7 @@ async function deleteBackup(url: string) { backupList = backupList.map(b => b.url === url ? { ...b, deleting: true } : b) try { - await fetch(`${serverUrl()}${url}`, { method: 'DELETE', headers: buildAuthHeaders() }) + await getAdapter().deleteBackup(url) backupList = backupList.filter(b => b.url !== url) await saveBackupList() } catch (e: any) { @@ -324,9 +322,7 @@ async function downloadBackup(backup: BackupEntry) { try { - const resp = await fetch(`${serverUrl()}${backup.url}`, { headers: buildAuthHeaders() }) - if (!resp.ok) throw new Error(`Server returned ${resp.status}`) - const blob = await resp.blob() + const blob = await getAdapter().downloadBackup(backup.url) if ('showSaveFilePicker' in window) { try { const handle = await (window as any).showSaveFilePicker({ @@ -349,12 +345,11 @@ let restoreLoading = $state(false) let restoreError = $state(null) - let restoreJobId = $state(null) - let restoreStatus = $state<{ mangaProgress: number; state: string; totalManga: number } | null>(null) + let restoreStatus = $state(null) let restorePollInterval = $state | null>(null) let validateLoading = $state(false) let validateError = $state(null) - let validateResult = $state<{ missingSources: { id: string; name: string }[]; missingTrackers: { name: string }[] } | null>(null) + let validateResult = $state(null) let restoreFile = $state(null) function stopRestorePoll() { @@ -363,62 +358,19 @@ async function pollRestoreStatus(id: string) { try { - const data = await gql<{ restoreStatus: { mangaProgress: number; state: string; totalManga: number } }>( - `query($id: String!) { restoreStatus(id: $id) { mangaProgress state totalManga } }`, - { id } - ) - const status = data.restoreStatus + const status = await getAdapter().pollRestoreStatus(id) restoreStatus = status if (status?.state === 'SUCCESS' || status?.state === 'FAILURE') stopRestorePoll() } catch {} } - function buildBackupFormData(file: File, query: string, variables: Record) { - const form = new FormData() - form.append('operations', JSON.stringify({ query, variables })) - form.append('map', JSON.stringify({ '0': ['variables.backup'] })) - form.append('0', file, file.name) - return form - } - - function buildAuthHeaders(): Record { - const headers: Record = { Accept: 'application/json' } - const pass = settingsState.settings.serverAuthPass ?? '', user = settingsState.settings.serverAuthUser ?? '' - if (settingsState.settings.serverAuthMode === 'BASIC_AUTH' && user && pass) - headers['Authorization'] = 'Basic ' + btoa(`${user}:${pass}`) - return headers - } - - function serverUrl(): string { - return (settingsState.settings.serverUrl ?? 'http://localhost:4567').replace(/\/$/, '') - } - - async function gql(query: string, variables?: Record): Promise { - const res = await fetch(`${serverUrl()}/api/graphql`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', ...buildAuthHeaders() }, - body: JSON.stringify({ query, variables }), - }) - const json = await res.json() - if (json.errors?.length) throw new Error(json.errors[0].message) - return json.data as T - } - async function submitRestore() { if (!restoreFile) return - restoreLoading = true; restoreError = null; restoreStatus = null; restoreJobId = null + restoreLoading = true; restoreError = null; restoreStatus = null stopRestorePoll() try { - const form = buildBackupFormData( - restoreFile, - `mutation RestoreBackup($backup: Upload!) { restoreBackup(input: { backup: $backup }) { id status { mangaProgress state totalManga } } }`, - { backup: null } - ) - const resp = await fetch(`${serverUrl()}/api/graphql`, { method: 'POST', headers: buildAuthHeaders(), body: form }) - const json = await resp.json() - if (json.errors?.length) throw new Error(json.errors[0].message) - const result = json.data.restoreBackup - restoreJobId = result.id; restoreStatus = result.status + const result = await requestManager.meta.restoreBackup(restoreFile) + restoreStatus = result.status if (result.status?.state !== 'SUCCESS' && result.status?.state !== 'FAILURE') restorePollInterval = setInterval(() => pollRestoreStatus(result.id), 1500) } catch (e: any) { restoreError = e?.message ?? 'Failed to start restore' } @@ -429,15 +381,7 @@ if (!restoreFile) return validateLoading = true; validateError = null; validateResult = null try { - const form = buildBackupFormData( - restoreFile, - `query ValidateBackup($backup: Upload!) { validateBackup(input: { backup: $backup }) { missingSources { id name } missingTrackers { name } } }`, - { backup: null } - ) - const resp = await fetch(`${serverUrl()}/api/graphql`, { method: 'POST', headers: buildAuthHeaders(), body: form }) - const json = await resp.json() - if (json.errors?.length) throw new Error(json.errors[0].message) - validateResult = json.data.validateBackup + validateResult = await requestManager.meta.validateBackup(restoreFile) } catch (e: any) { validateError = e?.message ?? 'Failed to validate backup' } finally { validateLoading = false } } diff --git a/src/lib/components/settings/sections/TrackingSettings.svelte b/src/lib/components/settings/sections/TrackingSettings.svelte index 40668b0..f0718c8 100644 --- a/src/lib/components/settings/sections/TrackingSettings.svelte +++ b/src/lib/components/settings/sections/TrackingSettings.svelte @@ -3,6 +3,7 @@ import { settingsState, updateSettings } from "$lib/state/settings.svelte"; import { toast } from "$lib/state/notifications.svelte"; import { getAdapter } from "$lib/request-manager"; + import { platformService } from "$lib/platform-service"; import { syncBackFromTracker } from "$lib/components/tracking/lib/trackingSync"; import { trackingState } from "$lib/state/tracking.svelte"; import type { Tracker, TrackRecord } from "$lib/types/index"; @@ -41,7 +42,7 @@ async function startOAuth(tracker: Tracker) { if (!tracker.authUrl) return; oauthTrackerId = tracker.id; oauthCallbackInput = ""; - window.open(tracker.authUrl, "_blank"); + await platformService.openExternal(tracker.authUrl); } async function submitOAuth() { @@ -274,6 +275,7 @@