Files
codey.lol/src/pages/api/discord/cached-image.js

70 lines
2.0 KiB
JavaScript
Raw Normal View History

/**
* 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 });
}
}