wa-dev-tools/routes/placeholder.js
treamz 5b528d243a Refactor: modular architecture, security, config
- Split monolithic server.js (1111 lines) into route modules
- Add .env config (port, session secret, quality, limits)
- Add SSRF protection for parser/proxy/redirect endpoints
- Add optional password auth middleware
- Add structured logger (replaces raw fs.appendFileSync)
- Add graceful shutdown with timeout
- Add extended /health endpoint (uptime, memory, pid)
- Add ecosystem.config.js for PM2 (memory limit, restart policy)
- Make compress quality configurable (was hardcoded 60)
- Expand SVG AI icons library (7 -> 20 icons)
- Add dotenv dependency
2026-03-21 22:18:54 +03:00

63 lines
2.2 KiB
JavaScript

const express = require('express');
const sharp = require('sharp');
const path = require('path');
const log = require('../lib/logger');
const router = express.Router();
function normalizeColor(color) {
if (!color) return '#cccccc';
if (/^[0-9A-Fa-f]{6}$/.test(color)) return '#' + color;
if (/^[0-9A-Fa-f]{3}$/.test(color)) return '#' + color;
return color.toLowerCase();
}
function escapeXml(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
async function placeholderHandler(req, res) {
const { size, bg, fg, text: textFromPath } = req.params;
const { text: textFromQuery, format = 'png', fontsize } = req.query;
const text = textFromQuery || textFromPath || '';
const [w, h] = size.split('x').map(Number);
const width = Math.min(Math.max(w || 300, 1), 4000);
const height = Math.min(Math.max(h || 200, 1), 4000);
const bgColor = normalizeColor(bg);
const fgColor = normalizeColor(fg);
const fontSize = fontsize ? parseInt(fontsize, 10) : Math.max(12, Math.floor(Math.min(width, height) / 8));
const svg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="${escapeXml(bgColor)}"/>
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle"
font-family="sans-serif" font-weight="bold" font-size="${fontSize}"
fill="${escapeXml(fgColor)}">${escapeXml(text)}</text>
</svg>`;
try {
let img = sharp(Buffer.from(svg));
if (format === 'jpg' || format === 'jpeg') {
res.type('jpeg').end(await img.jpeg({ quality: 90 }).toBuffer());
} else if (format === 'webp') {
res.type('webp').end(await img.webp({ quality: 90 }).toBuffer());
} else {
res.type('png').end(await img.png().toBuffer());
}
} catch (err) {
log.error('Placeholder error', { error: err.message });
res.status(500).json({ error: 'Generation failed' });
}
}
// Placeholder page
router.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '..', 'public', 'placeholder.html'));
});
router.get('/:size/:bg/:fg', placeholderHandler);
router.get('/:size/:bg/:fg/:text', placeholderHandler);
module.exports = router;