Files
tourrism_site/src/app.js
Andrey K. Choi b4e513e996 🚀 Korea Tourism Agency - Complete implementation
 Features:
- Modern tourism website with responsive design
- AdminJS admin panel with image editor integration
- PostgreSQL database with comprehensive schema
- Docker containerization
- Image upload and gallery management

🛠 Tech Stack:
- Backend: Node.js + Express.js
- Database: PostgreSQL 13+
- Frontend: HTML/CSS/JS with responsive design
- Admin: AdminJS with custom components
- Deployment: Docker + Docker Compose
- Image Processing: Sharp with optimization

📱 Admin Features:
- Routes/Tours management (city, mountain, fishing)
- Guides profiles with specializations
- Articles and blog system
- Image editor with upload/gallery/URL options
- User management and authentication
- Responsive admin interface

🎨 Design:
- Korean tourism focused branding
- Mobile-first responsive design
- Custom CSS with modern aesthetics
- Image optimization and gallery
- SEO-friendly structure

🔒 Security:
- Helmet.js security headers
- bcrypt password hashing
- Input validation and sanitization
- CORS protection
- Environment variables
2025-11-30 00:53:15 +09:00

236 lines
7.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 SiteSettingsHelper from './helpers/site-settings.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'", "'unsafe-eval'", "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 with site settings
app.use(async (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
// Load site settings for templates
try {
res.locals.siteSettings = await SiteSettingsHelper.getAllSettings();
} catch (error) {
console.error('Error loading site settings for templates:', error);
res.locals.siteSettings = {};
}
// 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 settingsRouter = (await import('./routes/settings.js')).default;
const ratingsRouter = (await import('./routes/ratings.js')).default;
const imagesRouter = (await import('./routes/images.js')).default;
const crudRouter = (await import('./routes/crud.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);
app.use('/', settingsRouter); // Settings routes (CSS and API)
app.use('/api/images', imagesRouter); // Image management routes
app.use('/api/crud', crudRouter); // CRUD API routes
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// Test image editor endpoint
app.get('/test-editor', (req, res) => {
res.sendFile(path.join(__dirname, '../public/test-editor.html'));
});
// Image system documentation
app.get('/image-docs', (req, res) => {
res.sendFile(path.join(__dirname, '../public/image-system-docs.html'));
});
// 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);