Компактные hero секции и улучшенная инициализация БД

🎨 UI улучшения:
- Уменьшена высота синих панелей с 100vh до 70vh на главной
- Добавлен класс .compact (25vh) для всех остальных страниц
- Улучшена адаптивность для мобильных устройств
- Обновлены все шаблоны с hero секциями

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

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

🐛 Исправления:
- Совместимость с ES модулями в Node.js
- Правильная обработка ошибок инициализации БД
- Корректные SQL запросы для PostgreSQL
This commit is contained in:
2025-11-29 18:47:42 +09:00
parent 409e6c146b
commit a461fea9d9
24 changed files with 1442 additions and 84 deletions

148
database/seed-guide-data.js Normal file
View File

@@ -0,0 +1,148 @@
import pool from '../src/config/database.js';
const seedGuideData = async () => {
try {
console.log('🌱 Добавляю тестовых гидов и их расписания...');
// Добавляем гидов, если их нет
const guidesCount = await pool.query('SELECT COUNT(*) FROM guides');
if (parseInt(guidesCount.rows[0].count) === 0) {
await pool.query(`
INSERT INTO guides (name, email, phone, languages, specialization, bio, experience, hourly_rate, is_active) VALUES
('Ли Мин Хо', 'lee@korea-tours.com', '+82-10-1234-5678', 'Корейский, Английский, Русский', 'city', 'Опытный гид по Сеулу с 8-летним стажем. Знаю все исторические места и современные достопримечательности столицы.', 8, 45000, true),
('Пак Со Ён', 'park@korea-tours.com', '+82-10-2345-6789', 'Корейский, Английский, Китайский', 'mountain', 'Профессиональный горный гид. Провожу походы в Сораксан, Чириксан и другие национальные парки Кореи.', 12, 55000, true),
('Ким Джун Су', 'kim@korea-tours.com', '+82-10-3456-7890', 'Корейский, Японский, Английский', 'fishing', 'Эксперт по рыбалке в морских и пресных водах. Организую туры на лучшие рыболовные места Кореи.', 15, 60000, true),
('Чой Хе Ран', 'choi@korea-tours.com', '+82-10-4567-8901', 'Корейский, Английский, Русский', 'general', 'Универсальный гид с широким спектром знаний о корейской культуре, истории и природе.', 6, 40000, true),
('Юн Тэ Хён', 'yoon@korea-tours.com', '+82-10-5678-9012', 'Корейский, Английский', 'city', 'Специалист по культурным турам и K-pop маршрутам. Покажу вам современную Корею глазами молодежи.', 4, 38000, true)
`);
}
// Получаем ID гидов
const guides = await pool.query('SELECT id FROM guides ORDER BY id');
// Добавляем расписания для каждого гида
for (const guide of guides.rows) {
const existingSchedule = await pool.query('SELECT * FROM guide_schedules WHERE guide_id = $1', [guide.id]);
if (existingSchedule.rows.length === 0) {
// Разные расписания для разных гидов
const schedules = [
{ monday: true, tuesday: true, wednesday: true, thursday: true, friday: true, saturday: true, sunday: false }, // Работает 6 дней
{ monday: true, tuesday: false, wednesday: true, thursday: true, friday: true, saturday: true, sunday: true }, // Выходной вторник
{ monday: true, tuesday: true, wednesday: true, thursday: true, friday: true, saturday: false, sunday: false }, // Только будни
{ monday: true, tuesday: true, wednesday: false, thursday: true, friday: true, saturday: true, sunday: true }, // Выходной среда
{ monday: false, tuesday: true, wednesday: true, thursday: true, friday: true, saturday: true, sunday: true } // Выходной понедельник
];
const schedule = schedules[(guide.id - 1) % schedules.length];
await pool.query(`
INSERT INTO guide_schedules (guide_id, monday, tuesday, wednesday, thursday, friday, saturday, sunday, start_time, end_time)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, '09:00', '18:00')
`, [
guide.id,
schedule.monday,
schedule.tuesday,
schedule.wednesday,
schedule.thursday,
schedule.friday,
schedule.saturday,
schedule.sunday
]);
}
}
// Добавляем несколько общих выходных дней
const holidays = [
{ date: '2025-01-01', title: 'Новый год', type: 'public' },
{ date: '2025-02-10', title: 'Лунный Новый год', type: 'public' },
{ date: '2025-03-01', title: 'День независимости', type: 'public' },
{ date: '2025-05-05', title: 'День детей', type: 'public' },
{ date: '2025-05-15', title: 'День Будды', type: 'public' },
{ date: '2025-06-06', title: 'День памяти', type: 'public' },
{ date: '2025-08-15', title: 'День освобождения', type: 'public' },
{ date: '2025-10-03', title: 'День основания государства', type: 'public' },
{ date: '2025-10-09', title: 'День корейского алфавита', type: 'public' },
{ date: '2025-12-25', title: 'Рождество', type: 'public' }
];
for (const holiday of holidays) {
const existing = await pool.query('SELECT * FROM holidays WHERE date = $1 AND type = $2', [holiday.date, holiday.type]);
if (existing.rows.length === 0) {
await pool.query(
'INSERT INTO holidays (date, title, type) VALUES ($1, $2, $3)',
[holiday.date, holiday.title, holiday.type]
);
}
}
// Добавляем несколько индивидуальных выходных
if (guides.rows.length > 0) {
const personalHolidays = [
{ date: '2025-12-01', title: 'Личный отпуск', guide_id: guides.rows[0].id },
{ date: '2025-12-15', title: 'Семейное мероприятие', guide_id: guides.rows[1].id },
{ date: '2025-11-30', title: 'Медицинский осмотр', guide_id: guides.rows[2].id }
];
for (const holiday of personalHolidays) {
const existing = await pool.query('SELECT * FROM holidays WHERE date = $1 AND guide_id = $2', [holiday.date, holiday.guide_id]);
if (existing.rows.length === 0) {
await pool.query(
'INSERT INTO holidays (date, title, type, guide_id) VALUES ($1, $2, $3, $4)',
[holiday.date, holiday.title, 'guide_personal', holiday.guide_id]
);
}
}
}
// Добавляем тестовые рейтинги
const routes = await pool.query('SELECT id FROM routes LIMIT 3');
const articles = await pool.query('SELECT id FROM articles LIMIT 3');
const testRatings = [
// Рейтинги для гидов
...guides.rows.slice(0, 3).map((guide, index) => ({
target_type: 'guide',
target_id: guide.id,
ratings: index === 0 ? [1, 1, 1, 1, -1] : index === 1 ? [1, 1, -1] : [1, 1, 1, 1, 1, 1, -1, -1]
})),
// Рейтинги для маршрутов
...routes.rows.map((route, index) => ({
target_type: 'route',
target_id: route.id,
ratings: index === 0 ? [1, 1, 1] : index === 1 ? [1, -1, 1, 1] : [1, 1, 1, 1, 1]
})),
// Рейтинги для статей
...articles.rows.map((article, index) => ({
target_type: 'article',
target_id: article.id,
ratings: index === 0 ? [1, 1] : index === 1 ? [1, 1, 1, -1] : [1]
}))
];
for (const item of testRatings) {
for (let i = 0; i < item.ratings.length; i++) {
const userIp = `192.168.1.${100 + i}`;
const existing = await pool.query(
'SELECT * FROM ratings WHERE user_ip = $1 AND target_type = $2 AND target_id = $3',
[userIp, item.target_type, item.target_id]
);
if (existing.rows.length === 0) {
await pool.query(
'INSERT INTO ratings (user_ip, target_type, target_id, rating) VALUES ($1, $2, $3, $4)',
[userIp, item.target_type, item.target_id, item.ratings[i]]
);
}
}
}
console.log('✅ Тестовые данные добавлены успешно!');
process.exit(0);
} catch (error) {
console.error('❌ Ошибка добавления тестовых данных:', error);
process.exit(1);
}
};
seedGuideData();