feat: Реализован полный CRUD для админ-панели и улучшена функциональность
- Portfolio CRUD: добавление, редактирование, удаление, переключение публикации - Services CRUD: полное управление услугами с возможностью активации/деактивации - Banner system: новая модель Banner с CRUD операциями и аналитикой кликов - Telegram integration: расширенные настройки бота, обнаружение чатов, отправка сообщений - Media management: улучшенная загрузка файлов с оптимизацией изображений и превью - UI improvements: обновлённые админ-панели с rich-text редактором и drag&drop загрузкой - Database: добавлена таблица banners с полями для баннеров и аналитики
This commit is contained in:
134
server.js
134
server.js
@@ -1,27 +1,54 @@
|
||||
const express = require('express');
|
||||
const mongoose = require('mongoose');
|
||||
const { sequelize, testConnection } = require('./config/database');
|
||||
const session = require('express-session');
|
||||
const MongoStore = require('connect-mongo');
|
||||
const SequelizeStore = require('connect-session-sequelize')(session.Store);
|
||||
const path = require('path');
|
||||
const helmet = require('helmet');
|
||||
const compression = require('compression');
|
||||
const cors = require('cors');
|
||||
const morgan = require('morgan');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const i18n = require('i18n');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
|
||||
// Настройка i18n
|
||||
i18n.configure({
|
||||
locales: ['ko', 'en', 'ru', 'kk'],
|
||||
defaultLocale: 'ru',
|
||||
directory: path.join(__dirname, 'locales'),
|
||||
objectNotation: true,
|
||||
updateFiles: false,
|
||||
syncFiles: false
|
||||
});
|
||||
|
||||
// i18n middleware
|
||||
app.use(i18n.init);
|
||||
|
||||
// Middleware для передачи переменных в шаблоны
|
||||
app.use((req, res, next) => {
|
||||
const currentLang = req.session?.language || req.getLocale() || 'ru';
|
||||
req.setLocale(currentLang);
|
||||
|
||||
res.locals.locale = currentLang;
|
||||
res.locals.__ = res.__;
|
||||
res.locals.theme = req.session?.theme || 'light';
|
||||
res.locals.currentLanguage = currentLang;
|
||||
res.locals.currentPage = req.path.split('/')[1] || 'home';
|
||||
next();
|
||||
});
|
||||
|
||||
// Security middleware
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"],
|
||||
fontSrc: ["'self'", "https://fonts.gstatic.com"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://cdn.tailwindcss.com"],
|
||||
fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", "https://unpkg.com", "https://cdn.tailwindcss.com"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'", "ws:", "wss:"]
|
||||
connectSrc: ["'self'", "ws:", "wss:", "https://cdnjs.cloudflare.com", "https://cdn.jsdelivr.net", "https://unpkg.com", "https://fonts.googleapis.com", "https://fonts.gstatic.com", "https://cdn.tailwindcss.com"]
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -48,23 +75,30 @@ app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
// Database connection
|
||||
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech', {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
})
|
||||
.then(() => console.log('✓ MongoDB connected'))
|
||||
.catch(err => console.error('✗ MongoDB connection error:', err));
|
||||
// Layout engine
|
||||
const expressLayouts = require('express-ejs-layouts');
|
||||
app.use(expressLayouts);
|
||||
app.set('layout', 'layout'); // Default layout for main site
|
||||
app.set('layout extractScripts', true);
|
||||
app.set('layout extractStyles', true);
|
||||
|
||||
// Database connection and testing
|
||||
testConnection();
|
||||
|
||||
// Session store configuration
|
||||
const sessionStore = new SequelizeStore({
|
||||
db: sequelize,
|
||||
tableName: 'sessions',
|
||||
checkExpirationInterval: 15 * 60 * 1000, // 15 minutes
|
||||
expiration: 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||
});
|
||||
|
||||
// Session configuration
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: MongoStore.create({
|
||||
mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/smartsoltech',
|
||||
touchAfter: 24 * 3600 // lazy session update
|
||||
}),
|
||||
store: sessionStore,
|
||||
cookie: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: true,
|
||||
@@ -80,8 +114,32 @@ app.use('/api/services', require('./routes/services'));
|
||||
app.use('/api/calculator', require('./routes/calculator'));
|
||||
app.use('/api/contact', require('./routes/contact'));
|
||||
app.use('/api/media', require('./routes/media'));
|
||||
app.use('/api/admin', require('./routes/api/admin'));
|
||||
app.use('/admin', require('./routes/admin'));
|
||||
|
||||
// Language switching routes
|
||||
app.get('/lang/:language', (req, res) => {
|
||||
const { language } = req.params;
|
||||
const supportedLanguages = ['ko', 'en', 'ru', 'kk'];
|
||||
|
||||
if (supportedLanguages.includes(language)) {
|
||||
req.setLocale(language);
|
||||
req.session.language = language;
|
||||
}
|
||||
|
||||
const referer = req.get('Referer') || '/';
|
||||
res.redirect(referer);
|
||||
});
|
||||
|
||||
// Theme switching routes
|
||||
app.get('/theme/:theme', (req, res) => {
|
||||
const { theme } = req.params;
|
||||
if (['light', 'dark'].includes(theme)) {
|
||||
req.session.theme = theme;
|
||||
}
|
||||
res.json({ success: true, theme: req.session.theme });
|
||||
});
|
||||
|
||||
// PWA Service Worker
|
||||
app.get('/sw.js', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'sw.js'));
|
||||
@@ -95,27 +153,47 @@ app.get('/manifest.json', (req, res) => {
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: process.env.NODE_ENV === 'production'
|
||||
? 'Something went wrong!'
|
||||
: err.message
|
||||
: err.message,
|
||||
currentPage: 'error'
|
||||
});
|
||||
});
|
||||
|
||||
// 404 handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).render('404', {
|
||||
title: '404 - Страница не найдена',
|
||||
message: 'Запрашиваемая страница не найдена'
|
||||
res.status(404).render('error', {
|
||||
title: '404 - 페이지를 찾을 수 없습니다',
|
||||
settings: {},
|
||||
message: '요청하신 페이지를 찾을 수 없습니다',
|
||||
currentPage: 'error'
|
||||
});
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Server running on port ${PORT}`);
|
||||
console.log(`🌐 Visit: http://localhost:${PORT}`);
|
||||
});
|
||||
// Sync database and start server
|
||||
async function startServer() {
|
||||
try {
|
||||
// Sync all models with database
|
||||
await sequelize.sync({ force: false });
|
||||
console.log('✓ Database synchronized');
|
||||
|
||||
// Create session table
|
||||
await sessionStore.sync();
|
||||
console.log('✓ Session store synchronized');
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Server running on port ${PORT}`);
|
||||
console.log(`🌐 Visit: http://localhost:${PORT}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('✗ Failed to start server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
startServer();
|
||||
Reference in New Issue
Block a user