This commit is contained in:
oliver
2026-08-28 10:11:11 -03:00
parent f533ff8e36
commit 585951c8ce
1380 changed files with 1540268 additions and 0 deletions
+360
View File
@@ -0,0 +1,360 @@
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 = process.env.REDIS_URL || 'https://poetic-goshawk-11700.upstash.io';
const UPSTASH_TOKEN = process.env.REDIS_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/${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/${key}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${UPSTASH_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
value: value,
ex: ttl
})
});
return response.ok;
} catch (error) {
console.error('Cache set error:', error.message);
return false;
}
}
async function invalidateCache(pattern) {
try {
// Get all keys matching pattern
const response = await fetch(`${UPSTASH_URL}/keys/${pattern}*`, {
headers: {
'Authorization': `Bearer ${UPSTASH_TOKEN}`
}
});
if (response.ok) {
const data = await response.json();
const keys = data.result || [];
// Delete all matching keys
for (const key of keys) {
await fetch(`${UPSTASH_URL}/del/${key}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${UPSTASH_TOKEN}`
}
});
}
}
} catch (error) {
console.error('Cache invalidation error:', error.message);
}
}
// Middleware for Upstash caching
async function upstashCacheMiddleware(req, res, next) {
// Skip caching for POST, PUT, DELETE requests
if (req.method !== 'GET') {
return next();
}
// Skip caching for admin or special URLs
if (req.path.includes('/api/') || req.path.includes('/admin')) {
return next();
}
const cacheKey = getCacheKey(req.url);
const ext = getFileExtension(req.path);
// Try to get from cache
const cached = await getFromCache(cacheKey);
if (cached) {
// Parse cached data
try {
const cachedData = JSON.parse(cached);
// Set cached headers
res.set(cachedData.headers);
res.set('X-Cache', 'HIT');
res.set('X-Cache-Key', cacheKey);
// Send cached content
if (cachedData.type === 'file') {
res.type(cachedData.contentType).send(Buffer.from(cachedData.content, 'base64'));
} else {
res.send(cachedData.content);
}
console.log(`Cache HIT: ${req.path}`);
return;
} catch (error) {
console.error('Cache parse error:', error);
}
}
// Cache MISS - continue with normal processing
console.log(`Cache MISS: ${req.path}`);
// Override res.send to cache the response
const originalSend = res.send;
res.send = function(data) {
res.send = originalSend;
// Cache the response if successful
if (res.statusCode === 200) {
const ttl = CACHE_TTL[ext] || CACHE_TTL.default;
const cacheData = {
content: data,
headers: {
'Content-Type': res.get('Content-Type') || 'text/html',
'Cache-Control': `public, max-age=${ttl}`
},
timestamp: Date.now()
};
// Store in cache asynchronously
setCache(cacheKey, JSON.stringify(cacheData), ttl).then(success => {
if (success) {
console.log(`Cached: ${req.path} for ${ttl}s`);
}
});
}
res.set('X-Cache', 'MISS');
res.set('X-Cache-Key', cacheKey);
return res.send(data);
};
next();
}
// HTTP Cache headers middleware (works alongside Upstash)
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();
});
// Apply Upstash caching middleware
app.use(upstashCacheMiddleware);
// Parse JSON bodies for cache API
app.use(express.json());
// Cache management API endpoints
app.post('/api/cache/invalidate', async (req, res) => {
const { pattern } = req.body;
if (!pattern) {
return res.status(400).json({ error: 'Pattern required' });
}
await invalidateCache(pattern);
res.json({ success: true, message: `Cache invalidated for pattern: ${pattern}` });
});
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 });
}
});
// 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');
}
}
}));
// Special handling for root route
app.get('/', async (req, res) => {
const indexPath = path.join(__dirname, 'index.html');
// Check cache first
const cacheKey = getCacheKey('/');
const cached = await getFromCache(cacheKey);
if (cached) {
try {
const cachedData = JSON.parse(cached);
res.set(cachedData.headers);
res.set('X-Cache', 'HIT');
res.send(cachedData.content);
console.log('Homepage served from cache');
return;
} catch (error) {
console.error('Cache error:', error);
}
}
// Read and cache the file
fs.readFile(indexPath, 'utf8', async (err, content) => {
if (err) {
res.status(404).send('Page not found');
return;
}
// Cache the content
const cacheData = {
content: content,
headers: {
'Content-Type': 'text/html',
'Cache-Control': 'public, max-age=3600'
},
timestamp: Date.now()
};
await setCache(cacheKey, JSON.stringify(cacheData), CACHE_TTL.html);
res.set('X-Cache', 'MISS');
res.send(content);
console.log('Homepage cached');
});
});
// Handle directory routes
app.get('*', (req, res) => {
const requestedPath = path.join(__dirname, req.path);
if (fs.existsSync(requestedPath) && fs.statSync(requestedPath).isFile()) {
res.sendFile(requestedPath);
return;
}
if (fs.existsSync(requestedPath) && fs.statSync(requestedPath).isDirectory()) {
const indexPath = path.join(requestedPath, 'index.html');
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} 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}`);
});