- 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
63 lines
2.2 KiB
JavaScript
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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
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;
|