import express from 'express'; import path from 'path'; import session from 'express-session'; import cors from 'cors'; import helmet from 'helmet'; import compression from 'compression'; import morgan from 'morgan'; import methodOverride from 'method-override'; import formatters from './helpers/formatters.js'; import { adminJs, router as adminRouter } from './config/adminjs-simple.js'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { createRequire } from 'module'; import dotenv from 'dotenv'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const require = createRequire(import.meta.url); dotenv.config(); const app = express(); const PORT = process.env.PORT || 3000; async function setupApp() { // Initialize database on startup try { console.log('🚀 Initializing database...'); const { initDatabase } = await import('../database/init-database.js'); await initDatabase(); console.log('✅ Database initialized successfully'); } catch (error) { console.error('❌ Database initialization failed:', error); console.log('⚠️ Continuing without database initialization...'); } // Security middleware app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"], fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://code.jquery.com"], imgSrc: ["'self'", "data:", "https:", "blob:"], connectSrc: ["'self'"], }, }, })); app.use(compression()); app.use(morgan('combined')); app.use(cors()); // Method override for PUT/DELETE requests app.use(methodOverride('_method')); // Static files app.use(express.static(path.join(__dirname, '../public'))); // Serve node_modules for AdminLTE assets app.use('/node_modules', express.static(path.join(__dirname, '../node_modules'))); // Session configuration app.use(session({ secret: process.env.SESSION_SECRET || 'korea-tourism-secret', resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production', maxAge: 24 * 60 * 60 * 1000 // 24 hours } })); // View engine setup app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, '../views')); // Global template variables app.use((req, res, next) => { res.locals.siteName = process.env.SITE_NAME || 'Корея Тур Агентство'; res.locals.siteDescription = process.env.SITE_DESCRIPTION || 'Откройте для себя красоту Кореи'; res.locals.user = req.session.user || null; res.locals.admin = req.session.admin || null; res.locals.currentPath = req.path; res.locals.page = 'home'; // default page // Add all helper functions to template globals Object.assign(res.locals, formatters); next(); }); // Layout middleware app.use((req, res, next) => { const originalRender = res.render; res.render = function(view, locals, callback) { if (typeof locals === 'function') { callback = locals; locals = {}; } locals = locals || {}; // Check if it's an admin route if (req.path.startsWith('/admin')) { // Check if a custom layout is specified if (locals.layout) { const customLayout = locals.layout; delete locals.layout; // Render the view content first originalRender.call(this, view, locals, (err, html) => { if (err) return callback ? callback(err) : next(err); // Then render the custom layout with the content locals.body = html; originalRender.call(res, customLayout, locals, callback); }); } else { return originalRender.call(this, view, locals, callback); } } else { // Render the view content first originalRender.call(this, view, locals, (err, html) => { if (err) return callback ? callback(err) : next(err); // Then render the layout with the content locals.body = html; originalRender.call(res, 'layout', locals, callback); }); } }; next(); }); // Routes app.use(adminJs.options.rootPath, adminRouter); // AdminJS routes // Body parser middleware (after AdminJS) app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Dynamic imports for CommonJS routes const indexRouter = (await import('./routes/index.js')).default; const toursRouter = (await import('./routes/tours.js')).default; const guidesRouter = (await import('./routes/guides.js')).default; const articlesRouter = (await import('./routes/articles.js')).default; const apiRouter = (await import('./routes/api.js')).default; const ratingsRouter = (await import('./routes/ratings.js')).default; app.use('/', indexRouter); app.use('/routes', toursRouter); app.use('/guides', guidesRouter); app.use('/articles', articlesRouter); app.use('/api', apiRouter); app.use('/api', ratingsRouter); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime() }); }); // Error handling app.use((req, res) => { res.status(404).render('error', { title: '404 - Page Not Found', message: 'The page you are looking for does not exist.', error: { status: 404 }, layout: 'layout' }); }); app.use((err, req, res, next) => { console.error('Error:', err.stack); // Don't expose stack trace in production const isDev = process.env.NODE_ENV === 'development'; res.status(err.status || 500).render('error', { title: `${err.status || 500} - Server Error`, message: isDev ? err.message : 'Something went wrong on our server.', error: isDev ? err : { status: err.status || 500 }, layout: 'layout' }); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully'); server.close(() => { console.log('Process terminated'); }); }); const server = app.listen(PORT, '0.0.0.0', () => { console.log(`🚀 Korea Tourism Agency server running on port ${PORT}`); console.log(`📍 Environment: ${process.env.NODE_ENV || 'development'}`); console.log(`🔧 Admin panel: http://localhost:${PORT}${adminJs.options.rootPath}`); console.log(`🏠 Website: http://localhost:${PORT}`); }); } // Start the application setupApp().catch(console.error);