279 lines
9.4 KiB
JavaScript
279 lines
9.4 KiB
JavaScript
const express = require('express');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const crypto = require('crypto');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 8888;
|
|
|
|
// Upstash Redis Configuration
|
|
const UPSTASH_URL = 'https://poetic-goshawk-11700.upstash.io';
|
|
const UPSTASH_TOKEN = 'AS20AAIjcDE5YTVjY2VhZDJjOWY0ZDQyOTMzMTlkODFjNjkzZWZmOXAxMA';
|
|
|
|
// Cache TTL configurations (in seconds)
|
|
const CACHE_TTL = {
|
|
html: 3600, // 1 hour for HTML pages
|
|
css: 86400, // 1 day for CSS
|
|
js: 86400, // 1 day for JS
|
|
images: 604800, // 1 week for images
|
|
api: 1800, // 30 minutes for API responses
|
|
default: 3600 // 1 hour default
|
|
};
|
|
|
|
// Helper function to get cache key
|
|
function getCacheKey(url) {
|
|
return `page_${crypto.createHash('md5').update(url).digest('hex')}`;
|
|
}
|
|
|
|
// Helper function to get file extension
|
|
function getFileExtension(filePath) {
|
|
return path.extname(filePath).toLowerCase().slice(1) || 'html';
|
|
}
|
|
|
|
// Upstash cache operations
|
|
async function getFromCache(key) {
|
|
try {
|
|
const response = await fetch(`${UPSTASH_URL}/get/${encodeURIComponent(key)}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${UPSTASH_TOKEN}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
return data.result;
|
|
}
|
|
} catch (error) {
|
|
console.error('Cache get error:', error.message);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function setCache(key, value, ttl = CACHE_TTL.default) {
|
|
try {
|
|
const response = await fetch(`${UPSTASH_URL}/set/${encodeURIComponent(key)}/EX/${ttl}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${UPSTASH_TOKEN}`,
|
|
'Content-Type': 'text/plain'
|
|
},
|
|
body: value
|
|
});
|
|
|
|
return response.ok;
|
|
} catch (error) {
|
|
console.error('Cache set error:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Parse JSON bodies for cache API
|
|
app.use(express.json());
|
|
|
|
// HTTP Cache headers middleware
|
|
app.use((req, res, next) => {
|
|
const ext = path.extname(req.path).toLowerCase();
|
|
|
|
// Set browser cache headers based on file type
|
|
if (['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.ico'].includes(ext)) {
|
|
res.setHeader('Cache-Control', 'public, max-age=604800, immutable'); // 1 week
|
|
} else if (['.css', '.js'].includes(ext) && !req.path.includes('animations.js')) {
|
|
res.setHeader('Cache-Control', 'public, max-age=86400, must-revalidate'); // 1 day
|
|
} else if (ext === '.html' || req.path === '/') {
|
|
res.setHeader('Cache-Control', 'public, max-age=3600, must-revalidate'); // 1 hour
|
|
} else if (['.woff', '.woff2', '.ttf', '.eot'].includes(ext)) {
|
|
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable'); // 30 days
|
|
}
|
|
|
|
// Security headers
|
|
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
|
|
next();
|
|
});
|
|
|
|
// Cache management API endpoint for stats
|
|
app.get('/api/cache/stats', async (req, res) => {
|
|
try {
|
|
const response = await fetch(`${UPSTASH_URL}/dbsize`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${UPSTASH_TOKEN}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
res.json({
|
|
success: true,
|
|
stats: {
|
|
keys: data.result || 0,
|
|
connected: true,
|
|
backend: 'Upstash Redis'
|
|
}
|
|
});
|
|
} else {
|
|
res.json({ success: false, error: 'Failed to get stats' });
|
|
}
|
|
} catch (error) {
|
|
res.status(500).json({ success: false, error: error.message });
|
|
}
|
|
});
|
|
|
|
// Middleware for Upstash caching - simplified version
|
|
async function upstashCacheMiddleware(req, res, next) {
|
|
// Skip caching for non-GET requests
|
|
if (req.method !== 'GET') {
|
|
return next();
|
|
}
|
|
|
|
// Skip caching for API endpoints
|
|
if (req.path.startsWith('/api/')) {
|
|
return next();
|
|
}
|
|
|
|
const cacheKey = getCacheKey(req.url);
|
|
const ext = getFileExtension(req.path);
|
|
|
|
// Try to get from cache
|
|
const cached = await getFromCache(cacheKey);
|
|
|
|
if (cached) {
|
|
// Set cache hit header
|
|
res.set('X-Cache', 'HIT');
|
|
res.set('X-Cache-Key', cacheKey);
|
|
res.set('Content-Type', ext === 'html' ? 'text/html' : `text/${ext}`);
|
|
res.send(cached);
|
|
console.log(`Cache HIT: ${req.path}`);
|
|
return;
|
|
}
|
|
|
|
// Cache MISS - continue with normal processing
|
|
console.log(`Cache MISS: ${req.path}`);
|
|
res.set('X-Cache', 'MISS');
|
|
res.set('X-Cache-Key', cacheKey);
|
|
|
|
next();
|
|
}
|
|
|
|
// Apply Upstash caching middleware for HTML pages only
|
|
app.get('/', upstashCacheMiddleware, async (req, res) => {
|
|
const indexPath = path.join(__dirname, 'index.html');
|
|
|
|
fs.readFile(indexPath, 'utf8', async (err, content) => {
|
|
if (err) {
|
|
res.status(404).send('Page not found');
|
|
return;
|
|
}
|
|
|
|
// Cache the content if it was a miss
|
|
if (res.get('X-Cache') === 'MISS') {
|
|
const cacheKey = getCacheKey(req.url);
|
|
await setCache(cacheKey, content, CACHE_TTL.html);
|
|
console.log('Homepage cached');
|
|
}
|
|
|
|
res.send(content);
|
|
});
|
|
});
|
|
|
|
// Serve static files with caching
|
|
app.use(express.static(__dirname, {
|
|
etag: true,
|
|
lastModified: true,
|
|
setHeaders: (res, filePath) => {
|
|
if (filePath.endsWith('.html')) {
|
|
res.setHeader('X-Powered-By', 'Odoo Expertos with Upstash Cache');
|
|
}
|
|
}
|
|
}));
|
|
|
|
// Handle directory routes
|
|
app.get('*', async (req, res) => {
|
|
const requestedPath = path.join(__dirname, req.path);
|
|
|
|
// Check cache for HTML pages
|
|
if (req.path.endsWith('/') || req.path.endsWith('.html')) {
|
|
const cacheKey = getCacheKey(req.url);
|
|
const cached = await getFromCache(cacheKey);
|
|
|
|
if (cached) {
|
|
res.set('X-Cache', 'HIT');
|
|
res.set('Content-Type', 'text/html');
|
|
res.send(cached);
|
|
console.log(`Cache HIT: ${req.path}`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (fs.existsSync(requestedPath) && fs.statSync(requestedPath).isFile()) {
|
|
const content = fs.readFileSync(requestedPath, 'utf8');
|
|
|
|
// Cache HTML files
|
|
if (requestedPath.endsWith('.html')) {
|
|
const cacheKey = getCacheKey(req.url);
|
|
await setCache(cacheKey, content, CACHE_TTL.html);
|
|
res.set('X-Cache', 'MISS');
|
|
console.log(`Cached: ${req.path}`);
|
|
}
|
|
|
|
res.sendFile(requestedPath);
|
|
return;
|
|
}
|
|
|
|
if (fs.existsSync(requestedPath) && fs.statSync(requestedPath).isDirectory()) {
|
|
const indexPath = path.join(requestedPath, 'index.html');
|
|
if (fs.existsSync(indexPath)) {
|
|
const content = fs.readFileSync(indexPath, 'utf8');
|
|
|
|
// Cache the directory index
|
|
const cacheKey = getCacheKey(req.url);
|
|
await setCache(cacheKey, content, CACHE_TTL.html);
|
|
res.set('X-Cache', 'MISS');
|
|
console.log(`Cached directory index: ${req.path}`);
|
|
|
|
res.send(content);
|
|
} else {
|
|
res.status(404).send('Directory index not found');
|
|
}
|
|
} else {
|
|
const notFoundPath = path.join(__dirname, '404.html');
|
|
if (fs.existsSync(notFoundPath)) {
|
|
res.status(404).sendFile(notFoundPath);
|
|
} else {
|
|
res.status(404).send('Page not found');
|
|
}
|
|
}
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`
|
|
╔════════════════════════════════════════════════════════════╗
|
|
║ ║
|
|
║ 🚀 Odoo Expertos with Upstash Redis Cache ║
|
|
║ ║
|
|
║ Local URL: http://localhost:${PORT} ║
|
|
║ ║
|
|
║ ✅ Upstash Redis caching active ║
|
|
║ ✅ Global page caching enabled ║
|
|
║ ✅ HTTP cache headers configured ║
|
|
║ ✅ Performance optimizations active ║
|
|
║ ║
|
|
║ Cache Stats: http://localhost:${PORT}/api/cache/stats ║
|
|
║ ║
|
|
║ Press Ctrl+C to stop the server ║
|
|
║ ║
|
|
╚════════════════════════════════════════════════════════════╝
|
|
`);
|
|
|
|
// Test Upstash connection
|
|
getFromCache('test').then(() => {
|
|
console.log('✅ Upstash Redis connected successfully');
|
|
}).catch(error => {
|
|
console.error('⚠️ Upstash connection error:', error.message);
|
|
});
|
|
|
|
// Open browser automatically
|
|
const opener = require('opener');
|
|
opener(`http://localhost:${PORT}`);
|
|
}); |