perf: cache admin job lists + targeted polling for audio/translation pages
Some checks failed
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Failing after 34s
Release / Upload source maps (push) Has been skipped
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Successful in 40s
Release / Docker / runner (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
Some checks failed
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Failing after 34s
Release / Upload source maps (push) Has been skipped
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Successful in 40s
Release / Docker / runner (push) Has been cancelled
Release / Gitea Release (push) Has been cancelled
Release / Docker / backend (push) Has been cancelled
- Add 30s Valkey cache to listScrapingTasks, listAudioJobs, listTranslationJobs (use listN(500) instead of unbounded listAll to cap at one request) - Delete listAudioCache() — derive AudioCacheEntry[] from jobs in server load - Add listBookSlugs() with 10min cache — replaces full listBooks() in translation load - Add GET /api/admin/audio-jobs, /api/admin/translation-jobs, /api/admin/scrape-tasks (lightweight polling endpoints backed by the Valkey cache) - Replace invalidateAll() interval polling in audio+translation pages with targeted fetch to the new endpoints (avoids re-running full server load)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { listAudioCache, listAudioJobs, type AudioCacheEntry, type AudioJob } from '$lib/server/pocketbase';
|
||||
import { listAudioJobs, type AudioCacheEntry, type AudioJob } from '$lib/server/pocketbase';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
@@ -8,16 +8,20 @@ export const load: PageServerLoad = async ({ locals }) => {
|
||||
redirect(302, '/');
|
||||
}
|
||||
|
||||
const [entries, jobs] = await Promise.all([
|
||||
listAudioCache().catch((e): AudioCacheEntry[] => {
|
||||
log.warn('admin/audio', 'failed to load audio cache', { err: String(e) });
|
||||
return [];
|
||||
}),
|
||||
listAudioJobs().catch((e): AudioJob[] => {
|
||||
log.warn('admin/audio', 'failed to load audio jobs', { err: String(e) });
|
||||
return [];
|
||||
})
|
||||
]);
|
||||
const jobs = await listAudioJobs().catch((e): AudioJob[] => {
|
||||
log.warn('admin/audio', 'failed to load audio jobs', { err: String(e) });
|
||||
return [];
|
||||
});
|
||||
|
||||
// Derive cache entries from done jobs — no second query needed.
|
||||
const entries: AudioCacheEntry[] = jobs
|
||||
.filter((j) => j.status === 'done')
|
||||
.map((j) => ({
|
||||
id: j.id,
|
||||
cache_key: j.cache_key,
|
||||
filename: `${j.cache_key}.mp3`,
|
||||
updated: j.finished
|
||||
}));
|
||||
|
||||
return { entries, jobs };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import type { PageData } from './$types';
|
||||
import type { AudioJob, AudioCacheEntry } from '$lib/server/pocketbase';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
@@ -21,8 +20,17 @@
|
||||
|
||||
$effect(() => {
|
||||
if (!hasInFlight) return;
|
||||
const id = setInterval(() => {
|
||||
invalidateAll();
|
||||
const id = setInterval(async () => {
|
||||
const res = await fetch('/api/admin/audio-jobs').catch(() => null);
|
||||
if (res?.ok) {
|
||||
const body = await res.json().catch(() => null);
|
||||
if (body?.jobs) {
|
||||
jobs = body.jobs;
|
||||
entries = (body.jobs as AudioJob[])
|
||||
.filter((j) => j.status === 'done')
|
||||
.map((j) => ({ id: j.id, cache_key: j.cache_key, filename: `${j.cache_key}.mp3`, updated: j.finished }));
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
return () => clearInterval(id);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { listBooks, listTranslationJobs, type TranslationJob } from '$lib/server/pocketbase';
|
||||
import { listBookSlugs, listTranslationJobs, type TranslationJob } from '$lib/server/pocketbase';
|
||||
import { backendFetch } from '$lib/server/scraper';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
@@ -10,8 +10,8 @@ export const load: PageServerLoad = async ({ locals }) => {
|
||||
}
|
||||
|
||||
const [books, jobs] = await Promise.all([
|
||||
listBooks().catch((e): Awaited<ReturnType<typeof listBooks>> => {
|
||||
log.warn('admin/translation', 'failed to load books', { err: String(e) });
|
||||
listBookSlugs().catch((e): Awaited<ReturnType<typeof listBookSlugs>> => {
|
||||
log.warn('admin/translation', 'failed to load book slugs', { err: String(e) });
|
||||
return [];
|
||||
}),
|
||||
listTranslationJobs().catch((e): TranslationJob[] => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { enhance } from '$app/forms';
|
||||
import type { PageData, ActionData } from './$types';
|
||||
import type { TranslationJob } from '$lib/server/pocketbase';
|
||||
@@ -19,8 +18,12 @@
|
||||
|
||||
$effect(() => {
|
||||
if (!hasInFlight) return;
|
||||
const id = setInterval(() => {
|
||||
invalidateAll();
|
||||
const id = setInterval(async () => {
|
||||
const res = await fetch('/api/admin/translation-jobs').catch(() => null);
|
||||
if (res?.ok) {
|
||||
const body = await res.json().catch(() => null);
|
||||
if (body?.jobs) jobs = body.jobs;
|
||||
}
|
||||
}, 3000);
|
||||
return () => clearInterval(id);
|
||||
});
|
||||
|
||||
16
ui/src/routes/api/admin/audio-jobs/+server.ts
Normal file
16
ui/src/routes/api/admin/audio-jobs/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { listAudioJobs } from '$lib/server/pocketbase';
|
||||
|
||||
/**
|
||||
* GET /api/admin/audio-jobs
|
||||
* Returns the current audio jobs list (served from 30 s Valkey cache).
|
||||
* Used by the admin audio page for lightweight polling instead of invalidateAll().
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ locals }) => {
|
||||
if (!locals.user || locals.user.role !== 'admin') {
|
||||
throw error(403, 'Forbidden');
|
||||
}
|
||||
const jobs = await listAudioJobs().catch(() => []);
|
||||
return json({ jobs });
|
||||
};
|
||||
16
ui/src/routes/api/admin/scrape-tasks/+server.ts
Normal file
16
ui/src/routes/api/admin/scrape-tasks/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { listScrapingTasks } from '$lib/server/pocketbase';
|
||||
|
||||
/**
|
||||
* GET /api/admin/scrape-tasks
|
||||
* Returns the current scraping task list (served from 30 s Valkey cache).
|
||||
* Used by the admin scrape page for lightweight polling instead of invalidateAll().
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ locals }) => {
|
||||
if (!locals.user || locals.user.role !== 'admin') {
|
||||
throw error(403, 'Forbidden');
|
||||
}
|
||||
const tasks = await listScrapingTasks().catch(() => []);
|
||||
return json({ tasks });
|
||||
};
|
||||
16
ui/src/routes/api/admin/translation-jobs/+server.ts
Normal file
16
ui/src/routes/api/admin/translation-jobs/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { listTranslationJobs } from '$lib/server/pocketbase';
|
||||
|
||||
/**
|
||||
* GET /api/admin/translation-jobs
|
||||
* Returns the current translation jobs list (served from 30 s Valkey cache).
|
||||
* Used by the admin translation page for lightweight polling instead of invalidateAll().
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ locals }) => {
|
||||
if (!locals.user || locals.user.role !== 'admin') {
|
||||
throw error(403, 'Forbidden');
|
||||
}
|
||||
const jobs = await listTranslationJobs().catch(() => []);
|
||||
return json({ jobs });
|
||||
};
|
||||
Reference in New Issue
Block a user