Update files
This commit is contained in:
parent
d278329ba6
commit
4f0f7bc3b7
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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 }));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue