Initial commit: Korea Tourism Agency website with AdminJS

- Full-stack Node.js/Express application with PostgreSQL
- Modern ES modules architecture
- AdminJS admin panel with Sequelize ORM
- Tourism routes, guides, articles, bookings management
- Responsive Bootstrap 5 frontend
- Docker containerization with docker-compose
- Complete database schema with migrations
- Authentication system for admin panel
- Dynamic placeholder images for tour categories
This commit is contained in:
2025-11-29 18:13:17 +09:00
commit 409e6c146b
53 changed files with 16195 additions and 0 deletions

75
database/migrate.js Normal file
View File

@@ -0,0 +1,75 @@
const fs = require('fs');
const path = require('path');
const db = require('../src/config/database');
async function runMigrations() {
try {
console.log('💾 Starting database migration...');
// Check if database is connected
await db.query('SELECT 1');
console.log('✅ Database connection successful');
// Read and execute schema
const schemaPath = path.join(__dirname, 'schema.sql');
const schema = fs.readFileSync(schemaPath, 'utf8');
await db.query(schema);
console.log('✅ Database schema created successfully');
// Insert default admin user
const bcrypt = require('bcryptjs');
const hashedPassword = await bcrypt.hash('admin123', 10);
try {
await db.query(`
INSERT INTO admins (username, password, name, email, role)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (username) DO NOTHING
`, ['admin', hashedPassword, 'Administrator', 'admin@example.com', 'admin']);
console.log('✅ Default admin user created');
} catch (error) {
console.log(' Admin user already exists');
}
// Insert default site settings
const defaultSettings = [
['site_name', 'Korea Tourism Agency', 'text', 'Website name'],
['site_description', 'Discover Korea\'s hidden gems with our guided tours', 'text', 'Site description'],
['contact_email', 'info@koreatourism.com', 'text', 'Contact email'],
['contact_phone', '+82-2-1234-5678', 'text', 'Contact phone'],
['contact_address', 'Seoul, South Korea', 'text', 'Contact address'],
['facebook_url', '', 'text', 'Facebook page URL'],
['instagram_url', '', 'text', 'Instagram profile URL'],
['twitter_url', '', 'text', 'Twitter profile URL'],
['booking_enabled', 'true', 'boolean', 'Enable online booking'],
['maintenance_mode', 'false', 'boolean', 'Maintenance mode']
];
for (const [key, value, type, description] of defaultSettings) {
try {
await db.query(`
INSERT INTO site_settings (setting_key, setting_value, setting_type, description)
VALUES ($1, $2, $3, $4)
ON CONFLICT (setting_key) DO NOTHING
`, [key, value, type, description]);
} catch (error) {
console.log(` Setting ${key} already exists`);
}
}
console.log('✅ Default site settings inserted');
console.log('✨ Migration completed successfully!');
process.exit(0);
} catch (error) {
console.error('❌ Migration failed:', error);
process.exit(1);
}
}
if (require.main === module) {
runMigrations();
}
module.exports = { runMigrations };

109
database/mock-data.sql Normal file
View File

@@ -0,0 +1,109 @@
-- Мокапные данные для туристического сайта
-- Удаляем старые данные и заполняем новыми
-- Очистка таблиц
TRUNCATE TABLE bookings, reviews, routes, guides, articles, admins, contact_messages, site_settings RESTART IDENTITY CASCADE;
-- Админы
INSERT INTO admins (username, password, name, email, role, created_at) VALUES
('admin', '$2b$12$LQv3c1yqBwEHbLn5F2x/3OlzqXrJQ9vSf9Gm7ZwTsYcAb3DeF4gHi', 'Администратор', 'admin@koreatour.com', 'super_admin', NOW()),
('manager', '$2b$12$LQv3c1yqBwEHbLn5F2x/3OlzqXrJQ9vSf9Gm7ZwTsYcAb3DeF4gHi', 'Менеджер', 'manager@koreatour.com', 'admin', NOW());
-- Гиды
INSERT INTO guides (name, email, phone, languages, specialization, bio, image_url, hourly_rate, is_active, created_at) VALUES
('Ким Мин Джун', 'kim.minjun@guide.com', '+82-10-1234-5678', ARRAY['русский', 'корейский', 'английский'], 'city', 'Опытный гид со знанием истории и культуры Кореи. Специализируется на дворцах Сеула и традиционных деревнях.', '/images/guides/kim-minjun.jpg', 25000, true, NOW()),
('Пак Со Ён', 'park.soyeon@guide.com', '+82-10-2345-6789', ARRAY['русский', 'корейский', 'китайский'], 'mountain', 'Инструктор по горному туризму. Знает все тропы национальных парков и безопасные маршруты.', '/images/guides/park-soyeon.jpg', 20000, true, NOW()),
('Ли Дон Хёк', 'lee.donhyuk@guide.com', '+82-10-3456-7890', ARRAY['корейский', 'английский', 'японский'], 'fishing', 'Капитан с большим опытом морской рыбалки. Знает лучшие места для ловли у берегов Пусана.', '/images/guides/lee-donhyuk.jpg', 30000, true, NOW()),
('Чой Ю На', 'choi.yuna@guide.com', '+82-10-4567-8901', ARRAY['русский', 'корейский'], 'city', 'Эксперт корейской кухни. Проводит кулинарные мастер-классы и дегустационные туры по Сеулу.', '/images/guides/choi-yuna.jpg', 22000, true, NOW()),
('Юн Тэ Гу', 'yun.taegu@guide.com', '+82-10-5678-9012', ARRAY['корейский', 'английский'], 'city', 'Историк и археолог. Специализируется на древних памятниках и буддийских храмах Кореи.', '/images/guides/yun-taegu.jpg', 24000, true, NOW());
-- Маршруты
INSERT INTO routes (title, description, type, duration, price, difficulty_level, max_group_size, guide_id, image_url, content, included_services, is_active, is_featured, created_at) VALUES
('Сеул: Дворцы и традиции', 'Познакомьтесь с королевскими дворцами Сеула, традиционными районами и современной культурой K-pop.', 'city', 3, 450000, 'easy', 12, 1, '/images/tours/seoul-palaces-1.jpg',
'День 1: Дворец Кёнбоккун, смена караула, район Букчон Ханок. День 2: Дворец Чхандоккун, Тайный сад, рынок Инсадон. День 3: Намдэмун, Мёндон, башня Намсан, шоу K-pop.',
ARRAY['Входные билеты в дворцы', 'Трансфер', 'Гид', 'Обеды'], true, true, NOW()),
('Пусан: Морской город', 'Откройте для себя главный порт Кореи, его пляжи, рыбные рынки и современную архитектуру.', 'city', 2, 320000, 'easy', 10, 4, '/images/tours/busan-1.jpg',
'День 1: Пляж Хэундэ, храм Хэдон Ёнгунса, рыбный рынок Чагальчи. День 2: Остров Орюкдо, деревня Камчон, башня Пусан.',
ARRAY['Трансфер', 'Гид', 'Входные билеты', 'Обеды'], true, true, NOW()),
('Кёнджу: Древняя столица', 'Посетите древнюю столицу династии Силла с её храмами, гробницами и археологическими памятниками.', 'city', 2, 380000, 'easy', 8, 5, '/images/tours/gyeongju-1.jpg',
'День 1: Грот Соккурам, храм Пульгукса, музей Кёнджу. День 2: Парк Тумули, пруд Анапчи, обсерватория Чхомсондэ.',
ARRAY['Трансфер', 'Гид-историк', 'Входные билеты', 'Обеды'], true, false, NOW()),
('Сораксан: Горные вершины', 'Треккинг в одном из красивейших национальных парков Кореи с водопадами и горными храмами.', 'mountain', 4, 680000, 'moderate', 8, 2, '/images/tours/seoraksan-1.jpg',
'День 1: Прибытие, водопад Юктам, храм Синхынса. День 2: Подъём на Ульсанбави, канатная дорога. День 3: Треккинг к пику Тэчхонбон. День 4: Долина Пэктам, возвращение.',
ARRAY['Проживание в горной хижине', 'Все питание', 'Гид-инструктор', 'Снаряжение'], true, true, NOW()),
('Чирисан: Духовный путь', 'Поход по священным тропам самого высокого материкового пика Кореи с посещением древних храмов.', 'mountain', 5, 750000, 'moderate', 6, 2, '/images/tours/jirisan-1.jpg',
'День 1: Храм Хваомса, начало восхождения. День 2: Восхождение на пик Чонванбон. День 3: Переход через хребет. День 4: Храм Ссанггеса, медитация. День 5: Спуск, завершение маршрута.',
ARRAY['Проживание в храмах и хижинах', 'Вегетарианское питание', 'Гид', 'Церемонии'], true, false, NOW()),
('Халласан (остров Чеджу)', 'Восхождение на высочайшую вершину Кореи на вулканическом острове Чеджу.', 'mountain', 3, 580000, 'moderate', 10, 2, '/images/tours/hallasan-1.jpg',
'День 1: Прибытие на Чеджу, тропа Сонпанак. День 2: Восхождение на Халласан, кратерное озеро. День 3: Водопад Чонбан, базальтовые колонны.',
ARRAY['Авиаперелёт', 'Проживание', 'Гид', 'Трансфер'], true, true, NOW()),
('Рыбалка у берегов Пусана', 'Морская рыбалка с опытным капитаном в богатых рыбой водах Корейского пролива.', 'fishing', 2, 420000, 'easy', 6, 3, '/images/tours/fishing-busan-1.jpg',
'День 1: Инструктаж, выход в море, рыбалка на морского леща. День 2: Глубоководная рыбалка, приготовление улова.',
ARRAY['Катер', 'Снасти', 'Инструктор', 'Приготовление рыбы'], true, true, NOW()),
('Рыбалка на Восточном море', 'Рыбалка в водах Восточного моря с ночёвкой на рыбацком судне и изучением традиционных методов ловли.', 'fishing', 3, 650000, 'moderate', 4, 3, '/images/tours/east-sea-fishing-1.jpg',
'День 1: Выход из порта Сокчо, дневная рыбалка. День 2: Ночная рыбалка на кальмаров, ночёвка на судне. День 3: Утренняя рыбалка, возвращение в порт.',
ARRAY['Судно', 'Все снасти', 'Питание на борту', 'Опытная команда'], true, false, NOW());
-- Статьи
INSERT INTO articles (title, content, excerpt, category, image_url, author_id, views, is_published, created_at, updated_at) VALUES
('Лучшее время для посещения Кореи',
'Корея прекрасна в любое время года, но каждый сезон имеет свои особенности. Весна (март-май) - один из лучших периодов для посещения Кореи. Температура комфортная (15-20°C), цветёт сакура, особенно красиво в парках Сеула и Пусана. Лето жаркое и влажное с частыми дождями. Осень - самый популярный сезон среди туристов с потрясающими красками в горах. Зима холодная, но отличное время для горнолыжного спорта.',
'Узнайте, когда лучше всего посещать Корею в зависимости от ваших предпочтений и планируемых активностей.',
'travel-tips', '/images/articles/korea-seasons.jpg', 1, 1247, true, NOW(), NOW()),
('Корейская кухня для туристов',
'Корейская кухня - это больше чем просто кимчи. Кимчи - ферментированные овощи, главное блюдо корейской кухни. Пибимпап - рис с овощами, мясом и яйцом. Пульгоги - маринованная говядина-гриль. Самгёпсаль - свиная грудинка на гриле. Соджу - корейская водка, самый популярный алкогольный напиток. Лучшие рестораны находятся в районах Мёндон и Хонгдэ в Сеуле.',
'Полный гид по корейской кухне: что попробовать, где поесть и как заказывать.',
'food', '/images/articles/korean-food.jpg', 1, 892, true, NOW(), NOW()),
('Топ-10 храмов Кореи',
'Буддийские храмы Кореи - архитектурные шедевры. Пульгукса (Кёнджу) - объект ЮНЕСКО. Хэинса - хранилище Трипитаки Кореаны. Чогеса - главный храм в Сеуле. Синхынса - храм в Сораксане с гигантской статуей Будды. При посещении снимайте обувь, говорите тихо, фотографируйте без вспышки.',
'Откройте для себя духовное наследие Кореи через посещение её самых значимых буддийских храмов.',
'culture', '/images/articles/korean-temples.jpg', 2, 634, true, NOW(), NOW()),
('Что взять с собой в поход по корейским горам',
'Для походов в корейские горы нужно: треккинговые ботинки с хорошим протектором, дождевик (погода меняется быстро), слои одежды, головной убор, солнцезащитный крем. Тропы хорошо размечены, есть места отдыха каждые 1-2 км, питьевая вода в хижинах. Обязательно регистрируйтесь на входе в парк и не сходите с троп.',
'Полный список снаряжения и советы по безопасности для походов в корейские горы.',
'nature', '/images/articles/hiking-gear.jpg', 2, 423, true, NOW(), NOW());
-- Отзывы
INSERT INTO reviews (route_id, customer_name, customer_email, rating, comment, is_approved, created_at) VALUES
(1, 'Анна Петрова', 'anna.petrova@email.com', 5, 'Три дня в Сеуле пролетели как один день. Гид Ким Мин Джун просто потрясающий - знает историю каждого камня во дворцах. Особенно понравился Тайный сад и шоу K-pop. Обязательно вернёмся!', true, NOW() - INTERVAL '5 days'),
(4, 'Дмитрий Соколов', 'dmitry.sokolov@email.com', 5, 'Сораксан превзошёл все ожидания! Пак Со Ён - профессиональный гид, который заботится о безопасности группы. Виды с Ульсанбави просто космические. Рекомендую всем любителям гор!', true, NOW() - INTERVAL '12 days'),
(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');
-- Настройки сайта
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 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'),
(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');
-- Сообщения от посетителей
INSERT INTO contact_messages (name, email, phone, subject, message, status, created_at) VALUES
('Анна Смирнова', 'anna.smirnova@email.com', '+7-912-567-8901', 'Вопрос о групповой скидке', 'Здравствуйте! Планируем поездку группой из 15 человек. Есть ли групповые скидки на туры по Сеулу?', 'unread', NOW() - INTERVAL '2 hours'),
('Петр Козлов', 'petr.kozlov@email.com', '', 'Безопасность горных походов', 'Интересует безопасность походов в Сораксан. Какое снаряжение предоставляете? Есть ли медицинская поддержка?', 'read', NOW() - INTERVAL '1 day'),
('Ольга Волкова', 'olga.volkova@email.com', '+7-967-678-9012', 'Индивидуальный тур', 'Можете ли организовать индивидуальный тур по Кёнджу на 5 дней с посещением всех исторических мест?', 'unread', NOW() - INTERVAL '6 hours');

155
database/schema.sql Normal file
View File

@@ -0,0 +1,155 @@
-- Korea Tourism Agency Database Schema
-- Создание основных таблиц для туристического агентства
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Таблица администраторов
CREATE TABLE IF NOT EXISTS admins (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
role VARCHAR(20) DEFAULT 'admin',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица гидов
CREATE TABLE IF NOT EXISTS guides (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20),
bio TEXT,
specialization VARCHAR(20) NOT NULL CHECK (specialization IN ('city', 'mountain', 'fishing')),
languages TEXT[], -- Массив языков
experience INTEGER DEFAULT 0, -- Опыт в годах
image_url VARCHAR(255),
hourly_rate DECIMAL(8,2),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица маршрутов/туров
CREATE TABLE IF NOT EXISTS routes (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
content TEXT, -- Полное описание
type VARCHAR(20) NOT NULL CHECK (type IN ('city', 'mountain', 'fishing')),
price DECIMAL(10,2) NOT NULL,
duration INTEGER NOT NULL, -- Длительность в часах
difficulty_level VARCHAR(10) DEFAULT 'easy' CHECK (difficulty_level IN ('easy', 'moderate', 'hard')),
max_group_size INTEGER DEFAULT 10,
included_services TEXT[],
meeting_point TEXT,
image_url VARCHAR(255),
guide_id INTEGER REFERENCES guides(id),
is_featured BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица статей
CREATE TABLE IF NOT EXISTS articles (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
excerpt TEXT,
content TEXT NOT NULL,
category VARCHAR(50) NOT NULL CHECK (category IN ('travel-tips', 'culture', 'food', 'nature', 'history')),
image_url VARCHAR(255),
author_id INTEGER REFERENCES admins(id),
views INTEGER DEFAULT 0,
is_published BOOLEAN DEFAULT false,
meta_description TEXT,
meta_keywords TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица бронирований
CREATE TABLE IF NOT EXISTS bookings (
id SERIAL PRIMARY KEY,
route_id INTEGER REFERENCES routes(id) NOT NULL,
guide_id INTEGER REFERENCES guides(id),
customer_name VARCHAR(100) NOT NULL,
customer_email VARCHAR(100) NOT NULL,
customer_phone VARCHAR(20),
preferred_date DATE,
group_size INTEGER DEFAULT 1,
total_price DECIMAL(10,2),
special_requirements TEXT,
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed', 'cancelled', 'completed')),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица отзывов
CREATE TABLE IF NOT EXISTS reviews (
id SERIAL PRIMARY KEY,
route_id INTEGER REFERENCES routes(id),
guide_id INTEGER REFERENCES guides(id),
booking_id INTEGER REFERENCES bookings(id),
customer_name VARCHAR(100) NOT NULL,
customer_email VARCHAR(100),
rating INTEGER CHECK (rating >= 1 AND rating <= 5) NOT NULL,
comment TEXT,
is_approved BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица сообщений с формы контактов
CREATE TABLE IF NOT EXISTS contact_messages (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
phone VARCHAR(20),
subject VARCHAR(200),
message TEXT NOT NULL,
status VARCHAR(20) DEFAULT 'unread' CHECK (status IN ('unread', 'read', 'replied')),
admin_notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица настроек сайта
CREATE TABLE IF NOT EXISTS site_settings (
id SERIAL PRIMARY KEY,
setting_key VARCHAR(100) UNIQUE NOT NULL,
setting_value TEXT,
setting_type VARCHAR(20) DEFAULT 'text' CHECK (setting_type IN ('text', 'number', 'boolean', 'json')),
description TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Создание индексов для производительности
CREATE INDEX IF NOT EXISTS idx_routes_type ON routes(type);
CREATE INDEX IF NOT EXISTS idx_routes_active ON routes(is_active);
CREATE INDEX IF NOT EXISTS idx_routes_featured ON routes(is_featured);
CREATE INDEX IF NOT EXISTS idx_guides_specialization ON guides(specialization);
CREATE INDEX IF NOT EXISTS idx_guides_active ON guides(is_active);
CREATE INDEX IF NOT EXISTS idx_articles_category ON articles(category);
CREATE INDEX IF NOT EXISTS idx_articles_published ON articles(is_published);
CREATE INDEX IF NOT EXISTS idx_bookings_status ON bookings(status);
CREATE INDEX IF NOT EXISTS idx_bookings_date ON bookings(preferred_date);
CREATE INDEX IF NOT EXISTS idx_reviews_rating ON reviews(rating);
-- Создание триггеров для автоматического обновления updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_admins_updated_at BEFORE UPDATE ON admins FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_guides_updated_at BEFORE UPDATE ON guides FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_routes_updated_at BEFORE UPDATE ON routes FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_articles_updated_at BEFORE UPDATE ON articles FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_bookings_updated_at BEFORE UPDATE ON bookings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_site_settings_updated_at BEFORE UPDATE ON site_settings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

250
database/seed.js Normal file
View File

@@ -0,0 +1,250 @@
const db = require('../src/config/database');
async function seedDatabase() {
try {
console.log('🌱 Starting database seeding...');
// Seed guides
const guides = [
{
name: 'Kim Min-jun',
email: 'minjun@koreatourism.com',
phone: '+82-10-1234-5678',
bio: 'Experienced Seoul city guide with 8 years of expertise in Korean history and culture. Fluent in English, Japanese, and Chinese.',
specialization: 'city',
languages: ['Korean', 'English', 'Japanese', 'Chinese'],
experience: 8,
hourly_rate: 50000
},
{
name: 'Park So-young',
email: 'soyoung@koreatourism.com',
phone: '+82-10-2345-6789',
bio: 'Mountain hiking specialist with deep knowledge of Korean national parks. Safety-certified guide with wilderness first aid training.',
specialization: 'mountain',
languages: ['Korean', 'English'],
experience: 6,
hourly_rate: 45000
},
{
name: 'Lee Sung-ho',
email: 'sungho@koreatourism.com',
phone: '+82-10-3456-7890',
bio: 'Professional fishing guide specializing in coastal and river fishing. 10 years of experience with traditional Korean fishing techniques.',
specialization: 'fishing',
languages: ['Korean', 'English'],
experience: 10,
hourly_rate: 60000
},
{
name: 'Choi Yeon-seo',
email: 'yeonseo@koreatourism.com',
phone: '+82-10-4567-8901',
bio: 'Cultural heritage expert specializing in traditional Korean architecture and temples. PhD in Korean History.',
specialization: 'city',
languages: ['Korean', 'English', 'Mandarin'],
experience: 12,
hourly_rate: 55000
}
];
for (const guide of guides) {
await db.query(`
INSERT INTO guides (name, email, phone, bio, specialization, languages, experience, hourly_rate)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (email) DO NOTHING
`, [guide.name, guide.email, guide.phone, guide.bio, guide.specialization, guide.languages, guide.experience, guide.hourly_rate]);
}
console.log('✅ Guides seeded successfully');
// Get guide IDs for routes
const guideRows = await db.query('SELECT id, name, specialization FROM guides ORDER BY id');
const guideMap = {};
guideRows.rows.forEach(guide => {
if (!guideMap[guide.specialization]) guideMap[guide.specialization] = [];
guideMap[guide.specialization].push(guide.id);
});
// Seed routes
const routes = [
{
title: 'Historic Seoul Walking Tour',
description: 'Explore the ancient palaces and traditional markets of Seoul with our expert guide.',
content: 'Discover the rich history of Seoul through a comprehensive walking tour that covers Gyeongbokgung Palace, Bukchon Hanok Village, and Insadong traditional market. Learn about Korean royal history, traditional architecture, and sample authentic Korean street food.',
type: 'city',
price: 75000,
duration: 4,
difficulty_level: 'easy',
max_group_size: 15,
included_services: ['Professional guide', 'Traditional tea ceremony', 'Palace entrance fees'],
meeting_point: 'Gyeongbokgung Palace Main Gate',
guide_id: guideMap.city ? guideMap.city[0] : null,
is_featured: true
},
{
title: 'Seoraksan National Park Hiking',
description: 'Experience the breathtaking beauty of Seoraksan with guided mountain hiking.',
content: 'Join us for an unforgettable hiking experience in Seoraksan National Park. This moderate-level hike takes you through stunning mountain landscapes, ancient temples, and offers spectacular views from the peaks. Perfect for nature lovers and photography enthusiasts.',
type: 'mountain',
price: 120000,
duration: 8,
difficulty_level: 'moderate',
max_group_size: 8,
included_services: ['Certified mountain guide', 'Safety equipment', 'Traditional lunch', 'Transportation'],
meeting_point: 'Sokcho Bus Terminal',
guide_id: guideMap.mountain ? guideMap.mountain[0] : null,
is_featured: true
},
{
title: 'East Sea Fishing Adventure',
description: 'Traditional Korean fishing experience in the beautiful East Sea.',
content: 'Learn traditional Korean fishing techniques while enjoying the scenic beauty of the East Sea. Our experienced guide will teach you about local marine life, traditional fishing methods, and you\'ll enjoy a fresh seafood lunch prepared from your catch.',
type: 'fishing',
price: 95000,
duration: 6,
difficulty_level: 'easy',
max_group_size: 6,
included_services: ['Fishing equipment', 'Boat charter', 'Fresh seafood lunch', 'Professional guide'],
meeting_point: 'Gangneung Harbor',
guide_id: guideMap.fishing ? guideMap.fishing[0] : null,
is_featured: true
},
{
title: 'Busan Coastal City Tour',
description: 'Discover the vibrant coastal city of Busan with its beaches, temples, and markets.',
content: 'Explore Busan\'s highlights including Haeundae Beach, Jagalchi Fish Market, Gamcheon Culture Village, and Beomeosa Temple. Experience the unique culture of Korea\'s largest port city.',
type: 'city',
price: 85000,
duration: 6,
difficulty_level: 'easy',
max_group_size: 12,
included_services: ['Professional guide', 'Market tasting', 'Temple entrance', 'Local transportation'],
meeting_point: 'Busan Station',
guide_id: guideMap.city ? guideMap.city[1] : null,
is_featured: false
},
{
title: 'Jirisan Mountain Expedition',
description: 'Challenge yourself with a multi-day trek through Korea\'s largest national park.',
content: 'Experience the wilderness of Jirisan National Park on this challenging multi-day expedition. Trek through ancient forests, visit remote temples, and enjoy spectacular mountain vistas.',
type: 'mountain',
price: 250000,
duration: 16,
difficulty_level: 'hard',
max_group_size: 4,
included_services: ['Expert guide', 'Camping equipment', 'All meals', 'Emergency support'],
meeting_point: 'Jirisan National Park Visitor Center',
guide_id: guideMap.mountain ? guideMap.mountain[0] : null,
is_featured: false
},
{
title: 'Jeju Island Fishing Charter',
description: 'Deep sea fishing adventure around the beautiful Jeju Island.',
content: 'Experience world-class deep sea fishing in the waters around Jeju Island. Target species include tuna, marlin, and various local fish while enjoying the stunning volcanic island scenery.',
type: 'fishing',
price: 180000,
duration: 10,
difficulty_level: 'moderate',
max_group_size: 8,
included_services: ['Charter boat', 'Professional crew', 'Equipment', 'Lunch', 'Fish preparation'],
meeting_point: 'Jeju Harbor',
guide_id: guideMap.fishing ? guideMap.fishing[0] : null,
is_featured: false
}
];
for (const route of routes) {
await db.query(`
INSERT INTO routes (
title, description, content, type, price, duration,
difficulty_level, max_group_size, included_services,
meeting_point, guide_id, is_featured
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
`, [
route.title, route.description, route.content, route.type,
route.price, route.duration, route.difficulty_level,
route.max_group_size, route.included_services,
route.meeting_point, route.guide_id, route.is_featured
]);
}
console.log('✅ Routes seeded successfully');
// Seed articles
const adminResult = await db.query('SELECT id FROM admins LIMIT 1');
const adminId = adminResult.rows[0]?.id;
const articles = [
{
title: 'Best Time to Visit Korea: A Seasonal Guide',
excerpt: 'Discover when to visit Korea for the perfect weather and experiences throughout the year.',
content: `Korea offers something special in every season, making it a year-round destination...
Spring (March-May): Cherry blossoms bloom across the country, creating stunning pink canopies. This is perhaps the most popular time to visit Korea. The weather is mild and perfect for outdoor activities.
Summer (June-August): Hot and humid with occasional monsoons. Great for mountain hiking and coastal activities. Many festivals happen during this time.
Autumn (September-November): Spectacular fall foliage and comfortable temperatures. Ideal for hiking and sightseeing. Clear skies offer great mountain views.
Winter (December-February): Cold but beautiful, especially with snow. Perfect for winter sports and enjoying hot springs. Christmas and New Year celebrations add to the charm.`,
category: 'travel-tips',
author_id: adminId,
is_published: true
},
{
title: 'Korean Temple Etiquette: Respectful Visiting Tips',
excerpt: 'Learn the proper way to visit Korean Buddhist temples and show respect for local customs.',
content: `Visiting Korean temples can be a deeply spiritual and cultural experience. Here are essential tips for respectful temple visits...
Dress Code: Wear modest clothing that covers shoulders and knees. Avoid revealing or tight clothing.
Behavior: Maintain quiet voices and respectful demeanor. Remove hats and sunglasses before entering temple halls.
Photography: Ask permission before taking photos of people, especially monks. Some areas may prohibit photography entirely.
Temple Stay: Many temples offer overnight experiences where you can participate in meditation and daily temple life.`,
category: 'culture',
author_id: adminId,
is_published: true
},
{
title: 'Korean Street Food You Must Try',
excerpt: 'A delicious guide to Korea\'s most popular street foods and where to find them.',
content: `Korean street food is an adventure for your taste buds. Here are the must-try dishes...
Tteokbokki: Spicy rice cakes in gochujang sauce, a Korean comfort food classic.
Hotteok: Sweet pancakes filled with sugar, nuts, and cinnamon, perfect for cold days.
Bungeoppang: Fish-shaped pastries filled with sweet red bean paste.
Kimbap: Korean rice rolls with various fillings, perfect for a quick meal.
Odeng: Fish cake soup served hot from street vendors, especially popular in winter.`,
category: 'food',
author_id: adminId,
is_published: true
}
];
for (const article of articles) {
await db.query(`
INSERT INTO articles (title, excerpt, content, category, author_id, is_published)
VALUES ($1, $2, $3, $4, $5, $6)
`, [article.title, article.excerpt, article.content, article.category, article.author_id, article.is_published]);
}
console.log('✅ Articles seeded successfully');
console.log('✨ Database seeding completed successfully!');
process.exit(0);
} catch (error) {
console.error('❌ Seeding failed:', error);
process.exit(1);
}
}
if (require.main === module) {
seedDatabase();
}
module.exports = { seedDatabase };