streamflix-api / cloudflare-worker-updated.js
Akshar2325
feat(image): add on-the-fly image resizing service
ee3e190
Raw
History Blame
4.14 kB
// Updated Cloudflare Worker for Backblaze B2 with NestJS Backend Resizing
export default {
async fetch(request, env, ctx) {
try {
const url = new URL(request.url);
// 1. Restrict to CDN domain only
if (url.hostname !== 'cdn.aksharbhesaniya.dev') {
return new Response('Forbidden', { status: 403 });
}
// 2. Serve from cache if available
const cache = caches.default;
const cacheKey = new Request(url.toString(), request);
const cached = await cache.match(cacheKey);
if (cached) {
console.log('Cache HIT:', url.toString());
return cached;
}
console.log('Cache MISS:', url.toString());
// 3. Read resize parameters
const width = url.searchParams.get('w');
const height = url.searchParams.get('h');
const quality = url.searchParams.get('q');
// 4. Check if resizing is requested
const needsResize = width || height || quality;
if (needsResize) {
// ===== RESIZE PATH: Proxy to NestJS Backend =====
const backendUrl = env.BACKEND_URL || 'https://api.streamflix.com';
const filePath = url.pathname.substring(1); // Remove leading slash
// Build backend resize URL
const resizeUrl = new URL(`${backendUrl}/backblaze/resize`);
resizeUrl.searchParams.set('path', filePath);
if (width) resizeUrl.searchParams.set('w', width);
if (height) resizeUrl.searchParams.set('h', height);
if (quality) resizeUrl.searchParams.set('q', quality);
console.log('Fetching resized image from backend:', resizeUrl.toString());
// Fetch resized image from NestJS backend
const backendResponse = await fetch(resizeUrl.toString());
if (!backendResponse.ok) {
return new Response('Resize failed', { status: backendResponse.status });
}
// Cache and return resized image
const response = new Response(backendResponse.body, {
headers: {
'Content-Type': backendResponse.headers.get('Content-Type') || 'image/jpeg',
'Cache-Control': 'public, max-age=31536000', // Cache for 1 year
'X-Resize-Backend': 'NestJS',
},
});
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
} else {
// ===== ORIGINAL PATH: Serve from B2 directly =====
// 5. Load B2 secrets
const KEY_ID = env.B2_KEY_ID;
const APP_KEY = env.B2_APP_KEY;
const BUCKET = env.B2_BUCKET;
if (!KEY_ID || !APP_KEY || !BUCKET) {
return new Response('Missing B2 config', { status: 500 });
}
// 6. Authorize Backblaze B2
const authRes = await fetch(
'https://api.backblazeb2.com/b2api/v2/b2_authorize_account',
{
headers: { Authorization: 'Basic ' + btoa(`${KEY_ID}:${APP_KEY}`) },
},
);
if (!authRes.ok) {
return new Response('B2 auth failed', { status: 502 });
}
const auth = await authRes.json();
// 7. Build file URL
const cleanPath = url.pathname;
const fileUrl = `${auth.downloadUrl}/file/${BUCKET}${cleanPath}`;
console.log('Fetching original from B2:', fileUrl);
// 8. Fetch file from B2
const origin = await fetch(fileUrl, {
headers: { Authorization: auth.authorizationToken },
});
if (!origin.ok) {
return new Response('File not found', { status: origin.status });
}
// 9. Cache and return original
const response = new Response(origin.body, {
headers: {
'Content-Type': origin.headers.get('Content-Type') || 'image/jpeg',
'Cache-Control': 'public, max-age=31536000', // Cache for 1 year
'X-Source': 'B2-Direct',
},
});
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
}
} catch (err) {
console.error('Worker error:', err);
return new Response('Internal Error: ' + err.message, { status: 500 });
}
},
};