Initial commit: Korea Tourism Agency website with AdminJS
- Full-stack Node.js/Express application with PostgreSQL - Modern ES modules architecture - AdminJS admin panel with Sequelize ORM - Tourism routes, guides, articles, bookings management - Responsive Bootstrap 5 frontend - Docker containerization with docker-compose - Complete database schema with migrations - Authentication system for admin panel - Dynamic placeholder images for tour categories
This commit is contained in:
198
src/app.js
Normal file
198
src/app.js
Normal file
@@ -0,0 +1,198 @@
|
||||
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() {
|
||||
|
||||
// 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;
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/routes', toursRouter);
|
||||
app.use('/guides', guidesRouter);
|
||||
app.use('/articles', articlesRouter);
|
||||
app.use('/api', apiRouter);
|
||||
|
||||
// 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);
|
||||
Reference in New Issue
Block a user