Update files

This commit is contained in:
glpshchn 2025-12-15 07:38:30 +03:00
parent d278329ba6
commit 4f0f7bc3b7
4 changed files with 149 additions and 10 deletions

View File

@ -92,3 +92,7 @@ def admin_confirmations_collection():
def notifications_collection(): def notifications_collection():
return get_collection('notifications') return get_collection('notifications')
def moderation_chat_messages_collection():
return get_collection('moderationchatmessages')

View File

@ -32,7 +32,19 @@ async def get_users(
query = {} query = {}
if filter == 'active': if filter == 'active':
# Активные пользователи: не забанены И активны за последние 7 дней
seven_days_ago = datetime.utcnow() - timedelta(days=7)
query['banned'] = {'$ne': True} 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': elif filter == 'banned':
query['banned'] = True query['banned'] = True
# 'all' - no filter # 'all' - no filter
@ -49,18 +61,50 @@ async def get_users(
# Serialize users # Serialize users
serialized_users = [] serialized_users = []
for u in 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({ serialized_users.append({
'id': str(u['_id']), 'id': str(u['_id']),
'telegramId': u.get('telegramId'), 'telegramId': str(u.get('telegramId')) if u.get('telegramId') else None,
'username': u.get('username'), 'username': u.get('username', ''),
'firstName': u.get('firstName'), 'firstName': u.get('firstName', ''),
'lastName': u.get('lastName'), 'lastName': u.get('lastName', ''),
'photoUrl': u.get('photoUrl'), 'photoUrl': u.get('photoUrl', ''),
'role': u.get('role', 'user'), 'role': u.get('role', 'user'),
'banned': u.get('banned', False), 'banned': bool(u.get('banned', False)),
'bannedUntil': u.get('bannedUntil').isoformat() if u.get('bannedUntil') else None, 'bannedUntil': banned_until,
'createdAt': u.get('createdAt', datetime.utcnow()).isoformat(), 'createdAt': created_at,
'lastActiveAt': u.get('lastActiveAt').isoformat() if u.get('lastActiveAt') else None 'lastActiveAt': last_active_at,
'referralsCount': int(u.get('referralsCount', 0)),
'isAdmin': is_admin
}) })
return { return {

View File

@ -7,7 +7,7 @@ from typing import Dict, Set, Optional
from datetime import datetime from datetime import datetime
from config import settings 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 from utils.auth import normalize_username
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -37,6 +37,9 @@ def broadcast_online():
unique[username] = data unique[username] = data
online_list = list(unique.values()) online_list = list(unique.values())
# Broadcast в корневой namespace (так как клиенты подключаются туда)
sio.emit('online', online_list)
# Также broadcast в /mod-chat на случай, если кто-то подключится туда
sio.emit('online', online_list, namespace='/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})") 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) await sio.emit('ready', room=sid)
broadcast_online() broadcast_online()
@ -113,6 +141,60 @@ async def on_auth_root(sid, data):
await sio.emit('unauthorized', room=sid) await sio.emit('unauthorized', room=sid)
await sio.disconnect(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 # Namespace handlers for /mod-chat
@sio.on('connect', namespace='/mod-chat') @sio.on('connect', namespace='/mod-chat')
async def on_connect(sid, environ): async def on_connect(sid, environ):

View File

@ -694,6 +694,15 @@ export default function App() {
setChatState((prev) => ({ ...prev, connected: true })); setChatState((prev) => ({ ...prev, connected: true }));
}); });
socket.on('history', (messages) => {
console.log(`[Chat] 📜 Получена история: ${messages.length} сообщений`);
// Загружаем историю сообщений при подключении
setChatState((prev) => ({
...prev,
messages: messages || []
}));
});
socket.on('unauthorized', () => { socket.on('unauthorized', () => {
console.error('Unauthorized в чате'); console.error('Unauthorized в чате');
setChatState((prev) => ({ ...prev, connected: false })); setChatState((prev) => ({ ...prev, connected: false }));