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:
2025-10-22 20:32:16 +09:00
parent 150891b29d
commit 9477ff6de0
69 changed files with 11451 additions and 2321 deletions

134
server.js
View File

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