require('dotenv').config(); const express = require('express'); const sessionMiddleware = require('./lib/session'); const log = require('./lib/logger'); const authMiddleware = require('./lib/auth'); const app = express(); app.set('trust proxy', 1); // Security headers const helmet = require('helmet'); app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", "https://mc.yandex.ru"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https://mc.yandex.ru"], connectSrc: ["'self'", "wss:", "ws:"], fontSrc: ["'self'"], objectSrc: ["'none'"], frameAncestors: ["'none'"], baseUri: ["'self'"], formAction: ["'self'"], scriptSrcAttr: ["'unsafe-inline'"], }, }, crossOriginEmbedderPolicy: false, })); // Session app.use(sessionMiddleware); // Static assets (before auth — always public) app.use(express.static('public')); // Auth routes (before auth middleware — public) app.use('/auth', require('./routes/auth')); // Public API (only specific endpoints for landing page) const apiRouter = require('./routes/api'); app.get('/api/settings', apiRouter); app.get('/api/tools', apiRouter); app.get('/api/content/advantages', apiRouter); app.get('/api/content/dashboard', apiRouter); // Public status page (before auth — no login required) app.use('/status', require('./routes/status')); // Auth middleware (protects everything below) app.use(authMiddleware); // Protected API (all other /api/* routes) app.use('/api', apiRouter); // Health check (extended) const os = require('os'); const startTime = Date.now(); app.get('/health', (req, res) => { const mem = process.memoryUsage(); const queue = require('./lib/queue'); const wsInfo = require('./lib/ws'); const loadavg = os.loadavg(); res.json({ status: 'ok', uptime: Math.floor((Date.now() - startTime) / 1000), memory: { rss: Math.round(mem.rss / 1024 / 1024) + 'MB', heap: Math.round(mem.heapUsed / 1024 / 1024) + '/' + Math.round(mem.heapTotal / 1024 / 1024) + 'MB', system: Math.round((1 - os.freemem() / os.totalmem()) * 100) + '%', }, cpu: { load1m: loadavg[0].toFixed(2), load5m: loadavg[1].toFixed(2), load15m: loadavg[2].toFixed(2), cores: os.cpus().length, }, queue: { stats: queue.getStats(), active: queue.getActiveCount(), pending: queue.getQueueDepth(), }, ws: { connections: wsInfo.getConnectionCount(), }, node: process.version, pid: process.pid, }); }); // Routes const compressRouter = require('./routes/compress'); app.use('/compress', compressRouter); // Download endpoint const fs = require('fs'); const path = require('path'); app.get('/download/:filename', (req, res) => { const filename = path.basename(req.params.filename); const filePath = path.join(require('./lib/storage').RESULTS_DIR, filename); if (!fs.existsSync(filePath)) return res.status(404).json({ error: 'Файл не найден' }); res.download(filePath, 'compressed.zip'); }); app.use(require('./routes/parser')); app.use('/placeholder', require('./routes/placeholder')); app.use('/placeholder-img', require('./routes/placeholder')); app.use(require('./routes/httpclient')); app.use(require('./routes/redirects')); app.use(require('./routes/svgeditor')); app.use('/video', require('./routes/video')); app.use('/pdf', require('./routes/pdf')); app.use('/favicon', require('./routes/favicon')); app.use(require('./routes/pages')); // Global error handler app.use((err, req, res, next) => { log.error('Unhandled error', { error: err.message, stack: err.stack }); res.status(500).json({ error: 'Внутренняя ошибка сервера' }); }); // Start server (async for AdminJS setup) const PORT = parseInt(process.env.PORT) || 3000; (async () => { // AdminJS — loaded async (ESM modules) try { const { setupAdmin } = require('./lib/admin'); await setupAdmin(app); log.info('AdminJS mounted at /admin'); } catch (err) { log.error('AdminJS setup failed', { error: err.message }); } const server = app.listen(PORT, () => { log.info(`Server started on http://localhost:${PORT}`); }); // WebSocket const wsServer = require('./lib/ws'); wsServer.attach(server); // Graceful shutdown function shutdown(signal) { log.info(`${signal} received, shutting down gracefully...`); server.close(() => { log.info('Server closed'); process.exit(0); }); setTimeout(() => { log.warn('Forced shutdown after timeout'); process.exit(1); }, 10000); } process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); })(); process.on('unhandledRejection', (err) => { log.error('Unhandled rejection', { error: err?.message || String(err) }); }); process.on('uncaughtException', (err) => { log.error('Uncaught exception', { error: err.message, stack: err.stack }); process.exit(1); });