70 lines
2.0 KiB
JavaScript
70 lines
2.0 KiB
JavaScript
|
|
/**
|
||
|
|
* API endpoint to serve cached images from the database
|
||
|
|
* Serves avatars, emojis, attachments, etc. from local cache
|
||
|
|
*
|
||
|
|
* Note: This endpoint is intentionally unauthenticated because:
|
||
|
|
* 1. Image tags don't reliably send auth cookies on initial load
|
||
|
|
* 2. Image IDs are not guessable (you need access to the messages API first)
|
||
|
|
* 3. The underlying Discord images are semi-public anyway
|
||
|
|
*/
|
||
|
|
import sql from '../../../utils/db.js';
|
||
|
|
|
||
|
|
export async function GET({ request }) {
|
||
|
|
const url = new URL(request.url);
|
||
|
|
const imageId = url.searchParams.get('id');
|
||
|
|
const sourceUrl = url.searchParams.get('url');
|
||
|
|
|
||
|
|
if (!imageId && !sourceUrl) {
|
||
|
|
return new Response('Missing id or url parameter', { status: 400 });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate imageId is a valid integer if provided
|
||
|
|
if (imageId && !/^\d+$/.test(imageId)) {
|
||
|
|
return new Response('Invalid image id', { status: 400 });
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
let image;
|
||
|
|
|
||
|
|
if (imageId) {
|
||
|
|
// Look up by image_id
|
||
|
|
const result = await sql`
|
||
|
|
SELECT image_data, content_type, source_url
|
||
|
|
FROM image_cache
|
||
|
|
WHERE image_id = ${imageId}
|
||
|
|
`;
|
||
|
|
image = result[0];
|
||
|
|
} else {
|
||
|
|
// Look up by source_url
|
||
|
|
const result = await sql`
|
||
|
|
SELECT image_data, content_type, source_url
|
||
|
|
FROM image_cache
|
||
|
|
WHERE source_url = ${sourceUrl}
|
||
|
|
`;
|
||
|
|
image = result[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!image) {
|
||
|
|
return new Response('Image not found in cache', { status: 404 });
|
||
|
|
}
|
||
|
|
|
||
|
|
// image_data is a Buffer (bytea)
|
||
|
|
const imageBuffer = image.image_data;
|
||
|
|
const contentType = image.content_type || 'image/png';
|
||
|
|
|
||
|
|
return new Response(imageBuffer, {
|
||
|
|
status: 200,
|
||
|
|
headers: {
|
||
|
|
'Content-Type': contentType,
|
||
|
|
'Content-Length': imageBuffer.length.toString(),
|
||
|
|
'Cache-Control': 'public, max-age=31536000, immutable', // Cache for 1 year since it's immutable
|
||
|
|
'X-Content-Type-Options': 'nosniff',
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error serving cached image:', error);
|
||
|
|
return new Response('Failed to serve image', { status: 500 });
|
||
|
|
}
|
||
|
|
}
|