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:
2025-12-03 13:27:37 -05:00
parent c3f0197115
commit 55e4c5ff0c
20 changed files with 7066 additions and 9 deletions

View 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 });
}
}