wa-dev-tools/server.js
treamz 6ac3366ea9 Security: lock down API — only landing endpoints public
- Only /api/settings, /api/tools, /api/content/advantages|dashboard public
- All other /api/* require authentication (401)
- Moved API mount after auth middleware, public endpoints before
2026-03-22 00:28:32 +03:00

124 lines
3.7 KiB
JavaScript
Executable File

require('dotenv').config();
const express = require('express');
const session = require('express-session');
const log = require('./lib/logger');
const authMiddleware = require('./lib/auth');
const app = express();
app.set('trust proxy', 1);
// Session
app.use(session({
secret: process.env.SESSION_SECRET || 'change-me-in-env',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 30 * 24 * 60 * 60 * 1000 },
}));
// 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);
// Auth middleware (protects everything below)
app.use(authMiddleware);
// Protected API (all other /api/* routes)
app.use('/api', apiRouter);
// Health check (extended)
const startTime = Date.now();
app.get('/health', (req, res) => {
const mem = process.memoryUsage();
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',
},
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(compressRouter.DOWNLOADS_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(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}`);
});
// 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);
});