Files
tourrism_site/src/config/adminjs-simple.js
Andrey K. Choi a461fea9d9 Компактные hero секции и улучшенная инициализация БД
🎨 UI улучшения:
- Уменьшена высота синих панелей с 100vh до 70vh на главной
- Добавлен класс .compact (25vh) для всех остальных страниц
- Улучшена адаптивность для мобильных устройств
- Обновлены все шаблоны с hero секциями

🚀 Инфраструктура:
- Автоматическая инициализация базы данных при деплое
- Улучшены мокапные данные (больше отзывов, бронирований, сообщений)
- Добавлены настройки сайта в базу данных
- Создан скрипт автоматического деплоя deploy.sh

📦 Система сборки:
- Обновлен .gitignore с полным покрытием файлов
- Добавлена папка для загрузок с .gitkeep
- Улучшен README с инструкциями по запуску
- ES модули для инициализации базы данных

🐛 Исправления:
- Совместимость с ES модулями в Node.js
- Правильная обработка ошибок инициализации БД
- Корректные SQL запросы для PostgreSQL
2025-11-29 18:47:42 +09:00

718 lines
26 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 AdminJS from 'adminjs';
import AdminJSExpress from '@adminjs/express';
import AdminJSSequelize from '@adminjs/sequelize';
import bcrypt from 'bcryptjs';
import pkg from 'pg';
import { Sequelize, DataTypes } from 'sequelize';
const { Pool } = pkg;
// Регистрируем адаптер Sequelize
AdminJS.registerAdapter(AdminJSSequelize);
// Создаем подключение Sequelize
const sequelize = new Sequelize(
process.env.DB_NAME || 'korea_tourism',
process.env.DB_USER || 'tourism_user',
process.env.DB_PASSWORD || 'tourism_password',
{
host: process.env.DB_HOST || 'postgres',
port: process.env.DB_PORT || 5432,
dialect: 'postgres',
logging: false,
}
);
// Создаем пул подключений для аутентификации (отдельно от Sequelize)
const authPool = new Pool({
host: process.env.DB_HOST || 'postgres',
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'korea_tourism',
user: process.env.DB_USER || 'tourism_user',
password: process.env.DB_PASSWORD || 'tourism_password',
});
// Определяем модели Sequelize
const Routes = sequelize.define('routes', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: DataTypes.STRING, allowNull: false },
description: { type: DataTypes.TEXT },
content: { type: DataTypes.TEXT },
type: { type: DataTypes.ENUM('city', 'mountain', 'fishing') },
difficulty_level: { type: DataTypes.ENUM('easy', 'moderate', 'hard') },
price: { type: DataTypes.DECIMAL(10, 2) },
duration: { type: DataTypes.INTEGER },
max_group_size: { type: DataTypes.INTEGER },
is_featured: { type: DataTypes.BOOLEAN, defaultValue: false },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'routes'
});
const Guides = sequelize.define('guides', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING },
phone: { type: DataTypes.STRING },
languages: { type: DataTypes.TEXT },
specialization: { type: DataTypes.ENUM('city', 'mountain', 'fishing', 'general') },
bio: { type: DataTypes.TEXT },
experience: { type: DataTypes.INTEGER },
hourly_rate: { type: DataTypes.DECIMAL(10, 2) },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'guides'
});
const Articles = sequelize.define('articles', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: DataTypes.STRING, allowNull: false },
excerpt: { type: DataTypes.TEXT },
content: { type: DataTypes.TEXT, allowNull: false },
category: { type: DataTypes.ENUM('travel-tips', 'culture', 'food', 'nature', 'history') },
is_published: { type: DataTypes.BOOLEAN, defaultValue: false },
views: { type: DataTypes.INTEGER, defaultValue: 0 },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'articles'
});
const Bookings = sequelize.define('bookings', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
customer_name: { type: DataTypes.STRING, allowNull: false },
customer_email: { type: DataTypes.STRING, allowNull: false },
customer_phone: { type: DataTypes.STRING },
preferred_date: { type: DataTypes.DATE, allowNull: false },
group_size: { type: DataTypes.INTEGER, allowNull: false },
status: { type: DataTypes.ENUM('pending', 'confirmed', 'cancelled', 'completed'), defaultValue: 'pending' },
total_price: { type: DataTypes.DECIMAL(10, 2), allowNull: false },
notes: { type: DataTypes.TEXT },
guide_id: { type: DataTypes.INTEGER },
route_id: { type: DataTypes.INTEGER },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'bookings'
});
const Reviews = sequelize.define('reviews', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
customer_name: { type: DataTypes.STRING, allowNull: false },
customer_email: { type: DataTypes.STRING },
rating: { type: DataTypes.INTEGER, validate: { min: 1, max: 5 } },
comment: { type: DataTypes.TEXT },
is_approved: { type: DataTypes.BOOLEAN, defaultValue: false },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'reviews'
});
const ContactMessages = sequelize.define('contact_messages', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
phone: { type: DataTypes.STRING },
subject: { type: DataTypes.STRING, allowNull: false },
message: { type: DataTypes.TEXT, allowNull: false },
status: { type: DataTypes.ENUM('unread', 'read', 'replied'), defaultValue: 'unread' },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'contact_messages'
});
const Admins = sequelize.define('admins', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
username: { type: DataTypes.STRING, allowNull: false, unique: true },
name: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
password: { type: DataTypes.STRING, allowNull: false },
role: { type: DataTypes.ENUM('admin', 'manager', 'editor'), defaultValue: 'admin' },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
last_login: { type: DataTypes.DATE },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'admins'
});
// Новые модели для системы рейтинга и расписания
const Ratings = sequelize.define('ratings', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
user_ip: { type: DataTypes.STRING(45), allowNull: false },
target_id: { type: DataTypes.INTEGER, allowNull: false },
target_type: { type: DataTypes.ENUM('route', 'guide', 'article'), allowNull: false },
rating: { type: DataTypes.INTEGER, allowNull: false, validate: { isIn: [[1, -1]] } },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'ratings'
});
const GuideSchedules = sequelize.define('guide_schedules', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
guide_id: { type: DataTypes.INTEGER, allowNull: false },
monday: { type: DataTypes.BOOLEAN, defaultValue: true },
tuesday: { type: DataTypes.BOOLEAN, defaultValue: true },
wednesday: { type: DataTypes.BOOLEAN, defaultValue: true },
thursday: { type: DataTypes.BOOLEAN, defaultValue: true },
friday: { type: DataTypes.BOOLEAN, defaultValue: true },
saturday: { type: DataTypes.BOOLEAN, defaultValue: false },
sunday: { type: DataTypes.BOOLEAN, defaultValue: false },
start_time: { type: DataTypes.TIME, defaultValue: '09:00' },
end_time: { type: DataTypes.TIME, defaultValue: '18:00' },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'guide_schedules'
});
const Holidays = sequelize.define('holidays', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
date: { type: DataTypes.DATEONLY, allowNull: false },
title: { type: DataTypes.STRING, allowNull: false },
type: { type: DataTypes.ENUM('public', 'guide_personal'), allowNull: false },
guide_id: { type: DataTypes.INTEGER },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'holidays'
});
// Определение связей между моделями
Guides.hasOne(GuideSchedules, { foreignKey: 'guide_id' });
GuideSchedules.belongsTo(Guides, { foreignKey: 'guide_id' });
Guides.hasMany(Holidays, { foreignKey: 'guide_id' });
Holidays.belongsTo(Guides, { foreignKey: 'guide_id' });
Guides.hasMany(Bookings, { foreignKey: 'guide_id' });
Bookings.belongsTo(Guides, { foreignKey: 'guide_id' });
Routes.hasMany(Bookings, { foreignKey: 'route_id' });
Bookings.belongsTo(Routes, { foreignKey: 'route_id' });
// Методы для получения рейтингов
const getRatingStats = async (targetType, targetId) => {
const result = await sequelize.query(
'SELECT * FROM calculate_rating(:targetType, :targetId)',
{
replacements: { targetType, targetId },
type: sequelize.QueryTypes.SELECT
}
);
return result[0] || { likes_count: 0, dislikes_count: 0, total_votes: 0, rating_percentage: 0 };
};
// Конфигурация AdminJS с ресурсами базы данных
// Конфигурация AdminJS с ресурсами Sequelize
const adminJsOptions = {
resources: [
{
resource: Routes,
options: {
parent: { name: 'Контент', icon: 'DocumentText' },
listProperties: ['id', 'title', 'type', 'price', 'duration', 'is_active', 'created_at'],
editProperties: ['title', 'description', 'content', 'type', 'difficulty_level', 'price', 'duration', 'max_group_size', 'is_featured', 'is_active'],
showProperties: ['id', 'title', 'description', 'content', 'type', 'difficulty_level', 'price', 'duration', 'max_group_size', 'is_featured', 'is_active', 'created_at', 'updated_at'],
filterProperties: ['title', 'type', 'is_active'],
properties: {
title: {
isTitle: true,
isRequired: true,
},
description: {
type: 'textarea',
isRequired: true,
},
content: {
type: 'textarea',
},
type: {
availableValues: [
{ value: 'city', label: 'Городской тур' },
{ value: 'mountain', label: 'Горный поход' },
{ value: 'fishing', label: 'Рыбалка' }
],
},
difficulty_level: {
availableValues: [
{ value: 'easy', label: 'Легкий' },
{ value: 'moderate', label: 'Средний' },
{ value: 'hard', label: 'Сложный' }
],
},
price: {
type: 'number',
isRequired: true,
},
duration: {
type: 'number',
isRequired: true,
},
max_group_size: {
type: 'number',
isRequired: true,
},
is_featured: { type: 'boolean' },
is_active: { type: 'boolean' },
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
},
updated_at: {
isVisible: { list: false, filter: false, show: true, edit: false },
}
},
}
},
{
resource: Guides,
options: {
parent: { name: 'Персонал', icon: 'Users' },
listProperties: ['id', 'name', 'email', 'specialization', 'experience', 'hourly_rate', 'is_active'],
editProperties: ['name', 'email', 'phone', 'languages', 'specialization', 'bio', 'experience', 'hourly_rate', 'is_active'],
showProperties: ['id', 'name', 'email', 'phone', 'languages', 'specialization', 'bio', 'experience', 'hourly_rate', 'is_active', 'created_at'],
filterProperties: ['name', 'specialization', 'is_active'],
properties: {
name: {
isTitle: true,
isRequired: true,
},
email: {
type: 'email',
isRequired: true,
},
phone: { type: 'string' },
languages: {
type: 'textarea',
description: 'Языки через запятую',
},
specialization: {
availableValues: [
{ value: 'city', label: 'Городские туры' },
{ value: 'mountain', label: 'Горные походы' },
{ value: 'fishing', label: 'Рыбалка' },
{ value: 'general', label: 'Универсальный' }
],
},
bio: { type: 'textarea' },
experience: {
type: 'number',
description: 'Опыт работы в годах',
},
hourly_rate: {
type: 'number',
description: 'Ставка за час в вонах',
},
is_active: { type: 'boolean' },
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
}
},
{
resource: Articles,
options: {
parent: { name: 'Контент', icon: 'DocumentText' },
listProperties: ['id', 'title', 'category', 'is_published', 'views', 'created_at'],
editProperties: ['title', 'excerpt', 'content', 'category', 'is_published'],
showProperties: ['id', 'title', 'excerpt', 'content', 'category', 'is_published', 'views', 'created_at', 'updated_at'],
filterProperties: ['title', 'category', 'is_published'],
properties: {
title: {
isTitle: true,
isRequired: true,
},
excerpt: {
type: 'textarea',
description: 'Краткое описание статьи',
},
content: {
type: 'textarea',
isRequired: true,
},
category: {
availableValues: [
{ value: 'travel-tips', label: 'Советы путешественникам' },
{ value: 'culture', label: 'Культура' },
{ value: 'food', label: 'Еда' },
{ value: 'nature', label: 'Природа' },
{ value: 'history', label: 'История' }
],
},
is_published: { type: 'boolean' },
views: {
type: 'number',
isVisible: { list: true, filter: true, show: true, edit: false },
},
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
},
updated_at: {
isVisible: { list: false, filter: false, show: true, edit: false },
}
},
}
},
{
resource: Bookings,
options: {
parent: { name: 'Заказы', icon: 'ShoppingCart' },
listProperties: ['id', 'customer_name', 'customer_email', 'preferred_date', 'status', 'total_price', 'created_at'],
editProperties: ['customer_name', 'customer_email', 'customer_phone', 'preferred_date', 'group_size', 'status', 'total_price', 'notes'],
showProperties: ['id', 'customer_name', 'customer_email', 'customer_phone', 'preferred_date', 'group_size', 'status', 'total_price', 'notes', 'created_at'],
filterProperties: ['customer_name', 'customer_email', 'status', 'preferred_date'],
properties: {
customer_name: {
isTitle: true,
isRequired: true,
},
customer_email: {
type: 'email',
isRequired: true,
},
customer_phone: { type: 'string' },
preferred_date: {
type: 'date',
isRequired: true,
},
group_size: {
type: 'number',
isRequired: true,
},
status: {
availableValues: [
{ value: 'pending', label: 'В ожидании' },
{ value: 'confirmed', label: 'Подтверждено' },
{ value: 'cancelled', label: 'Отменено' },
{ value: 'completed', label: 'Завершено' }
],
},
total_price: {
type: 'number',
isRequired: true,
},
notes: { type: 'textarea' },
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
}
},
{
resource: Reviews,
options: {
parent: { name: 'Отзывы', icon: 'Star' },
listProperties: ['id', 'customer_name', 'rating', 'is_approved', 'created_at'],
editProperties: ['customer_name', 'customer_email', 'rating', 'comment', 'is_approved'],
showProperties: ['id', 'customer_name', 'customer_email', 'rating', 'comment', 'is_approved', 'created_at'],
filterProperties: ['customer_name', 'rating', 'is_approved'],
properties: {
customer_name: {
isTitle: true,
isRequired: true,
},
customer_email: { type: 'email' },
rating: {
type: 'number',
availableValues: [
{ value: 1, label: '1 звезда' },
{ value: 2, label: '2 звезды' },
{ value: 3, label: '3 звезды' },
{ value: 4, label: '4 звезды' },
{ value: 5, label: '5 звезд' }
]
},
comment: { type: 'textarea' },
is_approved: { type: 'boolean' },
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
}
},
{
resource: ContactMessages,
options: {
parent: { name: 'Сообщения', icon: 'Email' },
listProperties: ['id', 'name', 'email', 'subject', 'status', 'created_at'],
editProperties: ['name', 'email', 'phone', 'subject', 'message', 'status'],
showProperties: ['id', 'name', 'email', 'phone', 'subject', 'message', 'status', 'created_at'],
filterProperties: ['name', 'email', 'status'],
properties: {
name: {
isTitle: true,
isRequired: true,
},
email: {
type: 'email',
isRequired: true,
},
phone: { type: 'string' },
subject: {
type: 'string',
isRequired: true,
},
message: {
type: 'textarea',
isRequired: true,
},
status: {
availableValues: [
{ value: 'unread', label: 'Не прочитано' },
{ value: 'read', label: 'Прочитано' },
{ value: 'replied', label: 'Отвечено' }
],
},
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
actions: {
new: { isAccessible: false },
edit: { isAccessible: true },
delete: { isAccessible: true },
list: { isAccessible: true },
show: { isAccessible: true }
}
}
},
{
resource: Admins,
options: {
parent: { name: 'Администрирование', icon: 'Settings' },
listProperties: ['id', 'username', 'name', 'email', 'role', 'is_active', 'created_at'],
editProperties: ['username', 'name', 'email', 'role', 'is_active'],
showProperties: ['id', 'username', 'name', 'email', 'role', 'is_active', 'last_login', 'created_at'],
filterProperties: ['username', 'name', 'role', 'is_active'],
properties: {
username: {
isTitle: true,
isRequired: true,
},
name: {
type: 'string',
isRequired: true,
},
email: {
type: 'email',
isRequired: true,
},
password: {
type: 'password',
isVisible: { list: false, filter: false, show: false, edit: true }
},
role: {
availableValues: [
{ value: 'admin', label: 'Администратор' },
{ value: 'manager', label: 'Менеджер' },
{ value: 'editor', label: 'Редактор' }
],
},
is_active: { type: 'boolean' },
last_login: {
isVisible: { list: false, filter: false, show: true, edit: false },
},
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
}
},
{
resource: Ratings,
options: {
parent: { name: 'Система рейтингов', icon: 'Star' },
listProperties: ['id', 'target_type', 'target_id', 'rating', 'user_ip', 'created_at'],
showProperties: ['id', 'target_type', 'target_id', 'rating', 'user_ip', 'created_at'],
filterProperties: ['target_type', 'target_id', 'rating'],
properties: {
target_type: {
availableValues: [
{ value: 'route', label: 'Маршрут' },
{ value: 'guide', label: 'Гид' },
{ value: 'article', label: 'Статья' }
],
},
rating: {
availableValues: [
{ value: 1, label: '👍 Лайк' },
{ value: -1, label: '👎 Дизлайк' }
],
},
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
actions: {
new: { isAccessible: false },
edit: { isAccessible: false },
delete: { isAccessible: true },
list: { isAccessible: true },
show: { isAccessible: true }
}
}
},
{
resource: GuideSchedules,
options: {
parent: { name: 'Управление гидами', icon: 'Calendar' },
listProperties: ['id', 'guide_id', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'start_time', 'end_time'],
editProperties: ['guide_id', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'start_time', 'end_time'],
showProperties: ['id', 'guide_id', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'start_time', 'end_time', 'created_at', 'updated_at'],
filterProperties: ['guide_id'],
properties: {
guide_id: {
isTitle: true,
isRequired: true,
},
monday: { type: 'boolean' },
tuesday: { type: 'boolean' },
wednesday: { type: 'boolean' },
thursday: { type: 'boolean' },
friday: { type: 'boolean' },
saturday: { type: 'boolean' },
sunday: { type: 'boolean' },
start_time: {
type: 'string',
description: 'Время начала работы (формат HH:MM)'
},
end_time: {
type: 'string',
description: 'Время окончания работы (формат HH:MM)'
},
created_at: {
isVisible: { list: false, filter: false, show: true, edit: false },
},
updated_at: {
isVisible: { list: false, filter: false, show: true, edit: false },
}
},
}
},
{
resource: Holidays,
options: {
parent: { name: 'Управление гидами', icon: 'Calendar' },
listProperties: ['id', 'date', 'title', 'type', 'guide_id', 'created_at'],
editProperties: ['date', 'title', 'type', 'guide_id'],
showProperties: ['id', 'date', 'title', 'type', 'guide_id', 'created_at'],
filterProperties: ['date', 'type', 'guide_id'],
properties: {
date: {
isTitle: true,
isRequired: true,
type: 'date'
},
title: {
isRequired: true,
description: 'Название выходного дня'
},
type: {
availableValues: [
{ value: 'public', label: 'Общий выходной' },
{ value: 'guide_personal', label: 'Личный выходной гида' }
],
isRequired: true
},
guide_id: {
description: 'Оставить пустым для общих выходных'
},
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
}
}
],
rootPath: '/admin',
branding: {
companyName: 'Korea Tourism Agency',
softwareBrothers: false,
theme: {
colors: {
primary100: '#ff6b6b',
primary80: '#ff5252',
primary60: '#ff3d3d',
primary40: '#ff2828',
primary20: '#ff1313',
grey100: '#151515',
grey80: '#333333',
grey60: '#666666',
grey40: '#999999',
grey20: '#cccccc',
filterBg: '#333333',
accent: '#38C172',
hoverBg: '#f0f0f0',
},
},
},
dashboard: {
component: false
}
};
// Создаем экземпляр AdminJS
const adminJs = new AdminJS(adminJsOptions);
// Настраиваем аутентификацию
const router = AdminJSExpress.buildAuthenticatedRouter(adminJs, {
authenticate: async (email, password) => {
try {
console.log('Attempting login for:', email);
const result = await authPool.query(
'SELECT * FROM admins WHERE username = $1 AND is_active = true',
[email]
);
if (result.rows.length === 0) {
console.log('No admin found with username:', email);
return null;
}
const admin = result.rows[0];
console.log('Admin found:', admin.name);
const isValid = await bcrypt.compare(password, admin.password);
if (isValid) {
console.log('Authentication successful for:', email);
return {
id: admin.id,
email: admin.username,
title: admin.name,
role: admin.role
};
}
console.log('Invalid password for:', email);
return null;
} catch (error) {
console.error('Auth error:', error);
return null;
}
},
cookiePassword: process.env.SESSION_SECRET || 'korea-tourism-secret-key-2024'
}, null, {
resave: false,
saveUninitialized: false,
secret: process.env.SESSION_SECRET || 'korea-tourism-secret-key-2024',
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 часа
}
});
export { adminJs, router };