✨ Компактные hero секции и улучшенная инициализация БД
🎨 UI улучшения: - Уменьшена высота синих панелей с 100vh до 70vh на главной - Добавлен класс .compact (25vh) для всех остальных страниц - Улучшена адаптивность для мобильных устройств - Обновлены все шаблоны с hero секциями 🚀 Инфраструктура: - Автоматическая инициализация базы данных при деплое - Улучшены мокапные данные (больше отзывов, бронирований, сообщений) - Добавлены настройки сайта в базу данных - Создан скрипт автоматического деплоя deploy.sh 📦 Система сборки: - Обновлен .gitignore с полным покрытием файлов - Добавлена папка для загрузок с .gitkeep - Улучшен README с инструкциями по запуску - ES модули для инициализации базы данных 🐛 Исправления: - Совместимость с ES модулями в Node.js - Правильная обработка ошибок инициализации БД - Корректные SQL запросы для PostgreSQL
This commit is contained in:
77
database/init-database.js
Normal file
77
database/init-database.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import db from '../src/config/database.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export async function initDatabase() {
|
||||
try {
|
||||
console.log('🚀 Starting complete database initialization...');
|
||||
|
||||
// Check if database is connected
|
||||
await db.query('SELECT 1');
|
||||
console.log('✅ Database connection successful');
|
||||
|
||||
// 1. Create schema
|
||||
console.log('📋 Creating database schema...');
|
||||
const schemaPath = path.join(__dirname, 'schema.sql');
|
||||
const schema = fs.readFileSync(schemaPath, 'utf8');
|
||||
await db.query(schema);
|
||||
console.log('✅ Database schema created successfully');
|
||||
|
||||
// 2. Check if tables are empty (first run)
|
||||
const checkResult = await db.query('SELECT COUNT(*) FROM admins');
|
||||
const isEmpty = parseInt(checkResult.rows[0].count) === 0;
|
||||
|
||||
if (isEmpty) {
|
||||
console.log('📝 Database is empty, inserting mock data...');
|
||||
|
||||
// Insert mock data
|
||||
const mockDataPath = path.join(__dirname, 'mock-data.sql');
|
||||
const mockData = fs.readFileSync(mockDataPath, 'utf8');
|
||||
await db.query(mockData);
|
||||
console.log('✅ Mock data inserted successfully');
|
||||
} else {
|
||||
console.log('ℹ️ Database already contains data, skipping mock data insertion');
|
||||
}
|
||||
|
||||
// 3. Run any pending migrations
|
||||
console.log('🔄 Checking for pending migrations...');
|
||||
|
||||
// Check if rating system exists
|
||||
try {
|
||||
await db.query('SELECT 1 FROM route_ratings LIMIT 1');
|
||||
console.log('ℹ️ Rating system already exists');
|
||||
} catch (error) {
|
||||
console.log('📈 Installing rating system...');
|
||||
const ratingMigrationPath = path.join(__dirname, 'rating-system-migration.sql');
|
||||
if (fs.existsSync(ratingMigrationPath)) {
|
||||
const ratingMigration = fs.readFileSync(ratingMigrationPath, 'utf8');
|
||||
await db.query(ratingMigration);
|
||||
console.log('✅ Rating system installed successfully');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✨ Database initialization completed successfully!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Database initialization failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
initDatabase()
|
||||
.then(() => {
|
||||
console.log('🎉 All done!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 Initialization failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -80,24 +80,44 @@ INSERT INTO reviews (route_id, customer_name, customer_email, rating, comment, i
|
||||
(7, 'Михаил Рыбаков', 'mikhail.rybakov@email.com', 4, 'Два дня рыбалки у берегов Пусана - это было здорово! Поймали много рыбы, капитан Ли очень опытный. Единственный минус - качка была сильная, но это природа. Улов приготовили прямо на борту - вкуснее не ел никогда!', true, NOW() - INTERVAL '8 days'),
|
||||
(2, 'Елена Иванова', 'elena.ivanova@email.com', 5, 'Морской воздух, свежие морепродукты, красивые пляжи - всё было идеально! Гид Чой Ю На показала лучшие места для фото и рассказала много интересного о жизни в портовом городе.', true, NOW() - INTERVAL '15 days'),
|
||||
(1, 'Сергей Морозов', 'sergey.morozov@email.com', 4, 'Тур по Сеулу организован очень хорошо. Группа небольшая, гид внимательный. Единственное - хотелось бы больше свободного времени для шопинга в Мёндоне. В целом очень доволен!', true, NOW() - INTERVAL '20 days'),
|
||||
(6, 'Ольга Кузнецова', 'olga.kuznetsova@email.com', 5, 'Халласан покорился легко благодаря отличной подготовке группы. Остров Чеджу вообще сказочный - вулканические пляжи, мандариновые рощи, дружелюбные местные жители. Хочу вернуться!', true, NOW() - INTERVAL '25 days');
|
||||
(6, 'Ольга Кузнецова', 'olga.kuznetsova@email.com', 5, 'Халласан покорился легко благодаря отличной подготовке группы. Остров Чеджу вообще сказочный - вулканические пляжи, мандариновые рощи, дружелюбные местные жители. Хочу вернуться!', true, NOW() - INTERVAL '25 days'),
|
||||
(3, 'Виктор Николаев', 'viktor.nikolaev@email.com', 5, 'Кёнджу - это путешествие во времени! Юн Тэ Гу просто энциклопедия корейской истории. Каждый камень в Пульгуксе имеет свою историю. Грот Соккурам поражает мастерством древних зодчих.', true, NOW() - INTERVAL '30 days'),
|
||||
(5, 'Татьяна Смирнова', 'tatyana.smirnova@email.com', 4, 'Чирисан - это духовное очищение. Ночёвки в храмах, медитации на рассвете, звенящая тишина гор. Физически тяжело, но душевно очень важно. Рекомендую всем, кто ищет смысл жизни.', true, NOW() - INTERVAL '35 days'),
|
||||
(8, 'Алексей Рыбников', 'alexey.rybnikov@email.com', 5, 'Три дня на рыбацком судне в Восточном море - незабываемо! Ночная рыбалка на кальмаров, рассвет над морем, свежайшая рыба на завтрак. Капитан Ли и команда - настоящие профессионалы!', true, NOW() - INTERVAL '40 days');
|
||||
|
||||
-- Настройки сайта
|
||||
INSERT INTO site_settings (setting_key, setting_value, description, updated_at) VALUES
|
||||
('site_name', 'Корея Тур Агентство', 'Название сайта', NOW()),
|
||||
('site_description', 'Откройте для себя красоту Кореи с нашими профессиональными турами', 'Описание сайта для SEO', NOW()),
|
||||
('contact_email', 'info@koreatour.ru', 'Email для связи', NOW()),
|
||||
('contact_phone', '+7 (495) 123-45-67', 'Телефон для связи', NOW()),
|
||||
('contact_address', 'Москва, ул. Примерная, д. 123', 'Адрес офиса', NOW()),
|
||||
('social_facebook', 'https://facebook.com/koreatour', 'Ссылка на Facebook', NOW()),
|
||||
('social_instagram', 'https://instagram.com/koreatour', 'Ссылка на Instagram', NOW()),
|
||||
('social_youtube', 'https://youtube.com/koreatour', 'Ссылка на YouTube', NOW()),
|
||||
('booking_email', 'booking@koreatour.ru', 'Email для бронирования', NOW()),
|
||||
('emergency_phone', '+82-10-911-1234', 'Экстренный телефон в Корее', NOW());
|
||||
INSERT INTO site_settings (setting_key, setting_value, setting_type, description, updated_at) VALUES
|
||||
('site_name', 'Корея Тур Агентство', 'text', 'Название сайта', NOW()),
|
||||
('site_description', 'Откройте для себя красоту Кореи с нашими профессиональными турами', 'text', 'Описание сайта для SEO', NOW()),
|
||||
('contact_email', 'info@koreatour.ru', 'text', 'Email для связи', NOW()),
|
||||
('contact_phone', '+7 (495) 123-45-67', 'text', 'Телефон для связи', NOW()),
|
||||
('contact_address', 'Москва, ул. Примерная, д. 123', 'text', 'Адрес офиса', NOW()),
|
||||
('social_facebook', 'https://facebook.com/koreatour', 'text', 'Ссылка на Facebook', NOW()),
|
||||
('social_instagram', 'https://instagram.com/koreatour', 'text', 'Ссылка на Instagram', NOW()),
|
||||
('social_youtube', 'https://youtube.com/koreatour', 'text', 'Ссылка на YouTube', NOW()),
|
||||
('booking_email', 'booking@koreatour.ru', 'text', 'Email для бронирования', NOW()),
|
||||
('emergency_phone', '+82-10-911-1234', 'text', 'Экстренный телефон в Корее', NOW()),
|
||||
('booking_enabled', 'true', 'boolean', 'Включить онлайн бронирование', NOW()),
|
||||
('maintenance_mode', 'false', 'boolean', 'Режим обслуживания', NOW()),
|
||||
('max_group_size', '15', 'number', 'Максимальный размер группы', NOW()),
|
||||
('default_currency', 'KRW', 'text', 'Валюта по умолчанию', NOW()),
|
||||
('facebook_url', 'https://facebook.com/koreatour', 'text', 'URL Facebook страницы', NOW()),
|
||||
('instagram_url', 'https://instagram.com/koreatour', 'text', 'URL Instagram профиля', NOW()),
|
||||
('twitter_url', 'https://twitter.com/koreatour', 'text', 'URL Twitter профиля', NOW());
|
||||
|
||||
-- Примеры бронирований
|
||||
INSERT INTO bookings (route_id, customer_name, customer_email, customer_phone, preferred_date, status, notes, created_at) VALUES
|
||||
(1, 'Иван Петров', 'ivan.petrov@email.com', '+7-915-123-4567', '2024-04-15', 'confirmed', 'Оплата получена, гид назначен', NOW() - INTERVAL '3 days'),
|
||||
INSERT INTO bookings (route_id, guide_id, customer_name, customer_email, customer_phone, preferred_date, group_size, total_price, status, notes, created_at) VALUES
|
||||
(1, 1, 'Иван Петров', 'ivan.petrov@email.com', '+7-915-123-4567', '2025-12-15', 2, 900000, 'confirmed', 'Оплата получена, гид назначен', NOW() - INTERVAL '3 days'),
|
||||
(4, 2, 'Мария Сидорова', 'maria.sidorova@email.com', '+7-916-234-5678', '2025-12-20', 4, 2720000, 'pending', 'Ожидает подтверждения оплаты', NOW() - INTERVAL '1 day'),
|
||||
(7, 3, 'Андрей Козлов', 'andrey.kozlov@email.com', '+7-917-345-6789', '2025-12-25', 3, 1260000, 'confirmed', 'Группа из трёх друзей, все опытные рыбаки', NOW() - INTERVAL '5 days'),
|
||||
(2, 4, 'Светлана Попова', 'svetlana.popova@email.com', '+7-918-456-7890', '2026-01-05', 1, 320000, 'pending', 'Первый раз в Корее, нужна подробная консультация', NOW() - INTERVAL '2 days');
|
||||
|
||||
-- Контактные сообщения
|
||||
INSERT INTO contact_messages (name, email, phone, subject, message, status, created_at) VALUES
|
||||
('Алексей Иванов', 'alexey.ivanov@email.com', '+7-921-123-4567', 'Вопрос о горных турах', 'Здравствуйте! Интересуют походы в горы для начинающих. Какой тур посоветуете для первого раза? Опыт походов минимальный, но физическая подготовка хорошая.', 'unread', NOW() - INTERVAL '2 hours'),
|
||||
('Наталья Кузнецова', 'natasha.kuznetsova@email.com', '+7-925-234-5678', 'Групповой тур в Сеул', 'Планируем поездку компанией из 8 человек в апреле. Возможно ли организовать индивидуальный тур по дворцам Сеула? Интересуют цены и программа.', 'read', NOW() - INTERVAL '1 day'),
|
||||
('Михаил Петров', 'mikhail.petrov@email.com', '+7-926-345-6789', 'Рыбалка для новичков', 'Никогда не рыбачил в море, но очень хочется попробовать. Есть ли туры для абсолютных новичков? Нужно ли своё снаряжение?', 'replied', NOW() - INTERVAL '3 days'),
|
||||
('Елена Смирнова', 'elena.smirnova@email.com', '+7-927-456-7890', 'Сезонные особенности', 'Какое время года лучше для посещения храмов и дворцов? Планируем поездку с детьми 12 и 15 лет. Спасибо!', 'unread', NOW() - INTERVAL '5 hours');
|
||||
(4, 'Мария Сидорова', 'maria.sidorova@email.com', '+7-926-234-5678', '2024-05-20', 'pending', 'Ожидаем подтверждение доступности адаптированного маршрута', NOW() - INTERVAL '1 day'),
|
||||
(7, 'Алексей Рыбак', 'alexey.rybak@email.com', '+7-903-345-6789', '2024-03-25', 'confirmed', 'Забронировано судно для опытных рыболовов', NOW() - INTERVAL '7 days'),
|
||||
(2, 'Екатерина Новикова', 'ekaterina.novikova@email.com', '+7-985-456-7890', '2024-04-08', 'completed', 'Тур завершён, клиент оставил отличный отзыв', NOW() - INTERVAL '14 days');
|
||||
|
||||
73
database/rating-system-migration.sql
Normal file
73
database/rating-system-migration.sql
Normal file
@@ -0,0 +1,73 @@
|
||||
-- Система лайков/дизлайков для туров, гидов и статей
|
||||
CREATE TABLE ratings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_ip VARCHAR(45) NOT NULL, -- IP адрес для анонимных пользователей
|
||||
target_id INTEGER NOT NULL,
|
||||
target_type VARCHAR(20) NOT NULL CHECK (target_type IN ('route', 'guide', 'article')),
|
||||
rating INTEGER NOT NULL CHECK (rating IN (1, -1)), -- 1 = лайк, -1 = дизлайк
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_ip, target_id, target_type) -- Один пользователь - один голос за объект
|
||||
);
|
||||
|
||||
-- График работы гидов
|
||||
CREATE TABLE guide_schedules (
|
||||
id SERIAL PRIMARY KEY,
|
||||
guide_id INTEGER NOT NULL REFERENCES guides(id) ON DELETE CASCADE,
|
||||
monday BOOLEAN DEFAULT true,
|
||||
tuesday BOOLEAN DEFAULT true,
|
||||
wednesday BOOLEAN DEFAULT true,
|
||||
thursday BOOLEAN DEFAULT true,
|
||||
friday BOOLEAN DEFAULT true,
|
||||
saturday BOOLEAN DEFAULT false,
|
||||
sunday BOOLEAN DEFAULT false,
|
||||
start_time TIME DEFAULT '09:00',
|
||||
end_time TIME DEFAULT '18:00',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(guide_id)
|
||||
);
|
||||
|
||||
-- Выходные дни (общие и индивидуальные)
|
||||
CREATE TABLE holidays (
|
||||
id SERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(20) NOT NULL CHECK (type IN ('public', 'guide_personal')), -- публичный или персональный
|
||||
guide_id INTEGER REFERENCES guides(id) ON DELETE CASCADE, -- NULL для публичных выходных
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(date, guide_id) -- Один выходной день для одного гида
|
||||
);
|
||||
|
||||
-- Обновляем таблицу бронирований для связи с гидом
|
||||
ALTER TABLE bookings ADD COLUMN guide_id INTEGER REFERENCES guides(id);
|
||||
ALTER TABLE bookings ADD COLUMN route_id INTEGER REFERENCES routes(id);
|
||||
|
||||
-- Добавляем индексы для производительности
|
||||
CREATE INDEX idx_ratings_target ON ratings(target_type, target_id);
|
||||
CREATE INDEX idx_ratings_user_ip ON ratings(user_ip);
|
||||
CREATE INDEX idx_bookings_date ON bookings(preferred_date);
|
||||
CREATE INDEX idx_bookings_guide ON bookings(guide_id);
|
||||
CREATE INDEX idx_holidays_date ON holidays(date);
|
||||
|
||||
-- Функция для подсчета рейтинга
|
||||
CREATE OR REPLACE FUNCTION calculate_rating(target_type_param VARCHAR, target_id_param INTEGER)
|
||||
RETURNS TABLE(
|
||||
likes_count BIGINT,
|
||||
dislikes_count BIGINT,
|
||||
total_votes BIGINT,
|
||||
rating_percentage NUMERIC(5,2)
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
COUNT(CASE WHEN rating = 1 THEN 1 END) as likes_count,
|
||||
COUNT(CASE WHEN rating = -1 THEN 1 END) as dislikes_count,
|
||||
COUNT(*) as total_votes,
|
||||
CASE
|
||||
WHEN COUNT(*) = 0 THEN 0
|
||||
ELSE ROUND((COUNT(CASE WHEN rating = 1 THEN 1 END)::NUMERIC / COUNT(*)::NUMERIC) * 100, 2)
|
||||
END as rating_percentage
|
||||
FROM ratings
|
||||
WHERE target_type = target_type_param AND target_id = target_id_param;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
106
database/run-migration.js
Normal file
106
database/run-migration.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import pool from '../src/config/database.js';
|
||||
|
||||
const migrateRatingSystem = async () => {
|
||||
try {
|
||||
console.log('🔄 Выполняю миграцию для системы рейтингов...');
|
||||
|
||||
// Создание таблицы ratings
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS ratings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_ip VARCHAR(45) NOT NULL,
|
||||
target_id INTEGER NOT NULL,
|
||||
target_type VARCHAR(20) NOT NULL CHECK (target_type IN ('route', 'guide', 'article')),
|
||||
rating INTEGER NOT NULL CHECK (rating IN (1, -1)),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_ip, target_id, target_type)
|
||||
);
|
||||
`);
|
||||
|
||||
// Создание таблицы guide_schedules
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS guide_schedules (
|
||||
id SERIAL PRIMARY KEY,
|
||||
guide_id INTEGER NOT NULL REFERENCES guides(id) ON DELETE CASCADE,
|
||||
monday BOOLEAN DEFAULT true,
|
||||
tuesday BOOLEAN DEFAULT true,
|
||||
wednesday BOOLEAN DEFAULT true,
|
||||
thursday BOOLEAN DEFAULT true,
|
||||
friday BOOLEAN DEFAULT true,
|
||||
saturday BOOLEAN DEFAULT false,
|
||||
sunday BOOLEAN DEFAULT false,
|
||||
start_time TIME DEFAULT '09:00',
|
||||
end_time TIME DEFAULT '18:00',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(guide_id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Создание таблицы holidays
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS holidays (
|
||||
id SERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(20) NOT NULL CHECK (type IN ('public', 'guide_personal')),
|
||||
guide_id INTEGER REFERENCES guides(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(date, guide_id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Добавление колонок в bookings (если не существуют)
|
||||
try {
|
||||
await pool.query('ALTER TABLE bookings ADD COLUMN guide_id INTEGER REFERENCES guides(id);');
|
||||
} catch (e) {
|
||||
console.log('Колонка guide_id уже существует');
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query('ALTER TABLE bookings ADD COLUMN route_id INTEGER REFERENCES routes(id);');
|
||||
} catch (e) {
|
||||
console.log('Колонка route_id уже существует');
|
||||
}
|
||||
|
||||
// Создание индексов
|
||||
await pool.query('CREATE INDEX IF NOT EXISTS idx_ratings_target ON ratings(target_type, target_id);');
|
||||
await pool.query('CREATE INDEX IF NOT EXISTS idx_ratings_user_ip ON ratings(user_ip);');
|
||||
await pool.query('CREATE INDEX IF NOT EXISTS idx_bookings_date ON bookings(preferred_date);');
|
||||
await pool.query('CREATE INDEX IF NOT EXISTS idx_bookings_guide ON bookings(guide_id);');
|
||||
await pool.query('CREATE INDEX IF NOT EXISTS idx_holidays_date ON holidays(date);');
|
||||
|
||||
// Функция для подсчета рейтинга
|
||||
await pool.query(`
|
||||
CREATE OR REPLACE FUNCTION calculate_rating(target_type_param VARCHAR, target_id_param INTEGER)
|
||||
RETURNS TABLE(
|
||||
likes_count BIGINT,
|
||||
dislikes_count BIGINT,
|
||||
total_votes BIGINT,
|
||||
rating_percentage NUMERIC(5,2)
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
COUNT(CASE WHEN rating = 1 THEN 1 END) as likes_count,
|
||||
COUNT(CASE WHEN rating = -1 THEN 1 END) as dislikes_count,
|
||||
COUNT(*) as total_votes,
|
||||
CASE
|
||||
WHEN COUNT(*) = 0 THEN 0
|
||||
ELSE ROUND((COUNT(CASE WHEN rating = 1 THEN 1 END)::NUMERIC / COUNT(*)::NUMERIC) * 100, 2)
|
||||
END as rating_percentage
|
||||
FROM ratings
|
||||
WHERE target_type = target_type_param AND target_id = target_id_param;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`);
|
||||
|
||||
console.log('✅ Миграция выполнена успешно!');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка миграции:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
migrateRatingSystem();
|
||||
148
database/seed-guide-data.js
Normal file
148
database/seed-guide-data.js
Normal 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();
|
||||
Reference in New Issue
Block a user