Update files

This commit is contained in:
glpshchn 2025-11-21 00:32:48 +03:00
parent b4ca4f2d9e
commit 1cdfd57cdf
21 changed files with 1881 additions and 33 deletions

23
.dockerignore Normal file
View File

@ -0,0 +1,23 @@
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
.DS_Store
*.md
README.md
LICENSE
.vscode
.idea
dist
build
coverage
.cache
frontend/node_modules
frontend/dist
moderation/frontend/node_modules
moderation/frontend/dist
backend/uploads/*
!backend/uploads/.gitkeep

438
CHANGES_SUMMARY.md Normal file
View File

@ -0,0 +1,438 @@
# 📋 Сводка изменений Nakama
## ✅ Выполненные задачи
### 1. ✨ Замена NakamaHost на Nakama
**Статус:** ✅ Завершено
**Изменения:**
- `backend/server.js` - изменено сообщение API
- `backend/bot.js` - обновлены подписи к медиа (3 места)
- `frontend/index.html` - обновлен заголовок страницы
- `frontend/src/pages/Feed.jsx` - изменен заголовок приложения
- `frontend/src/pages/Profile.jsx` - обновлен текст о поддержке проекта
---
### 2. 🔧 Улучшение меню репортов в системе модерации
**Статус:** ✅ Завершено
**Изменения в `moderation/frontend/src/App.jsx`:**
- Добавлено улучшенное отображение причины жалобы
- Добавлен полный просмотр информации о посте (автор, содержание, медиа)
- Добавлены превью изображений (до 3 штук)
- Добавлены действия для работы с постом прямо из репорта:
- Удалить пост
- Забанить автора
- Решено
- Отклонить репорт
- Добавлено сообщение "Нет активных репортов" когда репортов нет
---
### 3. ✏️ Возможность редактирования постов в системе модерации
**Статус:** ✅ Завершено
**Изменения в `backend/models/Post.js`:**
- Добавлено поле `publishedToChannel` (Boolean) - пост опубликован в канал
- Добавлено поле `channelMessageId` (Number) - ID сообщения в Telegram канале
- Добавлено поле `adminNumber` (Number) - номер админа, который опубликовал
- Добавлено поле `editedAt` (Date) - время последнего редактирования
**Изменения в `backend/routes/modApp.js`:**
- Обновлен `PUT /posts/:id` с проверкой прав:
- Владелец может редактировать любые посты
- Админы могут редактировать только свои посты из канала (по adminNumber)
- Добавлено автоматическое обновление поста в Telegram канале при редактировании
- Обновлен `GET /posts` для возврата информации о публикации в канале
- Обновлен `POST /channel/publish` для создания записи в БД с информацией о канале
**Изменения в `backend/bots/serverMonitor.js`:**
- `sendChannelMediaGroup` теперь возвращает messageId
- Добавлена функция `updateChannelMessage` для обновления подписи к сообщению в канале
---
### 4. 🖼️ Исправление загрузки медиа в систему модерации
**Статус:** ✅ Завершено
**Изменения в `moderation/frontend/src/App.jsx`:**
- Добавлено преобразование относительных путей к изображениям в абсолютные URLs
- Добавлена обработка ошибок загрузки изображений с fallback
- Исправлено отображение медиа в постах
- Исправлено отображение медиа в репортах
- Добавлены console.error для отладки проблем с загрузкой
---
### 5. 💬 Исправление админского чата
**Статус:** ✅ Завершено
**Изменения в `moderation/frontend/src/App.jsx`:**
- Добавлен расширенный логгинг для отладки:
- Логирование подключения WebSocket
- Логирование авторизации
- Логирование получения/отправки сообщений
- Логирование ошибок подключения
- Увеличен timeout подключения до 10 секунд
- Добавлен обработчик `connect_error` для логирования ошибок
- Исправлена задержка прокрутки к последнему сообщению (setTimeout 100ms)
- Добавлено предупреждение при попытке отправить сообщение без подключения
---
### 6. 🗄️ Настройка подключения к БД на удаленном сервере
**Статус:** ✅ Завершено (требуется ручная настройка)
**Создан файл `docker-compose.yml`:**
- MongoDB URI настроен на `mongodb://103.80.87.247:27017/nakama`
- Добавлена переменная окружения `MONGODB_URI`
- Настроено монтирование директорий для данных БД
**Создан файл `setup-remote-storage.sh`:**
- Скрипт для автоматической настройки SSHFS
- Монтирование удаленных директорий:
- `/var/nakama/db``/mnt/nakama-db`
- `/var/nakama/media``/mnt/nakama-media`
- `/var/nakama/backups``/mnt/nakama-backups`
- Опция автомонтирования через `/etc/fstab`
**Создана документация `DEPLOYMENT_GUIDE.md`:**
- Подробная инструкция по установке MongoDB на удаленном сервере
- Настройка аутентификации MongoDB
- Настройка удаленного доступа
- Настройка firewall
---
### 7. 💾 Настройка автоматических бекапов БД
**Статус:** ✅ Завершено (требуется ручная настройка cron)
**Создан файл `backup-cron.sh`:**
- Автоматическое создание бекапов через `mongodump`
- Сжатие бекапов в .tar.gz архивы
- Автоматическое удаление старых бекапов (по умолчанию 30 дней)
- Логирование всех операций
- Цветной вывод для удобства
- Опциональные Telegram уведомления
**Создана документация `CRON_SETUP.md`:**
- Пошаговая инструкция настройки cron
- Примеры различных расписаний:
- Еженедельные бекапы (воскресенье в 3:00)
- Ежедневные бекапы
- Несколько раз в неделю
- Настройка Telegram уведомлений
- Инструкции по восстановлению из бекапа
**Добавлен сервис `backup` в `docker-compose.yml`:**
- Готовый контейнер для запуска бекапов
- Смонтированная директория для бекапов
---
### 8. 📁 Настройка хранения медиа на удаленном сервере
**Статус:** ✅ Завершено (требуется ручная настройка)
**Изменения в `docker-compose.yml`:**
- Backend монтирует `/mnt/nakama-media` в `/app/backend/uploads`
- Все загруженные медиа автоматически сохраняются на удаленный сервер
- Настроено через SSHFS монтирование
**Создан скрипт `setup-remote-storage.sh`:**
- Автоматическая установка SSHFS
- Создание директорий на удаленном сервере
- Монтирование через SSH
- Опция автомонтирования при загрузке системы
---
### 9. 🐳 Настройка Docker для всех компонентов
**Статус:** ✅ Завершено
**Созданные файлы:**
1. **`Dockerfile.backend`**
- Multi-stage сборка для оптимизации
- Node 20 Alpine (минимальный размер)
- Production зависимости
- Автоматическое создание директорий для uploads
2. **`Dockerfile.frontend`**
- Multi-stage сборка (builder + nginx)
- Vite сборка с оптимизацией
- Nginx для раздачи статики
- Gzip сжатие
- Кэширование статических файлов
3. **`Dockerfile.moderation`**
- Аналогично frontend
- Отдельный контейнер для системы модерации
- Nginx с оптимизацией
4. **`docker-compose.yml`**
- Полная оркестрация всех сервисов:
- backend (Node.js API)
- frontend (основное приложение)
- moderation (система модерации)
- mongodb (база данных)
- backup (сервис бекапов)
- Настроенные сети
- Volumes для данных
- Health checks
- Переменные окружения
- Зависимости между сервисами
5. **`nginx.conf` и `nginx-moderation.conf`**
- Оптимизированная конфигурация nginx
- Gzip сжатие
- Кэширование статики
- SPA роутинг (fallback на index.html)
6. **`.dockerignore`**
- Исключение ненужных файлов из образов
- Оптимизация размера образов
- Ускорение сборки
7. **`.env.example`**
- Полный пример конфигурации
- Все необходимые переменные окружения
- Комментарии и значения по умолчанию
---
## 📚 Созданная документация
### 1. **DEPLOYMENT_GUIDE.md** - Полное руководство по развертыванию
- Требования к системе
- Настройка удаленного сервера
- Установка и настройка MongoDB
- Развертывание с Docker
- Настройка nginx reverse proxy
- SSL сертификаты
- Мониторинг и обслуживание
- Решение проблем
### 2. **CRON_SETUP.md** - Настройка автоматических бекапов
- Пошаговая инструкция
- Синтаксис cron
- Примеры расписаний
- Настройка уведомлений
- Управление бекапами
- Восстановление из бекапа
### 3. **CHANGES_SUMMARY.md** - Этот файл
- Полная сводка всех изменений
- Инструкции по запуску
---
## 🚀 Быстрый старт
### Подготовка
1. **Настройте удаленный сервер:**
```bash
# Следуйте инструкциям в DEPLOYMENT_GUIDE.md
ssh root@103.80.87.247
# Установите MongoDB и создайте директории
```
2. **Настройте локальное окружение:**
```bash
cd /Users/glpshchn/Desktop/nakama
# Создайте .env файл
cp .env.example .env
nano .env # Заполните переменные
```
3. **Настройте удаленное хранилище (опционально):**
```bash
./setup-remote-storage.sh
```
### Запуск с Docker
```bash
# Сборка всех сервисов
docker-compose build
# Запуск в фоновом режиме
docker-compose up -d
# Проверка статуса
docker-compose ps
# Просмотр логов
docker-compose logs -f
```
### Настройка бекапов
```bash
# Скопируйте скрипт на удаленный сервер
scp backup-cron.sh root@103.80.87.247:/usr/local/bin/
# Следуйте инструкциям в CRON_SETUP.md
ssh root@103.80.87.247
chmod +x /usr/local/bin/backup-cron.sh
crontab -e
# Добавьте: 0 3 * * 0 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
```
### Доступ к приложению
После запуска:
- **Frontend (основное приложение):** http://localhost:5173
- **Moderation (система модерации):** http://localhost:5174
- **Backend API:** http://localhost:3000
- **Health check:** http://localhost:3000/health
---
## 🔧 Команды для управления
### Docker
```bash
# Остановить все сервисы
docker-compose down
# Перезапустить конкретный сервис
docker-compose restart backend
# Пересобрать и запустить
docker-compose up -d --build
# Просмотр логов конкретного сервиса
docker-compose logs -f backend
# Выполнить команду в контейнере
docker-compose exec backend sh
```
### Бекапы
```bash
# Ручной бекап
ssh root@103.80.87.247 '/usr/local/bin/backup-cron.sh'
# Список бекапов
ssh root@103.80.87.247 'ls -lh /var/nakama/backups/'
# Восстановление
ssh root@103.80.87.247
cd /var/nakama/backups
tar -xzf nakama_backup_YYYY-MM-DD_HH-MM-SS.tar.gz
mongorestore --uri="mongodb://localhost:27017" --drop --gzip --db nakama nakama_backup_*/nakama/
```
---
## 📊 Мониторинг
### Проверка здоровья системы
```bash
# Статус Docker контейнеров
docker-compose ps
# Использование ресурсов
docker stats
# Логи в реальном времени
docker-compose logs -f
# Проверка MongoDB
ssh root@103.80.87.247 'systemctl status mongod'
# Свободное место на диске
ssh root@103.80.87.247 'df -h'
```
---
## ⚠️ Важные замечания
1. **Безопасность MongoDB:**
- Настройте аутентификацию MongoDB (см. DEPLOYMENT_GUIDE.md)
- Используйте firewall для ограничения доступа к порту 27017
- Регулярно обновляйте MongoDB
2. **Переменные окружения:**
- Никогда не коммитьте `.env` файл в git
- Используйте надежные пароли и секретные ключи
- JWT_SECRET должен быть случайной строкой минимум 32 символа
3. **Бекапы:**
- Проверяйте успешность создания бекапов
- Периодически проверяйте возможность восстановления
- Храните бекапы на отдельном диске/сервере
4. **Обновления:**
- Создавайте бекап перед обновлением
- Тестируйте обновления на dev окружении
- Читайте CHANGELOG перед обновлением
5. **Производительность:**
- Мониторьте использование ресурсов
- Настройте индексы в MongoDB для часто используемых запросов
- Используйте Redis для кэширования (опционально)
---
## 🆘 Поддержка
При возникновении проблем:
1. **Проверьте логи:**
```bash
docker-compose logs -f
```
2. **Проверьте документацию:**
- DEPLOYMENT_GUIDE.md
- CRON_SETUP.md
3. **Свяжитесь с поддержкой:**
- Telegram: https://t.me/NakamaReportbot
- GitHub Issues: [создайте issue]
---
## 📝 Список файлов для коммита
Все изменения готовы к коммиту:
### Измененные файлы:
- backend/server.js
- backend/bot.js
- backend/models/Post.js
- backend/routes/modApp.js
- backend/bots/serverMonitor.js
- frontend/index.html
- frontend/src/pages/Feed.jsx
- frontend/src/pages/Profile.jsx
- moderation/frontend/src/App.jsx
### Новые файлы:
- Dockerfile.backend
- Dockerfile.frontend
- Dockerfile.moderation
- docker-compose.yml
- nginx.conf
- nginx-moderation.conf
- .dockerignore
- backup-cron.sh
- setup-remote-storage.sh
- DEPLOYMENT_GUIDE.md
- CRON_SETUP.md
- CHANGES_SUMMARY.md
---
**Версия:** 2.2.0
**Дата:** 20 ноября 2025
**Автор:** AI Assistant (Claude Sonnet 4.5)

288
CRON_SETUP.md Normal file
View File

@ -0,0 +1,288 @@
# ⏰ Настройка автоматических бекапов через Cron
## 📋 Инструкция
### 1. Подключитесь к удаленному серверу
```bash
ssh root@103.80.87.247
```
### 2. Установите необходимые инструменты
```bash
# Убедитесь, что mongo-tools установлены
apt update
apt install -y mongodb-database-tools
```
### 3. Скопируйте скрипт бекапа на сервер
С вашего локального компьютера:
```bash
scp backup-cron.sh root@103.80.87.247:/usr/local/bin/
```
Или создайте файл напрямую на сервере:
```bash
nano /usr/local/bin/backup-cron.sh
# Вставьте содержимое из backup-cron.sh
```
### 4. Сделайте скрипт исполняемым
```bash
chmod +x /usr/local/bin/backup-cron.sh
```
### 5. Настройте cron
```bash
# Откройте crontab для редактирования
crontab -e
```
### 6. Добавьте задачи в crontab
Выберите один из вариантов:
#### Вариант 1: Еженедельные бекапы (воскресенье в 3:00 ночи)
```cron
# Еженедельный бекап базы данных Nakama
0 3 * * 0 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
```
#### Вариант 2: Ежедневные бекапы (каждый день в 3:00 ночи)
```cron
# Ежедневный бекап базы данных Nakama
0 3 * * * /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
```
#### Вариант 3: Несколько бекапов в неделю (пн, ср, пт в 3:00)
```cron
# Бекапы по понедельникам, средам и пятницам
0 3 * * 1,3,5 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
```
### 7. Проверьте настройку cron
```bash
# Просмотреть текущие задачи cron
crontab -l
# Проверить статус службы cron
systemctl status cron
```
### 8. Тестирование
Запустите бекап вручную:
```bash
/usr/local/bin/backup-cron.sh
```
Проверьте созданные бекапы:
```bash
ls -lh /mnt/nakama-backups/
```
Просмотрите лог:
```bash
tail -50 /var/log/nakama-backup.log
```
---
## 📊 Синтаксис Cron
```
* * * * * команда
│ │ │ │ │
│ │ │ │ └─── День недели (0-7, где 0 и 7 = воскресенье)
│ │ │ └───── Месяц (1-12)
│ │ └─────── День месяца (1-31)
│ └───────── Час (0-23)
└─────────── Минута (0-59)
```
### Примеры расписаний
| Расписание | Синтаксис Cron | Описание |
|-----------|----------------|----------|
| Каждую минуту | `* * * * *` | Выполняется каждую минуту |
| Каждый час | `0 * * * *` | Выполняется в начале каждого часа |
| Раз в день (в полночь) | `0 0 * * *` | Выполняется в 00:00 каждый день |
| Раз в день (в 3:00) | `0 3 * * *` | Выполняется в 03:00 каждый день |
| Раз в неделю (воскресенье) | `0 3 * * 0` | Выполняется в воскресенье в 03:00 |
| Раз в месяц | `0 0 1 * *` | Выполняется 1-го числа каждого месяца в 00:00 |
| Каждые 6 часов | `0 */6 * * *` | Выполняется в 00:00, 06:00, 12:00, 18:00 |
| Рабочие дни в 9:00 | `0 9 * * 1-5` | Выполняется пн-пт в 09:00 |
---
## 🔧 Настройка уведомлений (опционально)
Чтобы получать уведомления о статусе бекапов в Telegram:
### 1. Создайте бота для уведомлений
1. Напишите [@BotFather](https://t.me/BotFather) в Telegram
2. Отправьте команду `/newbot`
3. Следуйте инструкциям
4. Скопируйте токен бота
### 2. Получите свой Chat ID
1. Напишите боту [@userinfobot](https://t.me/userinfobot)
2. Скопируйте свой Chat ID
### 3. Обновите скрипт бекапа
Откройте `/usr/local/bin/backup-cron.sh` и раскомментируйте последние строки:
```bash
# В конце скрипта найдите и раскомментируйте:
curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/sendMessage" \
-d "chat_id=<YOUR_CHAT_ID>" \
-d "text=✅ Резервная копия Nakama успешно создана: ${BACKUP_NAME}.tar.gz (${BACKUP_SIZE})"
```
Замените:
- `<YOUR_BOT_TOKEN>` на токен вашего бота
- `<YOUR_CHAT_ID>` на ваш Chat ID
---
## 📝 Полезные команды
### Управление cron
```bash
# Открыть crontab для редактирования
crontab -e
# Показать текущие задачи
crontab -l
# Удалить все задачи
crontab -r
# Открыть crontab другого пользователя (требуются права root)
crontab -u username -e
```
### Просмотр логов
```bash
# Показать последние 50 строк лога бекапов
tail -50 /var/log/nakama-backup.log
# Следить за логом в реальном времени
tail -f /var/log/nakama-backup.log
# Показать все ошибки в логе
grep -i error /var/log/nakama-backup.log
# Показать системный лог cron
grep CRON /var/log/syslog
```
### Управление бекапами
```bash
# Список всех бекапов
ls -lht /mnt/nakama-backups/
# Размер директории с бекапами
du -sh /mnt/nakama-backups/
# Удалить бекапы старше 30 дней
find /mnt/nakama-backups/ -name "nakama_backup_*.tar.gz" -type f -mtime +30 -delete
# Подсчитать количество бекапов
ls -1 /mnt/nakama-backups/ | wc -l
```
---
## ⚠️ Важные замечания
1. **Время выполнения**: Убедитесь, что время в cron указано в часовом поясе сервера. Проверьте:
```bash
timedatectl
```
2. **Права доступа**: Убедитесь, что у пользователя, под которым запускается cron, есть права на запись в директорию бекапов.
3. **Место на диске**: Регулярно проверяйте свободное место:
```bash
df -h /mnt/nakama-backups/
```
4. **Ротация логов**: Настройте logrotate для `/var/log/nakama-backup.log`:
```bash
nano /etc/logrotate.d/nakama-backup
```
Добавьте:
```
/var/log/nakama-backup.log {
weekly
rotate 4
compress
missingok
notifempty
}
```
5. **Мониторинг**: Регулярно проверяйте, что бекапы создаются успешно:
```bash
ls -lt /mnt/nakama-backups/ | head
```
---
## 🔄 Восстановление из бекапа
Если понадобится восстановить базу данных:
```bash
# 1. Перейти в директорию с бекапами
cd /mnt/nakama-backups/
# 2. Найти нужный бекап
ls -lt | grep nakama_backup
# 3. Распаковать бекап
tar -xzf nakama_backup_YYYY-MM-DD_HH-MM-SS.tar.gz
# 4. Восстановить базу данных
mongorestore --uri="mongodb://localhost:27017" \
--drop \
--gzip \
--db nakama \
nakama_backup_YYYY-MM-DD_HH-MM-SS/nakama/
# 5. Удалить распакованную директорию
rm -rf nakama_backup_YYYY-MM-DD_HH-MM-SS/
```
---
## 📞 Помощь
Если у вас возникли проблемы с настройкой cron:
1. Проверьте логи: `tail -f /var/log/nakama-backup.log`
2. Проверьте системный лог: `grep CRON /var/log/syslog`
3. Убедитесь, что cron запущен: `systemctl status cron`
4. Запустите скрипт вручную для отладки: `/usr/local/bin/backup-cron.sh`

477
DEPLOYMENT_GUIDE.md Normal file
View File

@ -0,0 +1,477 @@
# 🚀 Руководство по развертыванию Nakama
## 📋 Содержание
1. [Требования](#требования)
2. [Настройка удаленного сервера](#настройка-удаленного-сервера)
3. [Настройка базы данных](#настройка-базы-данных)
4. [Настройка автоматических бекапов](#настройка-автоматических-бекапов)
5. [Развертывание с Docker](#развертывание-с-docker)
6. [Настройка nginx](#настройка-nginx)
7. [Мониторинг и обслуживание](#мониторинг-и-обслуживание)
---
## ✅ Требования
### Локальный сервер (где запускается приложение)
- Docker 20.10+
- Docker Compose 2.0+
- SSHFS (для монтирования удаленных директорий)
- 2GB+ RAM
- 10GB+ свободного места на диске
### Удаленный сервер (103.80.87.247)
- SSH доступ
- MongoDB 7+
- 50GB+ свободного места для базы данных и медиа
- Открытый порт 27017 для MongoDB
---
## 🗄️ Настройка удаленного сервера
### 1. Подключение по SSH
```bash
ssh root@103.80.87.247
```
### 2. Установка MongoDB
```bash
# Обновить систему
apt update && apt upgrade -y
# Импортировать GPG ключ MongoDB
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
# Добавить репозиторий MongoDB
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | \
sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
# Установить MongoDB
apt update
apt install -y mongodb-org
# Запустить MongoDB
systemctl start mongod
systemctl enable mongod
```
### 3. Настройка MongoDB для удаленного доступа
```bash
# Редактировать конфигурацию
nano /etc/mongod.conf
```
Изменить `bindIp`:
```yaml
net:
port: 27017
bindIp: 0.0.0.0 # Разрешить подключения со всех IP
```
Настроить аутентификацию (рекомендуется):
```bash
# Подключиться к MongoDB
mongosh
# Создать администратора
use admin
db.createUser({
user: "admin",
pwd: "your_secure_password",
roles: [ { role: "root", db: "admin" } ]
})
# Создать пользователя для приложения
use nakama
db.createUser({
user: "nakama_user",
pwd: "your_app_password",
roles: [ { role: "readWrite", db: "nakama" } ]
})
```
Перезапустить MongoDB:
```bash
systemctl restart mongod
```
### 4. Создание директорий для хранения данных
```bash
# Создать директории
mkdir -p /var/nakama/media
mkdir -p /var/nakama/db
mkdir -p /var/nakama/backups
# Установить права доступа
chmod -R 755 /var/nakama
```
---
## 💾 Настройка базы данных
### Строка подключения
Обновите `.env` файл:
```env
# Без аутентификации
MONGODB_URI=mongodb://103.80.87.247:27017/nakama
# С аутентификацией
MONGODB_URI=mongodb://nakama_user:your_app_password@103.80.87.247:27017/nakama
```
---
## 🔄 Настройка автоматических бекапов
### 1. Установка cron на удаленном сервере
```bash
# Скопировать скрипт бекапа на удаленный сервер
scp backup-cron.sh root@103.80.87.247:/usr/local/bin/
# Подключиться к удаленному серверу
ssh root@103.80.87.247
# Сделать скрипт исполняемым
chmod +x /usr/local/bin/backup-cron.sh
# Открыть crontab
crontab -e
```
### 2. Добавить задачу в crontab
Добавьте следующую строку для запуска бекапа каждое воскресенье в 3:00 ночи:
```cron
0 3 * * 0 /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
```
Для ежедневных бекапов в 3:00 ночи:
```cron
0 3 * * * /usr/local/bin/backup-cron.sh >> /var/log/nakama-backup.log 2>&1
```
### 3. Проверка работы бекапов
```bash
# Запустить бекап вручную
/usr/local/bin/backup-cron.sh
# Проверить созданные бекапы
ls -lh /var/nakama/backups/
# Просмотреть лог
tail -f /var/log/nakama-backup.log
```
### 4. Восстановление из бекапа
```bash
# Распаковать бекап
cd /var/nakama/backups
tar -xzf nakama_backup_YYYY-MM-DD_HH-MM-SS.tar.gz
# Восстановить базу данных
mongorestore --uri="mongodb://103.80.87.247:27017" --gzip --db nakama nakama_backup_YYYY-MM-DD_HH-MM-SS/nakama/
```
---
## 🐳 Развертывание с Docker
### 1. Подготовка
```bash
cd /Users/glpshchn/Desktop/nakama
# Скопировать и настроить .env
cp .env.example .env
nano .env # Заполнить необходимые переменные
```
### 2. Монтирование удаленных директорий (опционально)
Если вы хотите хранить медиа и бекапы на удаленном сервере:
```bash
# Запустить скрипт настройки
./setup-remote-storage.sh
```
### 3. Сборка и запуск контейнеров
```bash
# Собрать все сервисы
docker-compose build
# Запустить в фоновом режиме
docker-compose up -d
# Проверить статус
docker-compose ps
# Просмотреть логи
docker-compose logs -f
```
### 4. Проверка работы
```bash
# Проверить backend
curl http://localhost:3000/health
# Проверить frontend
curl http://localhost:5173
# Проверить moderation
curl http://localhost:5174
```
### 5. Остановка и обновление
```bash
# Остановить все сервисы
docker-compose down
# Пересобрать и перезапустить
docker-compose up -d --build
# Перезапустить конкретный сервис
docker-compose restart backend
```
---
## 🌐 Настройка nginx (reverse proxy)
### Конфигурация для production
Создайте файл `/etc/nginx/sites-available/nakama`:
```nginx
# Frontend (основное приложение)
server {
listen 80;
server_name nakama.yourdomain.com;
location / {
proxy_pass http://localhost:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
# Moderation (система модерации)
server {
listen 80;
server_name mod.nakama.yourdomain.com;
location / {
proxy_pass http://localhost:5174;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
# Backend API
server {
listen 80;
server_name api.nakama.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
# WebSocket support
proxy_read_timeout 86400;
}
}
```
Активировать конфигурацию:
```bash
# Создать символическую ссылку
ln -s /etc/nginx/sites-available/nakama /etc/nginx/sites-enabled/
# Проверить конфигурацию
nginx -t
# Перезапустить nginx
systemctl restart nginx
```
### SSL сертификаты (Let's Encrypt)
```bash
# Установить certbot
apt install certbot python3-certbot-nginx
# Получить сертификаты
certbot --nginx -d nakama.yourdomain.com -d mod.nakama.yourdomain.com -d api.nakama.yourdomain.com
# Автоматическое обновление
certbot renew --dry-run
```
---
## 📊 Мониторинг и обслуживание
### Просмотр логов
```bash
# Логи backend
docker-compose logs -f backend
# Логи всех сервисов
docker-compose logs -f
# Логи MongoDB на удаленном сервере
ssh root@103.80.87.247 'tail -f /var/log/mongodb/mongod.log'
```
### Мониторинг ресурсов
```bash
# Использование Docker контейнеров
docker stats
# Использование диска
df -h
# Статус MongoDB (на удаленном сервере)
ssh root@103.80.87.247 'systemctl status mongod'
```
### Обновление приложения
```bash
# 1. Создать бекап
./backup-cron.sh
# 2. Получить обновления
git pull
# 3. Остановить контейнеры
docker-compose down
# 4. Пересобрать и запустить
docker-compose up -d --build
# 5. Проверить работу
docker-compose ps
docker-compose logs -f
```
### Очистка
```bash
# Удалить неиспользуемые образы
docker image prune -a
# Удалить неиспользуемые volumes
docker volume prune
# Полная очистка Docker
docker system prune -a --volumes
```
---
## 🔧 Решение проблем
### База данных недоступна
```bash
# Проверить подключение к MongoDB
mongosh --host 103.80.87.247 --port 27017
# Проверить статус MongoDB на удаленном сервере
ssh root@103.80.87.247 'systemctl status mongod'
# Проверить логи
ssh root@103.80.87.247 'tail -100 /var/log/mongodb/mongod.log'
```
### Контейнеры не запускаются
```bash
# Проверить логи
docker-compose logs
# Пересоздать контейнеры
docker-compose down -v
docker-compose up -d --force-recreate
```
### Проблемы с медиа
```bash
# Проверить монтирование директорий
df -h | grep nakama
# Проверить права доступа
ls -la /mnt/nakama-media
```
---
## 📝 Дополнительные команды
### Создание ручного бекапа
```bash
docker-compose exec mongodb mongodump --uri="mongodb://103.80.87.247:27017/nakama" --out=/backups/manual_backup
```
### Экспорт/импорт данных
```bash
# Экспорт коллекции
docker-compose exec mongodb mongoexport --uri="mongodb://103.80.87.247:27017/nakama" --collection=posts --out=/backups/posts.json
# Импорт коллекции
docker-compose exec mongodb mongoimport --uri="mongodb://103.80.87.247:27017/nakama" --collection=posts --file=/backups/posts.json
```
### Масштабирование
Для горизонтального масштабирования backend:
```bash
docker-compose up -d --scale backend=3
```
---
## 📞 Поддержка
При возникновении проблем:
1. Проверьте логи: `docker-compose logs -f`
2. Проверьте статус сервисов: `docker-compose ps`
3. Создайте issue на GitHub или напишите в https://t.me/NakamaReportbot

21
Dockerfile.backend Normal file
View File

@ -0,0 +1,21 @@
FROM node:20-alpine
WORKDIR /app
# Установка зависимостей
COPY package*.json ./
RUN npm ci --only=production
# Копирование backend кода
COPY backend ./backend
# Создание директории для uploads
RUN mkdir -p backend/uploads/posts backend/uploads/mod-channel
EXPOSE 3000
# Переменные окружения
ENV NODE_ENV=production
CMD ["node", "backend/server.js"]

29
Dockerfile.frontend Normal file
View File

@ -0,0 +1,29 @@
FROM node:20-alpine AS builder
WORKDIR /app
# Установка зависимостей
COPY frontend/package*.json ./
RUN npm ci
# Копирование исходников
COPY frontend ./
# Сборка проекта
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build
# Production stage
FROM nginx:alpine
# Копирование собранного приложения
COPY --from=builder /app/dist /usr/share/nginx/html
# Копирование конфигурации nginx
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

29
Dockerfile.moderation Normal file
View File

@ -0,0 +1,29 @@
FROM node:20-alpine AS builder
WORKDIR /app
# Установка зависимостей
COPY moderation/frontend/package*.json ./
RUN npm ci
# Копирование исходников
COPY moderation/frontend ./
# Сборка проекта
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build
# Production stage
FROM nginx:alpine
# Копирование собранного приложения
COPY --from=builder /app/dist /usr/share/nginx/html
# Копирование конфигурации nginx
COPY nginx-moderation.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -213,7 +213,7 @@ async function sendPhotosToUser(userId, photos) {
media.push({
type: isVideo ? 'video' : 'photo',
media: photoUrl,
caption: index === 0 ? `<b>Из NakamaHost</b>\n${batch.length} фото` : undefined,
caption: index === 0 ? `<b>Из Nakama</b>\n${batch.length} фото` : undefined,
parse_mode: 'HTML',
...(isVideo ? { supports_streaming: true } : {})
});
@ -224,7 +224,7 @@ async function sendPhotosToUser(userId, photos) {
media.push({
type: isVideo ? 'video' : 'photo',
media: photoUrl,
caption: index === 0 ? `<b>Из NakamaHost</b>\n${batch.length} фото` : undefined,
caption: index === 0 ? `<b>Из Nakama</b>\n${batch.length} фото` : undefined,
parse_mode: 'HTML',
...(isVideo ? { supports_streaming: true } : {})
});
@ -252,7 +252,7 @@ async function handleWebAppData(userId, dataString) {
const data = JSON.parse(dataString);
if (data.action === 'send_image') {
const caption = `<b>Из NakamaHost</b>\n\n${data.caption || ''}`;
const caption = `<b>Из Nakama</b>\n\n${data.caption || ''}`;
await sendPhotoToUser(userId, data.url, caption);
return { success: true, message: 'Изображение отправлено!' };
}

View File

@ -376,9 +376,13 @@ const sendChannelMediaGroup = async (files, caption) => {
});
try {
await axios.post(`${TELEGRAM_API}/sendMediaGroup`, form, {
const response = await axios.post(`${TELEGRAM_API}/sendMediaGroup`, form, {
headers: form.getHeaders()
});
// Вернуть ID первого сообщения из группы
const messageId = response.data?.result?.[0]?.message_id;
return messageId;
} catch (error) {
log('error', 'Не удалось отправить медиа-группу в канал', { error: error.response?.data || error.message });
throw error;
@ -410,10 +414,41 @@ const sendMessageToUser = async (userId, message) => {
}
};
/**
* Обновить сообщение в канале (только текст)
*/
const updateChannelMessage = async (messageId, content, hashtags) => {
if (!TELEGRAM_API) {
throw new Error('Бот модерации не инициализирован');
}
const chatId = config.moderationChannelUsername || '@reichenbfurry';
// Формируем текст с хэштегами
const text = [content, hashtags?.length ? hashtags.map(tag => `#${tag}`).join(' ') : ''].filter(Boolean).join('\n\n');
try {
await axios.post(`${TELEGRAM_API}/editMessageCaption`, {
chat_id: chatId,
message_id: messageId,
caption: `${text}${ERROR_SUPPORT_SUFFIX}`,
parse_mode: 'HTML'
});
log('info', 'Сообщение в канале обновлено', { messageId });
} catch (error) {
log('error', 'Не удалось обновить сообщение в канале', {
messageId,
error: error.response?.data || error.message
});
throw error;
}
};
module.exports = {
startServerMonitorBot,
sendChannelMediaGroup,
sendMessageToUser,
updateChannelMessage,
isModerationAdmin
};

View File

@ -59,6 +59,20 @@ const PostSchema = new mongoose.Schema({
type: Number,
default: 0
},
// Поля для постов из канала
publishedToChannel: {
type: Boolean,
default: false
},
channelMessageId: {
type: Number
},
adminNumber: {
type: Number
},
editedAt: {
type: Date
},
createdAt: {
type: Date,
default: Date.now

View File

@ -191,6 +191,9 @@ router.get('/posts', authenticateModeration, requireModerationAccess, async (req
commentsCount: post.comments?.length || 0,
likesCount: post.likes?.length || 0,
isNSFW: post.isNSFW,
publishedToChannel: post.publishedToChannel,
adminNumber: post.adminNumber,
editedAt: post.editedAt,
createdAt: post.createdAt
}));
@ -205,11 +208,29 @@ router.get('/posts', authenticateModeration, requireModerationAccess, async (req
router.put('/posts/:id', authenticateModeration, requireModerationAccess, async (req, res) => {
const { content, hashtags, tags, isNSFW } = req.body;
const post = await Post.findById(req.params.id);
const post = await Post.findById(req.params.id).populate('author');
if (!post) {
return res.status(404).json({ error: 'Пост не найден' });
}
// Проверить, может ли админ редактировать этот пост
// Админ может редактировать:
// 1. Любой пост, если он владелец (req.isOwner)
// 2. Только свои посты из канала (где adminNumber совпадает)
if (!req.isOwner) {
// Получить админа текущего пользователя
const admin = await ModerationAdmin.findOne({ telegramId: req.user.telegramId });
// Если это пост из канала, проверить, что админ - автор
if (post.publishedToChannel && post.adminNumber) {
if (!admin || admin.adminNumber !== post.adminNumber) {
return res.status(403).json({ error: 'Вы можете редактировать только свои посты из канала' });
}
}
// Если это обычный пост, владелец может редактировать любой, остальные админы - нет
// (это поведение можно изменить по необходимости)
}
if (content !== undefined) {
post.content = content;
post.hashtags = Array.isArray(hashtags)
@ -230,6 +251,19 @@ router.put('/posts/:id', authenticateModeration, requireModerationAccess, async
await post.populate('author', 'username firstName lastName role banned bannedUntil');
// Если пост был опубликован в канале, обновить его там
if (post.publishedToChannel && post.channelMessageId) {
try {
const { updateChannelMessage } = require('../bots/serverMonitor');
if (updateChannelMessage) {
await updateChannelMessage(post.channelMessageId, post.content, post.hashtags);
}
} catch (error) {
console.error('Не удалось обновить сообщение в канале:', error);
// Продолжаем выполнение, даже если не удалось обновить в канале
}
}
res.json({
post: {
id: post._id,
@ -239,6 +273,8 @@ router.put('/posts/:id', authenticateModeration, requireModerationAccess, async
tags: post.tags,
images: post.images,
isNSFW: post.isNSFW,
publishedToChannel: post.publishedToChannel,
adminNumber: post.adminNumber,
editedAt: post.editedAt,
createdAt: post.createdAt
}
@ -688,8 +724,25 @@ router.post(
const caption = captionLines.join('\n');
try {
await sendChannelMediaGroup(files, caption);
res.json({ success: true });
// Отправить в канал и получить message_id
const messageId = await sendChannelMediaGroup(files, caption);
// Создать пост в базе данных
const newPost = new Post({
author: req.user._id,
content: description,
hashtags: tagsArray.map(tag => tag.replace('#', '').toLowerCase()),
images: [], // Медиа хранится в Telegram
tags: ['other'], // Можно настроить определение типа контента
publishedToChannel: true,
channelMessageId: messageId,
adminNumber: slotNumber,
isNSFW: false
});
await newPost.save();
res.json({ success: true, postId: newPost._id, messageId });
} catch (error) {
logSecurityEvent('CHANNEL_PUBLISH_FAILED', req, { error: error.message });
res.status(500).json({ error: 'Не удалось опубликовать в канал' });

View File

@ -175,7 +175,7 @@ app.use('/api/mod-app', require('./routes/modApp'));
// Базовый роут
app.get('/', (req, res) => {
res.json({ message: 'NakamaHost API работает' });
res.json({ message: 'Nakama API работает' });
});
// 404 handler

72
backup-cron.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/bash
# Скрипт для автоматического резервного копирования MongoDB
# Запускается раз в неделю через cron
# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Конфигурация
BACKUP_DIR="/mnt/nakama-backups"
MONGODB_URI="mongodb://103.80.87.247:27017/nakama"
DB_NAME="nakama"
DATE=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_NAME="nakama_backup_${DATE}"
RETENTION_DAYS=30
echo -e "${GREEN}=== Начало резервного копирования ===${NC}"
echo "Дата: $(date)"
echo "База данных: ${DB_NAME}"
echo "Директория бекапов: ${BACKUP_DIR}"
# Создать директорию для бекапов, если она не существует
mkdir -p "${BACKUP_DIR}"
# Выполнить mongodump
echo -e "${YELLOW}Создание резервной копии...${NC}"
if mongodump --uri="${MONGODB_URI}" --db="${DB_NAME}" --out="${BACKUP_DIR}/${BACKUP_NAME}" --gzip; then
echo -e "${GREEN}✓ Резервная копия успешно создана${NC}"
# Создать архив
echo -e "${YELLOW}Создание архива...${NC}"
cd "${BACKUP_DIR}" || exit 1
if tar -czf "${BACKUP_NAME}.tar.gz" "${BACKUP_NAME}"; then
echo -e "${GREEN}✓ Архив создан: ${BACKUP_NAME}.tar.gz${NC}"
# Удалить временную директорию
rm -rf "${BACKUP_NAME}"
# Получить размер архива
BACKUP_SIZE=$(du -h "${BACKUP_NAME}.tar.gz" | cut -f1)
echo -e "${GREEN}Размер архива: ${BACKUP_SIZE}${NC}"
else
echo -e "${RED}✗ Ошибка создания архива${NC}"
exit 1
fi
else
echo -e "${RED}✗ Ошибка создания резервной копии${NC}"
exit 1
fi
# Удалить старые бекапы (старше RETENTION_DAYS дней)
echo -e "${YELLOW}Удаление старых бекапов (старше ${RETENTION_DAYS} дней)...${NC}"
find "${BACKUP_DIR}" -name "nakama_backup_*.tar.gz" -type f -mtime +${RETENTION_DAYS} -delete
REMAINING_BACKUPS=$(find "${BACKUP_DIR}" -name "nakama_backup_*.tar.gz" -type f | wc -l)
echo -e "${GREEN}Оставшихся бекапов: ${REMAINING_BACKUPS}${NC}"
# Вывести список последних бекапов
echo -e "${YELLOW}Последние 5 бекапов:${NC}"
ls -lht "${BACKUP_DIR}"/nakama_backup_*.tar.gz | head -5
echo -e "${GREEN}=== Резервное копирование завершено ===${NC}"
echo ""
# Отправить уведомление (опционально)
# Раскомментируйте, если хотите получать уведомления
# curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
# -d "chat_id=${YOUR_CHAT_ID}" \
# -d "text=✅ Резервная копия Nakama успешно создана: ${BACKUP_NAME}.tar.gz (${BACKUP_SIZE})"

114
docker-compose.yml Normal file
View File

@ -0,0 +1,114 @@
version: '3.8'
services:
backend:
build:
context: .
dockerfile: Dockerfile.backend
container_name: nakama-backend
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
- MONGODB_URI=${MONGODB_URI:-mongodb://103.80.87.247:27017/nakama}
- JWT_SECRET=${JWT_SECRET}
- JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- MODERATION_BOT_TOKEN=${MODERATION_BOT_TOKEN}
- MODERATION_OWNER_USERNAMES=${MODERATION_OWNER_USERNAMES:-glpshchn00}
- MODERATION_CHANNEL_USERNAME=${MODERATION_CHANNEL_USERNAME:-@reichenbfurry}
- GELBOORU_API_KEY=${GELBOORU_API_KEY}
- GELBOORU_USER_ID=${GELBOORU_USER_ID}
- FRONTEND_URL=${FRONTEND_URL:-http://localhost:5173}
- CORS_ORIGIN=${CORS_ORIGIN:-*}
- REDIS_URL=${REDIS_URL}
volumes:
# Медиа хранится на удаленном сервере, монтируем через NFS или SSH
- /mnt/nakama-media:/app/backend/uploads
networks:
- nakama-network
depends_on:
- mongodb
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
args:
VITE_API_URL: ${VITE_API_URL:-http://localhost:3000/api}
container_name: nakama-frontend
restart: unless-stopped
ports:
- "5173:80"
networks:
- nakama-network
depends_on:
- backend
moderation:
build:
context: .
dockerfile: Dockerfile.moderation
args:
VITE_API_URL: ${VITE_API_URL:-http://localhost:3000/api}
container_name: nakama-moderation
restart: unless-stopped
ports:
- "5174:80"
networks:
- nakama-network
depends_on:
- backend
mongodb:
image: mongo:7
container_name: nakama-mongodb
restart: unless-stopped
ports:
- "27017:27017"
environment:
- MONGO_INITDB_DATABASE=nakama
volumes:
# База данных на удаленном сервере
- /mnt/nakama-db:/data/db
networks:
- nakama-network
command: mongod --bind_ip_all
backup:
image: mongo:7
container_name: nakama-backup
restart: "no"
environment:
- MONGODB_URI=${MONGODB_URI:-mongodb://103.80.87.247:27017/nakama}
volumes:
- /mnt/nakama-backups:/backups
- ./backend/scripts/backup.js:/backup.js
networks:
- nakama-network
depends_on:
- mongodb
entrypoint: /bin/bash
command: -c "echo 'Backup container ready. Run manual backups or set up cron.'"
networks:
nakama-network:
driver: bridge
volumes:
nakama-db:
driver: local
nakama-media:
driver: local
nakama-backups:
driver: local

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<title>NakamaHost</title>
<title>Nakama</title>
<!-- Telegram Web App SDK - прямая загрузка -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>

View File

@ -106,7 +106,7 @@ export default function Feed({ user }) {
<div className="feed-page">
{/* Хедер */}
<div className="feed-header">
<h1>NakamaHost</h1>
<h1>Nakama</h1>
<button className="create-btn" onClick={handleCreatePost}>
<Plus size={20} />
</button>

View File

@ -170,7 +170,7 @@ export default function Profile({ user, setUser }) {
</div>
<div className="donation-text">
<h3>Поддержите проект</h3>
<p>Каждый взнос помогает развивать NakamaHost и запускать новые функции.</p>
<p>Каждый взнос помогает развивать Nakama и запускать новые функции.</p>
</div>
</div>
<button className="donation-button" onClick={handleDonate}>

View File

@ -286,14 +286,18 @@ export default function App() {
import.meta.env.PROD ? window.location.origin : 'http://localhost:3000'
);
console.log('Инициализация чата, подключение к:', API_URL);
const socket = io(`${API_URL}/mod-chat`, {
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5
reconnectionAttempts: 5,
timeout: 10000
});
socket.on('connect', () => {
console.log('WebSocket подключен, отправка auth...');
socket.emit('auth', {
username: user.username,
telegramId: user.telegramId
@ -301,42 +305,57 @@ export default function App() {
});
socket.on('ready', () => {
console.log('Авторизация успешна!');
setChatState((prev) => ({ ...prev, connected: true }));
});
socket.on('unauthorized', () => {
console.error('Unauthorized в чате');
setChatState((prev) => ({ ...prev, connected: false }));
socket.disconnect();
});
socket.on('message', (message) => {
console.log('Получено сообщение:', message);
setChatState((prev) => ({
...prev,
messages: [...prev.messages, message]
}));
if (chatListRef.current) {
chatListRef.current.scrollTo({
top: chatListRef.current.scrollHeight,
behavior: 'smooth'
});
}
setTimeout(() => {
if (chatListRef.current) {
chatListRef.current.scrollTo({
top: chatListRef.current.scrollHeight,
behavior: 'smooth'
});
}
}, 100);
});
socket.on('online', (online) => {
console.log('Обновление списка онлайн:', online);
setChatState((prev) => ({ ...prev, online }));
});
socket.on('disconnect', () => {
socket.on('disconnect', (reason) => {
console.log('WebSocket отключен:', reason);
setChatState((prev) => ({ ...prev, connected: false }));
});
socket.on('connect_error', (error) => {
console.error('Ошибка подключения WebSocket:', error);
});
chatSocketRef.current = socket;
};
const handleSendChat = () => {
if (!chatSocketRef.current || !chatState.connected) return;
if (!chatSocketRef.current || !chatState.connected) {
console.warn('Чат не подключен');
return;
}
const text = chatInput.trim();
if (!text) return;
console.log('Отправка сообщения:', text);
chatSocketRef.current.emit('message', { text });
setChatInput('');
};
@ -515,14 +534,24 @@ export default function App() {
</div>
{post.images?.length ? (
<div className="image-grid">
{post.images.map((img, idx) => (
<div key={idx} className="image-thumb">
<img src={img} alt="" />
<button className="image-remove" onClick={() => handleRemoveImage(post.id, idx)}>
<Trash2 size={16} />
</button>
</div>
))}
{post.images.map((img, idx) => {
// Преобразовать относительный путь в абсолютный
const imageUrl = img.startsWith('http')
? img
: `${import.meta.env.VITE_API_URL || (import.meta.env.PROD ? window.location.origin : 'http://localhost:3000')}${img}`;
return (
<div key={idx} className="image-thumb">
<img src={imageUrl} alt="" onError={(e) => {
e.target.style.display = 'none';
console.error('Failed to load image:', imageUrl);
}} />
<button className="image-remove" onClick={() => handleRemoveImage(post.id, idx)}>
<Trash2 size={16} />
</button>
</div>
);
})}
</div>
) : null}
</div>
@ -569,24 +598,84 @@ export default function App() {
</div>
<div className="list-item-subtitle">Статус: {report.status}</div>
<div className="report-content">
<p>{report.reason || 'Причина не указана'}</p>
<div style={{ marginBottom: '12px', padding: '12px', backgroundColor: 'var(--bg-secondary)', borderRadius: '8px' }}>
<strong>Причина жалобы:</strong>
<p style={{ marginTop: '4px' }}>{report.reason || 'Причина не указана'}</p>
</div>
{report.post && (
<div className="report-post">
<strong>Пост:</strong> {report.post.content || 'Без текста'}
<div className="report-post" style={{ padding: '12px', backgroundColor: 'var(--bg-secondary)', borderRadius: '8px', marginBottom: '12px' }}>
<div style={{ marginBottom: '8px' }}>
<strong>Пост от @{report.post.author?.username || 'Удалён'}</strong>
</div>
<div style={{ marginBottom: '8px' }}>
{report.post.content || 'Без текста'}
</div>
{report.post.images?.length > 0 && (
<div className="image-grid" style={{ marginTop: '8px' }}>
{report.post.images.slice(0, 3).map((img, idx) => {
const imageUrl = img.startsWith('http')
? img
: `${import.meta.env.VITE_API_URL || (import.meta.env.PROD ? window.location.origin : 'http://localhost:3000')}${img}`;
return (
<img
key={idx}
src={imageUrl}
alt=""
style={{
width: '80px',
height: '80px',
objectFit: 'cover',
borderRadius: '4px',
marginRight: '4px'
}}
onError={(e) => {
e.target.style.display = 'none';
console.error('Failed to load image:', imageUrl);
}}
/>
);
})}
{report.post.images.length > 3 && (
<span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>
+{report.post.images.length - 3} ещё
</span>
)}
</div>
)}
</div>
)}
</div>
</div>
<div className="list-item-actions">
{report.post && (
<>
<button className="btn danger" onClick={() => handlePostDelete(report.post.id)}>
<Trash2 size={16} />
Удалить пост
</button>
{report.post.author && (
<button className="btn warn" onClick={() => handleBanAuthor(report.post.id)}>
<Ban size={16} />
Забанить автора
</button>
)}
</>
)}
<button className="btn" onClick={() => handleReportStatus(report.id, 'resolved')}>
Решено
</button>
<button className="btn warn" onClick={() => handleReportStatus(report.id, 'dismissed')}>
Отклонить
<button className="btn" onClick={() => handleReportStatus(report.id, 'dismissed')}>
Отклонить репорт
</button>
</div>
</div>
))}
{reportsData.reports.length === 0 && (
<div style={{ textAlign: 'center', padding: '40px', color: 'var(--text-secondary)' }}>
Нет активных репортов
</div>
)}
</div>
)}
</div>

26
nginx-moderation.conf Normal file
View File

@ -0,0 +1,26 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
location / {
try_files $uri $uri/ /index.html;
}
# Кэширование статических файлов
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

26
nginx.conf Normal file
View File

@ -0,0 +1,26 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
location / {
try_files $uri $uri/ /index.html;
}
# Кэширование статических файлов
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

114
setup-remote-storage.sh Executable file
View File

@ -0,0 +1,114 @@
#!/bin/bash
# Скрипт для настройки удаленного хранилища медиа и базы данных
# Использует SSHFS для монтирования удаленных директорий
# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Конфигурация удаленного сервера
REMOTE_SERVER="103.80.87.247"
REMOTE_USER="root" # Измените на вашего пользователя
REMOTE_MEDIA_PATH="/var/nakama/media"
REMOTE_DB_PATH="/var/nakama/db"
REMOTE_BACKUPS_PATH="/var/nakama/backups"
# Локальные точки монтирования
LOCAL_MEDIA_MOUNT="/mnt/nakama-media"
LOCAL_DB_MOUNT="/mnt/nakama-db"
LOCAL_BACKUPS_MOUNT="/mnt/nakama-backups"
echo -e "${GREEN}=== Настройка удаленного хранилища ===${NC}"
# Проверка наличия SSHFS
if ! command -v sshfs &> /dev/null; then
echo -e "${RED}✗ SSHFS не установлен${NC}"
echo -e "${YELLOW}Установка SSHFS...${NC}"
# Определить ОС и установить SSHFS
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
sudo apt-get update && sudo apt-get install -y sshfs
elif [[ "$OSTYPE" == "darwin"* ]]; then
brew install macfuse sshfs
else
echo -e "${RED}Неподдерживаемая ОС. Установите SSHFS вручную.${NC}"
exit 1
fi
fi
echo -e "${GREEN}✓ SSHFS установлен${NC}"
# Создать локальные директории для монтирования
echo -e "${YELLOW}Создание локальных директорий...${NC}"
sudo mkdir -p "$LOCAL_MEDIA_MOUNT" "$LOCAL_DB_MOUNT" "$LOCAL_BACKUPS_MOUNT"
# Создать директории на удаленном сервере
echo -e "${YELLOW}Создание директорий на удаленном сервере...${NC}"
ssh "${REMOTE_USER}@${REMOTE_SERVER}" "mkdir -p ${REMOTE_MEDIA_PATH} ${REMOTE_DB_PATH} ${REMOTE_BACKUPS_PATH}"
# Монтировать директории
echo -e "${YELLOW}Монтирование удаленных директорий...${NC}"
# Медиа
if mountpoint -q "$LOCAL_MEDIA_MOUNT"; then
echo -e "${YELLOW}Медиа уже смонтированы${NC}"
else
sshfs "${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_MEDIA_PATH}" "$LOCAL_MEDIA_MOUNT" \
-o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
echo -e "${GREEN}✓ Медиа смонтированы: ${LOCAL_MEDIA_MOUNT}${NC}"
fi
# База данных
if mountpoint -q "$LOCAL_DB_MOUNT"; then
echo -e "${YELLOW}БД уже смонтирована${NC}"
else
sshfs "${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_DB_PATH}" "$LOCAL_DB_MOUNT" \
-o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
echo -e "${GREEN}✓ БД смонтирована: ${LOCAL_DB_MOUNT}${NC}"
fi
# Бекапы
if mountpoint -q "$LOCAL_BACKUPS_MOUNT"; then
echo -e "${YELLOW}Бекапы уже смонтированы${NC}"
else
sshfs "${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_BACKUPS_PATH}" "$LOCAL_BACKUPS_MOUNT" \
-o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
echo -e "${GREEN}✓ Бекапы смонтированы: ${LOCAL_BACKUPS_MOUNT}${NC}"
fi
# Добавить в /etc/fstab для автоматического монтирования при загрузке (опционально)
echo -e "${YELLOW}Хотите добавить автомонтирование при загрузке? (y/n)${NC}"
read -r RESPONSE
if [[ "$RESPONSE" =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Добавление в /etc/fstab...${NC}"
# Создать резервную копию fstab
sudo cp /etc/fstab /etc/fstab.backup
# Добавить записи в fstab
cat <<EOF | sudo tee -a /etc/fstab
# Nakama remote storage
${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_MEDIA_PATH} ${LOCAL_MEDIA_MOUNT} fuse.sshfs defaults,_netdev,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 0 0
${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_DB_PATH} ${LOCAL_DB_MOUNT} fuse.sshfs defaults,_netdev,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 0 0
${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_BACKUPS_PATH} ${LOCAL_BACKUPS_MOUNT} fuse.sshfs defaults,_netdev,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 0 0
EOF
echo -e "${GREEN}✓ Автомонтирование настроено${NC}"
fi
echo -e "${GREEN}=== Настройка завершена ===${NC}"
echo ""
echo "Монтированные директории:"
echo " Медиа: ${LOCAL_MEDIA_MOUNT}"
echo " БД: ${LOCAL_DB_MOUNT}"
echo " Бекапы: ${LOCAL_BACKUPS_MOUNT}"
echo ""
echo "Для размонтирования используйте:"
echo " sudo umount ${LOCAL_MEDIA_MOUNT}"
echo " sudo umount ${LOCAL_DB_MOUNT}"
echo " sudo umount ${LOCAL_BACKUPS_MOUNT}"