streamflow/backend/routes/logo-proxy.js

123 lines
3.7 KiB
JavaScript
Raw Normal View History

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;