123 lines
3.7 KiB
JavaScript
123 lines
3.7 KiB
JavaScript
|
|
const express = require('express');
|
||
|
|
const axios = require('axios');
|
||
|
|
const logger = require('../utils/logger');
|
||
|
|
const { db } = require('../database/db');
|
||
|
|
const path = require('path');
|
||
|
|
const fs = require('fs').promises;
|
||
|
|
|
||
|
|
const router = express.Router();
|
||
|
|
|
||
|
|
// Middleware to fix CORS for public image serving
|
||
|
|
const fixImageCORS = (req, res, next) => {
|
||
|
|
// Remove credentials header set by global CORS middleware
|
||
|
|
res.removeHeader('Access-Control-Allow-Credentials');
|
||
|
|
// Set proper CORS for public images
|
||
|
|
res.set({
|
||
|
|
'Access-Control-Allow-Origin': '*',
|
||
|
|
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||
|
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||
|
|
'Cross-Origin-Resource-Policy': 'cross-origin'
|
||
|
|
});
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
|
||
|
|
// Handle OPTIONS preflight requests
|
||
|
|
router.options('/', fixImageCORS, (req, res) => {
|
||
|
|
res.status(204).end();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Proxy external logos to handle CORS issues
|
||
|
|
router.get('/', fixImageCORS, async (req, res) => {
|
||
|
|
const { url } = req.query;
|
||
|
|
|
||
|
|
if (!url) {
|
||
|
|
return res.status(400).json({ error: 'URL parameter is required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Check if logo is cached
|
||
|
|
const cached = await new Promise((resolve, reject) => {
|
||
|
|
const query = 'SELECT logo_url, local_path FROM logo_cache WHERE logo_url = ? LIMIT 1';
|
||
|
|
db.get(query, [url], (err, row) => {
|
||
|
|
console.log(`[LogoProxy] SQL: ${query} with url="${url}"`);
|
||
|
|
if (err) {
|
||
|
|
console.error(`[LogoProxy] DB Error:`, err);
|
||
|
|
reject(err);
|
||
|
|
} else {
|
||
|
|
console.log(`[LogoProxy] DB Result:`, row ? JSON.stringify(row) : 'null');
|
||
|
|
resolve(row);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log(`[LogoProxy] Cache lookup for ${url}: ${cached ? 'FOUND at ' + cached.local_path : 'NOT FOUND'}`);
|
||
|
|
|
||
|
|
// If cached, serve from disk
|
||
|
|
if (cached && cached.local_path) {
|
||
|
|
const cachedPath = cached.local_path;
|
||
|
|
try {
|
||
|
|
const fileData = await fs.readFile(cachedPath);
|
||
|
|
const ext = path.extname(cachedPath).toLowerCase();
|
||
|
|
const contentType = {
|
||
|
|
'.png': 'image/png',
|
||
|
|
'.jpg': 'image/jpeg',
|
||
|
|
'.jpeg': 'image/jpeg',
|
||
|
|
'.gif': 'image/gif',
|
||
|
|
'.webp': 'image/webp',
|
||
|
|
'.svg': 'image/svg+xml'
|
||
|
|
}[ext] || 'image/png';
|
||
|
|
|
||
|
|
res.set({
|
||
|
|
'Content-Type': contentType,
|
||
|
|
'Cache-Control': 'public, max-age=2592000' // Cache for 30 days
|
||
|
|
});
|
||
|
|
return res.send(fileData);
|
||
|
|
} catch (err) {
|
||
|
|
logger.warn('Cached logo file not found, fetching fresh:', err.message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate URL
|
||
|
|
const logoUrl = new URL(url);
|
||
|
|
|
||
|
|
// Fetch the image
|
||
|
|
const response = await axios.get(url, {
|
||
|
|
responseType: 'arraybuffer',
|
||
|
|
timeout: 10000,
|
||
|
|
headers: {
|
||
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||
|
|
'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
|
||
|
|
'Accept-Language': 'en-US,en;q=0.9',
|
||
|
|
'Cache-Control': 'no-cache',
|
||
|
|
'Pragma': 'no-cache'
|
||
|
|
},
|
||
|
|
maxRedirects: 5
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set appropriate headers
|
||
|
|
const contentType = response.headers['content-type'] || 'image/png';
|
||
|
|
res.set({
|
||
|
|
'Content-Type': contentType,
|
||
|
|
'Cache-Control': 'public, max-age=86400' // Cache for 24 hours
|
||
|
|
});
|
||
|
|
|
||
|
|
// Send the image
|
||
|
|
res.send(response.data);
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Logo proxy error:', {
|
||
|
|
url,
|
||
|
|
error: error.message,
|
||
|
|
status: error.response?.status
|
||
|
|
});
|
||
|
|
|
||
|
|
// Return a 404 or error response
|
||
|
|
res.status(error.response?.status || 500).json({
|
||
|
|
error: 'Failed to fetch logo',
|
||
|
|
message: error.message
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
module.exports = router;
|