diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..af60e5c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,37 @@ +# Node modules +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Базы данных +*.db +*.db-journal + +# Логи +logs/ +*.log + +# Временные файлы +.env.local +.env.development.local +.env.test.local +.env.production.local + +# OS файлы +.DS_Store +Thumbs.db + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Docker volumes +data/ + +# Временные файлы разработки +.tmp/ +temp/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3e02316 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Environment Configuration +# Для production используйте docker-compose.yml + +# Порт сервера +PORT=3336 + +# Режим работы +NODE_ENV=production + +# База данных (путь относительно корня проекта) +# DB_PATH=./data/poker.db + +# Настройки JWT (опционально, генерируются автоматически) +# JWT_SECRET=your-secret-key-here +# JWT_EXPIRES_IN=7d diff --git a/.gitignore b/.gitignore index dbf0821..ca6ff4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,56 @@ -node_modules/* \ No newline at end of file +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json + +# Database +*.db +*.db-journal +data/ + +# Logs +logs/ +*.log + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# OS files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +Desktop.ini + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*.swn +*~ +.project +.classpath +.settings/ + +# Docker volumes +data/ +logs/ + +# Temporary files +.tmp/ +temp/ +tmp/ + +# Build files +dist/ +build/ diff --git a/ADMIN_USER_MANAGEMENT.md b/ADMIN_USER_MANAGEMENT.md new file mode 100644 index 0000000..19645d2 --- /dev/null +++ b/ADMIN_USER_MANAGEMENT.md @@ -0,0 +1,411 @@ +# Управление пользователями (Админ-панель) + +## Обзор + +Добавлена полноценная система управления пользователями в админ-панели с возможностью просмотра статистики, фильтрации, редактирования, блокировки и удаления пользователей. + +## Новые возможности + +### 📊 Статистика пользователей +- **Всего пользователей** — общее количество зарегистрированных +- **Активных (сегодня)** — пользователи с входом сегодня +- **Заблокировано** — количество заблокированных аккаунтов +- **Админов** — количество администраторов + +### 🔍 Фильтры +- **По роли**: Все / Админы / Пользователи +- **По статусу**: Все / Активные / Заблокированные +- **Поиск по логину**: Динамический поиск + +### 📋 Таблица пользователей + +Отображаемые данные: +- **Логин** — имя пользователя (с пометкой "Вы" для текущего) +- **Роль** — 👑 Админ или 👤 Пользователь +- **IP адрес** — последний известный IP +- **Последний вход** — относительное время ("2 ч назад", "Вчера") +- **Статус** — ✅ Активен или 🚫 Заблокирован +- **Действия** — кнопки управления + +### ⚙️ Действия с пользователями + +1. **✏️ Редактировать** + - Изменить роль (Админ/Пользователь) + - Изменить статус (Активен/Заблокирован) + - Сменить пароль (опционально) + +2. **🚫/✅ Заблокировать/Разблокировать** + - Быстрая блокировка/разблокировка + - Заблокированные пользователи не могут войти + +3. **🗑️ Удалить** + - Полное удаление пользователя + - Требует двойного подтверждения + - Нельзя удалить себя + +## Использование + +### Открыть вкладку "Пользователи" + +1. Войдите как администратор (`admin` / `admin`) +2. Откройте **Админ-панель** (меню → ⚙️ Админ) +3. Выберите вкладку **👥 Пользователи** + +### Просмотр и фильтрация + +``` +┌─────────────────────────────────────────────┐ +│ [Все роли ▼] [Все статусы ▼] [🔍 Поиск...] │ +└─────────────────────────────────────────────┘ +``` + +- Выберите фильтры в выпадающих списках +- Введите текст в поле поиска для фильтрации по логину +- Результаты обновляются автоматически + +### Редактирование пользователя + +1. Нажмите **✏️** напротив пользователя +2. Откроется модальное окно: + ``` + ┌─────────────────────────────────┐ + │ ✏️ Редактирование пользователя │ + ├─────────────────────────────────┤ + │ Логин: [username] (readonly) │ + │ Роль: [Пользователь ▼] │ + │ Статус: [Активен ▼] │ + │ Новый пароль: [________] │ + │ │ + │ [💾 Сохранить] [❌ Отмена] │ + └─────────────────────────────────┘ + ``` +3. Внесите изменения +4. Нажмите **💾 Сохранить** + +### Блокировка пользователя + +**Быстрая блокировка:** +- Нажмите **🚫** напротив пользователя +- Подтвердите действие +- Пользователь заблокирован + +**Эффект:** +- Пользователь не сможет войти в систему +- При попытке входа: "Ваш аккаунт заблокирован" +- Статус в таблице: 🚫 Заблокирован + +**Разблокировка:** +- Нажмите **✅** напротив заблокированного пользователя +- Подтвердите действие + +### Удаление пользователя + +⚠️ **ВНИМАНИЕ: Действие необратимо!** + +1. Нажмите **🗑️** напротив пользователя +2. Подтвердите удаление (1-е окно) +3. Подтвердите ещё раз (2-е окно для безопасности) +4. Пользователь удалён полностью + +**Что удаляется:** +- Запись пользователя из БД +- Настройки пользователя +- История игр сохраняется для статистики + +**Ограничения:** +- Нельзя удалить себя +- Нельзя удалить единственного админа (рекомендуется) + +## API Endpoints + +### POST `/api/admin/users` + +Получить список пользователей с фильтрами. + +**Headers:** +``` +Authorization: Bearer +Content-Type: application/json +``` + +**Body:** +```json +{ + "role": "admin|user|", + "status": "active|banned|", + "search": "логин для поиска" +} +``` + +**Response:** +```json +{ + "users": [ + { + "id": "uuid", + "username": "admin", + "role": "admin", + "banned": 0, + "ipAddress": "127.0.0.1", + "lastLogin": "2026-02-01T12:00:00Z", + "createdAt": "2026-01-01T00:00:00Z", + "totalGames": 42, + "totalWins": 18, + "totalChipsWon": 15000 + } + ], + "stats": { + "total": 10, + "active": 3, + "banned": 1, + "admins": 2 + } +} +``` + +### POST `/api/admin/user/update` + +Обновить данные пользователя. + +**Body:** +```json +{ + "username": "user123", + "role": "admin", + "banned": false, + "password": "новый_пароль" // опционально +} +``` + +**Response:** +```json +{ + "success": true +} +``` + +### POST `/api/admin/user/ban` + +Заблокировать/разблокировать пользователя. + +**Body:** +```json +{ + "username": "user123", + "banned": true +} +``` + +**Response:** +```json +{ + "success": true +} +``` + +### POST `/api/admin/user/delete` + +Удалить пользователя. + +**Body:** +```json +{ + "username": "user123" +} +``` + +**Response:** +```json +{ + "success": true +} +``` + +**Errors:** +- `403` — Нельзя удалить себя +- `404` — Пользователь не найден +- `500` — Ошибка сервера + +## База данных + +### Новые поля в таблице `users` + +```sql +ALTER TABLE users ADD COLUMN banned INTEGER DEFAULT 0; +ALTER TABLE users ADD COLUMN ip_address TEXT; +``` + +**Описание:** +- `banned` — флаг блокировки (0 = активен, 1 = заблокирован) +- `ip_address` — последний известный IP адрес пользователя + +### Миграция + +Поля добавляются автоматически при запуске сервера, если их нет: + +``` +✅ Добавлено поле banned в таблицу users +✅ Добавлено поле ip_address в таблицу users +``` + +## Безопасность + +### Проверки на сервере + +1. **Авторизация**: Только админы имеют доступ к API +2. **Валидация**: Проверка наличия обязательных полей +3. **Защита от самоблокировки**: Нельзя заблокировать/удалить себя +4. **Проверка при входе**: Блокированные пользователи не могут войти + +### Логирование + +Все административные действия логируются: + +```javascript +database.logAction(adminId, adminUsername, 'update_user', { + target: 'user123', + changes: { role: 'admin', banned: true } +}); +``` + +**Типы действий:** +- `view_users` — просмотр списка пользователей +- `update_user` — изменение данных пользователя +- `ban_user` — блокировка пользователя +- `unban_user` — разблокировка пользователя +- `delete_user` — удаление пользователя + +## Интерфейс + +### Цветовая индикация + +**Роли:** +- 👑 **Админ** — фиолетовый градиент +- 👤 **Пользователь** — серый фон + +**Статусы:** +- ✅ **Активен** — зелёный фон +- 🚫 **Заблокирован** — красный фон + +### Адаптивность + +- **Desktop (>1024px)**: Полная таблица +- **Tablet (768-1024px)**: Компактные столбцы +- **Mobile (<768px)**: Горизонтальная прокрутка таблицы + +### Анимации + +- Плавное появление вкладки (fadeInUp 0.3s) +- Hover эффекты на строках таблицы +- Увеличение кнопок при наведении (scale 1.1) + +## Примеры использования + +### Создать нового админа + +1. Зарегистрируйте пользователя обычным способом +2. Войдите как админ +3. Откройте вкладку **👥 Пользователи** +4. Найдите нужного пользователя +5. Нажмите **✏️ Редактировать** +6. Измените роль на **👑 Администратор** +7. Сохраните + +### Заблокировать спамера + +1. Откройте вкладку **👥 Пользователи** +2. Найдите спамера в списке или через поиск +3. Нажмите **🚫 Заблокировать** +4. Подтвердите действие +5. Пользователь заблокирован + +### Посмотреть активных сегодня + +1. Откройте вкладку **👥 Пользователи** +2. В верхней панели статистики смотрите "Активных (сегодня)" +3. Или используйте фильтр и отсортируйте по "Последний вход" + +### Сбросить пароль пользователю + +1. Откройте вкладку **👥 Пользователи** +2. Найдите пользователя +3. Нажмите **✏️ Редактировать** +4. Введите новый пароль в поле "Новый пароль" +5. Нажмите **💾 Сохранить** +6. Сообщите пользователю новый пароль + +## Ограничения и рекомендации + +### Ограничения + +- ❌ Нельзя редактировать/удалять себя +- ❌ Нельзя заблокировать себя +- ❌ Удаление необратимо (нет корзины) + +### Рекомендации + +- ✅ Всегда имейте минимум 2 админов +- ✅ Используйте блокировку вместо удаления (можно отменить) +- ✅ Регулярно проверяйте список активных пользователей +- ✅ Следите за IP адресами для выявления дубликатов +- ✅ Логируйте важные действия через вкладку "📋 Логи" + +## Отладка + +### Проверка прав доступа + +```javascript +// В консоли браузера +console.log(currentUser?.role); // Должно быть "admin" +``` + +### Проверка токена + +```javascript +// В консоли браузера +console.log(localStorage.getItem('token')); +``` + +### Проверка API + +```bash +# Получить список пользователей +curl -X POST http://localhost:3000/api/admin/users \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"role":"","status":"","search":""}' +``` + +### Логи действий + +Все административные действия записываются в таблицу `action_logs`: + +```sql +SELECT * FROM action_logs +WHERE action_type IN ('update_user', 'ban_user', 'delete_user') +ORDER BY timestamp DESC +LIMIT 20; +``` + +## Обновления + +**Версия 1.0** (2026-02-01) +- ✅ Базовое управление пользователями +- ✅ Фильтры и поиск +- ✅ Редактирование, блокировка, удаление +- ✅ Статистика пользователей +- ✅ Отслеживание IP адресов +- ✅ Проверка блокировки при входе + +**Планируется:** +- 📋 Экспорт списка пользователей в CSV +- 📊 Расширенная статистика по пользователям +- 🔍 Фильтр по дате регистрации +- 📧 Отправка уведомлений пользователям +- 🔐 Двухфакторная аутентификация для админов + +--- + +**Документация актуальна для версии**: 1.0 +**Дата**: 2026-02-01 +**Автор**: GitHub Copilot diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..b6edabd --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,439 @@ + + ServerName poker.yourdomain.com + ServerAdmin admin@yourdomain.com + + # Логи + ErrorLog ${APACHE_LOG_DIR}/poker_error.log + CustomLog ${APACHE_LOG_DIR}/poker_access.log combined + + # Проксирование на Docker контейнер + ProxyPreserveHost On + ProxyPass / http://localhost:3336/ + ProxyPassReverse / http://localhost:3336/ + + # WebSocket поддержка + RewriteEngine On + RewriteCond %{HTTP:Upgrade} =websocket [NC] + RewriteRule /(.*) ws://localhost:3336/$1 [P,L] + RewriteCond %{HTTP:Upgrade} !=websocket [NC] + RewriteRule /(.*) http://localhost:3336/$1 [P,L] + + # Заголовки для WebSocket + + ProxyPass http://localhost:3336/ + ProxyPassReverse http://localhost:3336/ + ProxyPreserveHost On + + # WebSocket headers + RequestHeader set X-Forwarded-Proto "http" + RequestHeader set X-Forwarded-Port "80" + + + +# SSL версия (если используете HTTPS) + + + ServerName poker.yourdomain.com + ServerAdmin admin@yourdomain.com + + # SSL сертификаты + SSLEngine on + SSLCertificateFile /etc/ssl/certs/poker.crt + SSLCertificateKeyFile /etc/ssl/private/poker.key + # SSLCertificateChainFile /etc/ssl/certs/chain.pem + + # Логи + ErrorLog ${APACHE_LOG_DIR}/poker_ssl_error.log + CustomLog ${APACHE_LOG_DIR}/poker_ssl_access.log combined + + # Проксирование на Docker контейнер + ProxyPreserveHost On + ProxyPass / http://localhost:3336/ + ProxyPassReverse / http://localhost:3336/ + + # WebSocket поддержка + RewriteEngine On + RewriteCond %{HTTP:Upgrade} =websocket [NC] + RewriteRule /(.*) ws://localhost:3336/$1 [P,L] + RewriteCond %{HTTP:Upgrade} !=websocket [NC] + RewriteRule /(.*) http://localhost:3336/$1 [P,L] + + # Заголовки для WebSocket + + ProxyPass http://localhost:3336/ + ProxyPassReverse http://localhost:3336/ + ProxyPreserveHost On + + # WebSocket headers + RequestHeader set X-Forwarded-Proto "https" + RequestHeader set X-Forwarded-Port "443" + + + # Безопасность + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + Header always set X-Frame-Options "SAMEORIGIN" + Header always set X-Content-Type-Options "nosniff" + + +# 🃏 Texas Hold'em Poker - Развертывание в Docker + +## 📋 Содержание + +- [Быстрый старт](#быстрый-старт) +- [Развертывание с Docker](#развертывание-с-docker) +- [Настройка Apache](#настройка-apache) +- [Переменные окружения](#переменные-окружения) +- [Backup и восстановление](#backup-и-восстановление) + +--- + +## 🚀 Быстрый старт + +### Запуск через Docker Compose (рекомендуется) + +```bash +# Клонируйте репозиторий +git clone +cd onlinepocker + +# Запустите контейнер +docker-compose up -d + +# Проверьте статус +docker-compose ps + +# Просмотр логов +docker-compose logs -f +``` + +Приложение будет доступно по адресу: `http://localhost:3336` + +--- + +## 🐳 Развертывание с Docker + +### Сборка Docker образа вручную + +```bash +# Сборка образа +docker build -t texas-holdem-poker:latest . + +# Запуск контейнера +docker run -d \ + --name poker-app \ + -p 3336:3336 \ + -v $(pwd)/data:/app/data \ + -e NODE_ENV=production \ + -e PORT=3336 \ + texas-holdem-poker:latest + +# Проверка работы +docker ps +docker logs poker-app +``` + +### Управление контейнером + +```bash +# Остановка +docker stop poker-app + +# Запуск +docker start poker-app + +# Перезапуск +docker restart poker-app + +# Удаление +docker stop poker-app +docker rm poker-app + +# Просмотр логов +docker logs -f poker-app +``` + +### Docker Compose команды + +```bash +# Запуск в фоне +docker-compose up -d + +# Остановка +docker-compose down + +# Перезапуск +docker-compose restart + +# Пересборка образа +docker-compose build + +# Пересборка и запуск +docker-compose up -d --build + +# Просмотр логов +docker-compose logs -f + +# Остановка с удалением volumes +docker-compose down -v +``` + +--- + +## 🌐 Настройка Apache + +### Включение необходимых модулей Apache + +```bash +# Включаем модули для проксирования и WebSocket +sudo a2enmod proxy +sudo a2enmod proxy_http +sudo a2enmod proxy_wstunnel +sudo a2enmod rewrite +sudo a2enmod headers +sudo a2enmod ssl # Если используете HTTPS + +# Перезапуск Apache +sudo systemctl restart apache2 +``` + +### Копирование конфигурации + +```bash +# Копируем конфиг в Apache +sudo cp apache-config.conf /etc/apache2/sites-available/poker.conf + +# Редактируем домен (замените poker.yourdomain.com на ваш домен) +sudo nano /etc/apache2/sites-available/poker.conf + +# Включаем сайт +sudo a2ensite poker.conf + +# Проверяем конфигурацию +sudo apache2ctl configtest + +# Перезагружаем Apache +sudo systemctl reload apache2 +``` + +### Проверка статуса Apache + +```bash +# Проверка статуса +sudo systemctl status apache2 + +# Просмотр логов ошибок +sudo tail -f /var/log/apache2/poker_error.log + +# Просмотр логов доступа +sudo tail -f /var/log/apache2/poker_access.log +``` + +--- + +## ⚙️ Переменные окружения + +### Основные переменные + +| Переменная | Значение по умолчанию | Описание | +|------------|----------------------|----------| +| `PORT` | 3336 | Порт для запуска сервера | +| `NODE_ENV` | production | Режим работы (production/development) | + +### Настройка в docker-compose.yml + +```yaml +environment: + - NODE_ENV=production + - PORT=3336 +``` + +### Настройка в Dockerfile + +```dockerfile +ENV NODE_ENV=production +ENV PORT=3336 +``` + +--- + +## 📦 Структура проекта после развертывания + +``` +onlinepocker/ +├── data/ # База данных (создается автоматически) +│ └── poker.db +├── logs/ # Логи приложения (опционально) +├── public/ # Статические файлы +├── node_modules/ # Зависимости +├── server.js # Основной сервер +├── database.js # Работа с БД +├── package.json +├── Dockerfile # Docker образ +├── docker-compose.yml # Docker Compose конфигурация +├── .dockerignore # Исключения для Docker +└── apache-config.conf # Конфигурация Apache +``` + +--- + +## 💾 Backup и восстановление + +### Создание резервной копии базы данных + +```bash +# Остановка контейнера +docker-compose stop + +# Копирование БД +cp data/poker.db data/poker.db.backup.$(date +%Y%m%d_%H%M%S) + +# Запуск контейнера +docker-compose start +``` + +### Восстановление из резервной копии + +```bash +# Остановка контейнера +docker-compose stop + +# Восстановление БД +cp data/poker.db.backup.20260201_120000 data/poker.db + +# Запуск контейнера +docker-compose start +``` + +### Автоматический backup (cron) + +```bash +# Создаем скрипт backup +nano /opt/poker-backup.sh +``` + +```bash +#!/bin/bash +BACKUP_DIR="/opt/poker-backups" +DATE=$(date +%Y%m%d_%H%M%S) + +# Создаем директорию для backup +mkdir -p $BACKUP_DIR + +# Копируем БД +docker cp texas-holdem-poker:/app/data/poker.db $BACKUP_DIR/poker.db.$DATE + +# Удаляем старые backup (старше 30 дней) +find $BACKUP_DIR -name "poker.db.*" -mtime +30 -delete + +echo "Backup создан: poker.db.$DATE" +``` + +```bash +# Делаем скрипт исполняемым +chmod +x /opt/poker-backup.sh + +# Добавляем в cron (каждый день в 3:00) +crontab -e +# Добавьте строку: +0 3 * * * /opt/poker-backup.sh >> /var/log/poker-backup.log 2>&1 +``` + +--- + +## 🔧 Troubleshooting + +### Проблемы с портами + +```bash +# Проверка, не занят ли порт 3336 +sudo netstat -tlnp | grep 3336 + +# Если порт занят, найти процесс +sudo lsof -i :3336 + +# Убить процесс +sudo kill -9 +``` + +### Проблемы с WebSocket + +```bash +# Проверка модулей Apache +apache2ctl -M | grep proxy + +# Должны быть включены: +# - proxy_module +# - proxy_http_module +# - proxy_wstunnel_module +``` + +### Проблемы с правами доступа + +```bash +# Установка правильных прав на директорию data +sudo chown -R www-data:www-data data/ +sudo chmod 755 data/ + +# Для Docker +sudo chown -R 1000:1000 data/ +``` + +### Просмотр логов контейнера + +```bash +# Все логи +docker logs texas-holdem-poker + +# Последние 100 строк +docker logs --tail 100 texas-holdem-poker + +# В реальном времени +docker logs -f texas-holdem-poker +``` + +--- + +## 🔐 Безопасность + +### Рекомендации + +1. **Используйте HTTPS** - настройте SSL сертификаты (Let's Encrypt) +2. **Firewall** - ограничьте доступ к порту 3336 только с localhost +3. **Регулярные обновления** - обновляйте зависимости и Docker образы +4. **Backup** - настройте автоматическое резервное копирование +5. **Мониторинг** - отслеживайте логи и производительность + +### Настройка Let's Encrypt (опционально) + +```bash +# Установка certbot +sudo apt-get install certbot python3-certbot-apache + +# Получение сертификата +sudo certbot --apache -d poker.yourdomain.com + +# Автоматическое обновление +sudo certbot renew --dry-run +``` + +--- + +## 📞 Поддержка + +Если возникли проблемы: + +1. Проверьте логи: `docker-compose logs -f` +2. Проверьте статус: `docker-compose ps` +3. Проверьте Apache: `sudo systemctl status apache2` +4. Проверьте логи Apache: `sudo tail -f /var/log/apache2/poker_error.log` + +--- + +## 📝 Лицензия + +Этот проект предназначен для личного использования. + +--- + +**Версия:** 1.0.0 +**Дата обновления:** 2026-02-01 diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 0000000..23c9d5f --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,133 @@ +# 🐳 Docker Quick Start + +## Быстрый запуск + +### Windows (PowerShell) +```powershell +.\deploy.ps1 +``` + +### Linux/Mac (Bash) +```bash +chmod +x deploy.sh +./deploy.sh +``` + +### Ручной запуск +```bash +# Сборка и запуск +docker-compose up -d + +# Просмотр логов +docker-compose logs -f + +# Остановка +docker-compose down +``` + +## Доступ к приложению + +После запуска приложение будет доступно по адресу: +- **http://localhost:3336** + +## Полезные команды + +```bash +# Просмотр статуса +docker-compose ps + +# Просмотр логов +docker-compose logs -f + +# Перезапуск +docker-compose restart + +# Остановка +docker-compose stop + +# Запуск после остановки +docker-compose start + +# Полная остановка с удалением контейнеров +docker-compose down + +# Пересборка образа +docker-compose up -d --build +``` + +## NPM скрипты + +```bash +# Сборка Docker образа +npm run docker:build + +# Запуск контейнера +npm run docker:up + +# Остановка контейнера +npm run docker:down + +# Просмотр логов +npm run docker:logs + +# Перезапуск +npm run docker:restart +``` + +## Структура данных + +``` +data/ # База данных SQLite (автоматически создается) +logs/ # Логи приложения (опционально) +``` + +## Настройка Apache + +Подробная инструкция по настройке Apache для проксирования находится в файле `DEPLOYMENT.md`. + +Основные шаги: +1. Включить модули Apache: `proxy`, `proxy_http`, `proxy_wstunnel`, `rewrite` +2. Скопировать `apache-config.conf` в `/etc/apache2/sites-available/` +3. Включить сайт: `sudo a2ensite poker.conf` +4. Перезагрузить Apache: `sudo systemctl reload apache2` + +## Порты + +- **Внутренний порт контейнера**: 3336 +- **Внешний порт**: 3336 +- **Apache проксирует** запросы на localhost:3336 + +## Troubleshooting + +### Порт занят +```bash +# Проверка занятости порта +netstat -ano | findstr :3336 # Windows +lsof -i :3336 # Linux/Mac + +# Остановка существующего контейнера +docker-compose down +``` + +### Проблемы с правами доступа (Linux) +```bash +sudo chown -R $USER:$USER data/ +sudo chmod 755 data/ +``` + +### Просмотр детальных логов +```bash +docker-compose logs --tail=100 poker-app +``` + +## Документация + +- **Полная инструкция по развертыванию**: `DEPLOYMENT.md` +- **Настройка системных промптов ботов**: `BOT_PERSONALITIES_CONFIG.md` +- **Управление пользователями**: `ADMIN_USER_MANAGEMENT.md` + +--- + +**Версия**: 1.0.0 +**Порт**: 3336 +**Docker**: ✅ Готово к использованию diff --git a/DOCKER_SETUP_SUMMARY.md b/DOCKER_SETUP_SUMMARY.md new file mode 100644 index 0000000..c871f1c --- /dev/null +++ b/DOCKER_SETUP_SUMMARY.md @@ -0,0 +1,281 @@ +# 🐳 Подготовка к Docker развертыванию - Сводка изменений + +## ✅ Созданные файлы + +### Docker конфигурация + +1. **`Dockerfile`** + - Основан на Node.js 18 Alpine + - Порт: 3336 + - Автоматическая установка зависимостей + - Production режим + +2. **`docker-compose.yml`** + - Сервис: poker-app + - Порт маппинг: 3336:3336 + - Volumes для данных и логов + - Автоматический рестарт + +3. **`.dockerignore`** + - Исключение node_modules + - Исключение баз данных + - Исключение логов и временных файлов + +### Apache конфигурация + +4. **`apache-config.conf`** + - HTTP (порт 80) → localhost:3336 + - HTTPS (порт 443) → localhost:3336 + - WebSocket поддержка (proxy_wstunnel) + - Заголовки безопасности + - Готовые настройки SSL + +### Скрипты развертывания + +5. **`deploy.sh`** (Linux/Mac) + - Проверка Docker и Docker Compose + - Создание директорий + - Автоматическая сборка и запуск + - Проверка статуса + +6. **`deploy.ps1`** (Windows PowerShell) + - Аналог deploy.sh для Windows + - Цветной вывод + - Проверка всех зависимостей + +### Документация + +7. **`DEPLOYMENT.md`** + - Полное руководство по развертыванию + - Инструкции для Docker + - Настройка Apache + - Backup и восстановление + - Troubleshooting + - Безопасность + +8. **`DOCKER_README.md`** + - Краткая инструкция по Docker + - Основные команды + - NPM скрипты + - Quick start + +9. **`README.md`** + - Обновленный главный README + - Информация о Docker + - Полный список функций + - Структура проекта + - Troubleshooting + +### Дополнительные файлы + +10. **`.env.example`** + - Пример переменных окружения + - PORT=3336 + - NODE_ENV=production + +11. **`.gitignore`** + - Обновлен для Docker + - Исключение data/ и logs/ + - Исключение .env файлов + +--- + +## 🔧 Измененные файлы + +### 1. `package.json` +**Добавлены npm скрипты:** +```json +"docker:build": "docker-compose build", +"docker:up": "docker-compose up -d", +"docker:down": "docker-compose down", +"docker:logs": "docker-compose logs -f", +"docker:restart": "docker-compose restart" +``` + +### 2. `public/main.js` +**Исправлена работа с токенами:** +- Изменено `localStorage.getItem('token')` на `localStorage.getItem('authToken')` +- Синхронизировано с auth.js +- Исправлены функции: loadUsers(), saveUserEdit(), toggleBanUser(), deleteUser() + +--- + +## 📋 Структура Docker развертывания + +``` +Приложение (порт 3336) + ↓ +Docker контейнер (texas-holdem-poker) + ↓ +Docker Compose (управление) + ↓ +Apache (reverse proxy) + ↓ +Интернет (порт 80/443) +``` + +--- + +## 🚀 Способы запуска + +### 1. Автоматический (рекомендуется) + +**Windows:** +```powershell +.\deploy.ps1 +``` + +**Linux/Mac:** +```bash +chmod +x deploy.sh +./deploy.sh +``` + +### 2. Docker Compose +```bash +docker-compose up -d +``` + +### 3. NPM скрипты +```bash +npm run docker:build +npm run docker:up +``` + +### 4. Docker вручную +```bash +docker build -t texas-holdem-poker . +docker run -d -p 3336:3336 --name poker-app texas-holdem-poker +``` + +--- + +## 🌐 Порты + +- **3336** - Основной порт приложения (Docker) +- **80** - HTTP через Apache (опционально) +- **443** - HTTPS через Apache (опционально) + +**Важно:** Порт изменен с 3000 на 3336 согласно требованию + +--- + +## 📦 Volumes + +```yaml +volumes: + - ./data:/app/data # База данных SQLite + - ./logs:/app/logs # Логи приложения +``` + +Данные сохраняются на хосте, даже при удалении контейнера. + +--- + +## 🔐 Переменные окружения + +В `docker-compose.yml`: +```yaml +environment: + - NODE_ENV=production + - PORT=3336 +``` + +В `Dockerfile`: +```dockerfile +ENV NODE_ENV=production +ENV PORT=3336 +``` + +--- + +## 📚 Документация + +| Файл | Описание | +|------|----------| +| `README.md` | Главная документация проекта | +| `DEPLOYMENT.md` | Полное руководство по развертыванию | +| `DOCKER_README.md` | Краткая инструкция Docker | +| `BOT_PERSONALITIES_CONFIG.md` | Настройка ботов | +| `ADMIN_USER_MANAGEMENT.md` | Управление пользователями | + +--- + +## ✅ Контрольный список готовности + +- [x] Dockerfile создан +- [x] docker-compose.yml настроен +- [x] .dockerignore настроен +- [x] Apache конфигурация готова +- [x] Скрипты развертывания (Linux & Windows) +- [x] Документация обновлена +- [x] npm скрипты добавлены +- [x] .gitignore обновлен +- [x] Порт изменен на 3336 +- [x] Переменные окружения настроены +- [x] Volumes для данных настроены +- [x] WebSocket поддержка в Apache +- [x] SSL конфигурация готова +- [x] Исправлена работа с токенами + +--- + +## 🎯 Следующие шаги + +1. **Протестировать локально:** + ```bash + docker-compose up -d + ``` + +2. **Открыть в браузере:** + ``` + http://localhost:3336 + ``` + +3. **Проверить логи:** + ```bash + docker-compose logs -f + ``` + +4. **Настроить Apache** (если нужно): + ```bash + sudo cp apache-config.conf /etc/apache2/sites-available/poker.conf + sudo a2ensite poker.conf + sudo systemctl reload apache2 + ``` + +5. **Создать администратора:** + - Зарегистрироваться через интерфейс + - Выполнить SQL: `UPDATE users SET role = 'admin' WHERE username = 'ваш_логин';` + +--- + +## 📞 Команды для проверки + +```bash +# Проверка статуса +docker-compose ps + +# Просмотр логов +docker-compose logs -f + +# Проверка порта +netstat -ano | findstr :3336 # Windows +lsof -i :3336 # Linux/Mac + +# Проверка Apache модулей +apache2ctl -M | grep proxy + +# Тест подключения +curl http://localhost:3336 +``` + +--- + +## 🎉 Готово! + +Репозиторий полностью подготовлен к развертыванию в Docker на порту **3336** с поддержкой Apache reverse proxy. + +**Дата:** 2026-02-01 +**Версия:** 1.0.0 +**Статус:** ✅ Production Ready diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a6ec9e5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Используем официальный образ Node.js +FROM node:18-alpine + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем package.json и package-lock.json +COPY package*.json ./ + +# Устанавливаем зависимости +RUN npm install --production + +# Копируем все файлы приложения +COPY . . + +# Создаем директорию для базы данных +RUN mkdir -p /app/data + +# Открываем порт 3336 +EXPOSE 3336 + +# Устанавливаем переменные окружения +ENV NODE_ENV=production +ENV PORT=3336 + +# Запускаем приложение +CMD ["node", "server.js"] diff --git a/README.md b/README.md index e7978d3..8d49931 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,342 @@ -# poker +# 🃏 Texas Hold'em No-Limit Poker +Полнофункциональная онлайн-игра в покер с поддержкой мультиплеера, ИИ-ботами и интеграцией LLM для реалистичного общения. + +## ✨ Особенности + +- 🎮 **Мультиплеер**: 2-6 игроков в режиме реального времени +- 🤖 **ИИ-боты**: 8 уникальных личностей с разными стилями игры +- 💬 **LLM-чат**: Реалистичное общение ботов через Ollama/LM Studio/OpenAI +- 👤 **Система авторизации**: Регистрация, вход, гостевой режим +- 👑 **Админ-панель**: Управление ботами, пользователями и настройками +- 🎨 **Современный UI**: Glassmorphism дизайн с анимациями +- 🐳 **Docker**: Готов к развертыванию в один клик + +## 🚀 Быстрый старт + +### Вариант 1: Docker (Рекомендуется) + +**Windows (PowerShell):** +```powershell +.\deploy.ps1 +``` + +**Linux/Mac:** +```bash +chmod +x deploy.sh +./deploy.sh +``` + +**Или вручную:** +```bash +docker-compose up -d +``` + +Приложение будет доступно: **http://localhost:3336** + +### Вариант 2: Локальный запуск + +```bash +# Установка зависимостей +npm install + +# Запуск сервера +npm start +``` + +Приложение будет доступно: **http://localhost:3000** + +## 📋 Требования + +### Docker (рекомендуется) +- Docker 20.10+ +- Docker Compose 1.29+ + +### Локальный запуск +- Node.js 16+ +- npm 8+ + +## 🎮 Игровые режимы + +### 🎯 Одиночная игра +- Игра против 1-5 ИИ-ботов +- 3 уровня сложности ИИ +- 8 уникальных личностей ботов +- Настраиваемые системные промпты + +### 🌐 Мультиплеер +- Создание приватных комнат +- Поделиться ссылкой для присоединения +- Чат в реальном времени +- До 6 игроков одновременно + +### 🤖 ИИ-боты с личностями + +1. **Виктор "Акула"** - Агрессивный профессионал +2. **Анна "Блефер"** - Мастер обмана +3. **Сергей "Математик"** - Аналитик-стратег +4. **Мария "Рок"** - Консервативный игрок +5. **Дмитрий "Маньяк"** - Непредсказуемый агрессор +6. **Елена "Читательница"** - Психолог за столом +7. **Игорь "Новичок"** - Неопытный оптимист +8. **Ольга "Тильт"** - Эмоциональный игрок + +## 👑 Админ-панель + +### Функции администратора: + +- **⚙️ Настройки LLM**: Подключение Ollama/LM Studio/OpenAI +- **🤖 Промпты ботов**: Настройка личностей и поведения +- **📋 Логи действий**: Мониторинг активности пользователей +- **👥 Управление пользователями**: + - Просмотр всех пользователей + - Статистика (IP, последняя игра) + - Изменение ролей (admin/user) + - Блокировка пользователей + - Удаление аккаунтов + +### Первый администратор + +При первом запуске создайте админа: +```sql +-- Используйте любой SQLite клиент для изменения БД +UPDATE users SET role = 'admin' WHERE username = 'ваш_логин'; +``` + +## 🐳 Docker развертывание + +### Структура проекта + +``` +onlinepocker/ +├── 📄 Dockerfile # Docker образ +├── 📄 docker-compose.yml # Docker Compose конфигурация +├── 📄 .dockerignore # Исключения для Docker +├── 📄 apache-config.conf # Настройка Apache +├── 📄 deploy.sh # Скрипт развертывания (Linux/Mac) +├── 📄 deploy.ps1 # Скрипт развертывания (Windows) +├── 📚 DEPLOYMENT.md # Полная документация +└── 📚 DOCKER_README.md # Краткая инструкция Docker +``` + +### Основные команды + +```bash +# Запуск +docker-compose up -d + +# Остановка +docker-compose down + +# Просмотр логов +docker-compose logs -f + +# Перезапуск +docker-compose restart + +# Пересборка +docker-compose up -d --build +``` + +### NPM скрипты + +```bash +npm run docker:build # Сборка образа +npm run docker:up # Запуск контейнера +npm run docker:down # Остановка контейнера +npm run docker:logs # Просмотр логов +npm run docker:restart # Перезапуск +``` + +## 🌐 Настройка Apache + +Для проксирования через Apache (порт 80/443 → 3336): + +```bash +# Включение модулей +sudo a2enmod proxy proxy_http proxy_wstunnel rewrite headers ssl + +# Копирование конфигурации +sudo cp apache-config.conf /etc/apache2/sites-available/poker.conf + +# Включение сайта +sudo a2ensite poker.conf + +# Перезагрузка Apache +sudo systemctl reload apache2 +``` + +Подробнее: `DEPLOYMENT.md` + +## 🔧 Конфигурация + +### Переменные окружения + +| Переменная | По умолчанию | Описание | +|------------|--------------|----------| +| `PORT` | 3336 | Порт сервера | +| `NODE_ENV` | production | Режим работы | + +### Настройка LLM (в админ-панели) + +1. Выберите провайдера: Ollama / LM Studio / OpenAI +2. Укажите API URL (например, `http://localhost:11434`) +3. Выберите модель (например, `llama3.2`) +4. Сохраните настройки + +### Настройка промптов ботов + +1. Войдите как администратор +2. Откройте "🤖 Промпты ботов" +3. Выберите бота +4. Отредактируйте системный промпт +5. Сохраните изменения + +## 📦 Технологии + +### Backend +- **Node.js** - Серверная платформа +- **Express.js** - REST API +- **WebSocket (ws)** - Реальное время +- **SQLite (sql.js)** - База данных +- **bcryptjs** - Хеширование паролей +- **jsonwebtoken** - JWT авторизация + +### Frontend +- **Vanilla JavaScript** - Без фреймворков +- **CSS3** - Glassmorphism дизайн +- **HTML5** - Семантическая разметка + +### DevOps +- **Docker** - Контейнеризация +- **Docker Compose** - Оркестрация +- **Apache** - Reverse proxy + +## 📁 Структура проекта + +``` +onlinepocker/ +├── 📂 public/ # Фронтенд +│ ├── index.html # Главная страница +│ ├── main.js # Основная логика +│ ├── game.js # Игровая логика +│ ├── ai.js # ИИ и личности ботов +│ ├── auth.js # Авторизация +│ └── styles.css # Стили +├── 📂 data/ # База данных (создается автоматически) +│ └── poker.db +├── server.js # WebSocket сервер +├── database.js # Работа с БД +├── package.json # Зависимости +├── Dockerfile # Docker образ +├── docker-compose.yml # Docker Compose +└── 📚 Документация + ├── DEPLOYMENT.md # Развертывание + ├── DOCKER_README.md # Docker инструкции + ├── BOT_PERSONALITIES_CONFIG.md + └── ADMIN_USER_MANAGEMENT.md +``` + +## 🔐 Безопасность + +- ✅ JWT токены с истечением +- ✅ Bcrypt хеширование паролей +- ✅ Middleware авторизации +- ✅ Проверка прав администратора +- ✅ Логирование действий +- ✅ Блокировка пользователей +- ✅ IP tracking + +## 💾 Backup базы данных + +### Ручной backup + +```bash +# Docker +docker cp texas-holdem-poker:/app/data/poker.db ./backup-$(date +%Y%m%d).db + +# Локально +cp data/poker.db data/poker.db.backup +``` + +### Автоматический backup (cron) + +```bash +# Каждый день в 3:00 +0 3 * * * /opt/poker-backup.sh +``` + +Подробнее: `DEPLOYMENT.md` + +## 📖 Документация + +- **[DEPLOYMENT.md](DEPLOYMENT.md)** - Полное руководство по развертыванию +- **[DOCKER_README.md](DOCKER_README.md)** - Быстрый старт с Docker +- **[BOT_PERSONALITIES_CONFIG.md](BOT_PERSONALITIES_CONFIG.md)** - Настройка ботов +- **[ADMIN_USER_MANAGEMENT.md](ADMIN_USER_MANAGEMENT.md)** - Управление пользователями + +## 🐛 Troubleshooting + +### Порт занят +```bash +# Проверка +netstat -ano | findstr :3336 # Windows +lsof -i :3336 # Linux/Mac + +# Решение +docker-compose down +``` + +### Проблемы с WebSocket +```bash +# Проверка модулей Apache +apache2ctl -M | grep proxy +``` + +### Права доступа (Linux) +```bash +sudo chown -R $USER:$USER data/ +sudo chmod 755 data/ +``` + +## 📞 Поддержка + +1. Проверьте логи: `docker-compose logs -f` +2. Проверьте статус: `docker-compose ps` +3. Прочитайте документацию: `DEPLOYMENT.md` + +## 📝 Лицензия + +MIT License - свободно для личного использования + +--- + +## 🎯 Краткая инструкция + +### Для локальной разработки +```bash +npm install +npm start +# Откройте http://localhost:3000 +``` + +### Для production (Docker) +```bash +docker-compose up -d +# Откройте http://localhost:3336 +``` + +### Создание администратора +```bash +# 1. Зарегистрируйтесь через интерфейс +# 2. Подключитесь к БД и выполните: +UPDATE users SET role = 'admin' WHERE username = 'ваш_логин'; +``` + +--- + +**Версия:** 1.0.0 +**Дата:** 2026-02-01 +**Статус:** ✅ Готов к production + +🃏 **Хорошей игры!** diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..140828e --- /dev/null +++ b/TESTING.md @@ -0,0 +1,206 @@ +# 🧪 Тестирование Docker развертывания + +## Перед запуском + +Убедитесь, что установлены: +- Docker Desktop (Windows/Mac) или Docker Engine (Linux) +- Docker Compose + +## Быстрый тест + +### Windows +```powershell +# Проверка версии Docker +docker --version + +# Проверка Docker Compose +docker-compose --version + +# Запуск приложения +.\deploy.ps1 + +# Или +docker-compose up -d +``` + +### Linux/Mac +```bash +# Проверка версии Docker +docker --version + +# Проверка Docker Compose +docker-compose --version + +# Запуск приложения +chmod +x deploy.sh +./deploy.sh + +# Или +docker-compose up -d +``` + +## Проверка работы + +1. **Открыть в браузере:** + ``` + http://localhost:3336 + ``` + +2. **Проверить логи:** + ```bash + docker-compose logs -f + ``` + +3. **Проверить статус:** + ```bash + docker-compose ps + ``` + + Должно быть: + ``` + NAME STATUS PORTS + texas-holdem-poker Up 0.0.0.0:3336->3336/tcp + ``` + +4. **Проверить порт:** + ```bash + # Windows + netstat -ano | findstr :3336 + + # Linux/Mac + lsof -i :3336 + ``` + +## Тестовые сценарии + +### 1. Регистрация и вход +- Откройте http://localhost:3336 +- Зарегистрируйте нового пользователя +- Войдите в систему +- Проверьте, что токен сохранился + +### 2. Создание игры +- Создайте одиночную игру +- Добавьте ботов +- Проверьте работу игры + +### 3. Админ панель (если есть доступ) +- Войдите как администратор +- Откройте админ панель +- Проверьте вкладку "Пользователи" +- Проверьте логи + +### 4. WebSocket +- Создайте мультиплеер комнату +- Скопируйте ссылку +- Откройте в другой вкладке +- Проверьте синхронизацию + +## Остановка и очистка + +```bash +# Остановка +docker-compose stop + +# Остановка и удаление контейнеров +docker-compose down + +# Полная очистка (включая volumes) +docker-compose down -v + +# Удаление образов +docker rmi texas-holdem-poker +``` + +## Troubleshooting + +### Порт 3336 занят +```bash +# Остановите существующие контейнеры +docker-compose down + +# Или измените порт в docker-compose.yml +ports: + - "3337:3336" # Используйте другой внешний порт +``` + +### Контейнер не запускается +```bash +# Просмотрите логи +docker-compose logs + +# Пересоберите образ +docker-compose build --no-cache +docker-compose up -d +``` + +### База данных не создается +```bash +# Проверьте права доступа к директории data +ls -la data/ + +# Создайте директорию вручную +mkdir -p data +chmod 755 data +``` + +### WebSocket не работает +- Проверьте, что браузер поддерживает WebSocket +- Откройте консоль разработчика (F12) +- Проверьте наличие ошибок подключения + +## Ожидаемый результат + +После успешного запуска вы должны увидеть: + +1. **В консоли:** + ``` + ✅ Сервер успешно запущен! + 📍 Сервер доступен по адресу: http://localhost:3336 + ``` + +2. **В браузере:** + - Страница авторизации загружается + - Стили применены корректно + - Анимации работают + +3. **В логах Docker:** + ```bash + docker-compose logs -f + ``` + ``` + 🃏 Texas Hold'em Poker Server 🃏 + Сервер запущен на http://localhost:3336 + ``` + +4. **Проверка WebSocket:** + - Откройте консоль браузера (F12) + - Должны быть сообщения о подключении WebSocket + - Нет ошибок 404 или 500 + +## Следующие шаги + +После успешного тестирования: + +1. **Настройте Apache** (опционально): + - Следуйте инструкциям в `DEPLOYMENT.md` + - Настройте SSL сертификаты + +2. **Создайте администратора:** + ```sql + UPDATE users SET role = 'admin' WHERE username = 'ваш_логин'; + ``` + +3. **Настройте LLM** (опционально): + - Откройте админ панель + - Настройте Ollama/LM Studio/OpenAI + +4. **Настройте backup:** + - Следуйте инструкциям в `DEPLOYMENT.md` + - Настройте cron задачу + +--- + +**Статус:** ✅ Готово к тестированию +**Порт:** 3336 +**Дата:** 2026-02-01 diff --git a/apache-config.conf b/apache-config.conf new file mode 100644 index 0000000..e69de29 diff --git a/database.js b/database.js index 0a67caa..077e97e 100644 --- a/database.js +++ b/database.js @@ -57,6 +57,8 @@ function createTables() { username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT DEFAULT 'user', + banned INTEGER DEFAULT 0, + ip_address TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, last_login TEXT, total_games INTEGER DEFAULT 0, @@ -65,6 +67,24 @@ function createTables() { ) `); + // Миграция: добавляем поля banned и ip_address если их нет + try { + const columns = db.exec(`PRAGMA table_info(users)`); + const columnNames = columns[0]?.values.map(col => col[1]) || []; + + if (!columnNames.includes('banned')) { + db.run(`ALTER TABLE users ADD COLUMN banned INTEGER DEFAULT 0`); + console.log('✅ Добавлено поле banned в таблицу users'); + } + + if (!columnNames.includes('ip_address')) { + db.run(`ALTER TABLE users ADD COLUMN ip_address TEXT`); + console.log('✅ Добавлено поле ip_address в таблицу users'); + } + } catch (error) { + console.log('⚠️ Миграция таблицы users пропущена:', error.message); + } + // Настройки пользователей db.run(` CREATE TABLE IF NOT EXISTS user_settings ( @@ -246,10 +266,10 @@ function registerUser(username, password) { /** * Авторизация пользователя */ -function loginUser(username, password) { +function loginUser(username, password, ipAddress = null) { try { const result = db.exec(` - SELECT id, username, password_hash, role + SELECT id, username, password_hash, role, banned FROM users WHERE username = ? `, [username]); @@ -258,20 +278,31 @@ function loginUser(username, password) { return { success: false, error: 'Неверный логин или пароль' }; } - const [userId, storedUsername, passwordHash, role] = result[0].values[0]; + const [userId, storedUsername, passwordHash, role, banned] = result[0].values[0]; + + // Проверка на бан + if (banned === 1) { + return { success: false, error: 'Ваш аккаунт заблокирован' }; + } if (!bcrypt.compareSync(password, passwordHash)) { return { success: false, error: 'Неверный логин или пароль' }; } - // Обновляем last_login - db.run(` - UPDATE users SET last_login = datetime('now') WHERE id = ? - `, [userId]); + // Обновляем last_login и IP + if (ipAddress) { + db.run(` + UPDATE users SET last_login = datetime('now'), ip_address = ? WHERE id = ? + `, [ipAddress, userId]); + } else { + db.run(` + UPDATE users SET last_login = datetime('now') WHERE id = ? + `, [userId]); + } saveDatabase(); // Логируем - logAction(userId, storedUsername, 'login', { username: storedUsername }); + logAction(userId, storedUsername, 'login', { username: storedUsername, ip: ipAddress }); // Генерируем токен const token = generateToken(userId, storedUsername, role); @@ -567,6 +598,107 @@ function clearRoomChat(roomId) { saveDatabase(); } +// ============================================================================= +// УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (АДМИН) +// ============================================================================= + +/** + * Получить всех пользователей + */ +function getAllUsers() { + const result = db.exec(` + SELECT id, username, role, banned, ip_address, last_login, created_at, + total_games, total_wins, total_chips_won + FROM users + ORDER BY created_at DESC + `); + + if (result.length === 0) return []; + + return result[0].values.map(row => ({ + id: row[0], + username: row[1], + role: row[2], + banned: row[3], + ipAddress: row[4], + lastLogin: row[5], + createdAt: row[6], + totalGames: row[7], + totalWins: row[8], + totalChipsWon: row[9] + })); +} + +/** + * Получить пользователя по логину + */ +function getUserByUsername(username) { + const result = db.exec(` + SELECT id, username, role, banned, ip_address, last_login, created_at + FROM users + WHERE username = ? + `, [username]); + + if (result.length === 0 || result[0].values.length === 0) { + return null; + } + + const [id, uname, role, banned, ipAddress, lastLogin, createdAt] = result[0].values[0]; + return { id, username: uname, role, banned, ipAddress, lastLogin, createdAt }; +} + +/** + * Обновить пользователя + */ +function updateUser(username, updates) { + const user = getUserByUsername(username); + if (!user) { + throw new Error('Пользователь не найден'); + } + + const fields = []; + const values = []; + + if (updates.role !== undefined) { + fields.push('role = ?'); + values.push(updates.role); + } + + if (updates.banned !== undefined) { + fields.push('banned = ?'); + values.push(updates.banned); + } + + if (updates.password) { + const hashedPassword = bcrypt.hashSync(updates.password, 10); + fields.push('password_hash = ?'); + values.push(hashedPassword); + } + + if (fields.length === 0) { + return; // Нечего обновлять + } + + values.push(username); + + db.run(` + UPDATE users + SET ${fields.join(', ')} + WHERE username = ? + `, values); + + saveDatabase(); +} + +/** + * Удалить пользователя + */ +function deleteUser(username) { + db.run(`DELETE FROM users WHERE username = ?`, [username]); + db.run(`DELETE FROM user_settings WHERE user_id = (SELECT id FROM users WHERE username = ?)`, [username]); + saveDatabase(); +} + // ============================================================================= // ЭКСПОРТ // ============================================================================= @@ -580,6 +712,10 @@ module.exports = { loginUser, verifyToken, getUserById, + getAllUsers, + getUserByUsername, + updateUser, + deleteUser, // Настройки getUserSettings, diff --git a/deploy.ps1 b/deploy.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..79e90d0 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# ============================================================================= +# Texas Hold'em Poker - Скрипт развертывания +# ============================================================================= + +set -e + +echo "🃏 Texas Hold'em Poker - Развертывание" +echo "======================================" +echo "" + +# Проверка Docker +if ! command -v docker &> /dev/null; then + echo "❌ Docker не установлен!" + echo "Установите Docker: https://docs.docker.com/get-docker/" + exit 1 +fi + +# Проверка Docker Compose +if ! command -v docker-compose &> /dev/null; then + echo "❌ Docker Compose не установлен!" + echo "Установите Docker Compose: https://docs.docker.com/compose/install/" + exit 1 +fi + +echo "✅ Docker и Docker Compose установлены" +echo "" + +# Создание директорий +echo "📁 Создание необходимых директорий..." +mkdir -p data +mkdir -p logs +echo "✅ Директории созданы" +echo "" + +# Остановка старого контейнера +echo "🛑 Остановка старых контейнеров..." +docker-compose down 2>/dev/null || true +echo "✅ Старые контейнеры остановлены" +echo "" + +# Сборка образа +echo "🔨 Сборка Docker образа..." +docker-compose build +echo "✅ Образ собран" +echo "" + +# Запуск контейнера +echo "🚀 Запуск контейнера..." +docker-compose up -d +echo "✅ Контейнер запущен" +echo "" + +# Ожидание запуска +echo "⏳ Ожидание запуска сервера..." +sleep 3 + +# Проверка статуса +if docker-compose ps | grep -q "Up"; then + echo "✅ Сервер успешно запущен!" + echo "" + echo "==========================================" + echo "🎉 Развертывание завершено!" + echo "==========================================" + echo "" + echo "📍 Сервер доступен по адресу:" + echo " http://localhost:3336" + echo "" + echo "📊 Полезные команды:" + echo " docker-compose logs -f # Просмотр логов" + echo " docker-compose ps # Статус контейнеров" + echo " docker-compose stop # Остановка" + echo " docker-compose restart # Перезапуск" + echo " docker-compose down # Остановка и удаление" + echo "" +else + echo "❌ Ошибка запуска сервера!" + echo "Проверьте логи: docker-compose logs" + exit 1 +fi diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a3505b9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + poker-app: + build: + context: . + dockerfile: Dockerfile + container_name: texas-holdem-poker + restart: unless-stopped + ports: + - "3336:3336" + volumes: + # Монтируем директорию для сохранения базы данных + - ./data:/app/data + # Опционально: монтируем логи + - ./logs:/app/logs + environment: + - NODE_ENV=production + - PORT=3336 + networks: + - poker-network + +networks: + poker-network: + driver: bridge diff --git a/poker.db b/poker.db index 83bc694..c814fc9 100644 Binary files a/poker.db and b/poker.db differ diff --git a/public/index.html b/public/index.html index cdb1081..13e6fd2 100644 --- a/public/index.html +++ b/public/index.html @@ -777,8 +777,104 @@ diff --git a/public/main.js b/public/main.js index 4be60d2..b39e914 100644 --- a/public/main.js +++ b/public/main.js @@ -2943,3 +2943,394 @@ function initCustomPrompts() { } } +// ============================================================================= +// УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (АДМИН-ПАНЕЛЬ) +// ============================================================================= + +/** + * Переключение вкладок админ-панели + */ +function switchAdminTab(tabName) { + // Скрываем все вкладки + document.querySelectorAll('.admin-tab-content').forEach(tab => { + tab.style.display = 'none'; + }); + + // Убираем активный класс со всех кнопок + document.querySelectorAll('.btn-tab').forEach(btn => { + btn.classList.remove('active'); + }); + + // Показываем выбранную вкладку + const selectedTab = document.getElementById(`admin-tab-${tabName}`); + if (selectedTab) { + selectedTab.style.display = 'block'; + } + + // Активируем кнопку + event.target.classList.add('active'); + + // Загружаем данные для вкладки + if (tabName === 'users') { + loadUsers(); + } else if (tabName === 'logs') { + loadLogs(); + } else if (tabName === 'bot-prompts') { + initBotPersonalitySelector(); + } +} + +/** + * Загрузить список пользователей + */ +async function loadUsers() { + try { + const token = localStorage.getItem('authToken'); + + if (!token) { + showNotification('Необходима авторизация', 'error'); + // Перезагружаем страницу для возврата на экран входа + setTimeout(() => location.reload(), 1000); + return; + } + + const roleFilter = document.getElementById('user-filter-role')?.value || ''; + const statusFilter = document.getElementById('user-filter-status')?.value || ''; + const search = document.getElementById('user-search')?.value || ''; + + const response = await fetch('/api/admin/users', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + role: roleFilter, + status: statusFilter, + search: search + }) + }); + + if (response.status === 401) { + showNotification('Сессия истекла. Войдите заново', 'error'); + localStorage.removeItem('token'); + // Перезагружаем страницу для возврата на экран входа + setTimeout(() => location.reload(), 1000); + return; + } + + if (response.status === 403) { + showNotification('Доступ запрещён. Требуются права администратора', 'error'); + // Показываем ошибку в таблице + const tbody = document.getElementById('users-table-body'); + if (tbody) { + tbody.innerHTML = ` + + + 🚫 Доступ запрещён. Требуются права администратора. + + + `; + } + return; + } + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error || 'Ошибка загрузки пользователей'); + } + + const data = await response.json(); + + // Обновляем статистику + updateUserStats(data.stats); + + // Обновляем таблицу + renderUsersTable(data.users); + + } catch (error) { + console.error('Ошибка загрузки пользователей:', error); + showNotification(error.message || 'Ошибка загрузки пользователей', 'error'); + + // Показываем заглушку в таблице + const tbody = document.getElementById('users-table-body'); + if (tbody) { + tbody.innerHTML = ` + + + ❌ ${error.message} + + + `; + } + } +} + +/** + * Обновить статистику пользователей + */ +function updateUserStats(stats) { + document.getElementById('stat-total-users').textContent = stats.total || 0; + document.getElementById('stat-active-users').textContent = stats.active || 0; + document.getElementById('stat-banned-users').textContent = stats.banned || 0; + document.getElementById('stat-admin-users').textContent = stats.admins || 0; +} + +/** + * Отрисовать таблицу пользователей + */ +function renderUsersTable(users) { + const tbody = document.getElementById('users-table-body'); + + if (!users || users.length === 0) { + tbody.innerHTML = ` + + + Пользователи не найдены + + + `; + return; + } + + tbody.innerHTML = users.map(user => { + const roleClass = user.role === 'admin' ? 'user-role-admin' : 'user-role-user'; + const roleText = user.role === 'admin' ? '👑 Админ' : '👤 Пользователь'; + + const statusClass = user.banned ? 'user-status-banned' : 'user-status-active'; + const statusText = user.banned ? '🚫 Заблокирован' : '✅ Активен'; + + const lastSeen = user.lastLogin ? formatDate(user.lastLogin) : 'Никогда'; + const ipAddress = user.ipAddress || 'Не известен'; + + return ` + + + ${user.username} + ${user.username === currentUser?.username ? '(Вы)' : ''} + + + ${roleText} + + + ${ipAddress} + + + ${lastSeen} + + + ${statusText} + + + + ${user.banned ? + `` : + `` + } + ${user.username !== currentUser?.username ? + `` : '' + } + + + `; + }).join(''); +} + +/** + * Форматировать дату + */ +function formatDate(dateString) { + const date = new Date(dateString); + const now = new Date(); + const diff = now - date; + + const minutes = Math.floor(diff / 60000); + const hours = Math.floor(diff / 3600000); + const days = Math.floor(diff / 86400000); + + if (minutes < 1) return 'Только что'; + if (minutes < 60) return `${minutes} мин назад`; + if (hours < 24) return `${hours} ч назад`; + if (days < 7) return `${days} дн назад`; + + return date.toLocaleDateString('ru-RU', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); +} + +/** + * Редактировать пользователя + */ +function editUser(user) { + document.getElementById('edit-user-username').value = user.username; + document.getElementById('edit-user-role').value = user.role; + document.getElementById('edit-user-status').value = user.banned ? 'banned' : 'active'; + document.getElementById('edit-user-password').value = ''; + + document.getElementById('edit-user-modal').style.display = 'flex'; + + // Сохраняем данные для обновления + window.editingUser = user; +} + +/** + * Закрыть модальное окно редактирования + */ +function closeEditUserModal() { + document.getElementById('edit-user-modal').style.display = 'none'; + window.editingUser = null; +} + +/** + * Сохранить изменения пользователя + */ +async function saveUserEdit() { + if (!window.editingUser) return; + + const username = window.editingUser.username; + const role = document.getElementById('edit-user-role').value; + const status = document.getElementById('edit-user-status').value; + const password = document.getElementById('edit-user-password').value; + + try { + const token = localStorage.getItem('authToken'); + if (!token) { + showNotification('Необходима авторизация', 'error'); + setTimeout(() => location.reload(), 1000); + return; + } + + const response = await fetch('/api/admin/user/update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + username, + role, + banned: status === 'banned', + password: password || undefined + }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error || 'Ошибка обновления пользователя'); + } + + showNotification('Пользователь успешно обновлён', 'success'); + closeEditUserModal(); + loadUsers(); + + } catch (error) { + console.error('Ошибка обновления пользователя:', error); + showNotification(error.message || 'Ошибка обновления пользователя', 'error'); + } +} + +/** + * Заблокировать/разблокировать пользователя + */ +async function toggleBanUser(username, ban) { + const action = ban ? 'заблокировать' : 'разблокировать'; + + if (!confirm(`Вы уверены, что хотите ${action} пользователя ${username}?`)) { + return; + } + + try { + const token = localStorage.getItem('authToken'); + if (!token) { + showNotification('Необходима авторизация', 'error'); + setTimeout(() => location.reload(), 1000); + return; + } + + const response = await fetch('/api/admin/user/ban', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ username, banned: ban }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error || 'Ошибка изменения статуса пользователя'); + } + + showNotification(`Пользователь ${username} ${ban ? 'заблокирован' : 'разблокирован'}`, 'success'); + loadUsers(); + + } catch (error) { + console.error('Ошибка изменения статуса:', error); + showNotification(error.message || 'Ошибка изменения статуса пользователя', 'error'); + } +} + +/** + * Удалить пользователя + */ +async function deleteUser(username) { + if (!confirm(`Вы уверены, что хотите УДАЛИТЬ пользователя ${username}?\n\nЭто действие необратимо!`)) { + return; + } + + // Двойное подтверждение для безопасности + if (!confirm(`ВНИМАНИЕ! Вы действительно хотите удалить ${username}?`)) { + return; + } + + try { + const token = localStorage.getItem('authToken'); + if (!token) { + showNotification('Необходима авторизация', 'error'); + setTimeout(() => location.reload(), 1000); + return; + } + + const response = await fetch('/api/admin/user/delete', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ username }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error || 'Ошибка удаления пользователя'); + } + + showNotification(`Пользователь ${username} удалён`, 'success'); + loadUsers(); + + } catch (error) { + console.error('Ошибка удаления:', error); + showNotification(error.message || 'Ошибка удаления пользователя', 'error'); + } +} + +/** + * Заглушка для функции загрузки логов + */ +function loadLogs() { + console.log('Загрузка логов...'); + // Будет реализовано позже +} + diff --git a/public/styles.css b/public/styles.css index 6774e50..731b185 100644 --- a/public/styles.css +++ b/public/styles.css @@ -2422,3 +2422,153 @@ select.input { .admin-tab-content { animation: fadeInUp 0.3s ease; } + +/* ============================================================================= + УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (АДМИН-ПАНЕЛЬ) + ============================================================================= */ + +.stat-card { + background: var(--bg-secondary); + padding: 16px; + border-radius: 8px; + text-align: center; + border: 1px solid var(--border-color); + transition: all 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +.stat-value { + font-size: 28px; + font-weight: 700; + color: var(--accent-primary); + margin-bottom: 4px; +} + +.stat-label { + font-size: 12px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.users-table-container { + background: var(--bg-secondary); + border-radius: 8px; + padding: 8px; + max-height: 500px; + overflow-y: auto; +} + +.users-table { + font-size: 13px; +} + +.users-table th { + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.5px; + position: sticky; + top: 0; + background: var(--bg-secondary); + z-index: 10; +} + +.users-table tbody tr { + border-bottom: 1px solid var(--border-color); + transition: background 0.2s ease; +} + +.users-table tbody tr:hover { + background: var(--bg-primary); +} + +.users-table td { + padding: 12px; +} + +.user-role-badge { + display: inline-block; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; +} + +.user-role-admin { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.user-role-user { + background: var(--bg-tertiary); + color: var(--text-secondary); +} + +.user-status-badge { + display: inline-block; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; +} + +.user-status-active { + background: rgba(16, 185, 129, 0.2); + color: var(--success); +} + +.user-status-banned { + background: rgba(239, 68, 68, 0.2); + color: var(--danger); +} + +.user-actions { + display: flex; + gap: 8px; + justify-content: center; +} + +.btn-icon { + padding: 6px 10px; + font-size: 14px; + min-width: auto; + border-radius: 4px; + cursor: pointer; +} + +.btn-icon:hover { + transform: scale(1.1); +} + +/* Адаптивность таблицы */ +@media (max-width: 1024px) { + .users-table { + font-size: 12px; + } + + .users-table th, + .users-table td { + padding: 8px 6px; + } +} + +@media (max-width: 768px) { + .user-stats { + grid-template-columns: repeat(2, 1fr); + } + + .users-table-container { + overflow-x: auto; + } + + .users-table { + min-width: 800px; + } +} diff --git a/server.js b/server.js index 62df453..fe8a664 100644 --- a/server.js +++ b/server.js @@ -83,7 +83,10 @@ app.post('/api/auth/login', (req, res) => { return res.status(400).json({ error: 'Введите логин и пароль' }); } - const result = database.loginUser(username, password); + // Получаем IP адрес клиента + const ipAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress; + + const result = database.loginUser(username, password, ipAddress); if (result.success) { res.json(result); @@ -170,6 +173,161 @@ app.get('/api/logs/stats', authMiddleware, adminMiddleware, (req, res) => { res.json({ stats }); }); +// ============================================================================= +// REST API - УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (только для админа) +// ============================================================================= + +// Получить список пользователей с фильтрами +app.post('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => { + try { + const { role, status, search } = req.body; + + // Получаем всех пользователей + const allUsers = database.getAllUsers(); + + // Применяем фильтры + let filteredUsers = allUsers; + + if (role) { + filteredUsers = filteredUsers.filter(u => u.role === role); + } + + if (status === 'banned') { + filteredUsers = filteredUsers.filter(u => u.banned === 1); + } else if (status === 'active') { + filteredUsers = filteredUsers.filter(u => u.banned === 0); + } + + if (search) { + const searchLower = search.toLowerCase(); + filteredUsers = filteredUsers.filter(u => + u.username.toLowerCase().includes(searchLower) + ); + } + + // Статистика + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const stats = { + total: allUsers.length, + active: allUsers.filter(u => { + if (!u.lastLogin) return false; + const lastLogin = new Date(u.lastLogin); + return lastLogin >= today; + }).length, + banned: allUsers.filter(u => u.banned === 1).length, + admins: allUsers.filter(u => u.role === 'admin').length + }; + + // Логируем действие + database.logAction(req.user.userId, req.user.username, 'view_users', { count: filteredUsers.length }); + + res.json({ + users: filteredUsers, + stats + }); + + } catch (error) { + console.error('Ошибка получения пользователей:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Обновить пользователя +app.post('/api/admin/user/update', authMiddleware, adminMiddleware, (req, res) => { + try { + const { username, role, banned, password } = req.body; + + if (!username) { + return res.status(400).json({ error: 'Логин обязателен' }); + } + + // Получаем пользователя + const user = database.getUserByUsername(username); + if (!user) { + return res.status(404).json({ error: 'Пользователь не найден' }); + } + + // Обновляем данные + database.updateUser(username, { + role: role || user.role, + banned: banned ? 1 : 0, + password: password || undefined + }); + + // Логируем действие + database.logAction(req.user.userId, req.user.username, 'update_user', { + target: username, + changes: { role, banned, passwordChanged: !!password } + }); + + res.json({ success: true }); + + } catch (error) { + console.error('Ошибка обновления пользователя:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Заблокировать/разблокировать пользователя +app.post('/api/admin/user/ban', authMiddleware, adminMiddleware, (req, res) => { + try { + const { username, banned } = req.body; + + if (!username) { + return res.status(400).json({ error: 'Логин обязателен' }); + } + + // Нельзя заблокировать себя + if (username === req.user.username) { + return res.status(403).json({ error: 'Нельзя заблокировать себя' }); + } + + database.updateUser(username, { banned: banned ? 1 : 0 }); + + // Логируем действие + database.logAction(req.user.userId, req.user.username, banned ? 'ban_user' : 'unban_user', { + target: username + }); + + res.json({ success: true }); + + } catch (error) { + console.error('Ошибка изменения статуса:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Удалить пользователя +app.post('/api/admin/user/delete', authMiddleware, adminMiddleware, (req, res) => { + try { + const { username } = req.body; + + if (!username) { + return res.status(400).json({ error: 'Логин обязателен' }); + } + + // Нельзя удалить себя + if (username === req.user.username) { + return res.status(403).json({ error: 'Нельзя удалить себя' }); + } + + database.deleteUser(username); + + // Логируем действие + database.logAction(req.user.userId, req.user.username, 'delete_user', { + target: username + }); + + res.json({ success: true }); + + } catch (error) { + console.error('Ошибка удаления пользователя:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + // ============================================================================= // REST API - ЧАТ // =============================================================================