Files
tourrism_site/src/app.js
Andrey K. Choi a461fea9d9 Компактные hero секции и улучшенная инициализация БД
🎨 UI улучшения:
- Уменьшена высота синих панелей с 100vh до 70vh на главной
- Добавлен класс .compact (25vh) для всех остальных страниц
- Улучшена адаптивность для мобильных устройств
- Обновлены все шаблоны с hero секциями

🚀 Инфраструктура:
- Автоматическая инициализация базы данных при деплое
- Улучшены мокапные данные (больше отзывов, бронирований, сообщений)
- Добавлены настройки сайта в базу данных
- Создан скрипт автоматического деплоя deploy.sh

📦 Система сборки:
- Обновлен .gitignore с полным покрытием файлов
- Добавлена папка для загрузок с .gitkeep
- Улучшен README с инструкциями по запуску
- ES модули для инициализации базы данных

🐛 Исправления:
- Совместимость с ES модулями в Node.js
- Правильная обработка ошибок инициализации БД
- Корректные SQL запросы для PostgreSQL
2025-11-29 18:47:42 +09:00

211 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);