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

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