From ed85d8f6dbd9a12b03496de948fad63a734e96a8 Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Mon, 1 Dec 2025 03:51:23 +0300 Subject: [PATCH] Update files --- FIX_MONGODB.md | 332 ++++++++++++++++++++++ MINIO_403_FIX.md | 113 ++++++++ backend/jobs/avatarUpdater.js | 3 +- backend/middleware/auth.js | 119 +++++++- backend/routes/auth.js | 40 ++- backend/server.js | 3 +- check-post-creation.md | 167 +++++++++++ fix-minio-public.sh | 54 ++++ frontend/src/components/CommentsModal.jsx | 12 +- frontend/src/components/PostCard.jsx | 7 +- frontend/src/pages/CommentsPage.jsx | 12 +- frontend/src/pages/PostMenuPage.jsx | 7 +- frontend/src/pages/Profile.jsx | 7 +- frontend/src/pages/UserProfile.jsx | 7 +- minio-public-policy.json | 19 ++ 15 files changed, 853 insertions(+), 49 deletions(-) create mode 100644 FIX_MONGODB.md create mode 100644 MINIO_403_FIX.md create mode 100644 check-post-creation.md create mode 100644 fix-minio-public.sh create mode 100644 minio-public-policy.json diff --git a/FIX_MONGODB.md b/FIX_MONGODB.md new file mode 100644 index 0000000..ea9137f --- /dev/null +++ b/FIX_MONGODB.md @@ -0,0 +1,332 @@ +# 🔴 Решение проблемы MongoDB Connection + +## Проблема +``` +MongoServerSelectionError: connect ECONNREFUSED 103.80.87.247:27017 +``` + +Сервер не может подключиться к MongoDB на `103.80.87.247:27017`. + +--- + +## 🔍 Диагностика + +### 1. Подключитесь к серверу +```bash +ssh user@103.80.87.247 +``` + +### 2. Проверьте, запущен ли MongoDB +```bash +# Проверка статуса +sudo systemctl status mongod +# или +sudo systemctl status mongodb + +# Если не запущен - запустите +sudo systemctl start mongod +sudo systemctl enable mongod # автозапуск +``` + +### 3. Проверьте порт 27017 +```bash +# Слушает ли MongoDB порт? +sudo netstat -tlnp | grep 27017 +# или +sudo ss -tlnp | grep 27017 + +# Проверка соединения локально +mongo --eval "db.version()" +# или для новых версий MongoDB +mongosh --eval "db.version()" +``` + +### 4. Проверьте конфигурацию MongoDB +```bash +# Откройте конфиг +sudo nano /etc/mongod.conf + +# Найдите секцию net: +# net: +# port: 27017 +# bindIp: 127.0.0.1 # <-- ПРОБЛЕМА! Слушает только localhost + +# Измените на: +# net: +# port: 27017 +# bindIp: 0.0.0.0 # Слушать все интерфейсы +``` + +### 5. Перезапустите MongoDB +```bash +sudo systemctl restart mongod + +# Проверьте снова +sudo netstat -tlnp | grep 27017 +``` + +--- + +## ✅ Решения + +### Решение 1: MongoDB на том же сервере (локально) + +Если ваше приложение **работает на том же сервере** (103.80.87.247), используйте **localhost**: + +#### В Docker (docker-compose.yml) +```yaml +environment: + - MONGODB_URI=mongodb://localhost:27017/nakama +``` + +#### Или в .env файле +```bash +MONGODB_URI=mongodb://localhost:27017/nakama +``` + +#### Если MongoDB в Docker контейнере +```bash +# В docker-compose.yml используйте имя сервиса: +MONGODB_URI=mongodb://mongo:27017/nakama + +# Где mongo - имя сервиса MongoDB в docker-compose.yml +``` + +--- + +### Решение 2: Настроить MongoDB для удаленного доступа + +Если MongoDB на отдельном сервере: + +#### 1. Измените конфиг MongoDB +```bash +sudo nano /etc/mongod.conf +``` + +```yaml +# /etc/mongod.conf +net: + port: 27017 + bindIp: 0.0.0.0 # Слушать все интерфейсы + +security: + authorization: enabled # Включить авторизацию! +``` + +#### 2. Создайте пользователя +```bash +mongosh +``` + +```javascript +use admin +db.createUser({ + user: "nakama_admin", + pwd: "СИЛЬНЫЙ_ПАРОЛЬ_ЗДЕСЬ", + roles: [ + { role: "readWrite", db: "nakama" }, + { role: "dbAdmin", db: "nakama" } + ] +}) +``` + +#### 3. Обновите connection string +```bash +# В .env или docker-compose.yml +MONGODB_URI=mongodb://nakama_admin:ПАРОЛЬ@103.80.87.247:27017/nakama?authSource=admin +``` + +#### 4. Настройте Firewall +```bash +# UFW +sudo ufw allow 27017/tcp +sudo ufw reload + +# iptables +sudo iptables -A INPUT -p tcp --dport 27017 -j ACCEPT +sudo iptables-save +``` + +⚠️ **ВАЖНО:** Открытый MongoDB без пароля - **огромная дыра в безопасности**! + +--- + +### Решение 3: Использовать MongoDB Atlas (Рекомендуется) ☁️ + +Самый безопасный и простой вариант: + +#### 1. Создайте кластер +1. Зайдите на https://www.mongodb.com/cloud/atlas +2. Создайте бесплатный M0 кластер +3. Создайте пользователя БД +4. Добавьте IP сервера в Network Access (или `0.0.0.0/0` для всех) + +#### 2. Получите connection string +``` +mongodb+srv://username:password@cluster.mongodb.net/nakama?retryWrites=true&w=majority +``` + +#### 3. Обновите конфигурацию +```bash +# .env или docker-compose.yml +MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/nakama?retryWrites=true&w=majority +``` + +#### 4. Перезапустите приложение +```bash +docker-compose down +docker-compose up -d +# или +pm2 restart all +``` + +✅ **Преимущества Atlas:** +- Автоматические бэкапы +- Мониторинг +- Безопасность из коробки +- Бесплатный tier (512 MB) + +--- + +## 🚀 Быстрое решение (для теста) + +Если MongoDB **на том же сервере**, просто замените IP на localhost: + +```bash +# Найдите, где запущено приложение (Docker или PM2) +docker ps +# или +pm2 list + +# Остановите +docker-compose down +# или +pm2 stop all + +# Отредактируйте docker-compose.yml или .env: +nano docker-compose.yml + +# Замените: +MONGODB_URI=mongodb://103.80.87.247:27017/nakama +# на: +MONGODB_URI=mongodb://localhost:27017/nakama +# или для Docker: +MONGODB_URI=mongodb://mongo:27017/nakama + +# Запустите снова +docker-compose up -d +# или +pm2 start all + +# Проверьте логи +docker-compose logs -f backend +# или +pm2 logs +``` + +--- + +## 🐳 Docker-compose пример + +Если используете Docker Compose: + +```yaml +version: '3.8' + +services: + # MongoDB сервис + mongo: + image: mongo:7 + restart: always + volumes: + - mongo-data:/data/db + ports: + - "27017:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: admin + MONGO_INITDB_ROOT_PASSWORD: secure_password_here + MONGO_INITDB_DATABASE: nakama + + # Backend + backend: + build: ./backend + depends_on: + - mongo + environment: + # Используйте имя сервиса 'mongo' + - MONGODB_URI=mongodb://admin:secure_password_here@mongo:27017/nakama?authSource=admin + - PORT=3000 + ports: + - "3000:3000" + +volumes: + mongo-data: +``` + +--- + +## 🔍 Проверка после исправления + +```bash +# Проверьте логи приложения +docker-compose logs -f backend +# или +pm2 logs + +# Должны увидеть: +# ✅ MongoDB подключена +# ✅ Сервер запущен на порту 3000 +``` + +--- + +## 📊 Текущая конфигурация + +Судя по вашим логам: +- **Сервер:** 103.80.87.247 +- **MongoDB:** пытается подключиться к 103.80.87.247:27017 +- **Проблема:** MongoDB недоступен на этом адресе + +**Скорее всего:** +1. MongoDB слушает только localhost (127.0.0.1) +2. Или MongoDB не запущен +3. Или нужно использовать внутренний IP/hostname + +--- + +## ⚡ Быстрый чеклист + +- [ ] MongoDB запущен? `sudo systemctl status mongod` +- [ ] Порт 27017 слушается? `sudo netstat -tlnp | grep 27017` +- [ ] bindIp настроен? Проверьте `/etc/mongod.conf` +- [ ] Firewall пропускает? `sudo ufw status` +- [ ] Правильный connection string в .env? +- [ ] Приложение перезапущено после изменений? + +--- + +## 🆘 Если ничего не помогло + +1. **Покажите вывод:** +```bash +sudo systemctl status mongod +sudo netstat -tlnp | grep 27017 +cat /etc/mongod.conf | grep -A5 "net:" +``` + +2. **Проверьте переменные окружения:** +```bash +# Если Docker +docker exec env | grep MONGODB + +# Если PM2 +pm2 env +``` + +3. **Используйте MongoDB Atlas** (самый простой вариант) + +--- + +**Рекомендация:** Используйте **MongoDB Atlas** для production - это безопасно, надежно и бесплатно для малых проектов! + + diff --git a/MINIO_403_FIX.md b/MINIO_403_FIX.md new file mode 100644 index 0000000..b46f9fe --- /dev/null +++ b/MINIO_403_FIX.md @@ -0,0 +1,113 @@ +# Исправление ошибки 403 в MinIO + +## 🔴 Проблема +``` +Failed to load resource: the server responded with a status of 403 () +``` + +Это означает, что bucket `nakama-media` не публичный и браузер не может загрузить изображения. + +--- + +## ✅ Быстрое решение (через MinIO Console) + +### Шаг 1: Откройте консоль MinIO +``` +http://103.80.87.247:9901/ +``` + +### Шаг 2: Войдите +- **Username**: `minioadmin` (или ваш логин) +- **Password**: `minioadmin` (или ваш пароль) + +### Шаг 3: Настройте публичный доступ +1. В боковом меню выберите **Buckets** +2. Найдите **nakama-media** +3. Нажмите на имя bucket +4. Перейдите на вкладку **Anonymous** +5. Нажмите **Add Access Rule** +6. Введите префикс: `*` (для всех файлов) +7. Права доступа: выберите **readonly** или **download** +8. Нажмите **Save** + +--- + +## ✅ Альтернатива: Через MinIO Client (mc) + +### На сервере с MinIO выполните: + +```bash +# Установите mc +curl -O https://dl.min.io/client/mc/release/linux-amd64/mc +chmod +x mc +sudo mv mc /usr/local/bin/ + +# Настройте подключение +mc alias set myminio http://localhost:9000 minioadmin minioadmin + +# Сделайте bucket публичным +mc anonymous set download myminio/nakama-media + +# Проверьте +mc anonymous get myminio/nakama-media +``` + +Должно вывести: `Access permission for 'myminio/nakama-media' is 'download'` + +--- + +## ✅ Автоматический скрипт + +```bash +bash fix-minio-public.sh +``` + +--- + +## 📝 Проверьте .env + +Убедитесь, что в `.env` (в корне проекта) установлено: + +```env +MINIO_ENABLED=true +MINIO_ENDPOINT=103.80.87.247 +MINIO_PORT=9000 +MINIO_USE_SSL=false +MINIO_PUBLIC_BUCKET=true +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin +MINIO_BUCKET=nakama-media +``` + +--- + +## 🔄 Перезапустите backend + +```bash +docker compose restart backend +``` + +--- + +## ✅ Проверка + +Откройте в браузере: +``` +http://103.80.87.247:9000/nakama-media/posts/test.jpg +``` + +Если файл существует, он должен загрузиться без ошибок. + +--- + +## 🔧 Если используете Nginx (minio.glpshchn.ru) + +Убедитесь, что: +1. **MINIO_ENDPOINT** = `minio.glpshchn.ru` +2. **MINIO_PORT** = `443` +3. **MINIO_USE_SSL** = `true` +4. **MINIO_PUBLIC_URL** = `https://minio.glpshchn.ru` + +И перезапустите backend! + + diff --git a/backend/jobs/avatarUpdater.js b/backend/jobs/avatarUpdater.js index a9aad29..8ab3bcf 100644 --- a/backend/jobs/avatarUpdater.js +++ b/backend/jobs/avatarUpdater.js @@ -119,7 +119,8 @@ function scheduleAvatarUpdates() { module.exports = { scheduleAvatarUpdates, - updateAllUserAvatars + updateAllUserAvatars, + fetchLatestAvatar }; diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index ff91ade..54de65f 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -2,6 +2,7 @@ const User = require('../models/User'); const { validateTelegramId } = require('./validator'); const { logSecurityEvent } = require('./logger'); const { validateAndParseInitData } = require('../utils/telegram'); +const { fetchLatestAvatar } = require('../jobs/avatarUpdater'); const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный клиент. Сообщите об ошибке в https://t.me/NakamaReportbot'; const ALLOWED_SEARCH_PREFERENCES = ['furry', 'anime']; @@ -47,6 +48,61 @@ const ensureUserSettings = async (user) => { } }; +// Подтянуть отсутствующие данные пользователя из Telegram +const ensureUserData = async (user, telegramUser) => { + if (!user || !telegramUser) return; + + let updated = false; + + // Обновить username, если отсутствует или пустой + if (!user.username || user.username.trim() === '') { + if (telegramUser.username) { + user.username = telegramUser.username; + updated = true; + } else if (telegramUser.first_name) { + user.username = telegramUser.first_name; + updated = true; + } + } + + // Обновить firstName, если отсутствует + if (!user.firstName && telegramUser.first_name) { + user.firstName = telegramUser.first_name; + updated = true; + } + + // Обновить lastName, если отсутствует + if (user.lastName === undefined || user.lastName === null) { + user.lastName = telegramUser.last_name || ''; + updated = true; + } + + // Обновить аватарку, если отсутствует + if (!user.photoUrl) { + // Сначала проверить photo_url из initData + if (telegramUser.photo_url) { + user.photoUrl = telegramUser.photo_url; + updated = true; + } else { + // Если нет в initData, попробовать получить через Bot API + try { + const avatarUrl = await fetchLatestAvatar(user.telegramId); + if (avatarUrl) { + user.photoUrl = avatarUrl; + updated = true; + } + } catch (error) { + // Игнорируем ошибки получения аватарки + console.log('Не удалось получить аватарку через Bot API:', error.message); + } + } + } + + if (updated) { + await user.save(); + } +}; + const authenticate = async (req, res, next) => { try { const authHeader = req.headers.authorization || ''; @@ -94,19 +150,34 @@ const authenticate = async (req, res, next) => { if (!user) { user = new User({ telegramId: telegramUser.id.toString(), - username: telegramUser.username || telegramUser.first_name, - firstName: telegramUser.first_name, - lastName: telegramUser.last_name, - photoUrl: telegramUser.photo_url + username: telegramUser.username || telegramUser.first_name || 'user', + firstName: telegramUser.first_name || '', + lastName: telegramUser.last_name || '', + photoUrl: telegramUser.photo_url || null }); await user.save(); } else { - user.username = telegramUser.username || telegramUser.first_name; - user.firstName = telegramUser.first_name; - user.lastName = telegramUser.last_name; + // Обновлять только если есть новые данные, не перезаписывать существующие пустыми значениями + if (telegramUser.username) { + user.username = telegramUser.username; + } else if (!user.username && telegramUser.first_name) { + // Если username пустой, использовать first_name как fallback + user.username = telegramUser.first_name; + } + + if (telegramUser.first_name) { + user.firstName = telegramUser.first_name; + } + + if (telegramUser.last_name !== undefined) { + user.lastName = telegramUser.last_name || ''; + } + + // Обновлять аватарку только если есть новая if (telegramUser.photo_url) { user.photoUrl = telegramUser.photo_url; } + await user.save(); } @@ -188,19 +259,34 @@ const authenticateModeration = async (req, res, next) => { if (!user) { user = new User({ telegramId: telegramUser.id.toString(), - username: telegramUser.username || telegramUser.first_name, - firstName: telegramUser.first_name, - lastName: telegramUser.last_name, - photoUrl: telegramUser.photo_url + username: telegramUser.username || telegramUser.first_name || 'user', + firstName: telegramUser.first_name || '', + lastName: telegramUser.last_name || '', + photoUrl: telegramUser.photo_url || null }); await user.save(); } else { - user.username = telegramUser.username || telegramUser.first_name; - user.firstName = telegramUser.first_name; - user.lastName = telegramUser.last_name; + // Обновлять только если есть новые данные, не перезаписывать существующие пустыми значениями + if (telegramUser.username) { + user.username = telegramUser.username; + } else if (!user.username && telegramUser.first_name) { + // Если username пустой, использовать first_name как fallback + user.username = telegramUser.first_name; + } + + if (telegramUser.first_name) { + user.firstName = telegramUser.first_name; + } + + if (telegramUser.last_name !== undefined) { + user.lastName = telegramUser.last_name || ''; + } + + // Обновлять аватарку только если есть новая if (telegramUser.photo_url) { user.photoUrl = telegramUser.photo_url; } + await user.save(); } @@ -208,6 +294,8 @@ const authenticateModeration = async (req, res, next) => { return res.status(403).json({ error: 'Пользователь заблокирован' }); } + // Подтянуть отсутствующие данные из Telegram + await ensureUserData(user, telegramUser); await ensureUserSettings(user); await touchUserActivity(user); @@ -226,5 +314,6 @@ module.exports = { requireModerator, requireAdmin, touchUserActivity, - ensureUserSettings + ensureUserSettings, + ensureUserData }; diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 3fd3878..e823bcf 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -6,7 +6,8 @@ const config = require('../config'); const { validateTelegramId } = require('../middleware/validator'); const { logSecurityEvent } = require('../middleware/logger'); const { strictAuthLimiter } = require('../middleware/security'); -const { authenticate, ensureUserSettings, touchUserActivity } = require('../middleware/auth'); +const { authenticate, ensureUserSettings, touchUserActivity, ensureUserData } = require('../middleware/auth'); +const { fetchLatestAvatar } = require('../jobs/avatarUpdater'); const ALLOWED_SEARCH_PREFERENCES = ['furry', 'anime']; @@ -174,22 +175,41 @@ router.post('/oauth', strictAuthLimiter, async (req, res) => { if (!user) { user = new User({ telegramId: telegramUser.id.toString(), - username: telegramUser.username || telegramUser.first_name, - firstName: telegramUser.first_name, - lastName: telegramUser.last_name, - photoUrl: telegramUser.photo_url + username: telegramUser.username || telegramUser.first_name || 'user', + firstName: telegramUser.first_name || '', + lastName: telegramUser.last_name || '', + photoUrl: telegramUser.photo_url || null }); await user.save(); console.log(`✅ Создан новый пользователь через OAuth: ${user.username}`); } else { - // Обновить данные пользователя - user.username = telegramUser.username || telegramUser.first_name; - user.firstName = telegramUser.first_name; - user.lastName = telegramUser.last_name; - user.photoUrl = telegramUser.photo_url; + // Обновлять только если есть новые данные, не перезаписывать существующие пустыми значениями + if (telegramUser.username) { + user.username = telegramUser.username; + } else if (!user.username && telegramUser.first_name) { + // Если username пустой, использовать first_name как fallback + user.username = telegramUser.first_name; + } + + if (telegramUser.first_name) { + user.firstName = telegramUser.first_name; + } + + if (telegramUser.last_name !== undefined) { + user.lastName = telegramUser.last_name || ''; + } + + // Обновлять аватарку только если есть новая + if (telegramUser.photo_url) { + user.photoUrl = telegramUser.photo_url; + } + await user.save(); } + // Подтянуть отсутствующие данные из Telegram + await ensureUserData(user, telegramUser); + // Получить полные данные пользователя const populatedUser = await User.findById(user._id).populate([ { path: 'followers', select: 'username firstName lastName photoUrl' }, diff --git a/backend/server.js b/backend/server.js index 9aa79d5..148a27d 100644 --- a/backend/server.js +++ b/backend/server.js @@ -259,7 +259,8 @@ app.use(errorHandler); // Инициализировать WebSocket initWebSocket(server); -scheduleAvatarUpdates(); +// Автообновление аватарок отключено - обновление происходит только при перезаходе +// scheduleAvatarUpdates(); startServerMonitorBot(); // Обработка необработанных ошибок diff --git a/check-post-creation.md b/check-post-creation.md new file mode 100644 index 0000000..35779bc --- /dev/null +++ b/check-post-creation.md @@ -0,0 +1,167 @@ +# Диагностика: Посты не сохраняются + +## 🔴 Проблема +Посты создаются в интерфейсе, но исчезают при обновлении страницы. + +--- + +## ✅ Шаг 1: Проверьте логи backend + +```bash +# Посмотрите логи backend +docker logs nakama-backend -f + +# Или только последние 100 строк +docker logs nakama-backend --tail 100 +``` + +**Что искать:** +- ❌ `Ошибка создания поста` +- ❌ `S3 клиент не инициализирован` +- ❌ `Ошибка загрузки в MinIO` +- ❌ `403` или `Access Denied` +- ✅ `Файлы загружены в MinIO` +- ✅ `POST /api/posts 201` + +--- + +## ✅ Шаг 2: Проверьте MinIO bucket + +### Вариант А: Через консоль браузера + +1. Откройте DevTools (F12) в браузере +2. Вкладка **Network** +3. Попробуйте создать пост +4. Найдите запрос `POST /api/posts` +5. Посмотрите на: + - **Status**: должен быть `201 Created` + - **Response**: должен содержать объект `post` с `_id` + - **Если 500**: смотрите `error` в ответе + +### Вариант Б: Проверьте bucket в MinIO Console + +1. Откройте http://103.80.87.247:9901/ +2. **Buckets** → **nakama-media** → **posts/** +3. Должны видеть загруженные файлы + +--- + +## ✅ Шаг 3: Убедитесь, что bucket публичный + +```bash +# На сервере с MinIO +mc alias set myminio http://localhost:9000 minioadmin minioadmin +mc anonymous get myminio/nakama-media + +# Должно быть: Access permission for 'myminio/nakama-media' is 'download' +# Если нет, выполните: +mc anonymous set download myminio/nakama-media +``` + +--- + +## ✅ Шаг 4: Проверьте .env + +Откройте `.env` (в корне проекта) и убедитесь: + +```env +# MinIO ДОЛЖЕН быть включен +MINIO_ENABLED=true + +# Правильные настройки +MINIO_ENDPOINT=103.80.87.247 +MINIO_PORT=9000 +MINIO_USE_SSL=false +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin +MINIO_BUCKET=nakama-media +MINIO_PUBLIC_BUCKET=true + +# База данных +MONGODB_URI=mongodb://103.80.87.247:27017/nakama +``` + +--- + +## ✅ Шаг 5: Перезапустите backend + +```bash +docker compose restart backend + +# Посмотрите логи запуска +docker logs nakama-backend --tail 50 +``` + +**Что должно быть в логах:** +``` +✅ [SUCCESS] MinIO успешно подключен +📝 [INFO] S3 клиент для MinIO инициализирован +📝 [INFO] Bucket nakama-media существует +``` + +--- + +## ✅ Шаг 6: Тестовый запрос + +```bash +# Создайте тестовый пост через curl +curl -X POST http://your-backend-url/api/posts \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "content=Test post" \ + -F "tags=[\"furry\"]" \ + -F "isNSFW=false" +``` + +--- + +## 🔧 Если все еще не работает + +### Проверьте подключение к MongoDB: + +```bash +# На сервере с MongoDB +docker exec -it nakama-mongodb mongosh + +# В консоли MongoDB +use nakama +db.posts.find().limit(5) +``` + +Если посты есть в БД, но не отображаются в интерфейсе - проблема в frontend или API запросе. + +Если постов нет - проблема в backend при сохранении. + +--- + +## 📋 Контрольный список + +- [ ] Логи backend не содержат ошибок +- [ ] MinIO bucket `nakama-media` существует +- [ ] Bucket публичный (anonymous download) +- [ ] `.env` настроен правильно (`MINIO_ENABLED=true`) +- [ ] Backend перезапущен +- [ ] MongoDB доступна (`mongodb://103.80.87.247:27017/nakama`) +- [ ] В консоли браузера нет ошибок при создании поста + +--- + +## 💡 Быстрое решение + +```bash +# 1. Сделайте bucket публичным +mc alias set myminio http://103.80.87.247:9000 minioadmin minioadmin +mc anonymous set download myminio/nakama-media + +# 2. Проверьте .env +grep MINIO .env + +# 3. Перезапустите +docker compose restart backend + +# 4. Проверьте логи +docker logs nakama-backend -f +``` + +Теперь попробуйте создать пост! + + diff --git a/fix-minio-public.sh b/fix-minio-public.sh new file mode 100644 index 0000000..409b9f3 --- /dev/null +++ b/fix-minio-public.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Скрипт для настройки публичного доступа к MinIO bucket +# Использование: bash fix-minio-public.sh + +MINIO_ENDPOINT="http://103.80.87.247:9000" +MINIO_ACCESS_KEY="minioadmin" +MINIO_SECRET_KEY="minioadmin" +BUCKET_NAME="nakama-media" + +echo "🔧 Настройка публичного доступа к MinIO bucket..." + +# Проверка наличия mc +if ! command -v mc &> /dev/null; then + echo "📥 Устанавливаю MinIO Client (mc)..." + curl -s -O https://dl.min.io/client/mc/release/linux-amd64/mc + chmod +x mc + sudo mv mc /usr/local/bin/ + echo "✅ MinIO Client установлен" +fi + +# Настройка alias +echo "🔗 Подключаюсь к MinIO..." +mc alias set myminio $MINIO_ENDPOINT $MINIO_ACCESS_KEY $MINIO_SECRET_KEY + +# Проверка существования bucket +echo "📦 Проверяю bucket $BUCKET_NAME..." +if ! mc ls myminio/$BUCKET_NAME &> /dev/null; then + echo "❌ Bucket $BUCKET_NAME не найден!" + echo "Создаю bucket..." + mc mb myminio/$BUCKET_NAME +fi + +# Установка публичной политики +echo "🔓 Делаю bucket публичным для чтения..." +mc anonymous set download myminio/$BUCKET_NAME + +# Проверка политики +echo "✅ Текущая политика:" +mc anonymous get myminio/$BUCKET_NAME + +echo "" +echo "🎉 Готово! Теперь файлы в bucket $BUCKET_NAME доступны публично" +echo "" +echo "📝 Не забудьте добавить в .env:" +echo "MINIO_PUBLIC_BUCKET=true" +echo "MINIO_ENDPOINT=103.80.87.247" +echo "MINIO_PORT=9000" +echo "MINIO_USE_SSL=false" +echo "" +echo "🔄 После изменений перезапустите backend:" +echo "docker compose restart backend" + + diff --git a/frontend/src/components/CommentsModal.jsx b/frontend/src/components/CommentsModal.jsx index e411a7f..22f4751 100644 --- a/frontend/src/components/CommentsModal.jsx +++ b/frontend/src/components/CommentsModal.jsx @@ -65,14 +65,15 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
{post.author.username}
- {post.author.firstName} {post.author.lastName} + {post.author.firstName || ''} {post.author.lastName || ''} + {!post.author.firstName && !post.author.lastName && 'Пользователь'}
-
@{post.author.username}
+
@{post.author.username || post.author.firstName || 'user'}
@@ -99,13 +100,14 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
{c.author.username}
- {c.author.firstName} {c.author.lastName} + {c.author.firstName || ''} {c.author.lastName || ''} + {!c.author.firstName && !c.author.lastName && 'Пользователь'} {formatDate(c.createdAt)}
diff --git a/frontend/src/components/PostCard.jsx b/frontend/src/components/PostCard.jsx index c65d633..016d5f4 100644 --- a/frontend/src/components/PostCard.jsx +++ b/frontend/src/components/PostCard.jsx @@ -104,15 +104,16 @@ export default function PostCard({ post, currentUser, onUpdate }) {
{post.author.username}
- {post.author.firstName} {post.author.lastName} + {post.author.firstName || ''} {post.author.lastName || ''} + {!post.author.firstName && !post.author.lastName && 'Пользователь'}
- @{post.author.username} · {formatDate(post.createdAt)} + @{post.author.username || post.author.firstName || 'user'} · {formatDate(post.createdAt)}
diff --git a/frontend/src/pages/CommentsPage.jsx b/frontend/src/pages/CommentsPage.jsx index ee6d30c..e574373 100644 --- a/frontend/src/pages/CommentsPage.jsx +++ b/frontend/src/pages/CommentsPage.jsx @@ -123,14 +123,15 @@ export default function CommentsPage({ user }) {
{post.author.username}
- {post.author.firstName} {post.author.lastName} + {post.author.firstName || ''} {post.author.lastName || ''} + {!post.author.firstName && !post.author.lastName && 'Пользователь'}
-
@{post.author.username}
+
@{post.author.username || post.author.firstName || 'user'}
@@ -164,13 +165,14 @@ export default function CommentsPage({ user }) {
{c.author.username}
- {c.author.firstName} {c.author.lastName} + {c.author.firstName || ''} {c.author.lastName || ''} + {!c.author.firstName && !c.author.lastName && 'Пользователь'} {formatDate(c.createdAt)} diff --git a/frontend/src/pages/PostMenuPage.jsx b/frontend/src/pages/PostMenuPage.jsx index a4a5c8b..0f795fd 100644 --- a/frontend/src/pages/PostMenuPage.jsx +++ b/frontend/src/pages/PostMenuPage.jsx @@ -189,14 +189,15 @@ export default function PostMenuPage({ user }) {
{post.author.username}
- {post.author.firstName} {post.author.lastName} + {post.author.firstName || ''} {post.author.lastName || ''} + {!post.author.firstName && !post.author.lastName && 'Пользователь'}
-
@{post.author.username}
+
@{post.author.username || post.author.firstName || 'user'}
diff --git a/frontend/src/pages/Profile.jsx b/frontend/src/pages/Profile.jsx index c40dbfa..017e6b1 100644 --- a/frontend/src/pages/Profile.jsx +++ b/frontend/src/pages/Profile.jsx @@ -122,18 +122,19 @@ export default function Profile({ user, setUser }) {
{user.username}

- {user.firstName} {user.lastName} + {user.firstName || ''} {user.lastName || ''} + {!user.firstName && !user.lastName && 'Пользователь'} {(user.role === 'moderator' || user.role === 'admin') && ( )}

-

@{user.username}

+

@{user.username || user.firstName || 'user'}

{user.bio ? (
diff --git a/frontend/src/pages/UserProfile.jsx b/frontend/src/pages/UserProfile.jsx index d057caf..ad73491 100644 --- a/frontend/src/pages/UserProfile.jsx +++ b/frontend/src/pages/UserProfile.jsx @@ -86,18 +86,19 @@ export default function UserProfile({ currentUser }) {
{user.username}

- {user.firstName} {user.lastName} + {user.firstName || ''} {user.lastName || ''} + {!user.firstName && !user.lastName && 'Пользователь'} {(user.role === 'moderator' || user.role === 'admin') && ( )}

-

@{user.username}

+

@{user.username || user.firstName || 'user'}

{user.bio && (
diff --git a/minio-public-policy.json b/minio-public-policy.json new file mode 100644 index 0000000..f798d3e --- /dev/null +++ b/minio-public-policy.json @@ -0,0 +1,19 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": ["*"] + }, + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::nakama-media/*" + ] + } + ] +} + +