- Local auth: registration/login with email+password (bcrypt, MariaDB) - Landing page: marketing page at / with hero, categories, advantages, CTA - Tool categories: Images (4), Code (3), Web (3), Utilities (2) - Sidebar: category dividers, user avatar with logout - Protected routes: all tools require auth, landing/login/register public - New files: lib/db.js, routes/auth.js, login.html, register.html, landing.html - Dependencies: bcrypt, mysql2
102 lines
3.0 KiB
JavaScript
Executable File
102 lines
3.0 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'));
|
|
|
|
// Auth middleware (protects everything below)
|
|
app.use(authMiddleware);
|
|
|
|
// 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(require('./routes/logs'));
|
|
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
|
|
const PORT = parseInt(process.env.PORT) || 3000;
|
|
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);
|
|
});
|
|
// Force close after 10 seconds
|
|
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);
|
|
});
|