commit 409e6c146be74997feceb3274dc70e52346de568 Author: Andrey K. Choi Date: Sat Nov 29 18:13:17 2025 +0900 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 diff --git a/.adminjs/bundle.js b/.adminjs/bundle.js new file mode 100644 index 0000000..014b9c3 --- /dev/null +++ b/.adminjs/bundle.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + AdminJS.UserComponents = {}; + +})(); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlcyI6WyJlbnRyeS5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJBZG1pbkpTLlVzZXJDb21wb25lbnRzID0ge31cbiJdLCJuYW1lcyI6WyJBZG1pbkpTIiwiVXNlckNvbXBvbmVudHMiXSwibWFwcGluZ3MiOiI7OztDQUFBQSxPQUFPLENBQUNDLGNBQWMsR0FBRyxFQUFFOzs7Ozs7In0= diff --git a/.adminjs/entry.js b/.adminjs/entry.js new file mode 100644 index 0000000..c9edeb9 --- /dev/null +++ b/.adminjs/entry.js @@ -0,0 +1 @@ +AdminJS.UserComponents = {} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3caaf55 --- /dev/null +++ b/.env.example @@ -0,0 +1,72 @@ +# Korea Tourism Agency - Environment Variables Example +# Копируйте этот файл в .env и настройте под ваши параметры + +# ============================================== +# Database Configuration +# ============================================== +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=korea_tourism +DB_USER=tourism_user +DB_PASSWORD=tourism_password + +# ============================================== +# Application Configuration +# ============================================== +PORT=3000 +NODE_ENV=development +SESSION_SECRET=korea-tourism-secret-key-2024-change-in-production + +# ============================================== +# File Upload Configuration +# ============================================== +UPLOAD_PATH=/app/public/uploads +MAX_FILE_SIZE=5242880 + +# ============================================== +# Site Information +# ============================================== +SITE_NAME=Korea Tourism Agency +SITE_DESCRIPTION=Discover Korea's hidden gems with our guided tours +CONTACT_EMAIL=info@koreatourism.com +CONTACT_PHONE=+82-2-1234-5678 +COMPANY_ADDRESS=서울특별시 중구 세종대로 110 +ADMIN_EMAIL=admin@koreatourism.com + +# ============================================== +# Admin Configuration +# ============================================== +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin123 + +# ============================================== +# Email Configuration (Optional) +# ============================================== +# SMTP_HOST=smtp.gmail.com +# SMTP_PORT=587 +# SMTP_USER=your-email@gmail.com +# SMTP_PASSWORD=your-app-password +# SMTP_FROM=Korea Tourism + +# ============================================== +# External API Keys (Optional) +# ============================================== +# GOOGLE_MAPS_API_KEY=your-google-maps-api-key +# WEATHER_API_KEY=your-weather-api-key +# KAKAO_MAP_API_KEY=your-kakao-map-api-key + +# ============================================== +# Security Configuration +# ============================================== +# CORS_ORIGIN=http://localhost:3000 +# RATE_LIMIT_WINDOW=900000 +# RATE_LIMIT_MAX=100 + +# ============================================== +# Production Settings +# ============================================== +# В production обязательно изменить: +# NODE_ENV=production +# SESSION_SECRET=генерировать-новый-безопасный-ключ +# ADMIN_PASSWORD=создать-безопасный-пароль +# Настроить SSL и реальные домены \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..73f6fd0 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,36 @@ +# Korea Tourism Agency Website + +Туристическое агентство для внутренних поездок по Корее. + +## Технический стек +- Backend: Node.js + Express.js +- Database: PostgreSQL +- Frontend: HTML/CSS/JavaScript с адаптивным дизайном +- Deployment: Docker + Docker Compose +- Environment: Переменные окружения через .env + +## Функциональность +- Каталог туристических маршрутов (города, горы, морские рыбалки) +- Управление гидами +- Система статей и блога +- Административная панель +- Адаптивный и стильный дизайн + +## Структура проекта +``` +/ +├── src/ # Исходный код приложения +├── public/ # Статические файлы (CSS, JS, images) +├── views/ # EJS шаблоны +├── database/ # Миграции и схемы БД +├── docker/ # Docker конфигурации +└── docs/ # Документация +``` + +## Основные сущности +- Routes (маршруты): city tours, mountain trips, fishing tours +- Guides (гиды): профили, специализации, языки +- Articles (статьи): блог, полезная информация +- Users (пользователи): администраторы, клиенты + +Все данные конфигурации вынесены в .env файл для безопасности. \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2a45ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Environment variables +.env +.env.local +.env.production + +# Logs +*.debug +*.log +logs/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Database files +*.sqlite +*.sqlite3 +*.db + +# Node modules +node_modules/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Build output +dist/ +build/ + +# History +.history/ + +# Docker +.dockerignore + +# Temporary files +tmp/ +temp/ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e4356a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM node:18-alpine + +# Set working directory +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm install + +# Create uploads directory +RUN mkdir -p public/uploads + +# Copy application files +COPY . . + +# Expose port +EXPOSE 3000 + +# Command to run the application +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..b3a08bf --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,44 @@ +FROM node:18-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy application files +COPY . . + +# Remove development files +RUN rm -rf .git .gitignore docker-compose.yml Dockerfile README.md + +# Final stage +FROM node:18-alpine + +WORKDIR /app + +# Create non-root user +RUN addgroup -g 1001 -S nodejs +RUN adduser -S tourism -u 1001 + +# Copy from builder +COPY --from=builder --chown=tourism:nodejs /app . + +# Create uploads directory +RUN mkdir -p public/uploads && chown tourism:nodejs public/uploads + +# Switch to non-root user +USER tourism + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" + +# Start application +CMD ["npm", "start"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ddc90a --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Korea Tourism Agency 🇰🇷 + +Современный сайт туристического агентства для внутренних поездок по Корее с профессиональной админ-панелью. + +![Korea Tourism](https://img.shields.io/badge/Korea-Tourism-blue?style=for-the-badge) +![Node.js](https://img.shields.io/badge/Node.js-v18+-green?style=for-the-badge) +![PostgreSQL](https://img.shields.io/badge/PostgreSQL-13+-blue?style=for-the-badge) +![Docker](https://img.shields.io/badge/Docker-Ready-blue?style=for-the-badge) + +## 🌟 Особенности + +### 🎯 Основные функции +- **Каталог туров**: Городские экскурсии, горные походы, морская рыбалка +- **Система гидов**: Профессиональные гиды с рейтингами и специализациями +- **Блог и статьи**: Полезная информация о путешествиях по Корее +- **Система бронирования**: Онлайн заявки с управлением статусами +- **Многоязычность**: Поддержка корейского и английского языков + +### 🎨 AdminLTE панель управления +- **Современный дизайн**: Профессиональная админ-панель на основе AdminLTE 3.2 +- **Управление контентом**: Маршруты, гиды, статьи, бронирования +- **Dashboard с аналитикой**: Статистика посещений и бронирований +- **Безопасность**: Аутентификация с хешированием паролей +- **Загрузка файлов**: Система загрузки изображений с превью + +### 🚀 Технологии +- **Backend**: Node.js + Express.js +- **Database**: PostgreSQL с миграциями +- **Frontend**: Bootstrap 5 + EJS шаблоны +- **Admin**: AdminLTE 3.2 + DataTables + Chart.js +- **Deployment**: Docker + Docker Compose +- **Styles**: Responsive дизайн с корейскими элементами + +## 🚀 Быстрый старт + +### Предварительные требования +- Docker и Docker Compose +- Git + +### Установка и запуск + +1. **Клонировать репозиторий** +```bash\ngit clone \ncd korea-tourism-agency\n```\n\n2. **Запустить среду разработки**\n```bash\n# Дать права на выполнение скрипту\nchmod +x start-dev.sh\n\n# Запустить полное окружение\n./start-dev.sh\n```\n\n3. **Ручной запуск (альтернатива)**\n```bash\n# Создать .env файл из примера\ncp .env.example .env\n\n# Запустить контейнеры\ndocker-compose up -d\n\n# Выполнить миграции\ndocker-compose exec app npm run db:migrate\n\n# Заполнить тестовыми данными\ndocker-compose exec app npm run db:seed\n```\n\n### 🌐 Доступ к сайту\n\nПосле успешного запуска:\n\n- **🏠 Основной сайт**: http://localhost:3000\n- **⚙️ Админ панель**: http://localhost:3000/admin\n- **🗄️ Adminer (БД)**: http://localhost:8080\n\n### 🔐 Данные для входа\n\n**Админ-панель:**\n- Username: `admin`\n- Password: `admin123`\n\n**База данных (Adminer):**\n- System: `PostgreSQL`\n- Server: `postgres`\n- Username: `tourism_user`\n- Password: `tourism_password`\n- Database: `korea_tourism`\n\n## 📁 Структура проекта\n\n```\nkorea-tourism-agency/\n├── 📂 src/ # Исходный код приложения\n│ ├── 📂 config/ # Конфигурация БД\n│ └── 📂 routes/ # Express маршруты\n├── 📂 views/ # EJS шаблоны\n│ ├── 📂 admin/ # Шаблоны админки\n│ ├── 📂 routes/ # Страницы туров\n│ ├── 📂 guides/ # Страницы гидов\n│ └── 📂 articles/ # Страницы статей\n├── 📂 public/ # Статические файлы\n│ ├── 📂 css/ # Стили\n│ ├── 📂 js/ # JavaScript\n│ ├── 📂 images/ # Изображения\n│ └── 📂 uploads/ # Загруженные файлы\n├── 📂 database/ # Миграции и схемы БД\n│ ├── schema.sql # Схема БД\n│ ├── migrate.js # Скрипт миграций\n│ └── seed.js # Тестовые данные\n├── 📂 docker/ # Docker конфигурации\n└── 📂 docs/ # Документация\n```\n\n## 🗄️ База данных\n\n### Основные таблицы\n\n- **routes** - Туристические маршруты\n- **guides** - Профили гидов\n- **articles** - Статьи блога\n- **bookings** - Бронирования\n- **admins** - Администраторы\n- **contact_messages** - Сообщения с формы контактов\n- **site_settings** - Настройки сайта\n\n### Типы туров\n\n- **city** - Городские экскурсии (서울, 부산)\n- **mountain** - Горные походы (설악산, 지리산)\n- **fishing** - Морская рыбалка (동해, 제주도)\n\n## 🛠️ Разработка\n\n### Полезные команды\n\n```bash\n# Просмотр логов\ndocker-compose logs -f app\n\n# Перезапуск приложения\ndocker-compose restart app\n\n# Выполнение команд в контейнере\ndocker-compose exec app npm run db:migrate\ndocker-compose exec app npm run db:seed\n\n# Остановка всех контейнеров\ndocker-compose down\n\n# Полная очистка (внимание: удалит данные БД)\ndocker-compose down -v\ndocker system prune -f\n```\n\n### Структура маршрутов\n\n- **/** - Главная страница\n- **/routes** - Каталог туров\n- **/routes/:id** - Детали тура\n- **/guides** - Список гидов\n- **/guides/:id** - Профиль гида\n- **/articles** - Блог статьи\n- **/admin** - Админ панель\n- **/api** - REST API\n\n### API эндпоинты\n\n- `GET /api/routes` - Получить туры с фильтрацией\n- `GET /api/guides` - Получить гидов\n- `POST /api/booking` - Создать бронирование\n- `GET /api/search` - Поиск по сайту\n\n## 🎨 Дизайн\n\n### Цветовая схема\n- **Основной цвет**: #2563eb (Blue)\n- **Корейский красный**: #c41e3a\n- **Корейский синий**: #003478\n- **Градиенты**: Современные переходы цветов\n\n### Компоненты UI\n- **AdminLTE 3.2** - Админ панель\n- **Bootstrap 5** - Фронтенд фреймворк\n- **Font Awesome** - Иконки\n- **AOS** - Анимации при скролле\n- **DataTables** - Таблицы в админке\n- **Chart.js** - Графики и диаграммы\n\n## 🌍 Локализация\n\nСайт поддерживает:\n- **Корейский язык** (основной)\n- **Английский язык** (для туристов)\n- **Правильные шрифты**: Noto Sans KR для корейского текста\n\n## 📱 Адаптивность\n\n- ✅ Desktop (1200px+)\n- ✅ Tablet (768px-1199px)\n- ✅ Mobile (до 767px)\n- ✅ Поддержка touch устройств\n\n## 🔧 Настройки\n\n### Переменные окружения (.env)\n\n```env\n# База данных\nDB_HOST=postgres\nDB_PORT=5432\nDB_NAME=korea_tourism\nDB_USER=tourism_user\nDB_PASSWORD=tourism_password\n\n# Приложение\nPORT=3000\nNODE_ENV=development\nSESSION_SECRET=your-secret-key\n\n# Загрузка файлов\nUPLOAD_PATH=/app/public/uploads\nMAX_FILE_SIZE=5242880\n\n# Контакты\nSITE_NAME=Korea Tourism Agency\nCONTACT_EMAIL=info@koreatourism.com\nCONTACT_PHONE=+82-2-1234-5678\n```\n\n## 🚀 Production развертывание\n\n```bash\n# Использовать production compose файл\ndocker-compose -f docker-compose.prod.yml up -d\n\n# Или создать .env.production\ncp .env.example .env.production\n# Отредактировать настройки для production\n\n# Запустить с production настройками\nNODE_ENV=production docker-compose up -d\n```\n\n## 📸 Скриншоты\n\n### 🏠 Главная страница\n- Hero секция с призывом к действию\n- Карточки популярных туров\n- Статистика и отзывы\n- Современный адаптивный дизайн\n\n### ⚙️ Админ панель\n- Dashboard с аналитикой\n- Управление турами и гидами\n- Система загрузки изображений\n- Таблицы с поиском и фильтрацией\n\n## 🤝 Участие в разработке\n\n1. Fork проекта\n2. Создайте feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit изменения (`git commit -m 'Add amazing feature'`)\n4. Push в branch (`git push origin feature/amazing-feature`)\n5. Создайте Pull Request\n\n## 📄 Лицензия\n\nЭтот проект использует MIT лицензию. Подробности в файле [LICENSE](LICENSE).\n\n## 🆘 Поддержка\n\nЕсли у вас есть вопросы:\n\n- 📧 Email: info@koreatourism.com\n- 🐛 Issues: Создайте issue в репозитории\n- 📖 Docs: Смотрите папку `/docs`\n\n---\n\n**Korea Tourism Agency** - Откройте для себя красоту Кореи! 🇰🇷✨ \ No newline at end of file diff --git a/database/migrate.js b/database/migrate.js new file mode 100644 index 0000000..750fd93 --- /dev/null +++ b/database/migrate.js @@ -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 }; \ No newline at end of file diff --git a/database/mock-data.sql b/database/mock-data.sql new file mode 100644 index 0000000..a78db23 --- /dev/null +++ b/database/mock-data.sql @@ -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'); \ No newline at end of file diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..961c1d5 --- /dev/null +++ b/database/schema.sql @@ -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(); \ No newline at end of file diff --git a/database/seed.js b/database/seed.js new file mode 100644 index 0000000..d5c5764 --- /dev/null +++ b/database/seed.js @@ -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 }; \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..717525d --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,66 @@ +version: '3.8' + +services: + # PostgreSQL Database + postgres: + image: postgres:15-alpine + container_name: korea_tourism_db_prod + restart: unless-stopped + environment: + POSTGRES_DB: ${DB_NAME} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - postgres_data_prod:/var/lib/postgresql/data + - ./database/init:/docker-entrypoint-initdb.d + networks: + - tourism_network_prod + + # Node.js Application + app: + build: + context: . + dockerfile: Dockerfile.prod + container_name: korea_tourism_app_prod + restart: unless-stopped + environment: + - NODE_ENV=production + - DB_HOST=postgres + - DB_PORT=5432 + - DB_NAME=${DB_NAME} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - PORT=3000 + - SESSION_SECRET=${SESSION_SECRET} + ports: + - "3000:3000" + volumes: + - ./public/uploads:/app/public/uploads + depends_on: + - postgres + networks: + - tourism_network_prod + + # Nginx Reverse Proxy + nginx: + image: nginx:alpine + container_name: korea_tourism_nginx + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf + - ./docker/nginx/ssl:/etc/ssl + - ./public:/var/www/public + depends_on: + - app + networks: + - tourism_network_prod + +volumes: + postgres_data_prod: + +networks: + tourism_network_prod: + driver: bridge \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..aa33128 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,64 @@ +version: '3.8' + +services: + # PostgreSQL Database + postgres: + image: postgres:15-alpine + container_name: korea_tourism_db + restart: unless-stopped + environment: + POSTGRES_DB: korea_tourism + POSTGRES_USER: tourism_user + POSTGRES_PASSWORD: tourism_password + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./database/init:/docker-entrypoint-initdb.d + networks: + - tourism_network + + # Node.js Application + app: + build: . + container_name: korea_tourism_app + restart: unless-stopped + environment: + - NODE_ENV=development + - DB_HOST=postgres + - DB_PORT=5432 + - DB_NAME=korea_tourism + - DB_USER=tourism_user + - DB_PASSWORD=tourism_password + - PORT=3000 + - SESSION_SECRET=dev-secret-change-in-production + ports: + - "3000:3000" + volumes: + - .:/app + - /app/node_modules + - ./public/uploads:/app/public/uploads + depends_on: + - postgres + networks: + - tourism_network + command: npm run dev + + # Adminer for database management (optional) + adminer: + image: adminer:latest + container_name: korea_tourism_adminer + restart: unless-stopped + ports: + - "8080:8080" + depends_on: + - postgres + networks: + - tourism_network + +volumes: + postgres_data: + +networks: + tourism_network: + driver: bridge \ No newline at end of file diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 0000000..3b4b23c --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,109 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + # Basic settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 10M; + + # Compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # Rate limiting + limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; + + upstream korea_tourism_app { + server app:3000; + } + + server { + listen 80; + server_name localhost; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Static files + location /uploads/ { + alias /var/www/public/uploads/; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + location /css/ { + alias /var/www/public/css/; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + location /js/ { + alias /var/www/public/js/; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + location /images/ { + alias /var/www/public/images/; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # API rate limiting + location /api/ { + limit_req zone=api burst=20 nodelay; + proxy_pass http://korea_tourism_app; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Admin login rate limiting + location /admin/login { + limit_req zone=login burst=3 nodelay; + proxy_pass http://korea_tourism_app; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # All other requests + location / { + proxy_pass http://korea_tourism_app; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + } +} \ No newline at end of file diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..a4e0b6b --- /dev/null +++ b/docs/PROJECT_STRUCTURE.md @@ -0,0 +1,217 @@ +# 📁 Korea Tourism Agency - Структура проекта + +## 🎯 Обзор проекта + +Полнофункциональный сайт туристического агентства для внутренних поездок по Корее с профессиональной админ-панелью на основе AdminLTE. + +## 📂 Структура файлов + +``` +korea-tourism-agency/ +│ +├── 📄 README.md # Документация проекта +├── 📄 package.json # Зависимости Node.js +├── 📄 docker-compose.yml # Конфигурация Docker +├── 📄 Dockerfile # Docker образ приложения +├── 📄 .env.example # Пример переменных окружения +├── 📄 .gitignore # Git игнорируемые файлы +├── 🚀 start-dev.sh # Скрипт быстрого запуска +│ +├── 📂 src/ # Исходный код приложения +│ ├── 📄 app.js # Основной файл приложения Express +│ ├── 📂 config/ +│ │ └── 📄 database.js # Конфигурация подключения к БД +│ └── 📂 routes/ # Express маршруты +│ ├── 📄 admin.js # Админ панель маршруты +│ ├── 📄 api.js # REST API маршруты +│ ├── 📄 routes.js # Маршруты туров +│ ├── 📄 guides.js # Маршруты гидов +│ └── 📄 articles.js # Маршруты статей +│ +├── 📂 views/ # EJS шаблоны +│ ├── 📄 layout.ejs # Общий layout +│ ├── 📄 index.ejs # Главная страница +│ ├── 📄 contact.ejs # Страница контактов +│ │ +│ ├── 📂 admin/ # Шаблоны админ-панели (AdminLTE) +│ │ ├── 📄 layout.ejs # Layout админки +│ │ ├── 📄 dashboard.ejs # Dashboard с аналитикой +│ │ ├── 📄 login.ejs # Страница входа +│ │ ├── 📄 routes.ejs # Управление турами +│ │ ├── 📄 guides.ejs # Управление гидами +│ │ ├── 📄 articles.ejs # Управление статьями +│ │ ├── 📄 bookings.ejs # Управление бронированиями +│ │ ├── 📄 settings.ejs # Настройки сайта +│ │ └── 📄 profile.ejs # Профиль администратора +│ │ +│ ├── 📂 routes/ # Страницы туров +│ │ ├── 📄 index.ejs # Список всех туров +│ │ └── 📄 detail.ejs # Детали тура +│ │ +│ ├── 📂 guides/ # Страницы гидов +│ │ ├── 📄 index.ejs # Список гидов +│ │ └── 📄 detail.ejs # Профиль гида +│ │ +│ ├── 📂 articles/ # Страницы статей +│ │ ├── 📄 index.ejs # Список статей +│ │ └── 📄 detail.ejs # Детали статьи +│ │ +│ └── 📂 partials/ # Компоненты +│ ├── 📄 header.ejs # Шапка сайта +│ ├── 📄 footer.ejs # Подвал сайта +│ ├── 📄 navbar.ejs # Навигация +│ └── 📄 meta.ejs # Meta теги +│ +├── 📂 public/ # Статические файлы +│ ├── 📂 css/ # Стили +│ │ ├── 📄 main.css # Основные стили (500+ строк) +│ │ └── 📄 admin.css # Дополнительные стили админки +│ │ +│ ├── 📂 js/ # JavaScript +│ │ ├── 📄 main.js # Основной JS (интерактивность) +│ │ └── 📄 admin.js # JS для админ-панели +│ │ +│ ├── 📂 images/ # Изображения +│ │ ├── 📄 logo.png # Логотип +│ │ ├── 📄 hero-bg.jpg # Фон для hero секции +│ │ └── 📂 placeholders/ # Placeholder изображения +│ │ ├── 📄 default-tour.svg # Заглушка для туров +│ │ └── 📄 default-guide.svg # Заглушка для гидов +│ │ +│ └── 📂 uploads/ # Загружаемые файлы +│ ├── 📂 routes/ # Изображения туров +│ ├── 📂 guides/ # Фото гидов +│ └── 📂 articles/ # Изображения статей +│ +├── 📂 database/ # База данных +│ ├── 📄 schema.sql # Полная схема БД (8 таблиц) +│ ├── 📄 migrate.js # Скрипт миграций +│ └── 📄 seed.js # Тестовые данные +│ +├── 📂 docker/ # Docker конфигурации +│ ├── 📄 Dockerfile.dev # Dockerfile для разработки +│ ├── 📄 Dockerfile.prod # Dockerfile для продакшена +│ └── 📄 nginx.conf # Конфиг Nginx (для продакшена) +│ +└── 📂 docs/ # Документация + ├── 📄 SETUP.md # Инструкции по настройке + ├── 📄 API.md # Документация API + └── 📄 DATABASE.md # Схема базы данных +``` + +## 🗄️ Структура базы данных + +### Основные таблицы: + +1. **routes** - Туристические маршруты + - Городские экскурсии (city) + - Горные походы (mountain) + - Морская рыбалка (fishing) + +2. **guides** - Профили гидов + - Личная информация + - Специализации + - Языки + - Рейтинги + +3. **articles** - Статьи блога + - Полезная информация + - Новости туризма + - Советы путешественникам + +4. **bookings** - Система бронирования + - Заявки от клиентов + - Статусы обработки + - Контактная информация + +5. **admins** - Администраторы + - Аутентификация + - Роли и права + +6. **contact_messages** - Сообщения + - Форма обратной связи + - Обращения клиентов + +7. **site_settings** - Настройки + - Конфигурация сайта + - SEO настройки + +8. **reviews** - Отзывы + - Оценки туров + - Комментарии клиентов + +## 🚀 Технологический стек + +### Backend: +- **Node.js + Express.js** - Сервер приложения +- **PostgreSQL** - База данных +- **EJS** - Шаблонизатор +- **Multer** - Загрузка файлов +- **bcrypt** - Хеширование паролей +- **express-session** - Управление сессиями + +### Frontend: +- **Bootstrap 5** - UI фреймворк +- **AdminLTE 3.2** - Админ панель +- **Font Awesome** - Иконки +- **AOS** - Анимации при скролле +- **Vanilla JavaScript** - Интерактивность + +### DevOps: +- **Docker + Docker Compose** - Контейнеризация +- **Nginx** - Веб-сервер (продакшн) +- **Adminer** - Управление БД + +### Дизайн: +- **Корейские цвета** (#c41e3a, #003478) +- **Noto Sans KR** - Корейские шрифты +- **Responsive Design** - Адаптивность +- **Modern UI/UX** - Современный интерфейс + +## 🌟 Ключевые возможности + +### ✅ Пользовательская часть: +- 🏠 Современная главная страница с hero секцией +- 🗺️ Каталог туров с фильтрацией по типам +- 👨‍💼 Профили гидов с рейтингами +- 📝 Блог с полезными статьями +- 📞 Форма контактов и обратной связи +- 🔍 Поиск по всему сайту +- 📱 Полная адаптивность для мобильных + +### ⚙️ Админ-панель (AdminLTE): +- 📊 Dashboard с аналитикой и статистикой +- 🎯 CRUD операции для всех сущностей +- 📸 Система загрузки изображений +- 📋 Таблицы с поиском и сортировкой +- 📈 Графики и диаграммы +- 🔐 Безопасная аутентификация + +### 🛠️ Техническое: +- 🐳 Docker контейнеризация +- 🗃️ PostgreSQL с миграциями +- 🔒 Безопасность и валидация +- 📦 Модульная архитектура +- 🚀 Готовность к продакшену + +## 📝 Статус готовности + +- ✅ Backend API - 100% +- ✅ База данных - 100% +- ✅ Админ панель - 100% +- ✅ Фронтенд дизайн - 100% +- ✅ Docker настройка - 100% +- ✅ Документация - 100% +- ✅ Скрипты запуска - 100% + +## 🎯 Готов к использованию! + +Проект полностью готов для: +- 🚀 **Разработки**: `./start-dev.sh` +- 🌐 **Деплоя**: Docker Compose +- 📊 **Управления**: AdminLTE панель +- 🎨 **Кастомизации**: Модульная структура + +--- + +**Korea Tourism Agency** - Профессиональное решение для туристического бизнеса! 🇰🇷✨ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d953362 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9420 @@ +{ + "name": "korea-tourism-agency", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "korea-tourism-agency", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@adminjs/express": "^6.1.0", + "@adminjs/sequelize": "^4.1.1", + "@adminjs/upload": "^4.0.2", + "adminjs": "^7.5.0", + "bcryptjs": "^2.4.3", + "bootstrap": "^5.3.2", + "compression": "^1.7.4", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "ejs": "^3.1.9", + "express": "^4.18.2", + "express-ejs-layouts": "^2.5.1", + "express-fileupload": "^1.4.3", + "express-rate-limit": "^7.1.5", + "express-session": "^1.17.3", + "helmet": "^7.1.0", + "jquery": "^3.7.1", + "method-override": "^3.0.0", + "moment": "^2.29.4", + "morgan": "^1.10.0", + "multer": "^2.0.0", + "pg": "^8.11.3", + "pg-hstore": "^2.3.4", + "sequelize": "^6.37.7", + "sequelize-cli": "^6.6.3" + }, + "devDependencies": { + "nodemon": "^3.0.2" + } + }, + "node_modules/@adminjs/design-system": { + "version": "4.1.1", + "license": "MIT", + "dependencies": { + "@hypnosphi/create-react-context": "^0.3.1", + "@tinymce/tinymce-react": "^4.3.2", + "@tiptap/core": "2.1.13", + "@tiptap/extension-bubble-menu": "2.1.13", + "@tiptap/extension-character-count": "2.1.13", + "@tiptap/extension-code": "2.1.13", + "@tiptap/extension-document": "2.1.13", + "@tiptap/extension-floating-menu": "2.1.13", + "@tiptap/extension-heading": "2.1.13", + "@tiptap/extension-image": "2.1.13", + "@tiptap/extension-link": "2.1.13", + "@tiptap/extension-table": "2.1.13", + "@tiptap/extension-table-cell": "2.1.13", + "@tiptap/extension-table-header": "2.1.13", + "@tiptap/extension-table-row": "2.1.13", + "@tiptap/extension-text": "2.1.13", + "@tiptap/extension-text-align": "2.1.13", + "@tiptap/extension-typography": "2.1.13", + "@tiptap/pm": "2.1.13", + "@tiptap/react": "2.1.13", + "@tiptap/starter-kit": "2.1.13", + "date-fns": "^2.29.3", + "flat": "^5.0.2", + "hoist-non-react-statics": "3.3.2", + "jw-paginate": "^1.0.4", + "lodash": "^4.17.21", + "polished": "^4.2.2", + "react-currency-input-field": "^3.6.10", + "react-datepicker": "^4.10.0", + "react-feather": "^2.0.10", + "react-phone-input-2": "^2.15.1", + "react-select": "^5.8.0", + "react-text-mask": "^5.5.0", + "styled-components": "5.3.9", + "styled-system": "^5.1.5", + "text-mask-addons": "^3.8.0" + }, + "peerDependencies": { + "react": "^18.1.0", + "react-dom": "^18.1.0" + } + }, + "node_modules/@adminjs/express": { + "version": "6.1.1", + "license": "SEE LICENSE IN LICENSE", + "peerDependencies": { + "adminjs": "^7.4.0", + "express": ">=4.18.2", + "express-formidable": "^1.2.0", + "express-session": ">=1.17.3", + "tslib": "^2.5.0" + } + }, + "node_modules/@adminjs/sequelize": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@adminjs/sequelize/-/sequelize-4.1.1.tgz", + "integrity": "sha512-HKOuuFn79tvW6xKP+v7FvKc6V7vERlmdEu9oAAR3gz6SE3T+V7Uz8N+trwngFhOpTWVcuws2FchFvKG3Xrm9iw==", + "license": "MIT", + "dependencies": { + "escape-regexp": "0.0.1" + }, + "peerDependencies": { + "adminjs": "^7.0.0", + "sequelize": ">=6" + } + }, + "node_modules/@adminjs/upload": { + "version": "4.0.2", + "license": "MIT", + "optionalDependencies": { + "@aws-sdk/client-s3": "^3.301.0", + "@aws-sdk/s3-request-presigner": "^3.301.0", + "@google-cloud/storage": "^6.9.4" + }, + "peerDependencies": { + "adminjs": "^7.0.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/credential-provider-node": "3.940.0", + "@aws-sdk/middleware-bucket-endpoint": "3.936.0", + "@aws-sdk/middleware-expect-continue": "3.936.0", + "@aws-sdk/middleware-flexible-checksums": "3.940.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-location-constraint": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-sdk-s3": "3.940.0", + "@aws-sdk/middleware-ssec": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/signature-v4-multi-region": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/eventstream-serde-browser": "^4.2.5", + "@smithy/eventstream-serde-config-resolver": "^4.3.5", + "@smithy/eventstream-serde-node": "^4.2.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-blob-browser": "^4.2.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/hash-stream-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/md5-js": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/credential-provider-env": "3.940.0", + "@aws-sdk/credential-provider-http": "3.940.0", + "@aws-sdk/credential-provider-login": "3.940.0", + "@aws-sdk/credential-provider-process": "3.940.0", + "@aws-sdk/credential-provider-sso": "3.940.0", + "@aws-sdk/credential-provider-web-identity": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.940.0", + "@aws-sdk/credential-provider-http": "3.940.0", + "@aws-sdk/credential-provider-ini": "3.940.0", + "@aws-sdk/credential-provider-process": "3.940.0", + "@aws-sdk/credential-provider-sso": "3.940.0", + "@aws-sdk/credential-provider-web-identity": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/client-sso": "3.940.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/token-providers": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-format-url": "3.936.0", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.940.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.1", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.5", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.4", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.28.3", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "license": "MIT" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "license": "MIT" + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "3.0.0", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "3.0.1", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage": { + "version": "6.12.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "fast-xml-parser": "^4.2.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage/node_modules/fast-xml-parser": { + "version": "4.5.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@google-cloud/storage/node_modules/strnum": { + "version": "1.1.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/@hello-pangea/dnd": { + "version": "16.6.0", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.24.1", + "css-box-model": "^1.2.1", + "memoize-one": "^6.0.0", + "raf-schd": "^4.0.3", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "use-memo-one": "^1.1.3" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@hypnosphi/create-react-context": { + "version": "0.3.1", + "license": "MIT", + "dependencies": { + "gud": "^1.0.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": ">=0.14.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@redux-devtools/extension": { + "version": "3.3.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "immutable": "^4.3.4" + }, + "peerDependencies": { + "redux": "^3.1.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@remirror/core-constants": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/@remix-run/router": { + "version": "1.23.1", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.8", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-inject": { + "version": "5.0.5", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.7", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.1", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.3", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.18.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/middleware-serde": "^4.2.6", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/eventstream-codec": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.6", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.6", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.3.12", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/core": "^3.18.5", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.12", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.6", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.9.8", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/core": "^3.18.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.9.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.11", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.14", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/service-error-classification": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.6", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.5", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@styled-system/background": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/border": { + "version": "5.1.5", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/color": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/core": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1" + } + }, + "node_modules/@styled-system/css": { + "version": "5.1.5", + "license": "MIT" + }, + "node_modules/@styled-system/flexbox": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/grid": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/layout": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/position": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/shadow": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/space": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/typography": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2" + } + }, + "node_modules/@styled-system/variant": { + "version": "5.1.5", + "license": "MIT", + "dependencies": { + "@styled-system/core": "^5.1.2", + "@styled-system/css": "^5.1.5" + } + }, + "node_modules/@tinymce/tinymce-react": { + "version": "4.3.2", + "license": "MIT", + "dependencies": { + "prop-types": "^15.6.2", + "tinymce": "^6.0.0 || ^5.5.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^17.0.1 || ^16.7.0", + "react-dom": "^18.0.0 || ^17.0.1 || ^16.7.0" + } + }, + "node_modules/@tiptap/core": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.1.13", + "license": "MIT", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-character-count": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.1.13", + "license": "MIT", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-image": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "2.1.13", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.1.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-table": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-table-cell": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-table-header": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-table-row": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-text-align": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-typography": { + "version": "2.1.13", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.1.13", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.2.0", + "prosemirror-collab": "^1.3.0", + "prosemirror-commands": "^1.3.1", + "prosemirror-dropcursor": "^1.5.0", + "prosemirror-gapcursor": "^1.3.1", + "prosemirror-history": "^1.3.0", + "prosemirror-inputrules": "^1.2.0", + "prosemirror-keymap": "^1.2.0", + "prosemirror-markdown": "^1.10.1", + "prosemirror-menu": "^1.2.1", + "prosemirror-model": "^1.18.1", + "prosemirror-schema-basic": "^1.2.0", + "prosemirror-schema-list": "^1.2.2", + "prosemirror-state": "^1.4.1", + "prosemirror-tables": "^1.3.0", + "prosemirror-trailing-node": "^2.0.2", + "prosemirror-transform": "^1.7.0", + "prosemirror-view": "^1.28.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "2.1.13", + "license": "MIT", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.1.13", + "@tiptap/extension-floating-menu": "^2.1.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.1.13", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^2.1.13", + "@tiptap/extension-blockquote": "^2.1.13", + "@tiptap/extension-bold": "^2.1.13", + "@tiptap/extension-bullet-list": "^2.1.13", + "@tiptap/extension-code": "^2.1.13", + "@tiptap/extension-code-block": "^2.1.13", + "@tiptap/extension-document": "^2.1.13", + "@tiptap/extension-dropcursor": "^2.1.13", + "@tiptap/extension-gapcursor": "^2.1.13", + "@tiptap/extension-hard-break": "^2.1.13", + "@tiptap/extension-heading": "^2.1.13", + "@tiptap/extension-history": "^2.1.13", + "@tiptap/extension-horizontal-rule": "^2.1.13", + "@tiptap/extension-italic": "^2.1.13", + "@tiptap/extension-list-item": "^2.1.13", + "@tiptap/extension-ordered-list": "^2.1.13", + "@tiptap/extension-paragraph": "^2.1.13", + "@tiptap/extension-strike": "^2.1.13", + "@tiptap/extension-text": "^2.1.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@remirror/core-constants": { + "version": "3.0.0", + "license": "MIT", + "peer": true + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/core": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-blockquote": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-bold": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-bullet-list": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-code-block": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-dropcursor": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-gapcursor": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-hard-break": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-history": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-italic": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-list-item": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-ordered-list": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-paragraph": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-strike": { + "version": "2.27.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/@tiptap/pm": { + "version": "2.27.1", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.23.0", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/starter-kit/node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "license": "MIT", + "peer": true, + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "license": "MIT" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.7", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "license": "MIT", + "peer": true + }, + "node_modules/@types/react": { + "version": "18.3.27", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/adminjs": { + "version": "7.8.17", + "license": "MIT", + "dependencies": { + "@adminjs/design-system": "^4.1.0", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-transform-runtime": "^7.23.9", + "@babel/preset-env": "^7.23.9", + "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.23.3", + "@babel/register": "^7.23.7", + "@hello-pangea/dnd": "^16.2.0", + "@redux-devtools/extension": "^3.2.5", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.5", + "axios": "^1.3.4", + "commander": "^10.0.0", + "flat": "^5.0.2", + "i18next": "^22.4.13", + "i18next-browser-languagedetector": "^7.0.1", + "i18next-http-backend": "^2.2.0", + "lodash": "^4.17.21", + "ora": "^6.2.0", + "prop-types": "^15.8.1", + "punycode": "^2.3.0", + "qs": "^6.11.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-feather": "^2.0.10", + "react-i18next": "^12.2.0", + "react-is": "^18.2.0", + "react-redux": "^8.0.5", + "react-router": "^6.9.0", + "react-router-dom": "^6.9.0", + "redux": "^4.2.1", + "regenerator-runtime": "^0.14.1", + "rollup": "^4.11.0", + "rollup-plugin-esbuild-minify": "^1.1.1", + "rollup-plugin-polyfill-node": "^0.13.0", + "slash": "^5.0.0", + "uuid": "^9.0.0", + "xss": "^1.0.14" + }, + "bin": { + "admin": "cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/adminjs/node_modules/uuid": { + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "2.0.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "license": "MIT" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.4", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-styled-components/node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.32", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bootstrap": { + "version": "5.3.8", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bowser": { + "version": "2.13.1", + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001757", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.47.0", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-box-model": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/cssfilter": { + "version": "0.0.10", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.262", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.2", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ent/node_modules/punycode": { + "version": "1.4.1", + "license": "MIT", + "optional": true + }, + "node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/escape-regexp": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/escape-regexp/-/escape-regexp-0.0.1.tgz", + "integrity": "sha512-jVgdsYRa7RKxTT6MKNC3gdT+BF0Gfhpel19+HMRZJC2L0PufB0XOBuXBoXj29NKHwuktnAXd1Z1lyiH/8vOTpw==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-ejs-layouts": { + "version": "2.5.1" + }, + "node_modules/express-fileupload": { + "version": "1.5.2", + "license": "MIT", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express-formidable": { + "version": "1.2.0", + "license": "MIT", + "peer": true, + "dependencies": { + "formidable": "^1.0.17" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express-session": { + "version": "1.18.2", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.13.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "license": "MIT", + "optional": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "1.2.6", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "5.1.3", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-auth-library/node_modules/lru-cache": { + "version": "6.0.0", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-auth-library/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC", + "optional": true + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "6.1.2", + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gud": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "7.2.0", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/i18next": { + "version": "22.5.1", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "7.2.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.7.3", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/immutable": { + "version": "4.3.7", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "engines": [ + "node >= 0.4.0" + ], + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "license": "MIT" + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jw-paginate": { + "version": "1.0.4", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "license": "MIT" + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "license": "MIT" + }, + "node_modules/lodash.startswith": { + "version": "4.2.1", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "5.1.0", + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/method-override": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/method-override/node_modules/debug": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/method-override/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/multer": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.2", + "license": "(BSD-3-Clause OR GPL-2.0)", + "optional": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "6.3.1", + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "license": "MIT", + "optional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pg": { + "version": "8.16.3", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "license": "MIT" + }, + "node_modules/pg-hstore": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.4.tgz", + "integrity": "sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==", + "license": "MIT", + "dependencies": { + "underscore": "^1.13.1" + }, + "engines": { + "node": ">= 0.8.x" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/prosemirror-changeset": { + "version": "2.3.1", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.2", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.5", + "license": "MIT", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.1", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.25.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.3", + "prosemirror-view": "^1.39.1" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "2.0.9", + "license": "MIT", + "dependencies": { + "@remirror/core-constants": "^2.0.2", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.5", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.3", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/raf-schd": { + "version": "4.0.3", + "license": "MIT" + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-currency-input-field": { + "version": "3.10.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-datepicker": { + "version": "4.25.0", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.11.8", + "classnames": "^2.2.6", + "date-fns": "^2.30.0", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0", + "react-popper": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "license": "MIT" + }, + "node_modules/react-feather": { + "version": "2.0.10", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">=16.8.6" + } + }, + "node_modules/react-i18next": { + "version": "12.3.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 19.0.0", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "license": "MIT" + }, + "node_modules/react-onclickoutside": { + "version": "6.13.2", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, + "node_modules/react-phone-input-2": { + "version": "2.15.1", + "license": "MIT", + "dependencies": { + "classnames": "^2.2.6", + "lodash.debounce": "^4.0.8", + "lodash.memoize": "^4.1.2", + "lodash.reduce": "^4.6.0", + "lodash.startswith": "^4.2.1", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", + "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" + } + }, + "node_modules/react-popper": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-redux": { + "version": "8.1.3", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.30.2", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.2", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.1", + "react-router": "6.30.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-select": { + "version": "5.10.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-text-mask": { + "version": "5.5.0", + "license": "Unlicense", + "dependencies": { + "prop-types": "^15.5.6" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/redux": { + "version": "4.2.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", + "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", + "license": "MIT" + }, + "node_modules/retry-request": { + "version": "5.0.2", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-esbuild-minify": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.3" + }, + "engines": { + "node": ">= 14.18" + }, + "peerDependencies": { + "rollup": "^2 || ^3 || ^4" + } + }, + "node_modules/rollup-plugin-polyfill-node": { + "version": "0.13.0", + "license": "MIT", + "dependencies": { + "@rollup/plugin-inject": "^5.0.4" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sequelize": { + "version": "6.37.7", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.7.tgz", + "integrity": "sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.6", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.1", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.4", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-cli": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.6.3.tgz", + "integrity": "sha512-1YYPrcSRt/bpMDDSKM5ubY1mnJ2TEwIaGZcqITw4hLtGtE64nIqaBnLtMvH8VKHg6FbWpXTiFNc2mS/BtQCXZw==", + "license": "MIT", + "dependencies": { + "fs-extra": "^9.1.0", + "js-beautify": "1.15.4", + "lodash": "^4.17.21", + "picocolors": "^1.1.1", + "resolve": "^1.22.1", + "umzug": "^2.3.0", + "yargs": "^16.2.0" + }, + "bin": { + "sequelize": "lib/sequelize", + "sequelize-cli": "lib/sequelize" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/sequelize/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "license": "MIT", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "license": "MIT", + "optional": true + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "2.1.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "license": "MIT", + "optional": true + }, + "node_modules/styled-components": { + "version": "5.3.9", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.7.5", + "license": "MIT" + }, + "node_modules/styled-system": { + "version": "5.1.5", + "license": "MIT", + "dependencies": { + "@styled-system/background": "^5.1.2", + "@styled-system/border": "^5.1.5", + "@styled-system/color": "^5.1.2", + "@styled-system/core": "^5.1.2", + "@styled-system/flexbox": "^5.1.2", + "@styled-system/grid": "^5.1.2", + "@styled-system/layout": "^5.1.2", + "@styled-system/position": "^5.1.2", + "@styled-system/shadow": "^5.1.2", + "@styled-system/space": "^5.1.2", + "@styled-system/typography": "^5.1.2", + "@styled-system/variant": "^5.1.5", + "object-assign": "^4.1.1" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "8.0.3", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/text-mask-addons": { + "version": "3.8.0", + "license": "Unlicense" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "license": "MIT" + }, + "node_modules/tinymce": { + "version": "6.8.6", + "license": "MIT" + }, + "node_modules/tippy.js": { + "version": "6.3.7", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==", + "license": "MIT" + }, + "node_modules/touch": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "license": "MIT" + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/umzug": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", + "integrity": "sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==", + "license": "MIT", + "dependencies": { + "bluebird": "^3.7.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.15.23", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", + "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "license": "MIT" + }, + "node_modules/warning": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/xss": { + "version": "1.0.15", + "license": "MIT", + "dependencies": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/xss/node_modules/commander": { + "version": "2.20.3", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8cf124d --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "korea-tourism-agency", + "version": "1.0.0", + "type": "module", + "description": "Туристическое агентство для внутренних поездок по Корее", + "main": "src/app.js", + "scripts": { + "start": "node src/app.js", + "dev": "nodemon src/app.js", + "db:migrate": "node database/migrate.js", + "db:seed": "node database/seed.js" + }, + "keywords": [ + "korea", + "tourism", + "travel", + "agency" + ], + "author": "Korea Tourism Agency", + "license": "MIT", + "dependencies": { + "@adminjs/express": "^6.1.0", + "@adminjs/sequelize": "^4.1.1", + "@adminjs/upload": "^4.0.2", + "adminjs": "^7.5.0", + "bcryptjs": "^2.4.3", + "bootstrap": "^5.3.2", + "compression": "^1.7.4", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "ejs": "^3.1.9", + "express": "^4.18.2", + "express-ejs-layouts": "^2.5.1", + "express-fileupload": "^1.4.3", + "express-rate-limit": "^7.1.5", + "express-session": "^1.17.3", + "helmet": "^7.1.0", + "jquery": "^3.7.1", + "method-override": "^3.0.0", + "moment": "^2.29.4", + "morgan": "^1.10.0", + "multer": "^2.0.0", + "pg": "^8.11.3", + "pg-hstore": "^2.3.4", + "sequelize": "^6.37.7", + "sequelize-cli": "^6.6.3" + }, + "devDependencies": { + "nodemon": "^3.0.2" + } +} diff --git a/public/css/admin-custom.css b/public/css/admin-custom.css new file mode 100644 index 0000000..79f6825 --- /dev/null +++ b/public/css/admin-custom.css @@ -0,0 +1,218 @@ +/* Korea Tourism Agency Admin Panel Custom Styles */ + +/* Brand Customization */ +.brand-link { + background-color: #1f2937 !important; +} + +.brand-text { + color: #f8f9fa !important; + font-weight: 600 !important; +} + +/* Sidebar Customization */ +.main-sidebar { + background: linear-gradient(180deg, #1f2937 0%, #111827 100%) !important; +} + +.nav-sidebar .nav-item > .nav-link { + color: #d1d5db !important; + transition: all 0.3s ease; +} + +.nav-sidebar .nav-item > .nav-link:hover { + background-color: rgba(255, 255, 255, 0.1) !important; + color: #ffffff !important; +} + +.nav-sidebar .nav-item > .nav-link.active { + background-color: #3b82f6 !important; + color: #ffffff !important; + border-radius: 0.375rem; + margin: 0.125rem; +} + +/* Cards Enhancement */ +.card { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + border: none; + border-radius: 0.5rem; +} + +.card-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 0.5rem 0.5rem 0 0 !important; +} + +.card-header .card-title { + color: white !important; + font-weight: 600; +} + +/* Small Boxes Enhancement */ +.small-box { + border-radius: 0.75rem; + overflow: hidden; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease; +} + +.small-box:hover { + transform: translateY(-2px); +} + +.small-box .icon { + top: 10px; + right: 10px; +} + +/* Buttons Enhancement */ +.btn { + border-radius: 0.375rem; + font-weight: 500; + transition: all 0.2s ease; +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +/* Table Enhancement */ +.table { + border-radius: 0.5rem; + overflow: hidden; +} + +.table thead th { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border: none; + font-weight: 600; + color: #495057; +} + +.table tbody tr:hover { + background-color: #f8f9fa; +} + +/* Form Enhancement */ +.form-control { + border-radius: 0.375rem; + border: 1px solid #d1d5db; + transition: all 0.2s ease; +} + +.form-control:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +/* Custom File Input */ +.custom-file-label { + border-radius: 0.375rem; +} + +.custom-file-input:focus ~ .custom-file-label { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +/* Alert Enhancement */ +.alert { + border-radius: 0.5rem; + border: none; +} + +.alert-success { + background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%); + color: #155724; +} + +.alert-danger { + background: linear-gradient(135deg, #f8d7da 0%, #f1b0b7 100%); + color: #721c24; +} + +.alert-warning { + background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); + color: #856404; +} + +/* Badge Enhancement */ +.badge { + font-size: 0.75rem; + font-weight: 500; + padding: 0.375rem 0.75rem; + border-radius: 0.375rem; +} + +/* Loading Spinner */ +.loading { + opacity: 0.7; + pointer-events: none; +} + +.spinner { + border: 3px solid #f3f3f3; + border-top: 3px solid #3b82f6; + border-radius: 50%; + width: 30px; + height: 30px; + animation: spin 1s linear infinite; + margin: 10px auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Mobile Responsiveness */ +@media (max-width: 768px) { + .card-header .card-title { + font-size: 1rem; + } + + .small-box .inner h3 { + font-size: 1.5rem; + } + + .btn-group .btn { + padding: 0.25rem 0.5rem; + } +} + +/* Dark Mode Support */ +@media (prefers-color-scheme: dark) { + .card { + background-color: #374151; + color: #f9fafb; + } + + .table { + color: #f9fafb; + } + + .form-control { + background-color: #374151; + color: #f9fafb; + border-color: #4b5563; + } +} + +/* Korean Typography */ +.korean-text { + font-family: 'Noto Sans KR', 'Malgun Gothic', '맑은 고딕', sans-serif; +} + +/* Success Animation */ +.success-animation { + animation: successPulse 0.6s ease-in-out; +} + +@keyframes successPulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } +} \ No newline at end of file diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000..366db19 --- /dev/null +++ b/public/css/main.css @@ -0,0 +1,497 @@ +/* Korea Tourism Agency - Main Styles */ + +/* CSS Variables */ +:root { + --primary-color: #2563eb; + --primary-light: #3b82f6; + --primary-dark: #1d4ed8; + --secondary-color: #dc2626; + --success-color: #059669; + --warning-color: #d97706; + --info-color: #0891b2; + --light-color: #f8fafc; + --dark-color: #0f172a; + --gray-100: #f1f5f9; + --gray-200: #e2e8f0; + --gray-300: #cbd5e1; + --gray-400: #94a3b8; + --gray-500: #64748b; + --gray-600: #475569; + --gray-700: #334155; + --gray-800: #1e293b; + --gray-900: #0f172a; + --korean-red: #c41e3a; + --korean-blue: #003478; + --font-korean: 'Noto Sans KR', 'Malgun Gothic', '맑은 고딕', sans-serif; + --font-display: 'Playfair Display', serif; + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +/* Base Styles */ +body { + font-family: var(--font-korean); + line-height: 1.7; + color: var(--gray-700); + padding-top: 76px; /* Account for fixed navbar */ +} + +.font-korean { + font-family: var(--font-korean); +} + +.font-display { + font-family: var(--font-display); +} + +/* Custom Bootstrap Overrides */ +.btn-primary { + background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%); + border: none; + box-shadow: var(--shadow-md); + transition: all 0.3s ease; +} + +.btn-primary:hover { + background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%); + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.text-gradient { + background: linear-gradient(135deg, var(--korean-red) 0%, var(--korean-blue) 100%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.bg-gradient-primary { + background: linear-gradient(135deg, var(--primary-color) 0%, var(--korean-blue) 100%); +} + +/* Navigation Styles */ +.navbar { + background: rgba(37, 99, 235, 0.95) !important; + backdrop-filter: blur(10px); + box-shadow: var(--shadow-md); + transition: all 0.3s ease; +} + +.navbar-brand { + font-family: var(--font-display); + font-weight: 700; + font-size: 1.5rem; +} + +.nav-link { + font-weight: 500; + position: relative; + transition: all 0.3s ease; +} + +.nav-link:hover { + color: #ffffff !important; +} + +.nav-link.active::after { + content: ''; + position: absolute; + bottom: -5px; + left: 50%; + transform: translateX(-50%); + width: 80%; + height: 2px; + background: var(--korean-red); + border-radius: 2px; +} + +/* Hero Section */ +.hero-section { + background: linear-gradient(135deg, var(--primary-color) 0%, var(--korean-blue) 100%); + min-height: 100vh; + position: relative; + overflow: hidden; +} + +.hero-background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url('/images/korea-bg.jpg'); + background-size: cover; + background-position: center; + opacity: 0.1; + z-index: 1; +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, rgba(37, 99, 235, 0.8) 0%, rgba(0, 52, 120, 0.9) 100%); + z-index: 2; + pointer-events: none; /* Позволяет кликам проходить через overlay */ +} + +.hero-section .container { + z-index: 3; /* Контент поверх overlay */ + position: relative; +} + +.hero-title { + font-family: var(--font-display); + font-size: clamp(2.5rem, 5vw, 4rem); + line-height: 1.2; +} + +.hero-subtitle { + font-size: clamp(1.1rem, 2vw, 1.3rem); + line-height: 1.6; +} + +.hero-image-container { + position: relative; +} + +.floating-card { + bottom: 20px; + right: 20px; + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-10px); } +} + +.icon-circle { + width: 50px; + height: 50px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; +} + +/* Search Section */ +.search-section { + margin-top: -100px; + position: relative; + z-index: 3; +} + +.search-form .form-control, +.search-form .form-select { + border: 2px solid transparent; + transition: all 0.3s ease; +} + +.search-form .form-control:focus, +.search-form .form-select:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 0.2rem rgba(37, 99, 235, 0.25); +} + +/* Section Titles */ +.section-title { + font-family: var(--font-display); + color: var(--dark-color); + position: relative; +} + +.section-subtitle { + max-width: 600px; + margin: 0 auto; +} + +/* Tour Cards */ +.tour-card { + transition: all 0.3s ease; + border-radius: 20px; + overflow: hidden; +} + +.tour-card:hover { + transform: translateY(-10px); + box-shadow: var(--shadow-xl); +} + +.tour-image { + height: 250px; + object-fit: cover; + transition: all 0.3s ease; +} + +.tour-card:hover .tour-image { + transform: scale(1.05); +} + +.tour-overlay { + background: linear-gradient(transparent, rgba(0, 0, 0, 0.6)); + opacity: 0; + transition: all 0.3s ease; +} + +.tour-card:hover .tour-overlay { + opacity: 1; +} + +.tour-price .badge { + font-size: 1rem; + border-radius: 20px; +} + +.tour-type { + top: 15px; + right: 15px; +} + +.tour-meta { + font-size: 0.9rem; +} + +/* Article Cards */ +.article-card { + transition: all 0.3s ease; + border-radius: 15px; + overflow: hidden; +} + +.article-card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-lg); +} + +.article-image { + height: 200px; + object-fit: cover; + transition: all 0.3s ease; +} + +.article-card:hover .article-image { + transform: scale(1.05); +} + +/* Stats Section */ +.stats-section { + background: linear-gradient(135deg, var(--primary-color) 0%, var(--korean-blue) 100%); +} + +.stat-item { + text-align: center; +} + +.stat-number { + font-family: var(--font-display); +} + +.stat-icon { + opacity: 0.8; +} + +/* Guide Cards */ +.guide-card { + transition: all 0.3s ease; + border-radius: 20px; + overflow: hidden; +} + +.guide-card:hover { + transform: translateY(-10px); + box-shadow: var(--shadow-xl); +} + +.guide-avatar { + width: 100px; + height: 100px; + object-fit: cover; + border-radius: 50%; + border: 4px solid white; + box-shadow: var(--shadow-md); +} + +.guide-specialization { + display: inline-block; + padding: 0.25rem 0.75rem; + background: var(--primary-color); + color: white; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 500; +} + +.rating-stars { + color: #fbbf24; +} + +/* Footer */ +footer { + background: linear-gradient(135deg, var(--gray-900) 0%, var(--dark-color) 100%); +} + +footer a { + transition: all 0.3s ease; +} + +footer a:hover { + color: var(--primary-light) !important; +} + +.social-links a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + transition: all 0.3s ease; +} + +.social-links a:hover { + background: var(--primary-color); + transform: translateY(-2px); +} + +/* Utility Classes */ +.rounded-4 { + border-radius: 1.5rem !important; +} + +.rounded-pill { + border-radius: 50rem !important; +} + +.shadow-soft { + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.08); +} + +/* Loading States */ +.loading { + position: relative; + pointer-events: none; +} + +.loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 20px; + height: 20px; + border: 2px solid transparent; + border-top: 2px solid var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; + transform: translate(-50%, -50%); +} + +@keyframes spin { + 0% { transform: translate(-50%, -50%) rotate(0deg); } + 100% { transform: translate(-50%, -50%) rotate(360deg); } +} + +/* Responsive Design */ +@media (max-width: 768px) { + body { + padding-top: 66px; + } + + .hero-title { + font-size: 2.5rem; + } + + .hero-subtitle { + font-size: 1.1rem; + } + + .floating-card { + display: none; + } + + .search-section { + margin-top: -50px; + } + + .tour-card, + .article-card, + .guide-card { + margin-bottom: 1rem; + } + + .stats-section .stat-number { + font-size: 2rem; + } +} + +@media (max-width: 576px) { + .hero-buttons .btn { + width: 100%; + margin-bottom: 0.5rem; + } + + .search-form .col-md-4 { + margin-bottom: 1rem; + } + + .search-form .btn { + width: 100%; + } +} + +/* Print Styles */ +@media print { + .navbar, + footer, + .btn, + .floating-card { + display: none !important; + } + + body { + padding-top: 0; + } + + .hero-section { + background: white; + color: black; + min-height: auto; + } +} + +/* High Contrast Mode */ +@media (prefers-contrast: high) { + .tour-overlay, + .hero-overlay { + background: rgba(0, 0, 0, 0.9); + } + + .text-gradient { + background: none; + color: var(--dark-color); + -webkit-text-fill-color: initial; + } +} + +/* Reduced Motion */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .floating-card { + animation: none; + } +} \ No newline at end of file diff --git a/public/images/city-tour-placeholder.webp b/public/images/city-tour-placeholder.webp new file mode 100644 index 0000000..ecd434f Binary files /dev/null and b/public/images/city-tour-placeholder.webp differ diff --git a/public/images/default-article.jpg b/public/images/default-article.jpg new file mode 100644 index 0000000..049ecf3 --- /dev/null +++ b/public/images/default-article.jpg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 한국 관광 + Korea Tourism + + + + + + + \ No newline at end of file diff --git a/public/images/default-tour.jpg b/public/images/default-tour.jpg new file mode 100644 index 0000000..049ecf3 --- /dev/null +++ b/public/images/default-tour.jpg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 한국 관광 + Korea Tourism + + + + + + + \ No newline at end of file diff --git a/public/images/fish-placeholder.jpg b/public/images/fish-placeholder.jpg new file mode 100644 index 0000000..18ae590 Binary files /dev/null and b/public/images/fish-placeholder.jpg differ diff --git a/public/images/korea-hero.jpg b/public/images/korea-hero.jpg new file mode 100644 index 0000000..049ecf3 --- /dev/null +++ b/public/images/korea-hero.jpg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 한국 관광 + Korea Tourism + + + + + + + \ No newline at end of file diff --git a/public/images/mountain-placeholder.jpg b/public/images/mountain-placeholder.jpg new file mode 100644 index 0000000..eeea41f Binary files /dev/null and b/public/images/mountain-placeholder.jpg differ diff --git a/public/images/placeholder.jpg b/public/images/placeholder.jpg new file mode 100644 index 0000000..c8ae073 Binary files /dev/null and b/public/images/placeholder.jpg differ diff --git a/public/images/placeholders/default-guide.svg b/public/images/placeholders/default-guide.svg new file mode 100644 index 0000000..e0e69f0 --- /dev/null +++ b/public/images/placeholders/default-guide.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 가이드 + Professional Guide + \ No newline at end of file diff --git a/public/images/placeholders/default-tour.svg b/public/images/placeholders/default-tour.svg new file mode 100644 index 0000000..049ecf3 --- /dev/null +++ b/public/images/placeholders/default-tour.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 한국 관광 + Korea Tourism + + + + + + + \ No newline at end of file diff --git a/public/js/admin-custom.js b/public/js/admin-custom.js new file mode 100644 index 0000000..3b876d9 --- /dev/null +++ b/public/js/admin-custom.js @@ -0,0 +1,288 @@ +/* Korea Tourism Agency Admin Panel Custom Scripts */ + +$(document).ready(function() { + // Initialize tooltips + $('[data-toggle="tooltip"]').tooltip(); + + // Initialize popovers + $('[data-toggle="popover"]').popover(); + + // Auto-hide alerts after 5 seconds + setTimeout(function() { + $('.alert').fadeOut('slow'); + }, 5000); + + // Confirm delete actions + $('.btn-delete').on('click', function(e) { + e.preventDefault(); + const item = $(this).data('item') || 'item'; + const url = $(this).attr('href') || $(this).data('url'); + + if (confirm(`Are you sure you want to delete this ${item}?`)) { + if ($(this).data('method') === 'DELETE') { + // AJAX delete + $.ajax({ + url: url, + method: 'DELETE', + success: function(response) { + if (response.success) { + location.reload(); + } else { + alert('Error: ' + response.message); + } + }, + error: function() { + alert('An error occurred while deleting.'); + } + }); + } else { + // Regular form submission or redirect + window.location.href = url; + } + } + }); + + // Form validation enhancement + $('form').on('submit', function() { + const submitBtn = $(this).find('button[type="submit"]'); + submitBtn.prop('disabled', true); + submitBtn.html(' Processing...'); + }); + + // Image preview functionality + function readURL(input, target) { + if (input.files && input.files[0]) { + const reader = new FileReader(); + reader.onload = function(e) { + $(target).attr('src', e.target.result).show(); + }; + reader.readAsDataURL(input.files[0]); + } + } + + $('input[type="file"]').on('change', function() { + const targetImg = $(this).closest('.form-group').find('.img-preview'); + if (targetImg.length) { + readURL(this, targetImg); + } + }); + + // Auto-save draft functionality for forms + let autoSaveTimer; + $('textarea, input[type="text"]').on('input', function() { + clearTimeout(autoSaveTimer); + autoSaveTimer = setTimeout(function() { + // Auto-save logic here + console.log('Auto-saving draft...'); + }, 2000); + }); + + // Enhanced DataTables configuration + if (typeof $.fn.dataTable !== 'undefined') { + $('.data-table').each(function() { + $(this).DataTable({ + responsive: true, + lengthChange: false, + autoWidth: false, + pageLength: 25, + language: { + search: "Search:", + lengthMenu: "Show _MENU_ entries", + info: "Showing _START_ to _END_ of _TOTAL_ entries", + paginate: { + first: "First", + last: "Last", + next: "Next", + previous: "Previous" + } + }, + dom: '<"row"<"col-sm-6"l><"col-sm-6"f>>' + + '<"row"<"col-sm-12"tr>>' + + '<"row"<"col-sm-5"i><"col-sm-7"p>>', + }); + }); + } + + // Status toggle functionality + $('.status-toggle').on('change', function() { + const checkbox = $(this); + const id = checkbox.data('id'); + const type = checkbox.data('type'); + const field = checkbox.data('field'); + const isChecked = checkbox.is(':checked'); + + $.ajax({ + url: `/admin/${type}/${id}/toggle`, + method: 'POST', + data: { + field: field, + value: isChecked + }, + success: function(response) { + if (response.success) { + showNotification('Status updated successfully!', 'success'); + } else { + checkbox.prop('checked', !isChecked); + showNotification('Error updating status: ' + response.message, 'error'); + } + }, + error: function() { + checkbox.prop('checked', !isChecked); + showNotification('Error updating status', 'error'); + } + }); + }); + + // Notification system + function showNotification(message, type = 'info') { + const alertClass = type === 'success' ? 'alert-success' : + type === 'error' ? 'alert-danger' : + type === 'warning' ? 'alert-warning' : 'alert-info'; + + const notification = ` + + `; + + $('body').append(notification); + + // Auto-hide after 3 seconds + setTimeout(function() { + $('.alert').last().fadeOut('slow', function() { + $(this).remove(); + }); + }, 3000); + } + + // Make notification function globally available + window.showNotification = showNotification; + + // Quick search functionality + $('#quick-search').on('input', function() { + const searchTerm = $(this).val().toLowerCase(); + const searchableElements = $('.searchable'); + + if (searchTerm === '') { + searchableElements.show(); + } else { + searchableElements.each(function() { + const text = $(this).text().toLowerCase(); + if (text.includes(searchTerm)) { + $(this).show(); + } else { + $(this).hide(); + } + }); + } + }); + + // Bulk actions functionality + $('#select-all').on('change', function() { + $('.item-checkbox').prop('checked', $(this).is(':checked')); + updateBulkActionButtons(); + }); + + $('.item-checkbox').on('change', function() { + updateBulkActionButtons(); + + // Update select-all checkbox state + const totalCheckboxes = $('.item-checkbox').length; + const checkedCheckboxes = $('.item-checkbox:checked').length; + + if (checkedCheckboxes === 0) { + $('#select-all').prop('indeterminate', false).prop('checked', false); + } else if (checkedCheckboxes === totalCheckboxes) { + $('#select-all').prop('indeterminate', false).prop('checked', true); + } else { + $('#select-all').prop('indeterminate', true); + } + }); + + function updateBulkActionButtons() { + const checkedItems = $('.item-checkbox:checked').length; + if (checkedItems > 0) { + $('.bulk-actions').show(); + $('.bulk-count').text(checkedItems); + } else { + $('.bulk-actions').hide(); + } + } + + // Image upload preview + $('.image-upload').on('change', function() { + const file = this.files[0]; + const preview = $(this).siblings('.image-preview'); + + if (file) { + const reader = new FileReader(); + reader.onload = function(e) { + preview.html(``); + }; + reader.readAsDataURL(file); + } + }); + + // Chart initialization (if Chart.js is available) + if (typeof Chart !== 'undefined' && $('#dashboard-chart').length) { + const ctx = document.getElementById('dashboard-chart').getContext('2d'); + new Chart(ctx, { + type: 'line', + data: { + labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], + datasets: [{ + label: 'Bookings', + data: [12, 19, 3, 5, 2, 3], + borderColor: '#3b82f6', + backgroundColor: 'rgba(59, 130, 246, 0.1)', + tension: 0.4 + }] + }, + options: { + responsive: true, + plugins: { + legend: { + position: 'top', + } + }, + scales: { + y: { + beginAtZero: true + } + } + } + }); + } +}); + +// Global utility functions +window.AdminUtils = { + // Format currency + formatCurrency: function(amount) { + return '₩' + new Intl.NumberFormat('ko-KR').format(amount); + }, + + // Format date + formatDate: function(date) { + return new Date(date).toLocaleDateString('ko-KR'); + }, + + // Validate email + isValidEmail: function(email) { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); + }, + + // Show loading spinner + showLoading: function(element) { + $(element).addClass('loading').append('
'); + }, + + // Hide loading spinner + hideLoading: function(element) { + $(element).removeClass('loading').find('.spinner').remove(); + } +}; \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..2e9fbfa --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,391 @@ +/** + * Korea Tourism Agency - Main JavaScript + * Основные интерактивные функции для сайта + */ + +document.addEventListener('DOMContentLoaded', function() { + + // ========================================== + // Инициализация AOS (Animate On Scroll) + // ========================================== + if (typeof AOS !== 'undefined') { + AOS.init({ + duration: 800, + easing: 'ease-in-out', + once: true, + offset: 100 + }); + } + + // ========================================== + // Навигация и мобильное меню + // ========================================== + const navbar = document.querySelector('.navbar'); + + // Добавление класса при скролле + window.addEventListener('scroll', function() { + if (window.scrollY > 100) { + if (navbar) navbar.classList.add('scrolled'); + } else { + if (navbar) navbar.classList.remove('scrolled'); + } + }); + + // ========================================== + // Поиск по сайту + // ========================================== + const searchInput = document.getElementById('search-input'); + const searchResults = document.getElementById('search-results'); + + if (searchInput && searchResults) { + let searchTimeout; + + searchInput.addEventListener('input', function() { + const query = this.value.trim(); + + clearTimeout(searchTimeout); + + if (query.length < 2) { + searchResults.style.display = 'none'; + return; + } + + searchTimeout = setTimeout(() => { + performSearch(query); + }, 300); + }); + + // Скрытие результатов при клике вне поиска + document.addEventListener('click', function(e) { + if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { + searchResults.style.display = 'none'; + } + }); + } + + async function performSearch(query) { + try { + const response = await fetch('/api/search?q=' + encodeURIComponent(query)); + const data = await response.json(); + + displaySearchResults(data); + } catch (error) { + console.error('Search error:', error); + } + } + + function displaySearchResults(results) { + if (!searchResults) return; + + let html = ''; + + if (results.routes && results.routes.length > 0) { + html += '
투어
'; + results.routes.forEach(route => { + html += ''; + }); + html += '
'; + } + + if (results.guides && results.guides.length > 0) { + html += '
가이드
'; + results.guides.forEach(guide => { + html += ''; + }); + html += '
'; + } + + if (results.articles && results.articles.length > 0) { + html += '
기사
'; + results.articles.forEach(article => { + html += ''; + }); + html += '
'; + } + + if (!html) { + html = '
검색 결과가 없습니다
'; + } + + searchResults.innerHTML = html; + searchResults.style.display = 'block'; + } + + // ========================================== + // Фильтрация туров + // ========================================== + const routeFilters = document.querySelectorAll('.route-filter'); + const routeCards = document.querySelectorAll('.route-card'); + + routeFilters.forEach(filter => { + filter.addEventListener('click', function() { + const category = this.dataset.category; + + // Обновление активного фильтра + routeFilters.forEach(f => f.classList.remove('active')); + this.classList.add('active'); + + // Фильтрация карточек + routeCards.forEach(card => { + if (category === 'all' || card.dataset.category === category) { + card.style.display = 'block'; + card.classList.add('fade-in'); + } else { + card.style.display = 'none'; + card.classList.remove('fade-in'); + } + }); + }); + }); + + // ========================================== + // Форма бронирования + // ========================================== + const bookingForm = document.getElementById('booking-form'); + + if (bookingForm) { + bookingForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + const submitBtn = this.querySelector('button[type="submit"]'); + const originalText = submitBtn.textContent; + + submitBtn.disabled = true; + submitBtn.innerHTML = ' 전송 중...'; + + try { + const formData = new FormData(this); + const response = await fetch('/api/booking', { + method: 'POST', + body: formData + }); + + const result = await response.json(); + + if (response.ok) { + showAlert('success', '예약 요청이 성공적으로 전송되었습니다!'); + this.reset(); + } else { + showAlert('danger', result.error || '오류가 발생했습니다.'); + } + } catch (error) { + console.error('Booking error:', error); + showAlert('danger', '네트워크 오류가 발생했습니다.'); + } finally { + submitBtn.disabled = false; + submitBtn.textContent = originalText; + } + }); + } + + // ========================================== + // Форма контактов + // ========================================== + const contactForm = document.getElementById('contact-form'); + + if (contactForm) { + contactForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + const submitBtn = this.querySelector('button[type="submit"]'); + const originalText = submitBtn.textContent; + + submitBtn.disabled = true; + submitBtn.innerHTML = ' 전송 중...'; + + try { + const formData = new FormData(this); + const response = await fetch('/api/contact', { + method: 'POST', + body: formData + }); + + const result = await response.json(); + + if (response.ok) { + showAlert('success', '메시지가 성공적으로 전송되었습니다!'); + this.reset(); + } else { + showAlert('danger', result.error || '오류가 발생했습니다.'); + } + } catch (error) { + console.error('Contact error:', error); + showAlert('danger', '네트워크 오류가 발생했습니다.'); + } finally { + submitBtn.disabled = false; + submitBtn.textContent = originalText; + } + }); + } + + // ========================================== + // Галерея изображений + // ========================================== + const galleryItems = document.querySelectorAll('.gallery-item'); + + galleryItems.forEach(item => { + item.addEventListener('click', function() { + const img = this.querySelector('img'); + if (img) { + showImageModal(img.src, img.alt || 'Gallery Image'); + } + }); + }); + + function showImageModal(src, alt) { + const modal = document.createElement('div'); + modal.className = 'image-modal'; + modal.innerHTML = + '
' + + '
' + + '' + + '' + alt + '' + + '
' + + '
'; + + document.body.appendChild(modal); + setTimeout(() => modal.classList.add('show'), 10); + + // Закрытие модального окна + const closeModal = () => { + modal.classList.remove('show'); + setTimeout(() => { + if (modal.parentNode) { + document.body.removeChild(modal); + } + }, 300); + }; + + modal.querySelector('.image-modal-close').addEventListener('click', closeModal); + modal.querySelector('.image-modal-backdrop').addEventListener('click', function(e) { + if (e.target === this) closeModal(); + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') closeModal(); + }); + } + + // ========================================== + // Плавная прокрутка к секциям + // ========================================== + const scrollLinks = document.querySelectorAll('a[href^="#"]'); + + scrollLinks.forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + + const targetId = this.getAttribute('href').substring(1); + const targetElement = document.getElementById(targetId); + + if (targetElement) { + targetElement.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); + + // ========================================== + // Валидация форм + // ========================================== + const forms = document.querySelectorAll('.needs-validation'); + + forms.forEach(form => { + form.addEventListener('submit', function(e) { + if (!form.checkValidity()) { + e.preventDefault(); + e.stopPropagation(); + } + form.classList.add('was-validated'); + }); + }); + + // ========================================== + // Tooltips и Popovers + // ========================================== + if (typeof bootstrap !== 'undefined') { + const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + + const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); + popoverTriggerList.map(function (popoverTriggerEl) { + return new bootstrap.Popover(popoverTriggerEl); + }); + } + + // ========================================== + // Lazy loading изображений + // ========================================== + const lazyImages = document.querySelectorAll('img[data-src]'); + + if ('IntersectionObserver' in window) { + const imageObserver = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.src; + img.classList.remove('lazy'); + imageObserver.unobserve(img); + } + }); + }); + + lazyImages.forEach(img => imageObserver.observe(img)); + } else { + // Fallback для старых браузеров + lazyImages.forEach(img => { + img.src = img.dataset.src; + img.classList.remove('lazy'); + }); + } + + // ========================================== + // Утилитарные функции + // ========================================== + function showAlert(type, message) { + const alertContainer = document.getElementById('alert-container') || createAlertContainer(); + + const alert = document.createElement('div'); + alert.className = 'alert alert-' + type + ' alert-dismissible fade show'; + alert.innerHTML = + message + + ''; + + alertContainer.appendChild(alert); + + // Автоскрытие через 5 секунд + setTimeout(() => { + if (alert.parentNode) { + alert.remove(); + } + }, 5000); + } + + function createAlertContainer() { + const container = document.createElement('div'); + container.id = 'alert-container'; + container.className = 'position-fixed top-0 end-0 p-3'; + container.style.zIndex = '9999'; + document.body.appendChild(container); + return container; + } + + console.log('Korea Tourism Agency - JavaScript loaded successfully! 🇰🇷'); +}); \ No newline at end of file diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..769b9d2 --- /dev/null +++ b/src/app.js @@ -0,0 +1,198 @@ +import express from 'express'; +import path from 'path'; +import session from 'express-session'; +import cors from 'cors'; +import helmet from 'helmet'; +import compression from 'compression'; +import morgan from 'morgan'; +import methodOverride from 'method-override'; +import formatters from './helpers/formatters.js'; +import { adminJs, router as adminRouter } from './config/adminjs-simple.js'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import { createRequire } from 'module'; +import dotenv from 'dotenv'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const require = createRequire(import.meta.url); + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3000; + +async function setupApp() { + +// Security middleware +app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"], + fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"], + scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://code.jquery.com"], + imgSrc: ["'self'", "data:", "https:", "blob:"], + connectSrc: ["'self'"], + }, + }, +})); +app.use(compression()); +app.use(morgan('combined')); +app.use(cors()); + +// Method override for PUT/DELETE requests +app.use(methodOverride('_method')); + +// Static files +app.use(express.static(path.join(__dirname, '../public'))); + +// Serve node_modules for AdminLTE assets +app.use('/node_modules', express.static(path.join(__dirname, '../node_modules'))); + +// Session configuration +app.use(session({ + secret: process.env.SESSION_SECRET || 'korea-tourism-secret', + resave: false, + saveUninitialized: false, + cookie: { + secure: process.env.NODE_ENV === 'production', + maxAge: 24 * 60 * 60 * 1000 // 24 hours + } +})); + +// View engine setup +app.set('view engine', 'ejs'); +app.set('views', path.join(__dirname, '../views')); + +// Global template variables +app.use((req, res, next) => { + res.locals.siteName = process.env.SITE_NAME || 'Корея Тур Агентство'; + res.locals.siteDescription = process.env.SITE_DESCRIPTION || 'Откройте для себя красоту Кореи'; + res.locals.user = req.session.user || null; + res.locals.admin = req.session.admin || null; + res.locals.currentPath = req.path; + res.locals.page = 'home'; // default page + + // Add all helper functions to template globals + Object.assign(res.locals, formatters); + + next(); +}); + +// Layout middleware +app.use((req, res, next) => { + const originalRender = res.render; + + res.render = function(view, locals, callback) { + if (typeof locals === 'function') { + callback = locals; + locals = {}; + } + locals = locals || {}; + + // Check if it's an admin route + if (req.path.startsWith('/admin')) { + // Check if a custom layout is specified + if (locals.layout) { + const customLayout = locals.layout; + delete locals.layout; + + // Render the view content first + originalRender.call(this, view, locals, (err, html) => { + if (err) return callback ? callback(err) : next(err); + + // Then render the custom layout with the content + locals.body = html; + originalRender.call(res, customLayout, locals, callback); + }); + } else { + return originalRender.call(this, view, locals, callback); + } + } else { + // Render the view content first + originalRender.call(this, view, locals, (err, html) => { + if (err) return callback ? callback(err) : next(err); + + // Then render the layout with the content + locals.body = html; + originalRender.call(res, 'layout', locals, callback); + }); + } + }; + + next(); +}); + +// Routes +app.use(adminJs.options.rootPath, adminRouter); // AdminJS routes + +// Body parser middleware (after AdminJS) +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); + +// Dynamic imports for CommonJS routes +const indexRouter = (await import('./routes/index.js')).default; +const toursRouter = (await import('./routes/tours.js')).default; +const guidesRouter = (await import('./routes/guides.js')).default; +const articlesRouter = (await import('./routes/articles.js')).default; +const apiRouter = (await import('./routes/api.js')).default; + +app.use('/', indexRouter); +app.use('/routes', toursRouter); +app.use('/guides', guidesRouter); +app.use('/articles', articlesRouter); +app.use('/api', apiRouter); + +// Health check endpoint +app.get('/health', (req, res) => { + res.json({ + status: 'OK', + timestamp: new Date().toISOString(), + uptime: process.uptime() + }); +}); + +// Error handling +app.use((req, res) => { + res.status(404).render('error', { + title: '404 - Page Not Found', + message: 'The page you are looking for does not exist.', + error: { status: 404 }, + layout: 'layout' + }); +}); + +app.use((err, req, res, next) => { + console.error('Error:', err.stack); + + // Don't expose stack trace in production + const isDev = process.env.NODE_ENV === 'development'; + + res.status(err.status || 500).render('error', { + title: `${err.status || 500} - Server Error`, + message: isDev ? err.message : 'Something went wrong on our server.', + error: isDev ? err : { status: err.status || 500 }, + layout: 'layout' + }); +}); + +// Graceful shutdown +process.on('SIGTERM', () => { + console.log('SIGTERM received, shutting down gracefully'); + server.close(() => { + console.log('Process terminated'); + }); +}); + +const server = app.listen(PORT, '0.0.0.0', () => { + console.log(`🚀 Korea Tourism Agency server running on port ${PORT}`); + console.log(`📍 Environment: ${process.env.NODE_ENV || 'development'}`); + console.log(`🔧 Admin panel: http://localhost:${PORT}${adminJs.options.rootPath}`); + console.log(`🏠 Website: http://localhost:${PORT}`); +}); + +} + +// Start the application +setupApp().catch(console.error); \ No newline at end of file diff --git a/src/config/adminjs-simple.js b/src/config/adminjs-simple.js new file mode 100644 index 0000000..a828474 --- /dev/null +++ b/src/config/adminjs-simple.js @@ -0,0 +1,542 @@ +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 }, + 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' +}); + + +// Конфигурация 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 }, + } + }, + } + } + ], + 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 }; \ No newline at end of file diff --git a/src/config/database.js b/src/config/database.js new file mode 100644 index 0000000..072f0c8 --- /dev/null +++ b/src/config/database.js @@ -0,0 +1,33 @@ +import pkg from 'pg'; +const { Pool } = pkg; +import dotenv from 'dotenv'; + +dotenv.config(); + +const pool = new Pool({ + host: process.env.DB_HOST || 'localhost', + 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', + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, +}); + +// Test database connection +pool.on('connect', () => { + console.log('💾 Connected to PostgreSQL database'); +}); + +pool.on('error', (err) => { + console.error('🔴 Database connection error:', err); +}); + +const db = { + pool, + query: (text, params) => pool.query(text, params) +}; + +export default db; +export { pool }; \ No newline at end of file diff --git a/src/helpers/formatters.js b/src/helpers/formatters.js new file mode 100644 index 0000000..54f24fd --- /dev/null +++ b/src/helpers/formatters.js @@ -0,0 +1,172 @@ +// Helper functions for EJS templates + +/** + * Format currency with thousands separators + * @param {number} amount - The amount to format + * @returns {string} - Formatted currency string + */ +function formatCurrency(amount) { + if (!amount || isNaN(amount)) return '0'; + return parseInt(amount).toLocaleString('ko-KR'); +} + +/** + * Format date for display + * @param {Date} date - Date to format + * @returns {string} - Formatted date string + */ +function formatDate(date) { + if (!date) return ''; + const options = { + year: 'numeric', + month: 'long', + day: 'numeric', + timeZone: 'Asia/Seoul' + }; + return new Date(date).toLocaleDateString('ru-RU', options); +} + +/** + * Format date for display (short version) + * @param {Date} date - Date to format + * @returns {string} - Formatted short date string + */ +function formatDateShort(date) { + if (!date) return ''; + const options = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + timeZone: 'Asia/Seoul' + }; + return new Date(date).toLocaleDateString('ru-RU', options); +} + +/** + * Truncate text to specified length + * @param {string} text - Text to truncate + * @param {number} length - Maximum length + * @returns {string} - Truncated text + */ +function truncateText(text, length = 150) { + if (!text) return ''; + if (text.length <= length) return text; + return text.substring(0, length).trim() + '...'; +} + +/** + * Get type label in Russian + * @param {string} type - Type key + * @returns {string} - Russian label + */ +function getTypeLabel(type) { + const labels = { + 'city': 'Городской тур', + 'mountain': 'Горный поход', + 'fishing': 'Рыбалка' + }; + return labels[type] || type; +} + +/** + * Get difficulty label in Russian + * @param {string} difficulty - Difficulty level + * @returns {string} - Russian label + */ +function getDifficultyLabel(difficulty) { + const labels = { + 'easy': 'Легко', + 'moderate': 'Средне', + 'hard': 'Сложно' + }; + return labels[difficulty] || difficulty; +} + +/** + * Get category label in Russian + * @param {string} category - Category key + * @returns {string} - Russian label + */ +function getCategoryLabel(category) { + const labels = { + 'travel-tips': 'Советы путешественникам', + 'culture': 'Культура', + 'food': 'Еда', + 'nature': 'Природа', + 'history': 'История' + }; + return labels[category] || category; +} + +/** + * Generate star rating HTML + * @param {number} rating - Rating value (1-5) + * @returns {string} - HTML with stars + */ +function generateStars(rating) { + if (!rating || isNaN(rating)) return ''; + + let stars = ''; + const fullStars = Math.floor(rating); + const hasHalfStar = rating % 1 >= 0.5; + + // Full stars + for (let i = 0; i < fullStars; i++) { + stars += ''; + } + + // Half star + if (hasHalfStar) { + stars += ''; + } + + // Empty stars + const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0); + for (let i = 0; i < emptyStars; i++) { + stars += ''; + } + + return stars; +} + +/** + * Get plural form for Russian + * @param {number} count - Number + * @param {string} one - Form for 1 + * @param {string} few - Form for 2-4 + * @param {string} many - Form for 5+ + * @returns {string} - Correct plural form + */ +function getPlural(count, one, few, many) { + const n = Math.abs(count) % 100; + const n1 = n % 10; + + if (n > 10 && n < 20) return many; + if (n1 > 1 && n1 < 5) return few; + if (n1 === 1) return one; + return many; +} + +export { + formatCurrency, + formatDate, + formatDateShort, + truncateText, + getTypeLabel, + getDifficultyLabel, + getCategoryLabel, + generateStars, + getPlural +}; + +export default { + formatCurrency, + formatDate, + formatDateShort, + truncateText, + getTypeLabel, + getDifficultyLabel, + getCategoryLabel, + generateStars, + getPlural +}; \ No newline at end of file diff --git a/src/routes/api.js b/src/routes/api.js new file mode 100644 index 0000000..984d6d1 --- /dev/null +++ b/src/routes/api.js @@ -0,0 +1,242 @@ +import express from 'express'; +import db from '../config/database.js'; +const router = express.Router(); + +// Get routes by type for filtering +router.get('/routes', async (req, res) => { + try { + const { type, search, limit = 12, offset = 0 } = req.query; + + let query = ` + SELECT r.*, g.name as guide_name + FROM routes r + LEFT JOIN guides g ON r.guide_id = g.id + WHERE r.is_active = true + `; + const params = []; + let paramCount = 0; + + if (type && ['city', 'mountain', 'fishing'].includes(type)) { + paramCount++; + query += ` AND r.type = $${paramCount}`; + params.push(type); + } + + if (search) { + paramCount++; + query += ` AND (r.title ILIKE $${paramCount} OR r.description ILIKE $${paramCount})`; + params.push(`%${search}%`); + } + + query += ' ORDER BY r.created_at DESC'; + + paramCount++; + query += ` LIMIT $${paramCount}`; + params.push(parseInt(limit)); + + paramCount++; + query += ` OFFSET $${paramCount}`; + params.push(parseInt(offset)); + + const routes = await db.query(query, params); + + res.json({ + success: true, + data: routes.rows + }); + } catch (error) { + console.error('API error loading routes:', error); + res.status(500).json({ + success: false, + message: 'Error loading routes' + }); + } +}); + +// Get guides by specialization +router.get('/guides', async (req, res) => { + try { + const { specialization } = req.query; + + let query = ` + SELECT id, name, specialization, languages, experience, image_url + FROM guides + WHERE is_active = true + `; + const params = []; + + if (specialization && ['city', 'mountain', 'fishing'].includes(specialization)) { + query += ' AND specialization = $1'; + params.push(specialization); + } + + query += ' ORDER BY name ASC'; + + const guides = await db.query(query, params); + + res.json({ + success: true, + data: guides.rows + }); + } catch (error) { + console.error('API error loading guides:', error); + res.status(500).json({ + success: false, + message: 'Error loading guides' + }); + } +}); + +// Submit booking request +router.post('/booking', async (req, res) => { + try { + const { + route_id, + customer_name, + customer_email, + customer_phone, + preferred_date, + group_size, + special_requirements, + guide_id + } = req.body; + + const booking = await db.query(` + INSERT INTO bookings ( + route_id, guide_id, customer_name, customer_email, + customer_phone, preferred_date, group_size, + special_requirements, status + ) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'pending') + RETURNING id + `, [ + route_id, guide_id, customer_name, customer_email, + customer_phone, preferred_date, group_size, + special_requirements + ]); + + res.json({ + success: true, + message: 'Booking request submitted successfully! We will contact you soon.', + booking_id: booking.rows[0].id + }); + } catch (error) { + console.error('API error submitting booking:', error); + res.status(500).json({ + success: false, + message: 'Error submitting booking request' + }); + } +}); + +// Search functionality +router.get('/search', async (req, res) => { + try { + const { q, type = 'all' } = req.query; + + if (!q || q.length < 2) { + return res.json({ + success: false, + message: 'Search query must be at least 2 characters' + }); + } + + let results = { routes: [], guides: [], articles: [] }; + + if (type === 'all' || type === 'routes') { + const routes = await db.query(` + SELECT id, title, description, type, price, image_url + FROM routes + WHERE (title ILIKE $1 OR description ILIKE $1) AND is_active = true + LIMIT 5 + `, [`%${q}%`]); + results.routes = routes.rows; + } + + if (type === 'all' || type === 'guides') { + const guides = await db.query(` + SELECT id, name, specialization, bio, image_url + FROM guides + WHERE (name ILIKE $1 OR bio ILIKE $1) AND is_active = true + LIMIT 5 + `, [`%${q}%`]); + results.guides = guides.rows; + } + + if (type === 'all' || type === 'articles') { + const articles = await db.query(` + SELECT id, title, excerpt, category, image_url + FROM articles + WHERE (title ILIKE $1 OR content ILIKE $1) AND is_published = true + LIMIT 5 + `, [`%${q}%`]); + results.articles = articles.rows; + } + + res.json({ + success: true, + data: results + }); + } catch (error) { + console.error('API search error:', error); + res.status(500).json({ + success: false, + message: 'Search error' + }); + } +}); + +// Submit review +router.post('/reviews', async (req, res) => { + try { + const { + route_id, + guide_id, + customer_name, + customer_email, + rating, + comment + } = req.body; + + // Validation + if (!customer_name || !rating || !comment) { + return res.status(400).json({ + success: false, + message: 'Имя, рейтинг и комментарий обязательны для заполнения' + }); + } + + if (rating < 1 || rating > 5) { + return res.status(400).json({ + success: false, + message: 'Рейтинг должен быть от 1 до 5' + }); + } + + const review = await db.query(` + INSERT INTO reviews ( + route_id, guide_id, customer_name, customer_email, + rating, comment, is_approved + ) + VALUES ($1, $2, $3, $4, $5, $6, false) + RETURNING id + `, [ + route_id, guide_id, customer_name, customer_email, + rating, comment + ]); + + res.json({ + success: true, + message: 'Спасибо за отзыв! Он будет опубликован после модерации.', + review_id: review.rows[0].id + }); + } catch (error) { + console.error('API error submitting review:', error); + res.status(500).json({ + success: false, + message: 'Ошибка при отправке отзыва' + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/routes/articles.js b/src/routes/articles.js new file mode 100644 index 0000000..3b3f5a9 --- /dev/null +++ b/src/routes/articles.js @@ -0,0 +1,135 @@ +import express from 'express'; +import db from '../config/database.js'; +const router = express.Router(); + +// List all articles +router.get('/', async (req, res) => { + try { + const { category, search, page = 1 } = req.query; + const limit = 12; + const offset = (page - 1) * limit; + + let query = ` + SELECT id, title, excerpt, content, category, image_url, + created_at, updated_at, views + FROM articles + WHERE is_published = true + `; + const params = []; + let paramCount = 0; + + // Filter by category + if (category && ['travel-tips', 'culture', 'food', 'nature', 'history'].includes(category)) { + paramCount++; + query += ` AND category = $${paramCount}`; + params.push(category); + } + + // Search functionality + if (search) { + paramCount++; + query += ` AND (title ILIKE $${paramCount} OR content ILIKE $${paramCount})`; + params.push(`%${search}%`); + } + + query += ' ORDER BY created_at DESC'; + + // Add pagination + paramCount++; + query += ` LIMIT $${paramCount}`; + params.push(limit); + + paramCount++; + query += ` OFFSET $${paramCount}`; + params.push(offset); + + const articles = await db.query(query, params); + + // Get total count for pagination + let countQuery = 'SELECT COUNT(*) FROM articles WHERE is_published = true'; + const countParams = []; + let countParamCount = 0; + + if (category) { + countParamCount++; + countQuery += ` AND category = $${countParamCount}`; + countParams.push(category); + } + + if (search) { + countParamCount++; + countQuery += ` AND (title ILIKE $${countParamCount} OR content ILIKE $${countParamCount})`; + countParams.push(`%${search}%`); + } + + const totalCount = await db.query(countQuery, countParams); + const totalPages = Math.ceil(totalCount.rows[0].count / limit); + + res.render('articles/index', { + title: 'Статьи и блог - Корея Тур Агентство', + articles: articles.rows, + currentCategory: category || 'all', + searchQuery: search || '', + currentPage: parseInt(page), + totalPages, + page: 'articles' + }); + } catch (error) { + console.error('Error loading articles:', error); + res.status(500).render('error', { + title: 'Ошибка', + message: 'Не удалось загрузить статьи', + error: error + }); + } +}); + +// Article detail +router.get('/:id', async (req, res) => { + try { + const articleId = req.params.id; + + // Increment view count + await db.query('UPDATE articles SET views = views + 1 WHERE id = $1', [articleId]); + + const article = await db.query(` + SELECT id, title, excerpt, content, category, image_url, + created_at, updated_at, views, meta_description + FROM articles + WHERE id = $1 AND is_published = true + `, [articleId]); + + if (article.rows.length === 0) { + return res.status(404).render('error', { + title: '404 - Статья не найдена', + message: 'Статья, которую вы ищете, не существует.', + error: { status: 404 } + }); + } + + // Get related articles + const relatedArticles = await db.query(` + SELECT id, title, excerpt, image_url, created_at + FROM articles + WHERE category = $1 AND id != $2 AND is_published = true + ORDER BY created_at DESC + LIMIT 4 + `, [article.rows[0].category, articleId]); + + res.render('articles/detail', { + title: `${article.rows[0].title} - Корея Тур Агентство`, + article: article.rows[0], + relatedArticles: relatedArticles.rows, + page: 'articles' + }); + } catch (error) { + console.error('Error loading article:', error); + res.status(500).render('error', { + title: 'Ошибка', + message: 'Не удалось загрузить статью', + error: error + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/routes/guides.js b/src/routes/guides.js new file mode 100644 index 0000000..838bfda --- /dev/null +++ b/src/routes/guides.js @@ -0,0 +1,132 @@ +import express from 'express'; +import db from '../config/database.js'; +const router = express.Router(); + +// List all guides +router.get('/', async (req, res) => { + try { + const { specialization, language, sort } = req.query; + let query = ` + SELECT g.*, + COUNT(r.id) as route_count, + AVG(rv.rating) as avg_rating + FROM guides g + LEFT JOIN routes r ON g.id = r.guide_id AND r.is_active = true + LEFT JOIN reviews rv ON g.id = rv.guide_id + WHERE g.is_active = true + `; + const params = []; + let paramCount = 0; + + // Filter by specialization + if (specialization && ['city', 'mountain', 'fishing'].includes(specialization)) { + paramCount++; + query += ` AND g.specialization = $${paramCount}`; + params.push(specialization); + } + + // Filter by language + if (language) { + paramCount++; + query += ` AND g.languages ILIKE $${paramCount}`; + params.push(`%${language}%`); + } + + query += ' GROUP BY g.id'; + + // Sorting + switch (sort) { + case 'experience': + query += ' ORDER BY g.experience DESC'; + break; + case 'rating': + query += ' ORDER BY avg_rating DESC NULLS LAST'; + break; + case 'routes': + query += ' ORDER BY route_count DESC'; + break; + default: + query += ' ORDER BY g.name ASC'; + } + + const guides = await db.query(query, params); + + res.render('guides/index', { + title: 'Наши гиды - Корея Тур Агентство', + guides: guides.rows, + currentSpecialization: specialization || 'all', + currentLanguage: language || '', + currentSort: sort || 'name', + page: 'guides' + }); + } catch (error) { + console.error('Error loading guides:', error); + res.status(500).render('error', { + title: 'Ошибка', + message: 'Не удалось загрузить список гидов', + error: error + }); + } +}); + +// Guide profile +router.get('/:id', async (req, res) => { + try { + const guideId = req.params.id; + + const guide = await db.query(` + SELECT g.*, + COUNT(DISTINCT r.id) as route_count, + AVG(rv.rating) as avg_rating, + COUNT(DISTINCT rv.id) as review_count + FROM guides g + LEFT JOIN routes r ON g.id = r.guide_id AND r.is_active = true + LEFT JOIN reviews rv ON g.id = rv.guide_id + WHERE g.id = $1 AND g.is_active = true + GROUP BY g.id + `, [guideId]); + + if (guide.rows.length === 0) { + return res.status(404).render('error', { + title: '404 - Гид не найден', + message: 'Гид, которого вы ищете, не существует.', + error: { status: 404 } + }); + } + + // Get guide's routes + const routes = await db.query(` + SELECT id, title, description, type, price, duration, image_url + FROM routes + WHERE guide_id = $1 AND is_active = true + ORDER BY created_at DESC + `, [guideId]); + + // Get reviews + const reviews = await db.query(` + SELECT rv.*, r.title as route_title + FROM reviews rv + LEFT JOIN routes r ON rv.route_id = r.id + WHERE rv.guide_id = $1 + ORDER BY rv.created_at DESC + LIMIT 10 + `, [guideId]); + + res.render('guides/profile', { + title: `${guide.rows[0].name} - Корея Тур Агентство`, + guide: guide.rows[0], + routes: routes.rows, + reviews: reviews.rows, + page: 'guides' + }); + } catch (error) { + console.error('Error loading guide profile:', error); + res.status(500).render('error', { + title: 'Ошибка', + message: 'Не удалось загрузить профиль гида', + error: error + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..5af2c5a --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,108 @@ +import express from 'express'; +import db from '../config/database.js'; +const router = express.Router(); + +// Main page +router.get('/', async (req, res) => { + try { + // Get featured routes (with fallback) + let featuredRoutes = []; + try { + const routesResult = await db.query(` + SELECT id, title, description, type, price, duration, image_url, + created_at, is_featured + FROM routes + WHERE is_featured = true AND is_active = true + ORDER BY created_at DESC + LIMIT 6 + `); + featuredRoutes = routesResult.rows; + } catch (err) { + console.log('No featured routes found, using empty array'); + } + + // Get recent articles (with fallback) + let recentArticles = []; + try { + const articlesResult = await db.query(` + SELECT id, title, excerpt, image_url, created_at + FROM articles + WHERE is_published = true + ORDER BY created_at DESC + LIMIT 3 + `); + recentArticles = articlesResult.rows; + } catch (err) { + console.log('No articles found, using empty array'); + } + + // Get guide stats (with fallback) + let guideStats = { total_guides: 0, city_guides: 0, mountain_guides: 0, fishing_guides: 0 }; + try { + const statsResult = await db.query(` + SELECT COUNT(*) as total_guides, + COUNT(CASE WHEN specialization = 'city' THEN 1 END) as city_guides, + COUNT(CASE WHEN specialization = 'mountain' THEN 1 END) as mountain_guides, + COUNT(CASE WHEN specialization = 'fishing' THEN 1 END) as fishing_guides + FROM guides + WHERE is_active = true + `); + guideStats = statsResult.rows[0] || guideStats; + } catch (err) { + console.log('No guide stats found, using defaults'); + } + + res.render('index', { + title: 'Корея Тур Агентство - Откройте Корею', + featuredRoutes: featuredRoutes, + recentArticles: recentArticles, + guideStats: guideStats, + page: 'home' + }); + } catch (error) { + console.error('Error loading home page:', error); + res.render('index', { + title: 'Корея Тур Агентство - Откройте Корею', + featuredRoutes: [], + recentArticles: [], + guideStats: { total_guides: 0, city_guides: 0, mountain_guides: 0, fishing_guides: 0 }, + page: 'home' + }); + } +}); + +// About page +router.get('/about', (req, res) => { + res.render('about', { + title: 'О нас - Корея Тур Агентство', + page: 'about' + }); +}); + +// Contact page +router.get('/contact', (req, res) => { + res.render('contact', { + title: 'Свяжитесь с нами - Корея Тур Агентство', + page: 'contact' + }); +}); + +// Contact form submission +router.post('/contact', async (req, res) => { + try { + const { name, email, phone, subject, message } = req.body; + + // Save contact form to database + await db.query(` + INSERT INTO contact_messages (name, email, phone, subject, message) + VALUES ($1, $2, $3, $4, $5) + `, [name, email, phone, subject, message]); + + res.json({ success: true, message: 'Thank you for your message. We will get back to you soon!' }); + } catch (error) { + console.error('Error submitting contact form:', error); + res.status(500).json({ success: false, message: 'Error submitting message. Please try again.' }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js new file mode 100644 index 0000000..1aa5d63 --- /dev/null +++ b/src/routes/routes.js @@ -0,0 +1,91 @@ +const express = require('express'); +const db = require('../config/database'); +const router = express.Router(); + +// Routes listing page +router.get('/', async (req, res) => { + try { + const { type } = req.query; + let query = ` + SELECT r.*, g.name as guide_name + FROM routes r + LEFT JOIN guides g ON r.guide_id = g.id + WHERE r.is_active = true + `; + const params = []; + + if (type && ['city', 'mountain', 'fishing'].includes(type)) { + query += ` AND r.type = $1`; + params.push(type); + } + + query += ` ORDER BY r.is_featured DESC, r.created_at DESC`; + + const result = await db.query(query, params); + const routes = result.rows; + + res.render('routes/index', { + title: 'Туры по Корее - Корея Тур Агентство', + routes: routes, + currentType: type || 'all', + page: 'routes' + }); + } catch (error) { + console.error('Error loading routes:', error); + res.render('routes/index', { + title: 'Туры по Корее - Корея Тур Агентство', + routes: [], + currentType: 'all', + page: 'routes' + }); + } +}); + +// Single route page +router.get('/:id', async (req, res) => { + try { + const routeId = parseInt(req.params.id); + + // Get route details + const routeResult = await db.query(` + SELECT r.*, g.name as guide_name, g.bio as guide_bio, g.image_url as guide_image + FROM routes r + LEFT JOIN guides g ON r.guide_id = g.id + WHERE r.id = $1 AND r.is_active = true + `, [routeId]); + + if (routeResult.rows.length === 0) { + return res.status(404).render('error', { + title: 'Тур не найден', + message: 'Запрашиваемый тур не существует или недоступен.', + page: 'error' + }); + } + + const route = routeResult.rows[0]; + + // Get reviews for this route + const reviewsResult = await db.query(` + SELECT * FROM reviews + WHERE route_id = $1 AND is_approved = true + ORDER BY created_at DESC + LIMIT 10 + `, [routeId]); + + res.render('routes/detail', { + title: `${route.title} - Корея Тур Агентство`, + route: route, + reviews: reviewsResult.rows, + page: 'routes' + }); + } catch (error) { + console.error('Error loading route details:', error); + res.status(500).render('error', { + title: 'Ошибка', + message: 'Произошла ошибка при загрузке информации о туре.', + page: 'error' + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/src/routes/tours.js b/src/routes/tours.js new file mode 100644 index 0000000..75cbf99 --- /dev/null +++ b/src/routes/tours.js @@ -0,0 +1,123 @@ +import express from 'express'; +import db from '../config/database.js'; +const router = express.Router(); + +// List all routes +router.get('/', async (req, res) => { + try { + const { type, sort, search } = req.query; + let query = ` + SELECT r.*, g.name as guide_name + FROM routes r + LEFT JOIN guides g ON r.guide_id = g.id + WHERE r.is_active = true + `; + const params = []; + let paramCount = 0; + + // Filter by type + if (type && ['city', 'mountain', 'fishing'].includes(type)) { + paramCount++; + query += ` AND r.type = $${paramCount}`; + params.push(type); + } + + // Search functionality + if (search) { + paramCount++; + query += ` AND (r.title ILIKE $${paramCount} OR r.description ILIKE $${paramCount})`; + params.push(`%${search}%`); + } + + // Sorting + switch (sort) { + case 'price_low': + query += ' ORDER BY r.price ASC'; + break; + case 'price_high': + query += ' ORDER BY r.price DESC'; + break; + case 'duration': + query += ' ORDER BY r.duration ASC'; + break; + default: + query += ' ORDER BY r.created_at DESC'; + } + + const routes = await db.query(query, params); + + res.render('routes/index', { + title: 'Туры по Корее - Корея Тур Агентство', + routes: routes.rows, + currentType: type || 'all', + currentSort: sort || 'newest', + searchQuery: search || '', + page: 'routes' + }); + } catch (error) { + console.error('Error loading routes:', error); + res.status(500).render('error', { + title: 'Ошибка', + message: 'Не удалось загрузить список туров', + error: error + }); + } +}); + +// Route details +router.get('/:id', async (req, res) => { + try { + const routeId = req.params.id; + + const route = await db.query(` + SELECT r.*, g.name as guide_name, g.bio as guide_bio, + g.phone as guide_phone, g.email as guide_email, + g.languages as guide_languages, g.experience as guide_experience + FROM routes r + LEFT JOIN guides g ON r.guide_id = g.id + WHERE r.id = $1 AND r.is_active = true + `, [routeId]); + + if (route.rows.length === 0) { + return res.status(404).render('error', { + title: '404 - Тур не найден', + message: 'Запрашиваемый тур не существует.', + error: { status: 404 } + }); + } + + // Get related routes + const relatedRoutes = await db.query(` + SELECT id, title, description, type, price, duration, image_url + FROM routes + WHERE type = $1 AND id != $2 AND is_active = true + ORDER BY RANDOM() + LIMIT 3 + `, [route.rows[0].type, routeId]); + + // Get reviews for this route + const reviewsResult = await db.query(` + SELECT * FROM reviews + WHERE route_id = $1 AND is_approved = true + ORDER BY created_at DESC + LIMIT 10 + `, [routeId]); + + res.render('routes/detail', { + title: `${route.rows[0].title} - Корея Тур Агентство`, + route: route.rows[0], + relatedRoutes: relatedRoutes.rows, + reviews: reviewsResult.rows, + page: 'routes' + }); + } catch (error) { + console.error('Error loading route details:', error); + res.status(500).render('error', { + title: 'Ошибка', + message: 'Не удалось загрузить детали тура', + error: error + }); + } +}); + +export default router; \ No newline at end of file diff --git a/start-dev.sh b/start-dev.sh new file mode 100755 index 0000000..918878d --- /dev/null +++ b/start-dev.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +# Korea Tourism Agency - Development Setup Script +# Скрипт для быстрого запуска среды разработки + +echo "🇰🇷 Korea Tourism Agency - Development Setup" +echo "=============================================" +echo "" + +# Проверка Docker +if ! command -v docker &> /dev/null; then + echo "❌ Docker не установлен. Установите Docker и попробуйте снова." + exit 1 +fi + +if ! command -v docker-compose &> /dev/null; then + echo "❌ Docker Compose не установлен. Установите Docker Compose и попробуйте снова." + exit 1 +fi + +if ! docker info > /dev/null 2>&1; then + echo "❌ Docker не запущен. Запустите Docker сначала." + exit 1 +fi + +echo "✅ Docker найден и запущен" + +# Создание .env файла если его нет +if [ ! -f .env ]; then + echo "📝 Создание файла .env..." + cat > .env << 'EOL' +# Database Configuration +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=korea_tourism +DB_USER=tourism_user +DB_PASSWORD=tourism_password + +# Application Configuration +PORT=3000 +NODE_ENV=development +SESSION_SECRET=korea-tourism-secret-key-2024 + +# File Upload Configuration +UPLOAD_PATH=/app/public/uploads +MAX_FILE_SIZE=5242880 + +# Site Information +SITE_NAME=Korea Tourism Agency +CONTACT_EMAIL=info@koreatourism.com +CONTACT_PHONE=+82-2-1234-5678 + +# Admin Configuration +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin123 +EOL + echo "✅ Файл .env создан" +else + echo "✅ Файл .env уже существует" +fi + +# Создание необходимых директорий +echo "📁 Создание директорий..." +mkdir -p public/uploads/routes +mkdir -p public/uploads/guides +mkdir -p public/uploads/articles +mkdir -p database/backups + +# Остановка существующих контейнеров +echo "" +echo "🛑 Остановка существующих контейнеров..." +docker-compose down + +# Сборка и запуск контейнеров +echo "" +echo "🏗️ Сборка и запуск контейнеров..." +docker-compose build +docker-compose up -d + +# Ожидание запуска базы данных +echo "" +echo "⏳ Ожидание запуска базы данных..." +sleep 15 + +# Проверка статуса контейнеров +echo "" +echo "📊 Статус контейнеров:" +docker-compose ps + +# Выполнение миграций +echo "" +echo "🔄 Выполнение миграций базы данных..." +docker-compose exec app node database/migrate.js + +# Заполнение тестовыми данными +echo "" +echo "📦 Заполнение тестовыми данными..." +docker-compose exec app node database/seed.js + +# Проверка логов +echo "" +echo "📝 Последние логи приложения:" +docker-compose logs --tail=5 app + +echo "" +echo "🎉 Установка завершена!" +echo "==================================" +echo "" +echo "🌐 Сайт доступен по адресу:" +echo " 🏠 Главная страница: http://localhost:3000" +echo " ⚙️ Админ панель: http://localhost:3000/admin" +echo " 🗄️ Adminer (БД): http://localhost:8080" +echo "" +echo "🔐 Данные для входа в админку:" +echo " Username: admin" +echo " Password: admin123" +echo "" +echo "🗄️ Данные для подключения к БД (Adminer):" +echo " System: PostgreSQL" +echo " Server: postgres" +echo " Username: tourism_user" +echo " Password: tourism_password" +echo " Database: korea_tourism" +echo "" +echo "📝 Полезные команды:" +echo " docker-compose logs -f app # Просмотр логов" +echo " docker-compose restart app # Перезапуск приложения" +echo " docker-compose down # Остановка контейнеров" +echo "" +echo "🎯 Готово к разработке! Откройте http://localhost:3000" diff --git a/views/about.ejs b/views/about.ejs new file mode 100644 index 0000000..76792f1 --- /dev/null +++ b/views/about.ejs @@ -0,0 +1,111 @@ + +
+
+
+
+

О нашей компании

+

Мы специализируемся на организации незабываемых путешествий по Южной Корее

+
+
+
+
+ + +
+
+
+
+

Наша миссия

+

Корея Тур Агентство основано с целью показать туристам настоящую Корею - её культуру, природу, традиции и современность.

+

Мы предлагаем широкий спектр туров: от городских экскурсий по историческим дворцам Сеула до горных походов в национальные парки и захватывающей морской рыбалки у берегов Пусана.

+

Наша команда состоит из профессиональных гидов, которые говорят на русском языке и имеют глубокие знания о корейской культуре и истории.

+
+
+ О нас +
+
+ +
+
+
+
+ +
Опытная команда
+

Более 50 проведенных туров и сотни довольных клиентов

+
+
+
+
+
+
+ +
Русскоязычные гиды
+

Все наши гиды свободно говорят по-русски и корейски

+
+
+
+
+
+
+ +
Безопасность
+

Полная страховка и соблюдение всех мер безопасности

+
+
+
+
+
+
+ + +
+
+

Почему выбирают нас?

+
+
+
+ +
+
Высокие рейтинги
+

4.8/5 средний рейтинг от наших клиентов

+
+
+
+ +
+
Уникальные маршруты
+

Эксклюзивные места, недоступные массовому туризму

+
+
+
+ +
+
Поддержка 24/7
+

Круглосуточная поддержка во время путешествия

+
+
+
+ +
+
Индивидуальный подход
+

Каждый тур адаптируется под ваши потребности

+
+
+
+
+ + +
+
+

Готовы исследовать Корею?

+

Свяжитесь с нами и начните планировать ваше незабываемое путешествие уже сегодня!

+ +
+
\ No newline at end of file diff --git a/views/articles/detail.ejs b/views/articles/detail.ejs new file mode 100644 index 0000000..14e3b9a --- /dev/null +++ b/views/articles/detail.ejs @@ -0,0 +1,195 @@ + +
+
+
+
+ + + <% if (article.category) { %> + + <%= getCategoryLabel(article.category) %> + + <% } %> + +

<%= article.title %>

+ + <% if (article.excerpt) { %> +

<%= article.excerpt %>

+ <% } %> + +
+ + <%= formatDate(article.created_at) %> + + <%= article.views %> просмотров +
+
+
+
+
+ + +
+
+
+
+
+
+ <%= article.title %> +
+ +
+ <%- article.content %> +
+
+ + +
+
Поделиться статьей:
+ +
+
+ + +
+
+
+
+ Похожие статьи +
+
+
+ <% if (relatedArticles && relatedArticles.length > 0) { %> + <% relatedArticles.forEach(related => { %> +
+
+ <%= related.title %> +
+
+ + <%= related.title %> + +
+ <% if (related.excerpt) { %> +

<%= truncateText(related.excerpt, 80) %>

+ <% } %> + + + <%= formatDateShort(related.created_at) %> + +
+ <% }); %> + <% } else { %> +

Нет похожих статей

+ <% } %> +
+
+ + + +
+
+
+
+ + +
+
+

Готовы посетить Корею?

+

Ознакомьтесь с нашими турами и найдите идеальное путешествие для себя!

+ + Посмотреть туры + + + Свяжитесь с нами + +
+
+ + + + \ No newline at end of file diff --git a/views/articles/index.ejs b/views/articles/index.ejs new file mode 100644 index 0000000..ac0b4ee --- /dev/null +++ b/views/articles/index.ejs @@ -0,0 +1,80 @@ + +
+
+
+
+

Полезные статьи

+

Советы путешественникам и интересная информация о Корее

+
+
+
+
+ + +
+
+ <% if (articles.length === 0) { %> +
+ +

Статьи не найдены

+

В данный момент нет опубликованных статей.

+
+ <% } else { %> +
+ <% articles.forEach(article => { %> +
+
+ <% if (article.image_url && article.image_url.trim()) { %> + <%= article.title %> + <% } else { %> + <%= article.title %> + <% } %> + +
+
+ + <%= getCategoryLabel(article.category) %> + +
+ +
<%= article.title %>
+

<%= article.excerpt || truncateText(article.content, 120) %>

+ +
+
+ + <%= article.views || 0 %> просмотров + + + <%= formatDate(article.created_at) %> + +
+ + + Читать + +
+
+
+
+ <% }); %> +
+ <% } %> +
+
+ + +
+
+

Не пропустите новые статьи!

+

Подпишитесь на нашу рассылку и получайте самые интересные материалы о Корее

+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/views/contact.ejs b/views/contact.ejs new file mode 100644 index 0000000..8423ea8 --- /dev/null +++ b/views/contact.ejs @@ -0,0 +1,191 @@ + +
+
+
+
+

Свяжитесь с нами

+

Мы готовы ответить на все ваши вопросы о турах по Корее

+
+
+
+
+ + +
+
+
+
+
+
+

Напишите нам

+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ +
+ +
+
+
Контактная информация
+
+ + Адрес:
+ Москва, ул. Примерная, д. 123 +
+
+ + Телефон:
+ +7 (495) 123-45-67 +
+
+ + Email:
+ info@koreatour.ru +
+
+ + Время работы:
+ Пн-Пт: 9:00-18:00
Сб: 10:00-16:00
Вс: выходной
+
+
+
+ + +
+
+
Мы в соцсетях
+ +
+
+
+
+
+
+ + +
+
+

Часто задаваемые вопросы

+
+
+
+
+

+ +

+
+
+ Граждане России могут находиться в Южной Корее без визы до 60 дней для туристических целей. Необходим загранпаспорт, действующий минимум 6 месяцев. +
+
+
+ +
+

+ +

+
+
+ В Корее используется корейская вона (KRW или ₩). Обменять деньги можно в банках, обменных пунктах или снять в банкоматах. +
+
+
+ +
+

+ +

+
+
+ Да, все наши туры включают базовую туристическую страховку. По желанию можно оформить расширенную страховку. +
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/views/error.ejs b/views/error.ejs new file mode 100644 index 0000000..3f448d6 --- /dev/null +++ b/views/error.ejs @@ -0,0 +1,34 @@ + + + + + + Ошибка - Корея Тур Агентство + + + + +
+
+
+
+
+ +

<%= title || 'Произошла ошибка' %>

+

<%= message || 'При загрузке страницы произошла ошибка.' %>

+ <% if (error && error.status === 404) { %> +

Страница, которую вы ищете, не существует или была перемещена.

+ <% } %> + + Вернуться на главную + + + Назад + +
+
+
+
+
+ + \ No newline at end of file diff --git a/views/guides/index.ejs b/views/guides/index.ejs new file mode 100644 index 0000000..493a8d0 --- /dev/null +++ b/views/guides/index.ejs @@ -0,0 +1,108 @@ + +
+
+
+
+

Наши гиды

+

Профессиональные и опытные гиды сделают ваше путешествие незабываемым

+
+
+
+
+ + +
+
+ <% if (guides.length === 0) { %> +
+ +

Гиды не найдены

+

В данный момент нет доступных гидов.

+
+ <% } else { %> +
+ <% guides.forEach(guide => { %> +
+
+ <% if (guide.image_url && guide.image_url.trim()) { %> + <%= guide.name %> + <% } else { %> + <%= guide.name %> + <% } %> + +
+
<%= guide.name %>
+ +
+ <% if (guide.specialization === 'city') { %> + + Городские туры + + <% } else if (guide.specialization === 'mountain') { %> + + Горные походы + + <% } else if (guide.specialization === 'fishing') { %> + + Рыбалка + + <% } %> +
+ + <% if (guide.bio) { %> +

<%= truncateText(guide.bio, 120) %>

+ <% } %> + +
+ <% if (guide.languages) { %> +
+ + + <%= guide.languages %> + +
+ <% } %> + + <% if (guide.avg_rating) { %> +
+ <%- generateStars(guide.avg_rating) %> + (<%= parseFloat(guide.avg_rating).toFixed(1) %>) +
+ <% } %> + +
+
+ + <%= guide.experience %> лет опыта + +
+
+ + <%= guide.route_count || 0 %> туров + +
+
+ + + Подробнее + +
+
+
+
+ <% }); %> +
+ <% } %> +
+
+ + +
+
+

Нужен индивидуальный подход?

+

Наши гиды готовы создать уникальный маршрут специально для вас!

+ + Связаться с нами + +
+
\ No newline at end of file diff --git a/views/guides/profile.ejs b/views/guides/profile.ejs new file mode 100644 index 0000000..a555187 --- /dev/null +++ b/views/guides/profile.ejs @@ -0,0 +1,260 @@ + +
+
+
+
+ <% if (guide.image_url && guide.image_url.trim()) { %> + <%= guide.name %> + <% } else { %> + <%= guide.name %> + <% } %> +
+
+

<%= guide.name %>

+ +
+ <% if (guide.specialization === 'city') { %> + + Городские туры + + <% } else if (guide.specialization === 'mountain') { %> + + Горные походы + + <% } else if (guide.specialization === 'fishing') { %> + + Рыбалка + + <% } %> + + + + <%= guide.experience %> лет опыта + +
+ + <% if (guide.languages) { %> +

+ Языки: <%= guide.languages %> +

+ <% } %> + + <% if (guide.avg_rating) { %> +
+ Рейтинг: + <%- generateStars(guide.avg_rating) %> + (<%= parseFloat(guide.avg_rating).toFixed(1) %>/5) + <% if (guide.review_count) { %> + <%= guide.review_count %> отзывов + <% } %> +
+ <% } %> + +
+
+

+ + <%= guide.route_count || 0 %> туров +

+
+ <% if (guide.phone) { %> +
+

+ + <%= guide.phone %> +

+
+ <% } %> +
+
+
+
+
+ + +
+
+
+
+ + <% if (guide.bio) { %> +
+
+

+ О гиде +

+
+
+

<%= guide.bio %>

+
+
+ <% } %> + + +
+
+

+ Туры с этим гидом +

+
+
+ <% if (routes && routes.length > 0) { %> +
+ <% routes.forEach(route => { %> +
+
+ <% + let placeholderImage = '/images/placeholder.jpg'; + if (route.type === 'city') { + placeholderImage = '/images/city-tour-placeholder.webp'; + } else if (route.type === 'mountain') { + placeholderImage = '/images/mountain-placeholder.jpg'; + } else if (route.type === 'fishing') { + placeholderImage = '/images/fish-placeholder.jpg'; + } + %> + <% if (route.image_url && route.image_url.trim()) { %> + <%= route.title %> + <% } else { %> + <%= route.title %> + <% } %> +
+
<%= route.title %>
+

<%= truncateText(route.description, 80) %>

+
+ ₩<%= formatCurrency(route.price) %> + <%= route.duration %> дн. +
+ + Подробнее + +
+
+
+ <% }); %> +
+ <% } else { %> +

В данный момент у этого гида нет активных туров.

+ <% } %> +
+
+ + +
+
+

+ Отзывы +

+
+
+ <% if (reviews && reviews.length > 0) { %> + <% reviews.forEach(review => { %> +
+
+
+
<%= review.author_name %>
+ <% if (review.route_title) { %> + Тур: <%= review.route_title %> + <% } %> +
+
+ <% if (review.rating) { %> +
+ <%- generateStars(review.rating) %> +
+ <% } %> + <%= formatDateShort(review.created_at) %> +
+
+

<%= review.comment %>

+
+ <% }); %> + <% } else { %> +

Пока нет отзывов об этом гиде.

+ <% } %> +
+
+
+ + +
+ +
+
+
+ Контактная информация +
+
+
+ <% if (guide.phone) { %> +

+ + <%= guide.phone %> +

+ <% } %> + + <% if (guide.email) { %> +

+ + <%= guide.email %> +

+ <% } %> + + <% if (guide.languages) { %> +

+ + <%= guide.languages %> +

+ <% } %> + + + Связаться с гидом + +
+
+ + +
+
+
+ Статистика +
+
+
+
+
+
+

<%= guide.route_count || 0 %>

+ Туров +
+
+
+

<%= guide.experience %>

+ Лет опыта +
+
+ <% if (guide.avg_rating) { %> +
+

<%= parseFloat(guide.avg_rating).toFixed(1) %>

+ Средний рейтинг +
+ <% } %> +
+
+ + + +
+
+
+
\ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs new file mode 100644 index 0000000..7a66039 --- /dev/null +++ b/views/index.ejs @@ -0,0 +1,283 @@ + +
+
+
+
+
+
+

+ Откройте красоту + Кореи +

+

+ Погрузитесь в аутентичную корейскую культуру, насладитесь захватывающими пейзажами и + незабываемыми приключениями с нашими экспертными местными гидами. +

+ +
+
+
+ Красивая Корея +
+
+
+
+
+ +
+
+
1000+ довольных туристов
+ Присоединяйтесь к нам +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+

Найдите своё идеальное корейское приключение

+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+
+
+
+ +
+

50+

+

Unique Tours

+
+
+
+
+
+ +
+

<%= guideStats.total_guides || 0 %>

+

Экспертные гиды

+
+
+
+
+
+ +
+

1000+

+

Happy Travelers

+
+
+
+
+
+ +
+

4.9

+

Average Rating

+
+
+
+
+
+ + +
+
+
+

Полезная информация о путешествиях

+

Tips, guides, and stories from Korea

+
+ +
+ <% if (recentArticles && recentArticles.length > 0) { %> + <% recentArticles.forEach(function(article, index) { %> +
+ +
+ <% }); %> + <% } else { %> +
+

No articles available at the moment.

+ Browse All Articles +
+ <% } %> +
+ + +
+
+ + +
+
+
+
+

Готовы к вашему корейскому приключению?

+

+ Join thousands of travelers who have discovered the magic of Korea with our expert guides. +

+
+ +
+
+
\ No newline at end of file diff --git a/views/layout.ejs b/views/layout.ejs new file mode 100644 index 0000000..05b146d --- /dev/null +++ b/views/layout.ejs @@ -0,0 +1,205 @@ + + + + + + <%= title || siteName %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ <%- body %> +
+ + + + + + + + + + + + + + + + + + + + <% if (typeof pageScripts !== 'undefined') { %> + <%- pageScripts %> + <% } %> + + \ No newline at end of file diff --git a/views/routes/detail.ejs b/views/routes/detail.ejs new file mode 100644 index 0000000..8b68c45 --- /dev/null +++ b/views/routes/detail.ejs @@ -0,0 +1,289 @@ + +
+
+
+
+

<%= route.title %>

+

<%= route.description %>

+ +
+ <% if (route.type === 'city') { %> + + Городской тур + + <% } else if (route.type === 'mountain') { %> + + Горный поход + + <% } else if (route.type === 'fishing') { %> + + Рыбалка + + <% } %> + + + <%= route.difficulty_level === 'easy' ? 'Легко' : route.difficulty_level === 'moderate' ? 'Средне' : 'Сложно' %> + +
+
+
+
+

Детали тура

+
+ Цена + ₩<%= formatCurrency(route.price) %> +
+
+ Продолжительность + <%= route.duration %> дней +
+
+ Максимум участников + <%= route.max_group_size %> человек +
+ + Забронировать + +
+
+
+
+
+ +
+
+
+ + <% + let placeholderImage = '/images/placeholder.jpg'; + if (route.type === 'city') { + placeholderImage = '/images/city-tour-placeholder.webp'; + } else if (route.type === 'mountain') { + placeholderImage = '/images/mountain-placeholder.jpg'; + } else if (route.type === 'fishing') { + placeholderImage = '/images/fish-placeholder.jpg'; + } + %> + <% if (route.image_url && route.image_url.trim()) { %> + <%= route.title %> + <% } else { %> + <%= route.title %> + <% } %> + + +
+

Описание маршрута

+
+ <%= route.content || 'Подробное описание маршрута будет доступно в ближайшее время.' %> +
+
+ + + <% if (route.included_services && route.included_services.length > 0) { %> +
+

Что включено

+
+ <% route.included_services.forEach(service => { %> +
+ <%= service %> +
+ <% }); %> +
+
+ <% } %> + + +
+

Отзывы (<%= reviews.length %>)

+ + +
+
+
Оставить отзыв
+
+
+
+ + <% if (route.guide_id) { %> + + <% } %> + +
+
+ + +
+
+ + +
+
+ +
+ +
+ + + + + + + + + + +
+
+ +
+ + +
+ + +
+
+
+ + + <% if (reviews.length === 0) { %> +

Пока нет отзывов об этом туре. Будьте первым!

+ <% } else { %> + <% reviews.forEach(review => { %> +
+
+
+
<%= review.customer_name %>
+
+ <%- generateStars(review.rating) %> +
+
+

<%= review.comment %>

+ <%= formatDate(review.created_at) %> +
+
+ <% }); %> + <% } %> +
+
+ + +
+ + <% if (route.guide_name) { %> +
+
+
Ваш гид
+ <% if (route.guide_image) { %> + <%= route.guide_name %> + <% } %> +
<%= route.guide_name %>
+ <% if (route.guide_bio) { %> +

<%= route.guide_bio %>

+ <% } %> +
+
+ <% } %> + + +
+
+
Нужна помощь?
+

Свяжитесь с нами для получения дополнительной информации о туре.

+
+ Телефон + +7 (495) 123-45-67 +
+
+ Email + info@koreatour.ru +
+ + Написать нам + +
+
+
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/views/routes/index.ejs b/views/routes/index.ejs new file mode 100644 index 0000000..1232356 --- /dev/null +++ b/views/routes/index.ejs @@ -0,0 +1,131 @@ + +
+
+
+
+

Туры по Корее

+

Откройте для себя удивительную Корею с нашими профессиональными гидами

+ + + +
+
+
+
+ + +
+
+ <% if (routes.length === 0) { %> +
+ +

Туры не найдены

+

В данной категории пока нет доступных туров.

+ Посмотреть все туры +
+ <% } else { %> +
+ <% routes.forEach(route => { %> +
+
+ <% + let placeholderImage = '/images/placeholder.jpg'; + if (route.type === 'city') { + placeholderImage = '/images/city-tour-placeholder.webp'; + } else if (route.type === 'mountain') { + placeholderImage = '/images/mountain-placeholder.jpg'; + } else if (route.type === 'fishing') { + placeholderImage = '/images/fish-placeholder.jpg'; + } + %> + <% if (route.image_url && route.image_url.trim()) { %> + <%= route.title %> + <% } else { %> + <%= route.title %> + <% } %> + + + <% if (route.is_featured) { %> + + Рекомендуем + + <% } %> + +
+
+ <% if (route.type === 'city') { %> + + Городской тур + + <% } else if (route.type === 'mountain') { %> + + Горный поход + + <% } else if (route.type === 'fishing') { %> + + Рыбалка + + <% } %> + + <% if (route.difficulty_level) { %> + + <%= route.difficulty_level === 'easy' ? 'Легко' : route.difficulty_level === 'moderate' ? 'Средне' : 'Сложно' %> + + <% } %> +
+ +
<%= route.title %>
+

<%= route.description %>

+ +
+
+
+ ₩<%= formatCurrency(route.price) %> +
+ + <%= route.duration %> дн. + +
+ + <% if (route.guide_name) { %> +

+ Гид: <%= route.guide_name %> +

+ <% } %> + + + Подробнее + +
+
+
+
+ <% }); %> +
+ <% } %> +
+
+ + +
+
+

Не нашли подходящий тур?

+

Мы можем организовать индивидуальный тур специально для вас!

+ + Свяжитесь с нами + +
+
\ No newline at end of file