From 4f0f7bc3b707448941e61950672539aac2a06e72 Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Mon, 15 Dec 2025 07:38:30 +0300 Subject: [PATCH] Update files --- moderation/backend-py/database.py | 4 ++ moderation/backend-py/routes/mod_app.py | 62 ++++++++++++++--- moderation/backend-py/websocket_server.py | 84 ++++++++++++++++++++++- moderation/frontend/src/App.jsx | 9 +++ 4 files changed, 149 insertions(+), 10 deletions(-) diff --git a/moderation/backend-py/database.py b/moderation/backend-py/database.py index 6531e25..3f406b1 100644 --- a/moderation/backend-py/database.py +++ b/moderation/backend-py/database.py @@ -92,3 +92,7 @@ def admin_confirmations_collection(): def notifications_collection(): return get_collection('notifications') + +def moderation_chat_messages_collection(): + return get_collection('moderationchatmessages') + diff --git a/moderation/backend-py/routes/mod_app.py b/moderation/backend-py/routes/mod_app.py index ba13fed..d4ed336 100644 --- a/moderation/backend-py/routes/mod_app.py +++ b/moderation/backend-py/routes/mod_app.py @@ -32,7 +32,19 @@ async def get_users( query = {} if filter == 'active': + # Активные пользователи: не забанены И активны за последние 7 дней + seven_days_ago = datetime.utcnow() - timedelta(days=7) query['banned'] = {'$ne': True} + query['lastActiveAt'] = {'$gte': seven_days_ago} + elif filter == 'inactive': + # Неактивные пользователи: не забанены И не активны более 7 дней + seven_days_ago = datetime.utcnow() - timedelta(days=7) + query['banned'] = {'$ne': True} + query['$or'] = [ + {'lastActiveAt': {'$lt': seven_days_ago}}, + {'lastActiveAt': None}, + {'lastActiveAt': {'$exists': False}} + ] elif filter == 'banned': query['banned'] = True # 'all' - no filter @@ -49,18 +61,50 @@ async def get_users( # Serialize users serialized_users = [] for u in users: + # Проверяем, является ли пользователь админом модерации + is_admin = False + if u.get('telegramId'): + admin = await moderation_admins_collection().find_one({ + 'telegramId': str(u.get('telegramId')) + }) + is_admin = admin is not None + + # Обработка дат + banned_until = None + if u.get('bannedUntil'): + if isinstance(u.get('bannedUntil'), datetime): + banned_until = u.get('bannedUntil').isoformat() + else: + banned_until = str(u.get('bannedUntil')) + + created_at = datetime.utcnow().isoformat() + if u.get('createdAt'): + if isinstance(u.get('createdAt'), datetime): + created_at = u.get('createdAt').isoformat() + else: + created_at = str(u.get('createdAt')) + + last_active_at = None + if u.get('lastActiveAt'): + if isinstance(u.get('lastActiveAt'), datetime): + last_active_at = u.get('lastActiveAt').isoformat() + else: + last_active_at = str(u.get('lastActiveAt')) + serialized_users.append({ 'id': str(u['_id']), - 'telegramId': u.get('telegramId'), - 'username': u.get('username'), - 'firstName': u.get('firstName'), - 'lastName': u.get('lastName'), - 'photoUrl': u.get('photoUrl'), + 'telegramId': str(u.get('telegramId')) if u.get('telegramId') else None, + 'username': u.get('username', ''), + 'firstName': u.get('firstName', ''), + 'lastName': u.get('lastName', ''), + 'photoUrl': u.get('photoUrl', ''), 'role': u.get('role', 'user'), - 'banned': u.get('banned', False), - 'bannedUntil': u.get('bannedUntil').isoformat() if u.get('bannedUntil') else None, - 'createdAt': u.get('createdAt', datetime.utcnow()).isoformat(), - 'lastActiveAt': u.get('lastActiveAt').isoformat() if u.get('lastActiveAt') else None + 'banned': bool(u.get('banned', False)), + 'bannedUntil': banned_until, + 'createdAt': created_at, + 'lastActiveAt': last_active_at, + 'referralsCount': int(u.get('referralsCount', 0)), + 'isAdmin': is_admin }) return { diff --git a/moderation/backend-py/websocket_server.py b/moderation/backend-py/websocket_server.py index b0cc5da..dbfa12a 100644 --- a/moderation/backend-py/websocket_server.py +++ b/moderation/backend-py/websocket_server.py @@ -7,7 +7,7 @@ from typing import Dict, Set, Optional from datetime import datetime from config import settings -from database import moderation_admins_collection +from database import moderation_admins_collection, moderation_chat_messages_collection from utils.auth import normalize_username logger = logging.getLogger(__name__) @@ -37,6 +37,9 @@ def broadcast_online(): unique[username] = data online_list = list(unique.values()) + # Broadcast в корневой namespace (так как клиенты подключаются туда) + sio.emit('online', online_list) + # Также broadcast в /mod-chat на случай, если кто-то подключится туда sio.emit('online', online_list, namespace='/mod-chat') @@ -103,6 +106,31 @@ async def on_auth_root(sid, data): } print(f"[WebSocket] ✅ Auth success в ROOT namespace: {username} (owner: {is_owner}, admin: {is_admin})") + + # Загружаем историю сообщений (последние 100) + try: + history = await moderation_chat_messages_collection().find().sort('createdAt', -1).limit(100).to_list(length=100) + # Переворачиваем, чтобы старые сообщения были первыми + history.reverse() + + # Форматируем сообщения для отправки + history_messages = [] + for msg in history: + history_messages.append({ + 'id': str(msg.get('_id', '')), + 'username': msg.get('username', ''), + 'telegramId': msg.get('telegramId', ''), + 'text': msg.get('text', ''), + 'createdAt': msg.get('createdAt').isoformat() if isinstance(msg.get('createdAt'), datetime) else msg.get('createdAt', '') + }) + + # Отправляем историю клиенту + await sio.emit('history', history_messages, room=sid) + print(f"[WebSocket] 📜 Sent {len(history_messages)} messages from history to {sid}") + except Exception as e: + print(f"[WebSocket] ⚠️ Failed to load message history: {e}") + logger.error(f"[WebSocket] Failed to load message history: {e}") + await sio.emit('ready', room=sid) broadcast_online() @@ -113,6 +141,60 @@ async def on_auth_root(sid, data): await sio.emit('unauthorized', room=sid) await sio.disconnect(sid) +# Обработчик сообщений в корневом namespace +@sio.on('message') +async def on_message_root(sid, data): + """Handle moderation chat message in root namespace""" + print(f"[WebSocket] 📨 Message в ROOT namespace от {sid}: {data}") + try: + if sid not in connected_moderators: + print(f"[WebSocket] ⚠️ Message from unauthorized client: {sid}") + logger.warning(f"[WebSocket] Message from unauthorized client: {sid}") + return + + user_data = connected_moderators[sid] + text = (data.get('text') or '').strip() + + if not text: + return + + message_id = f"{int(datetime.utcnow().timestamp() * 1000)}-{sid[:8]}" + created_at = datetime.utcnow() + + message = { + 'id': message_id, + 'username': user_data['username'], + 'telegramId': user_data['telegramId'], + 'text': text, + 'createdAt': created_at.isoformat() + } + + # Сохраняем сообщение в MongoDB + try: + await moderation_chat_messages_collection().insert_one({ + '_id': message_id, + 'username': user_data['username'], + 'telegramId': user_data['telegramId'], + 'text': text, + 'createdAt': created_at, + 'isOwner': user_data.get('isOwner', False) + }) + print(f"[WebSocket] 💾 Message saved to DB: {message_id}") + except Exception as e: + print(f"[WebSocket] ⚠️ Failed to save message to DB: {e}") + logger.error(f"[WebSocket] Failed to save message to DB: {e}") + + # Broadcast to all in root namespace (без указания room - broadcast всем в namespace) + await sio.emit('message', message) + print(f"[WebSocket] ✅ Message broadcasted в ROOT namespace: {user_data['username']}: {text[:50]}...") + logger.info(f"[WebSocket] Message from {user_data['username']} (ROOT): {text[:50]}...") + + except Exception as e: + print(f"[WebSocket] ❌ Error handling message (ROOT): {e}") + logger.error(f"[WebSocket] Error handling message (ROOT): {e}") + import traceback + traceback.print_exc() + # Namespace handlers for /mod-chat @sio.on('connect', namespace='/mod-chat') async def on_connect(sid, environ): diff --git a/moderation/frontend/src/App.jsx b/moderation/frontend/src/App.jsx index c8d68f7..1a0ddd1 100644 --- a/moderation/frontend/src/App.jsx +++ b/moderation/frontend/src/App.jsx @@ -694,6 +694,15 @@ export default function App() { setChatState((prev) => ({ ...prev, connected: true })); }); + socket.on('history', (messages) => { + console.log(`[Chat] 📜 Получена история: ${messages.length} сообщений`); + // Загружаем историю сообщений при подключении + setChatState((prev) => ({ + ...prev, + messages: messages || [] + })); + }); + socket.on('unauthorized', () => { console.error('Unauthorized в чате'); setChatState((prev) => ({ ...prev, connected: false }));