diff --git a/src/lib/image-cache.server.ts b/src/lib/image-cache.server.ts index 450e05a..302f353 100644 --- a/src/lib/image-cache.server.ts +++ b/src/lib/image-cache.server.ts @@ -218,3 +218,27 @@ export function resolveExtension(params: ImageTransformParams, contentType?: str if (contentType) return extForContentType(contentType); return 'jpg'; } + +/** + * Wipe in-memory + on-disk image cache. Returns counts cleared. + */ +export async function clearImageCache(): Promise<{ memory: number; disk: number }> { + const memory = memoryCache.size; + memoryCache.clear(); + let disk = 0; + try { + const dir = ensureCacheDir(); + const files = await readdir(dir); + for (const f of files) { + try { + await unlink(join(dir, f)); + disk++; + } catch { + /* ignore */ + } + } + } catch { + /* ignore */ + } + return { memory, disk }; +} diff --git a/src/routes/api/cache/clear/+server.ts b/src/routes/api/cache/clear/+server.ts new file mode 100644 index 0000000..1048d17 --- /dev/null +++ b/src/routes/api/cache/clear/+server.ts @@ -0,0 +1,45 @@ +import type { RequestHandler } from './$types'; +import { json, error } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; +import { env as envPublic } from '$env/dynamic/public'; +import { invalidateAll } from '$lib/cms'; +import { clearImageCache } from '$lib/image-cache.server'; + +/** + * Manual full-cache purge: + * - SvelteKit-side TTL cache (cms.ts) + * - SvelteKit image cache (memory + disk) + * - Optionally: forwards POST to RustyCMS /api/transform/cache/invalidate + * when an API key is configured (RUSTYCMS_API_KEY env). + * + * Auth: same `REVALIDATE_TOKEN` as /api/revalidate. + */ +export const POST: RequestHandler = async ({ request, url }) => { + const token = env.REVALIDATE_TOKEN; + if (!token) throw error(500, 'REVALIDATE_TOKEN not configured'); + const provided = + request.headers.get('x-revalidate-token') ?? + request.headers.get('x-webhook-secret') ?? + url.searchParams.get('token'); + if (provided !== token) throw error(401, 'invalid token'); + + invalidateAll(); + const img = await clearImageCache(); + + let cms: unknown = 'skipped'; + const apiKey = env.RUSTYCMS_API_KEY; + const cmsUrl = (envPublic.PUBLIC_CMS_URL || '').replace(/\/$/, ''); + if (apiKey && cmsUrl) { + try { + const res = await fetch(`${cmsUrl}/api/transform/cache/invalidate`, { + method: 'POST', + headers: { authorization: `Bearer ${apiKey}` }, + }); + cms = res.ok ? await res.json() : { error: res.status }; + } catch (e) { + cms = { error: e instanceof Error ? e.message : String(e) }; + } + } + + return json({ ok: true, content_cache: 'cleared', image_cache: img, cms }); +}; diff --git a/src/routes/api/revalidate/+server.ts b/src/routes/api/revalidate/+server.ts index e1a7588..0386162 100644 --- a/src/routes/api/revalidate/+server.ts +++ b/src/routes/api/revalidate/+server.ts @@ -2,6 +2,7 @@ import type { RequestHandler } from './$types'; import { json, error } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; import { invalidateAll, invalidateCollection } from '$lib/cms'; +import { clearImageCache } from '$lib/image-cache.server'; /** * RustyCMS Webhook Receiver. @@ -50,7 +51,10 @@ export const POST: RequestHandler = async ({ request, url }) => { if (event.startsWith('asset.')) { // Assets referenziert überall → safe bet: purge all collections mit resolve invalidateAll(); - return json({ ok: true, purged: 'all', reason: event }); + // Same image bytes (focal change, re-upload, alt edit) → image-cache must + // also flush so /cms-images stops serving the previous transform. + const img = await clearImageCache(); + return json({ ok: true, purged: 'all', reason: event, image_cache: img }); } if (collection) {