220 lines
6.8 KiB
JavaScript
220 lines
6.8 KiB
JavaScript
const express = require('express');
|
|
const { sequelize, testConnection } = require('./config/database');
|
|
const session = require('express-session');
|
|
const SequelizeStore = require('connect-session-sequelize')(session.Store);
|
|
const path = require('path');
|
|
const helmet = require('helmet');
|
|
const compression = require('compression');
|
|
const cors = require('cors');
|
|
const morgan = require('morgan');
|
|
const rateLimit = require('express-rate-limit');
|
|
const i18n = require('i18n');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
|
|
// Настройка i18n
|
|
i18n.configure({
|
|
locales: ['ko', 'en', 'ru', 'kk'],
|
|
defaultLocale: 'ru',
|
|
directory: path.join(__dirname, 'locales'),
|
|
objectNotation: true,
|
|
updateFiles: false,
|
|
syncFiles: false
|
|
});
|
|
|
|
// i18n middleware
|
|
app.use(i18n.init);
|
|
|
|
// Security middleware
|
|
app.use(helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdn.tailwindcss.com"],
|
|
fontSrc: ["'self'", "https://fonts.gstatic.com"],
|
|
scriptSrc: ["'self'", "'unsafe-inline'", "https://unpkg.com", "https://cdn.tailwindcss.com"],
|
|
imgSrc: ["'self'", "data:", "https:"],
|
|
connectSrc: ["'self'", "ws:", "wss:", "https://fonts.googleapis.com", "https://fonts.gstatic.com", "https://cdn.tailwindcss.com"]
|
|
}
|
|
}
|
|
}));
|
|
|
|
// Rate limiting
|
|
const limiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 100 // limit each IP to 100 requests per windowMs
|
|
});
|
|
app.use('/api/', limiter);
|
|
|
|
// Middleware
|
|
app.use(compression());
|
|
app.use(cors());
|
|
app.use(morgan('combined'));
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// Static files
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));
|
|
|
|
// AdminLTE static files
|
|
app.use('/node_modules', express.static(path.join(__dirname, 'node_modules')));
|
|
|
|
// View engine
|
|
app.set('view engine', 'ejs');
|
|
app.set('views', path.join(__dirname, 'views'));
|
|
|
|
// Layout engine
|
|
const expressLayouts = require('express-ejs-layouts');
|
|
app.use(expressLayouts);
|
|
app.set('layout', 'layout'); // Default layout for main site
|
|
app.set('layout extractScripts', true);
|
|
app.set('layout extractStyles', true);
|
|
|
|
// Database connection and testing
|
|
testConnection();
|
|
|
|
// Session store configuration
|
|
const sessionStore = new SequelizeStore({
|
|
db: sequelize,
|
|
tableName: 'sessions',
|
|
checkExpirationInterval: 15 * 60 * 1000, // 15 minutes
|
|
expiration: 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
});
|
|
|
|
// Session configuration
|
|
app.use(session({
|
|
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
store: sessionStore,
|
|
cookie: {
|
|
secure: process.env.NODE_ENV === 'production',
|
|
httpOnly: true,
|
|
maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days
|
|
}
|
|
}));
|
|
|
|
// Middleware для передачи переменных в шаблоны
|
|
app.use((req, res, next) => {
|
|
const currentLang = req.session?.language || req.getLocale() || 'ru';
|
|
req.setLocale(currentLang);
|
|
|
|
res.locals.locale = currentLang;
|
|
res.locals.__ = res.__;
|
|
res.locals.theme = req.session?.theme || 'light';
|
|
res.locals.currentLanguage = currentLang;
|
|
res.locals.currentPage = req.path.split('/')[1] || 'home';
|
|
|
|
// Debug logging for theme
|
|
if (req.url !== '/sw.js' && !req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) {
|
|
console.log(`Request URL: ${req.url}, Theme from session: ${req.session?.theme}, Setting theme to: ${res.locals.theme}`);
|
|
}
|
|
|
|
// Устанавливаем заголовки для предотвращения кеширования языкового контента
|
|
if (!req.url.startsWith('/css/') && !req.url.startsWith('/js/') && !req.url.startsWith('/images/')) {
|
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
res.setHeader('Pragma', 'no-cache');
|
|
res.setHeader('Expires', '0');
|
|
res.setHeader('Vary', 'Accept-Language');
|
|
}
|
|
|
|
// Отладочная информация
|
|
console.log(`Request URL: ${req.url}, Current Language: ${currentLang}, Session Language: ${req.session?.language}, Locale: ${req.getLocale()}`);
|
|
|
|
next();
|
|
});
|
|
|
|
// Routes
|
|
app.use('/', require('./routes/index'));
|
|
app.use('/api/auth', require('./routes/auth'));
|
|
app.use('/api/portfolio', require('./routes/portfolio'));
|
|
app.use('/api/services', require('./routes/services'));
|
|
app.use('/api/calculator', require('./routes/calculator'));
|
|
app.use('/api/contact', require('./routes/contact'));
|
|
app.use('/api/media', require('./routes/media'));
|
|
app.use('/api/admin', require('./routes/api/admin'));
|
|
app.use('/admin', require('./routes/admin'));
|
|
|
|
// Language switching routes
|
|
app.get('/lang/:language', (req, res) => {
|
|
const { language } = req.params;
|
|
const supportedLanguages = ['ko', 'en', 'ru', 'kk'];
|
|
|
|
if (supportedLanguages.includes(language)) {
|
|
req.setLocale(language);
|
|
req.session.language = language;
|
|
console.log(`Language switched to: ${language}, session language: ${req.session.language}`);
|
|
}
|
|
|
|
const referer = req.get('Referer') || '/';
|
|
res.redirect(referer);
|
|
});
|
|
|
|
// Theme switching routes
|
|
app.get('/theme/:theme', (req, res) => {
|
|
const { theme } = req.params;
|
|
if (['light', 'dark'].includes(theme)) {
|
|
req.session.theme = theme;
|
|
}
|
|
res.json({ success: true, theme: req.session.theme });
|
|
});
|
|
|
|
// PWA Service Worker
|
|
app.get('/sw.js', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'public', 'sw.js'));
|
|
});
|
|
|
|
// PWA Manifest
|
|
app.get('/manifest.json', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'public', 'manifest.json'));
|
|
});
|
|
|
|
// Error handling middleware
|
|
app.use((err, req, res, next) => {
|
|
console.error(err.stack);
|
|
res.status(500).render('error', {
|
|
title: 'Error',
|
|
settings: {},
|
|
message: process.env.NODE_ENV === 'production'
|
|
? 'Something went wrong!'
|
|
: err.message,
|
|
currentPage: 'error'
|
|
});
|
|
});
|
|
|
|
// 404 handler
|
|
app.use((req, res) => {
|
|
res.status(404).render('error', {
|
|
title: '404 - 페이지를 찾을 수 없습니다',
|
|
settings: {},
|
|
message: '요청하신 페이지를 찾을 수 없습니다',
|
|
currentPage: 'error'
|
|
});
|
|
});
|
|
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// Sync database and start server
|
|
async function startServer() {
|
|
try {
|
|
// Sync all models with database
|
|
await sequelize.sync({ force: false });
|
|
console.log('✓ Database synchronized');
|
|
|
|
// Create session table
|
|
await sessionStore.sync();
|
|
console.log('✓ Session store synchronized');
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`🚀 Server running on port ${PORT}`);
|
|
console.log(`🌐 Visit: http://localhost:${PORT}`);
|
|
});
|
|
} catch (error) {
|
|
console.error('✗ Failed to start server:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
startServer(); |