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:
2025-11-29 18:13:17 +09:00
commit 409e6c146b
53 changed files with 16195 additions and 0 deletions

198
src/app.js Normal file
View 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);