diff --git a/GAME_IMPROVEMENTS_README.md b/GAME_IMPROVEMENTS_README.md new file mode 100644 index 0000000..6a44950 --- /dev/null +++ b/GAME_IMPROVEMENTS_README.md @@ -0,0 +1,134 @@ +# Улучшения игры - Полный список + +## 🆕 Новые функции + +### 1. Уведомления о сообщениях в Telegram +- **Описание**: Показываются уведомления о новых сообщениях, когда Telegram не открыт +- **Как работает**: + - WebSocket событие `newMessage` обрабатывается на клиенте + - Уведомление появляется в правом верхнем углу экрана + - Автоматически исчезает через 5 секунд + - Анимированное появление и исчезновение +- **Файлы**: `src/Game.js` - добавлена функция `showMessageNotification()` + +### 2. Уведомления о перезагрузке сервера +- **Описание**: Игроки получают предупреждение о перезагрузке сервера +- **Как работает**: + - Сервер отправляет событие `serverRestart` всем клиентам + - Показывается красное уведомление с обратным отсчетом + - Сервер корректно завершает работу через 5 секунд +- **Файлы**: + - `server.js` - добавлена функция `gracefulShutdown()` + - `src/Game.js` - добавлена функция `showServerRestartNotification()` + +## 🎮 Улучшения управления + +### 3. Исправление застревания в текстурах +- **Проблема**: Игрок застревал в текстурах и не мог выбраться +- **Решение**: + - Уменьшены размеры коллизионной коробки игрока (с 0.25 до 0.2) + - Добавлено скольжение вдоль стен при столкновении + - Увеличен зазор между игроком и объектами (с 0.01 до 0.05) + - Улучшена система определения направления скольжения +- **Файлы**: `src/Game.js` - улучшена функция `updateFirstPersonMovement()` + +### 4. Улучшенное управление камерой +- **Новые возможности**: + - `Ctrl + колесо` = вертикальный поворот камеры (как было) + - `Shift + Ctrl + колесо` = горизонтальный поворот камеры (±90 градусов) +- **Ограничения**: Горизонтальный поворот ограничен для предотвращения дезориентации +- **Файлы**: `src/Game.js` - улучшена функция `onMouseWheel()` + +### 5. Отключение браузерного масштабирования +- **Проблема**: Браузер масштабировал страницу при Ctrl + колесо +- **Решение**: Добавлены обработчики событий для предотвращения масштабирования +- **Файлы**: `src/Game.js` - добавлены обработчики `wheel` и `keydown` + +## 🚀 Оптимизация производительности + +### 6. Улучшенная система загрузки ресурсов +- **Проблема**: Панель загрузки блокировала управление игрой +- **Решение**: + - Панель показывается только при начальной загрузке или загрузке >5 ресурсов + - Малые загрузки происходят в фоне без блокировки + - Добавлен флаг `isInitialLoad` для контроля отображения +- **Файлы**: `src/Game.js` - улучшен `LoadingManager` + +## 🔧 Технические улучшения + +### 7. Graceful Shutdown сервера +- **Описание**: Сервер корректно завершает работу при получении сигналов +- **Сигналы**: `SIGTERM`, `SIGINT` (Ctrl+C) +- **Процесс**: + 1. Уведомление всех клиентов + 2. Ожидание 5 секунд + 3. Закрытие HTTP сервера + 4. Принудительное завершение через 10 секунд + +### 8. Улучшенная система коллизий +- **Алгоритм скольжения**: + - Определение ближайшего препятствия + - Вычисление направления скольжения + - Проверка возможности движения в направлении скольжения + - Применение скольжения с уменьшенной дистанцией + +## 📱 Пользовательский интерфейс + +### 9. Подсказки по управлению +- **Описание**: Автоматически показывается подсказка об управлении камерой +- **Время**: Появляется через 3 секунды после загрузки игры +- **Длительность**: Висит 10 секунд, затем плавно исчезает +- **Стиль**: Темная полупрозрачная панель с анимацией + +## 🧪 Тестирование + +### 10. Тестовые файлы +- `test_telegram_status.html` - тест API статуса пользователей +- `TELEGRAM_STATUS_README.md` - документация по системе статусов +- `TESTING_INSTRUCTIONS.md` - инструкции по тестированию +- `GAME_IMPROVEMENTS_README.md` - этот файл + +## 🚀 Как запустить + +1. **Запуск сервера**: + ```bash + node server.js + ``` + +2. **Тестирование уведомлений**: + - Откройте игру в браузере + - Войдите в систему + - Откройте Telegram и отправьте сообщение + - Проверьте уведомления + +3. **Тестирование перезагрузки**: + - Нажмите Ctrl+C в терминале сервера + - Проверьте уведомление о перезагрузке + +4. **Тестирование управления**: + - `Ctrl + колесо` = вертикальный поворот + - `Shift + Ctrl + колесо` = горизонтальный поворот + +## 🔍 Отладка + +- **Сервер**: Логи в консоли терминала +- **Клиент**: Логи в консоли браузера (F12) +- **WebSocket**: Проверка соединения в Network tab + +## 📋 Чек-лист тестирования + +- [ ] Уведомления о сообщениях работают +- [ ] Уведомления о перезагрузке сервера работают +- [ ] Игрок не застревает в текстурах +- [ ] Управление камерой работает корректно +- [ ] Браузерное масштабирование отключено +- [ ] Панель загрузки не блокирует игру +- [ ] Подсказки по управлению отображаются + +## 🎯 Следующие шаги + +1. **Кэширование**: Добавить кэширование статуса пользователей +2. **Группировка**: Группировать пользователей по статусу в Telegram +3. **Уведомления**: Push-уведомления для важных событий +4. **Статистика**: Время онлайн, активность пользователей +5. **Оптимизация**: Дальнейшее улучшение производительности diff --git a/TELEGRAM_IMPROVEMENTS_README.md b/TELEGRAM_IMPROVEMENTS_README.md new file mode 100644 index 0000000..02ff1a3 --- /dev/null +++ b/TELEGRAM_IMPROVEMENTS_README.md @@ -0,0 +1,164 @@ +# Улучшения Telegram в игре + +## Обзор + +Этот документ описывает улучшения, внесенные в приложение Telegram (Shipgram) в игре, включая систему уведомлений, индикаторы непрочитанных сообщений и исправление проблемы "неизвестного отправителя". + +## Основные улучшения + +### 1. Уведомления о новых сообщениях + +**Проблема**: При получении новых сообщений в Telegram не было уведомлений, если приложение не было открыто. + +**Решение**: Реализована система уведомлений, которая показывает красивые всплывающие уведомления в правом верхнем углу экрана. + +**Функциональность**: +- Уведомления появляются только когда Telegram не открыт +- Отображают имя отправителя и текст сообщения +- Автоматически исчезают через 5 секунд +- Красивый дизайн с градиентом и анимацией + +**Код**: `showMessageNotification(senderId, messageText)` в `src/Game.js` + +### 2. Исправление проблемы "неизвестного отправителя" + +**Проблема**: В уведомлениях отображалось "Неизвестный" вместо имени отправителя. + +**Решение**: Реализована система поиска информации об отправителе: +1. Сначала ищет в списке контактов +2. Если не найден, загружает информацию с сервера через новый API endpoint + +**Новые API endpoints**: +- `GET /api/users/:userId` - получение информации о пользователе по ID +- `GET /api/messages-read/:contactId` - получение количества непрочитанных сообщений + +**Код**: Обновленная функция `showMessageNotification` в `src/Game.js` + +### 3. Индикаторы непрочитанных сообщений + +**Проблема**: Не было визуального индикатора непрочитанных сообщений в списке контактов. + +**Решение**: Добавлены счетчики непрочитанных сообщений: +- Красные бейджи с количеством непрочитанных сообщений +- Имена контактов выделяются жирным шрифтом при наличии непрочитанных сообщений +- Автоматическое обновление счетчиков + +**Функциональность**: +- `updateUnreadCount(senderId)` - обновляет счетчик для конкретного отправителя +- Автоматическое обновление при получении новых сообщений +- Периодическое обновление каждые 30 секунд + +**Код**: Функция `updateUnreadCount` и обновленный UI в `src/Game.js` + +## Технические детали + +### Серверная часть (`server.js`) + +#### Новые API endpoints + +```javascript +// Получение информации о пользователе по ID +app.get('/api/users/:userId', authenticate, async (req, res) => { + // Возвращает: id, firstName, lastName, avatarURL, isOnline, lastSeen +}); + +// Получение количества непрочитанных сообщений +app.get('/api/messages-read/:contactId', authenticate, async (req, res) => { + // Возвращает: { unreadCount: number } +}); +``` + +#### Исправления для onlineUsers и lastSeenTimes + +- Заменены `onlineUsers.hasOwnProperty()` на `onlineUsers.has()` +- Заменены `lastSeenTimes[userId]` на `lastSeenTimes.get(userId)` +- Исправлен `console.log` для отображения ключей Map + +### Клиентская часть (`src/Game.js`) + +#### Новые функции + +```javascript +// Показ уведомлений о сообщениях +const showMessageNotification = async (senderId, messageText) => { + // Логика поиска имени отправителя и показа уведомления +}; + +// Обновление счетчика непрочитанных сообщений +const updateUnreadCount = async (senderId) => { + // Загрузка и обновление счетчика +}; +``` + +#### Обновленный UI + +- Счетчики непрочитанных сообщений (красные бейджи) +- Выделение имен контактов жирным шрифтом +- Улучшенная структура контактов + +### API функции (`src/api/auth.js`) + +```javascript +// Загрузка информации о пользователе по ID +export const loadUserInfo = async (userId, token) => { + // Запрос к /api/users/:userId +}; + +// Обновленная функция получения статуса пользователей +export const getUsersStatus = async (token) => { + // Улучшенная обработка ошибок +}; +``` + +## Тестирование + +### Тестовый файл + +Создан `test_telegram_improvements.html` для тестирования новых API endpoints: + +1. **Тест статуса пользователей** - `/api/users/status` +2. **Тест информации о пользователе** - `/api/users/:userId` +3. **Тест количества непрочитанных сообщений** - `/api/messages-read/:contactId` + +### Инструкции по тестированию + +1. Откройте `test_telegram_improvements.html` в браузере +2. Введите JWT токен пользователя +3. Протестируйте каждый endpoint +4. Проверьте корректность возвращаемых данных + +## Интеграция с существующим кодом + +### WebSocket события + +- `newMessage` - автоматически вызывает `updateUnreadCount` и `showMessageNotification` +- `userStatusChanged` - обновляет статус пользователей в реальном времени + +### Интервалы обновления + +- `statusInterval` - каждые 30 секунд обновляет статусы и счетчики непрочитанных сообщений + +### Состояние компонента + +- `telegramContacts` - расширен для хранения `unreadCount` +- Автоматическое обновление при изменении данных + +## Преимущества + +1. **Лучший UX**: Пользователи видят уведомления о новых сообщениях +2. **Информативность**: Четко видно, от кого пришло сообщение +3. **Отслеживание**: Легко понять, какие чаты требуют внимания +4. **Производительность**: Эффективное обновление данных через интервалы +5. **Надежность**: Обработка ошибок и fallback для неизвестных отправителей + +## Возможные улучшения в будущем + +1. **Звуковые уведомления** - добавление звуковых сигналов +2. **Настройки уведомлений** - возможность отключения для определенных контактов +3. **Push-уведомления** - интеграция с браузерными push-уведомлениями +4. **Группировка уведомлений** - объединение уведомлений от одного отправителя +5. **История уведомлений** - сохранение истории показанных уведомлений + +## Заключение + +Реализованные улучшения значительно повышают удобство использования Telegram в игре, решая основные проблемы с уведомлениями и отображением информации об отправителях. Система стала более информативной и удобной для пользователей. diff --git a/TELEGRAM_STATUS_README.md b/TELEGRAM_STATUS_README.md new file mode 100644 index 0000000..c437472 --- /dev/null +++ b/TELEGRAM_STATUS_README.md @@ -0,0 +1,78 @@ +# Система статуса пользователей для Telegram (Shipgram) + +## Описание + +Реализована система отображения статуса "online/offline" для пользователей в Telegram приложении (Shipgram) игрового телефона. Статус показывает, действительно ли игрок находится онлайн в игре. + +## Что реализовано + +### 1. Серверная часть (server.js) + +- **Отслеживание онлайн пользователей**: Переменная `onlineUsers` хранит ID пользователей, которые в данный момент подключены к WebSocket +- **Отслеживание времени последнего онлайн**: Переменная `lastSeenTimes` хранит время последнего подключения/отключения каждого пользователя +- **WebSocket события**: + - При подключении: `userStatusChanged` с `isOnline: true` + - При отключении: `userStatusChanged` с `isOnline: false` +- **API endpoint**: `/api/users/status` возвращает список пользователей с их статусом + +### 2. Клиентская часть (Game.js) + +- **Обновленный API вызов**: `loadTelegramContacts()` теперь использует `/api/users/status` вместо `/api/users` +- **WebSocket обработчик**: Слушает события `userStatusChanged` и обновляет статус в реальном времени +- **Периодическое обновление**: Каждые 30 секунд обновляет статус пользователей +- **Визуальные индикаторы**: + - Зеленая точка для онлайн пользователей + - Цветной текст статуса (зеленый для онлайн, серый для офлайн) + - Время последнего онлайн для офлайн пользователей + +### 3. API функции (auth.js) + +- Добавлена функция `getUsersStatus()` для получения статуса пользователей + +## Как это работает + +1. **Подключение пользователя**: + - Пользователь входит в игру + - WebSocket middleware добавляет его в `onlineUsers` + - Обновляется `lastSeenTimes` + - Отправляется событие `userStatusChanged` всем клиентам + +2. **Отключение пользователя**: + - Пользователь выходит из игры или теряет соединение + - Обновляется `lastSeenTimes` + - Отправляется событие `userStatusChanged` всем клиентам + - Пользователь удаляется из `onlineUsers` + +3. **Отображение в Telegram**: + - При открытии Telegram загружается список пользователей с их статусом + - Статус обновляется в реальном времени через WebSocket + - Периодически обновляется через API для синхронизации + +## Файлы, которые были изменены + +- `server.js` - добавлена логика отслеживания статуса и WebSocket события +- `src/Game.js` - обновлено Telegram приложение для отображения статуса +- `src/api/auth.js` - добавлена функция для получения статуса пользователей +- `test_telegram_status.html` - тестовый файл для проверки API + +## Тестирование + +1. Запустите сервер +2. Откройте `test_telegram_status.html` в браузере +3. Введите JWT токен пользователя +4. Нажмите "Тестировать API" +5. Проверьте, что статус пользователей отображается корректно + +## Особенности + +- **Реальное время**: Статус обновляется мгновенно при подключении/отключении +- **Надежность**: Периодическое обновление через API обеспечивает синхронизацию +- **Производительность**: WebSocket события отправляются только при изменении статуса +- **Визуальная обратная связь**: Зеленые точки и цветной текст для лучшего UX + +## Возможные улучшения + +1. **Кэширование**: Добавить кэширование статуса пользователей +2. **Группировка**: Группировать пользователей по статусу +3. **Уведомления**: Уведомления о том, что пользователь стал онлайн +4. **Статистика**: Время, проведенное онлайн, активность и т.д. diff --git a/TESTING_INSTRUCTIONS.md b/TESTING_INSTRUCTIONS.md new file mode 100644 index 0000000..291b48f --- /dev/null +++ b/TESTING_INSTRUCTIONS.md @@ -0,0 +1,60 @@ +# Инструкция по тестированию системы статуса пользователей + +## Быстрый тест + +1. **Запустите сервер**: + ```bash + node server.js + ``` + +2. **Откройте игру в браузере** и войдите в систему + +3. **Откройте Telegram в игровом телефоне** и проверьте: + - Отображается ли статус "Онлайн" для текущего пользователя + - Есть ли зеленая точка рядом с аватаром + - Показывает ли статус "Офлайн" для других пользователей + +## Детальное тестирование + +### Тест 1: Проверка API +1. Откройте `test_telegram_status.html` в браузере +2. Введите JWT токен из localStorage браузера +3. Нажмите "Тестировать API" +4. Проверьте, что возвращается список пользователей с полями: + - `isOnline`: boolean + - `lastSeen`: timestamp или null + +### Тест 2: Проверка WebSocket событий +1. Откройте консоль браузера +2. Войдите в игру +3. Проверьте логи: + ``` + Статус пользователя изменился: {userId: X, isOnline: true} + ``` + +### Тест 3: Проверка реального времени +1. Откройте игру в двух вкладках браузера +2. Войдите под разными пользователями +3. В одной вкладке откройте Telegram +4. В другой вкладке закройте игру +5. Проверьте, что статус изменился на "Офлайн" в реальном времени + +## Ожидаемые результаты + +- ✅ Статус "Онлайн" отображается только для пользователей, которые действительно в игре +- ✅ Зеленая точка появляется рядом с аватаром онлайн пользователей +- ✅ Статус обновляется в реальном времени при подключении/отключении +- ✅ Время последнего онлайн отображается для офлайн пользователей +- ✅ В консоли сервера видны логи подключения/отключения пользователей + +## Возможные проблемы + +1. **Статус не обновляется**: Проверьте WebSocket соединение +2. **API возвращает ошибку**: Проверьте JWT токен и права доступа +3. **Статус не синхронизируется**: Проверьте логи сервера на наличие ошибок + +## Отладка + +- **Сервер**: Смотрите логи в консоли сервера +- **Клиент**: Смотрите логи в консоли браузера +- **WebSocket**: Проверьте соединение в Network tab браузера diff --git a/daily_availability.py b/daily_availability.py new file mode 100644 index 0000000..bc74973 --- /dev/null +++ b/daily_availability.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# daily_availability.py + +import logging +from datetime import date +import httpx +from apscheduler.schedulers.blocking import BlockingScheduler +from apscheduler.triggers.cron import CronTrigger +from config import * + +# ====== CONFIGURATION ====== +API_BASE = "http://127.0.0.1:8000" +TIMEOUT = httpx.Timeout(connect=5.0, read=30.0, write=5.0, pool=5.0) + +# ====== LOGGING SETUP ====== +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s | %(levelname)s | %(message)s', + handlers=[logging.FileHandler("daily_availability.log", encoding="utf-8")] +) +logger = logging.getLogger(__name__) + +# ====== JOB FUNCTION ====== +def check_availability(): + today = date.today().isoformat() + url = f"{API_BASE}/availability_group" + headers = { + "X-API-KEY": API_KEY, + "Content-Type": "application/json" # хотя httpx сам проставит, но можно явно + } + payload = {"dates": [today]} + + logger.info(f"Запрос создания доступности групп на {today}") + try: + with httpx.Client(timeout=TIMEOUT) as client: + response = client.post(url, headers=headers, json=payload) + response.raise_for_status() + data = response.json() + logger.info(f"Успешно создана доступность: {data}") + except httpx.HTTPStatusError as e: + allow = e.response.headers.get("Allow") + logger.error(f"Метод не разрешён (status={e.response.status_code}), Allow={allow}") + except Exception as e: + logger.error(f"Ошибка при запросе доступности: {e}") + + +# ====== SCHEDULER SETUP ====== +if __name__ == "__main__": + scheduler = BlockingScheduler(timezone="Asia/Yerevan") + # Запускаем задачу каждый день в 00:01 + scheduler.add_job( + check_availability, + trigger=CronTrigger(hour=0, minute=1), + name="daily_availability_check" + ) + logger.info("Scheduler запущен. Ожидание выполнения задачи в 00:01...") + try: + scheduler.start() + except (KeyboardInterrupt, SystemExit): + logger.info("Scheduler остановлен пользователем") diff --git a/saves/game_time.json b/saves/game_time.json index d733d7f..14ae2d3 100644 --- a/saves/game_time.json +++ b/saves/game_time.json @@ -1 +1 @@ -{"time":"2025-04-23T00:30:00.608Z","lastReal":1756243565634} \ No newline at end of file +{"time":"2025-06-16T00:46:41.600Z","lastReal":1756826890758} \ No newline at end of file diff --git a/server.js b/server.js index 00ddff1..99c6a96 100644 --- a/server.js +++ b/server.js @@ -33,7 +33,7 @@ catch (e) { console.error('Ошибка при импорте db1 - virtual_World:', e); throw e; } - +/* try { new_quest_Base = require('./db2'); console.log('db2 - new_quest_Base - успешно импортирован'); @@ -42,7 +42,7 @@ catch (e) { console.error('Ошибка при импорте db2 - new_quest_Base: ', e); throw e; } - +*/ try { db = require('./db'); console.log('db успешно импортирован'); @@ -99,7 +99,8 @@ const io = require('socket.io')(http, { } }); -let onlineUsers = {}; +let onlineUsers = new Map(); +let lastSeenTimes = new Map(); // Добавляем отслеживание времени последнего онлайн const organizationsRouter = require('./server/organizations')(io, onlineUsers); app.use('/api/organizations', organizationsRouter); @@ -120,7 +121,19 @@ io.use((socket, next) => { try { const payload = jwt.verify(token, process.env.JWT_SECRET); socket.userId = payload.id; - onlineUsers[socket.userId] = socket.id; // Добавить пользователя в онлайн + onlineUsers.set(socket.userId, socket.id); // Добавить пользователя в онлайн + + // Обновляем время последнего онлайн + lastSeenTimes.set(socket.userId, new Date()); + console.log(`Пользователь ${socket.userId} стал онлайн, время: ${lastSeenTimes.get(socket.userId)}`); + + // Уведомляем всех клиентов о том, что пользователь стал онлайн + socket.broadcast.emit('userStatusChanged', { + userId: socket.userId, + isOnline: true + }); + console.log(`Отправлено событие userStatusChanged для пользователя ${socket.userId} (онлайн)`); + next(); } catch (err) { next(new Error('Invalid token')); @@ -315,7 +328,7 @@ io.on('connection', socket => { ); const newMessage = result.rows[0]; - const receiverSocketId = onlineUsers[recvId]; + const receiverSocketId = onlineUsers.get(recvId); // Отправка получателю if (receiverSocketId) { @@ -404,7 +417,18 @@ io.on('connection', socket => { // --- Отключение --- socket.on('disconnect', async () => { - delete onlineUsers[socket.userId]; + // Обновляем время последнего онлайн + lastSeenTimes.set(socket.userId, new Date()); + console.log(`Пользователь ${socket.userId} стал офлайн, время: ${lastSeenTimes.get(socket.userId)}`); + + // Уведомляем всех клиентов о том, что пользователь стал офлайн + socket.broadcast.emit('userStatusChanged', { + userId: socket.userId, + isOnline: false + }); + console.log(`Отправлено событие userStatusChanged для пользователя ${socket.userId} (офлайн)`); + + onlineUsers.delete(socket.userId); const cityId = socket.cityId; const player = playersByCity[cityId]?.[socket.id]; if (player) { @@ -439,6 +463,82 @@ app.get('/api/users', authenticate, async (req, res) => { } }); +// API endpoint для получения статуса пользователей для Telegram +app.get('/api/users/status', authenticate, async (req, res) => { + try { + console.log(`Запрос статуса пользователей от пользователя ${req.user.id}`); + + const { rows } = await db.query(` + SELECT id, first_name AS "firstName", last_name AS "lastName", avatar_url AS "avatarURL" + FROM users + WHERE id != $1 + `, [req.user.id]); + + // Добавляем статус online и время последнего онлайн для каждого пользователя + const usersWithStatus = rows.map(user => ({ + ...user, + isOnline: onlineUsers.has(user.id), + lastSeen: lastSeenTimes.get(user.id) || null + })); + + console.log(`Возвращено ${usersWithStatus.length} пользователей с статусом`); + console.log('Онлайн пользователи:', Array.from(onlineUsers.keys())); + + res.json(usersWithStatus); + } catch (e) { + console.error('Ошибка получения статуса пользователей', e); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// API endpoint to get user information by ID +app.get('/api/users/:userId', authenticate, async (req, res) => { + const userId = parseInt(req.params.userId, 10); + try { + const { rows } = await db.query(` + SELECT id, first_name AS "firstName", last_name AS "lastName", avatar_url AS "avatarURL" + FROM users + WHERE id = $1 + `, [userId]); + if (rows.length === 0) { + return res.status(404).json({ error: 'User not found' }); + } + const user = rows[0]; + const isOnline = onlineUsers.has(user.id); + const lastSeen = lastSeenTimes.get(user.id) || new Date(); + res.json({ + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + avatarURL: user.avatarURL, + isOnline: isOnline, + lastSeen: lastSeen + }); + } catch (e) { + console.error('Ошибка получения информации о пользователе по ID', e); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// API endpoint to get unread message count for a specific contact +app.get('/api/messages-read/:contactId', authenticate, async (req, res) => { + const userId = req.user.id; + const contactId = parseInt(req.params.contactId, 10); + + try { + const { rows } = await db.query(` + SELECT COUNT(*) as unread_count + FROM messages + WHERE sender_id = $1 AND recipient_id = $2 AND is_read = false + `, [contactId, userId]); + + res.json({ unreadCount: parseInt(rows[0].unread_count) }); + } catch (e) { + console.error('Ошибка получения количества непрочитанных сообщений:', e); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + // Новый маршрут для получения сообщений с конкретным контактом app.get('/api/messages/:contactId', authenticate, async (req, res) => { const userId = req.user.id; @@ -595,7 +695,7 @@ app.post('/api/messages/send', authenticate, async (req, res) => { ); const newMessage = result.rows[0]; - const receiverSocketId = onlineUsers[recvId]; + const receiverSocketId = onlineUsers.get(recvId); if (receiverSocketId) { io.to(receiverSocketId).emit('newMessage', { id: newMessage.id, @@ -1531,10 +1631,46 @@ app.use((req, res) => { }); const PORT = process.env.PORT || 4000; -http.listen(PORT, () => { +const server = http.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); +// Обработка сигналов для graceful shutdown +process.on('SIGTERM', () => { + console.log('SIGTERM received, shutting down gracefully...'); + gracefulShutdown(); +}); + +process.on('SIGINT', () => { + console.log('SIGINT received, shutting down gracefully...'); + gracefulShutdown(); +}); + +function gracefulShutdown() { + console.log('Уведомляем всех клиентов о перезагрузке сервера...'); + + // Отправляем уведомление всем подключенным клиентам + io.emit('serverRestart', { + message: 'Сервер будет перезагружен через 5 секунд. Пожалуйста, сохраните прогресс.', + restartIn: 5000 + }); + + // Даем время клиентам получить уведомление + setTimeout(() => { + console.log('Закрываем сервер...'); + server.close(() => { + console.log('HTTP server closed'); + process.exit(0); + }); + + // Принудительно закрываем через 10 секунд + setTimeout(() => { + console.error('Could not close connections in time, forcefully shutting down'); + process.exit(1); + }, 10000); + }, 5000); +} + // Логирование всех маршрутов и middleware ['get', 'post', 'put', 'delete', 'use'].forEach(method => { const orig = app[method]; diff --git a/server/organizations.js b/server/organizations.js index 3eeb5cd..39b5b6d 100644 --- a/server/organizations.js +++ b/server/organizations.js @@ -169,7 +169,7 @@ module.exports = function(io, onlineUsers) { let thirst = parseFloat(rows[0].thirst ?? 100); if (balance < price) { - const sock = onlineUsers[req.user.id]; + const sock = onlineUsers.get(req.user.id); if (sock) io.to(sock).emit('chatMessage', { playerId: 0, name: 'Система', message: `Вам недостаточно средств для покупки ${itemDef.name}` }); return res.status(400).json({ error: 'insufficient funds' }); } @@ -196,7 +196,7 @@ module.exports = function(io, onlineUsers) { satiety = Math.min(100, satiety + parseFloat(itemDef.hunger_gain)); thirst = Math.min(100, thirst + parseFloat(itemDef.thirst_gain)); - const sock = onlineUsers[req.user.id]; + const sock = onlineUsers.get(req.user.id); if (sock) io.to(sock).emit('chatMessage', { playerId: 0, name: 'Система', message: `Вы купили ${itemDef.name}` }); res.json({ success: true, balance, satiety, thirst }); diff --git a/src/Game.js b/src/Game.js index c14b02d..ade4600 100644 --- a/src/Game.js +++ b/src/Game.js @@ -15,6 +15,8 @@ import Inventory from './components/Inventory'; import OrgControlPanel from './components/OrgControlPanel'; import DoubleTapWrapper from './pages/DoubleTapWrapper'; import WaveformPlayer from './pages/WaveformPlayer'; +import { getUsersStatus, loadUserInfo } from './api/auth.js'; + function Game({ avatarUrl, gender }) { // 1) реф для хранилища сцены @@ -31,6 +33,7 @@ function Game({ avatarUrl, gender }) { const cleanupTimerRef = useRef(null); // Глобальный менеджер прогресса загрузки (используем в GLTFLoader) const loadingManagerRef = useRef(null); + const overlayTimeoutRef = useRef(null); // Кликабельные объекты внутри интерьера const interiorInteractablesRef = useRef([]); const npcMeshesRef = useRef([]); @@ -1341,14 +1344,19 @@ function Game({ avatarUrl, gender }) { const token = localStorage.getItem('token'); try { setTgError(null); - const res = await fetch('/api/users', { + const res = await fetch('/api/users/status', { headers: { Authorization: `Bearer ${token}` }, credentials: 'include', cache: 'no-cache' }); if (res.ok) { const data = await res.json(); - setTelegramContacts(data); + // Добавляем счетчик непрочитанных сообщений для каждого пользователя + const dataWithUnread = data.map(user => ({ + ...user, + unreadCount: 0 + })); + setTelegramContacts(dataWithUnread); } else { const txt = await res.text().catch(() => ''); console.error('Ошибка загрузки контактов Telegram', res.status, txt); @@ -1367,6 +1375,223 @@ function Game({ avatarUrl, gender }) { //const [readmes, setReadmes] = useState('false'); const [userProfile, setUserProfile] = useState(null); + // Функция показа уведомлений о сообщениях + const showMessageNotification = async (senderId, messageText) => { + try { + // Сначала пытаемся найти отправителя в контактах + let senderName = 'Неизвестный'; + const contact = telegramContacts.find(c => c.id === senderId); + + if (contact) { + senderName = contact.firstName || contact.lastName || 'Неизвестный'; + } else { + // Если не найден в контактах, загружаем информацию о пользователе + try { + const userInfo = await loadUserInfo(senderId, localStorage.getItem('token')); + senderName = userInfo.firstName || userInfo.lastName || 'Неизвестный'; + } catch (error) { + console.error('Ошибка загрузки информации о пользователе:', error); + senderName = 'Неизвестный'; + } + } + + // Создаем уведомление + const notification = document.createElement('div'); + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 15px 20px; + border-radius: 10px; + box-shadow: 0 8px 32px rgba(0,0,0,0.3); + z-index: 10000; + font-family: 'Arial', sans-serif; + font-size: 14px; + max-width: 300px; + transform: translateX(400px); + transition: transform 0.3s ease-out; + backdrop-filter: blur(10px); + border: 1px solid rgba(255,255,255,0.2); + `; + + notification.innerHTML = ` +