feat(api): add endpoints for fetching reaction users and searching messages
- Implemented GET endpoint to fetch users who reacted with a specific emoji on a message. - Added validation for messageId and emoji parameters. - Enhanced user data retrieval with display names and avatar URLs. - Created a search endpoint for Discord messages with support for content and embed searches. - Included pagination and rate limiting for search results. feat(api): introduce image proxy and link preview endpoints - Developed an image proxy API to securely fetch images from untrusted domains. - Implemented HMAC signing for image URLs to prevent abuse. - Created a link preview API to fetch Open Graph metadata from URLs. - Added support for trusted domains and safe image URL generation. style(pages): create Discord logs page with authentication - Added a new page for displaying archived Discord channel logs. - Integrated authentication check to ensure user access. refactor(utils): enhance API authentication and database connection - Improved API authentication helper to manage user sessions and token refresh. - Established a PostgreSQL database connection utility for Discord logs.
This commit is contained in:
69
src/pages/api/discord/cached-image.js
Normal file
69
src/pages/api/discord/cached-image.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user