Add cache purge API with version-based invalidation
All checks were successful
TypeScript CI / build (push) Successful in 31s
Deploy to R2 / deploy (push) Successful in 2m37s

- POST /api/purge/:customer - purge cache for specific customer
- POST /api/purge-all - purge all cache globally
- Cache keys now include version from KV, incrementing version invalidates all cached entries
- No need for Cloudflare Dashboard access to purge
This commit is contained in:
Heimdall
2026-04-01 11:45:27 +00:00
parent 8ef88de748
commit 6de97af27b

View File

@@ -434,18 +434,28 @@ async function handleAPI(request, env, url) {
}); });
} }
// POST /api/purge/:customer - 고객 사이트 캐시 퍼지 // POST /api/purge/:customer - 고객 사이트 캐시 퍼지 (캐시 버전 증가)
const purgeMatch = path.match(/^\/api\/purge\/([^\/]+)$/); const purgeMatch = path.match(/^\/api\/purge\/([^\/]+)$/);
if (purgeMatch && method === 'POST') { if (purgeMatch && method === 'POST') {
const customer = purgeMatch[1]; const customer = purgeMatch[1];
// Workers Cache API에서 해당 고객 캐시 삭제는 개별 키 기반이라 const newVersion = Date.now().toString();
// 실질적으로는 캐시 바이패스 플래그를 설정 await env.USAGE.put(`cache-version:${customer}`, newVersion);
// Cloudflare Edge 캐시는 zone-level purge 필요
// 여기서는 Worker 내부 캐시(caches.default)를 무효화
return jsonResponse({ return jsonResponse({
success: true, success: true,
message: `Cache bypass enabled. Use ?nocache query param to bypass cache for ${customer}`, customer,
hint: 'For full purge, use Cloudflare Dashboard or API zone purge', cache_version: newVersion,
message: `Cache purged for ${customer}. All cached pages will be refreshed on next request.`,
});
}
// POST /api/purge-all - 전체 캐시 퍼지
if (path === '/api/purge-all' && method === 'POST') {
const newVersion = Date.now().toString();
await env.USAGE.put('cache-version:__global__', newVersion);
return jsonResponse({
success: true,
cache_version: newVersion,
message: 'Global cache purged. All cached pages will be refreshed on next request.',
}); });
} }
@@ -455,7 +465,8 @@ async function handleAPI(request, env, url) {
'PUT /api/tier/:customer {"tier": "free|basic|pro"}', 'PUT /api/tier/:customer {"tier": "free|basic|pro"}',
'GET /api/stats', 'GET /api/stats',
'DELETE /api/customer/:customer', 'DELETE /api/customer/:customer',
'POST /api/purge/:customer', 'POST /api/purge/:customer - purge cache for a customer',
'POST /api/purge-all - purge all cache',
]}, 404); ]}, 404);
} }
@@ -519,8 +530,15 @@ export default {
const bypassCache = url.searchParams.has('nocache') || const bypassCache = url.searchParams.has('nocache') ||
request.headers.get('Cache-Control') === 'no-cache'; request.headers.get('Cache-Control') === 'no-cache';
// 캐시 키 생성 // 캐시 버전 조회 (퍼지 시 버전이 변경되어 캐시 무효화)
const cacheKey = new Request(`https://cache.internal/${customer}${filePath}`); const [customerVersion, globalVersion] = await Promise.all([
env.USAGE.get(`cache-version:${customer}`),
env.USAGE.get('cache-version:__global__'),
]);
const cacheVersion = customerVersion || globalVersion || '0';
// 캐시 키 생성 (버전 포함)
const cacheKey = new Request(`https://cache.internal/${customer}/v${cacheVersion}${filePath}`);
const cache = caches.default; const cache = caches.default;
// 1. 캐시에서 먼저 확인 (바이패스가 아닌 경우) // 1. 캐시에서 먼저 확인 (바이패스가 아닌 경우)