Updates
This commit is contained in:
414
ETB-API/collaboration_war_rooms/consumers.py
Normal file
414
ETB-API/collaboration_war_rooms/consumers.py
Normal file
@@ -0,0 +1,414 @@
|
||||
"""
|
||||
WebSocket consumers for real-time chat functionality
|
||||
"""
|
||||
import json
|
||||
import uuid
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
from channels.db import database_sync_to_async
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import WarRoom, WarRoomMessage, MessageReaction, ChatCommand
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class ChatConsumer(AsyncWebsocketConsumer):
|
||||
"""WebSocket consumer for real-time chat in war rooms"""
|
||||
|
||||
async def connect(self):
|
||||
"""Connect to WebSocket"""
|
||||
self.room_id = self.scope['url_route']['kwargs']['room_id']
|
||||
self.room_group_name = f'chat_{self.room_id}'
|
||||
|
||||
# Join room group
|
||||
await self.channel_layer.group_add(
|
||||
self.room_group_name,
|
||||
self.channel_name
|
||||
)
|
||||
|
||||
await self.accept()
|
||||
|
||||
# Send room info
|
||||
room_info = await self.get_room_info()
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'room_info',
|
||||
'data': room_info
|
||||
}))
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
"""Disconnect from WebSocket"""
|
||||
# Leave room group
|
||||
await self.channel_layer.group_discard(
|
||||
self.room_group_name,
|
||||
self.channel_name
|
||||
)
|
||||
|
||||
async def receive(self, text_data):
|
||||
"""Receive message from WebSocket"""
|
||||
try:
|
||||
data = json.loads(text_data)
|
||||
message_type = data.get('type')
|
||||
|
||||
if message_type == 'chat_message':
|
||||
await self.handle_chat_message(data)
|
||||
elif message_type == 'reaction':
|
||||
await self.handle_reaction(data)
|
||||
elif message_type == 'command':
|
||||
await self.handle_command(data)
|
||||
elif message_type == 'typing':
|
||||
await self.handle_typing(data)
|
||||
else:
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'error',
|
||||
'message': 'Unknown message type'
|
||||
}))
|
||||
|
||||
except json.JSONDecodeError:
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'error',
|
||||
'message': 'Invalid JSON'
|
||||
}))
|
||||
except Exception as e:
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'error',
|
||||
'message': str(e)
|
||||
}))
|
||||
|
||||
async def handle_chat_message(self, data):
|
||||
"""Handle chat message"""
|
||||
content = data.get('content', '').strip()
|
||||
message_type = data.get('message_type', 'TEXT')
|
||||
reply_to_id = data.get('reply_to_id')
|
||||
|
||||
if not content:
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'error',
|
||||
'message': 'Message content cannot be empty'
|
||||
}))
|
||||
return
|
||||
|
||||
# Create message
|
||||
message = await self.create_message(content, message_type, reply_to_id)
|
||||
|
||||
if message:
|
||||
# Send message to room group
|
||||
await self.channel_layer.group_send(
|
||||
self.room_group_name,
|
||||
{
|
||||
'type': 'chat_message',
|
||||
'message': await self.serialize_message(message)
|
||||
}
|
||||
)
|
||||
|
||||
# Check for mentions and send notifications
|
||||
await self.handle_mentions(message)
|
||||
|
||||
# Check for commands
|
||||
await self.check_for_commands(message)
|
||||
|
||||
async def handle_reaction(self, data):
|
||||
"""Handle message reaction"""
|
||||
message_id = data.get('message_id')
|
||||
emoji = data.get('emoji')
|
||||
action = data.get('action', 'add') # 'add' or 'remove'
|
||||
|
||||
if not message_id or not emoji:
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'error',
|
||||
'message': 'Message ID and emoji are required'
|
||||
}))
|
||||
return
|
||||
|
||||
# Handle reaction
|
||||
if action == 'add':
|
||||
reaction = await self.add_reaction(message_id, emoji)
|
||||
else:
|
||||
reaction = await self.remove_reaction(message_id, emoji)
|
||||
|
||||
if reaction is not False:
|
||||
# Send reaction update to room group
|
||||
await self.channel_layer.group_send(
|
||||
self.room_group_name,
|
||||
{
|
||||
'type': 'reaction_update',
|
||||
'message_id': message_id,
|
||||
'reaction': await self.serialize_reaction(reaction) if reaction else None,
|
||||
'action': action
|
||||
}
|
||||
)
|
||||
|
||||
async def handle_command(self, data):
|
||||
"""Handle ChatOps command"""
|
||||
message_id = data.get('message_id')
|
||||
command_text = data.get('command_text')
|
||||
|
||||
if not message_id or not command_text:
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'error',
|
||||
'message': 'Message ID and command text are required'
|
||||
}))
|
||||
return
|
||||
|
||||
# Execute command
|
||||
result = await self.execute_command(message_id, command_text)
|
||||
|
||||
# Send command result to room group
|
||||
await self.channel_layer.group_send(
|
||||
self.room_group_name,
|
||||
{
|
||||
'type': 'command_result',
|
||||
'message_id': message_id,
|
||||
'result': result
|
||||
}
|
||||
)
|
||||
|
||||
async def handle_typing(self, data):
|
||||
"""Handle typing indicator"""
|
||||
is_typing = data.get('is_typing', False)
|
||||
|
||||
# Send typing indicator to room group
|
||||
await self.channel_layer.group_send(
|
||||
self.room_group_name,
|
||||
{
|
||||
'type': 'typing_indicator',
|
||||
'user': await self.get_user_info(),
|
||||
'is_typing': is_typing
|
||||
}
|
||||
)
|
||||
|
||||
async def chat_message(self, event):
|
||||
"""Send chat message to WebSocket"""
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'chat_message',
|
||||
'data': event['message']
|
||||
}))
|
||||
|
||||
async def reaction_update(self, event):
|
||||
"""Send reaction update to WebSocket"""
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'reaction_update',
|
||||
'data': {
|
||||
'message_id': event['message_id'],
|
||||
'reaction': event['reaction'],
|
||||
'action': event['action']
|
||||
}
|
||||
}))
|
||||
|
||||
async def command_result(self, event):
|
||||
"""Send command result to WebSocket"""
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'command_result',
|
||||
'data': {
|
||||
'message_id': event['message_id'],
|
||||
'result': event['result']
|
||||
}
|
||||
}))
|
||||
|
||||
async def typing_indicator(self, event):
|
||||
"""Send typing indicator to WebSocket"""
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'typing_indicator',
|
||||
'data': {
|
||||
'user': event['user'],
|
||||
'is_typing': event['is_typing']
|
||||
}
|
||||
}))
|
||||
|
||||
@database_sync_to_async
|
||||
def get_room_info(self):
|
||||
"""Get room information"""
|
||||
try:
|
||||
room = WarRoom.objects.get(id=self.room_id)
|
||||
return {
|
||||
'id': str(room.id),
|
||||
'name': room.name,
|
||||
'incident_id': str(room.incident.id),
|
||||
'incident_title': room.incident.title,
|
||||
'participant_count': room.allowed_users.count(),
|
||||
'message_count': room.message_count
|
||||
}
|
||||
except WarRoom.DoesNotExist:
|
||||
return None
|
||||
|
||||
@database_sync_to_async
|
||||
def get_user_info(self):
|
||||
"""Get current user information"""
|
||||
user = self.scope['user']
|
||||
if user.is_authenticated:
|
||||
return {
|
||||
'id': str(user.id),
|
||||
'username': user.username,
|
||||
'display_name': getattr(user, 'display_name', user.username)
|
||||
}
|
||||
return None
|
||||
|
||||
@database_sync_to_async
|
||||
def create_message(self, content, message_type, reply_to_id=None):
|
||||
"""Create a new message"""
|
||||
try:
|
||||
room = WarRoom.objects.get(id=self.room_id)
|
||||
user = self.scope['user']
|
||||
|
||||
if not user.is_authenticated:
|
||||
return None
|
||||
|
||||
# Check if user has access to room
|
||||
if not room.can_user_access(user):
|
||||
return None
|
||||
|
||||
reply_to = None
|
||||
if reply_to_id:
|
||||
try:
|
||||
reply_to = WarRoomMessage.objects.get(id=reply_to_id)
|
||||
except WarRoomMessage.DoesNotExist:
|
||||
pass
|
||||
|
||||
message = WarRoomMessage.objects.create(
|
||||
war_room=room,
|
||||
content=content,
|
||||
message_type=message_type,
|
||||
sender=user,
|
||||
sender_name=user.username,
|
||||
reply_to=reply_to
|
||||
)
|
||||
|
||||
# Update room message count
|
||||
room.message_count += 1
|
||||
room.last_activity = timezone.now()
|
||||
room.save(update_fields=['message_count', 'last_activity'])
|
||||
|
||||
return message
|
||||
|
||||
except WarRoom.DoesNotExist:
|
||||
return None
|
||||
|
||||
@database_sync_to_async
|
||||
def serialize_message(self, message):
|
||||
"""Serialize message for WebSocket"""
|
||||
return {
|
||||
'id': str(message.id),
|
||||
'content': message.content,
|
||||
'message_type': message.message_type,
|
||||
'sender': {
|
||||
'id': str(message.sender.id) if message.sender else None,
|
||||
'username': message.sender.username if message.sender else None,
|
||||
'display_name': message.sender_name
|
||||
},
|
||||
'is_pinned': message.is_pinned,
|
||||
'reply_to_id': str(message.reply_to.id) if message.reply_to else None,
|
||||
'created_at': message.created_at.isoformat(),
|
||||
'reactions': list(message.get_reactions_summary())
|
||||
}
|
||||
|
||||
@database_sync_to_async
|
||||
def add_reaction(self, message_id, emoji):
|
||||
"""Add reaction to message"""
|
||||
try:
|
||||
message = WarRoomMessage.objects.get(id=message_id)
|
||||
user = self.scope['user']
|
||||
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
|
||||
reaction = message.add_reaction(user, emoji)
|
||||
return reaction
|
||||
|
||||
except WarRoomMessage.DoesNotExist:
|
||||
return False
|
||||
|
||||
@database_sync_to_async
|
||||
def remove_reaction(self, message_id, emoji):
|
||||
"""Remove reaction from message"""
|
||||
try:
|
||||
message = WarRoomMessage.objects.get(id=message_id)
|
||||
user = self.scope['user']
|
||||
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
|
||||
message.remove_reaction(user, emoji)
|
||||
return True
|
||||
|
||||
except WarRoomMessage.DoesNotExist:
|
||||
return False
|
||||
|
||||
@database_sync_to_async
|
||||
def serialize_reaction(self, reaction):
|
||||
"""Serialize reaction for WebSocket"""
|
||||
return {
|
||||
'id': str(reaction.id),
|
||||
'emoji': reaction.emoji,
|
||||
'user': {
|
||||
'id': str(reaction.user.id),
|
||||
'username': reaction.user.username
|
||||
},
|
||||
'created_at': reaction.created_at.isoformat()
|
||||
}
|
||||
|
||||
@database_sync_to_async
|
||||
def execute_command(self, message_id, command_text):
|
||||
"""Execute ChatOps command"""
|
||||
try:
|
||||
message = WarRoomMessage.objects.get(id=message_id)
|
||||
user = self.scope['user']
|
||||
|
||||
if not user.is_authenticated:
|
||||
return {'error': 'Authentication required'}
|
||||
|
||||
# Parse command
|
||||
command_type = self._parse_command_type(command_text)
|
||||
parameters = self._parse_command_parameters(command_text)
|
||||
|
||||
# Create chat command
|
||||
chat_command = ChatCommand.objects.create(
|
||||
message=message,
|
||||
command_type=command_type,
|
||||
command_text=command_text,
|
||||
parameters=parameters
|
||||
)
|
||||
|
||||
# Execute command
|
||||
result = chat_command.execute_command(user)
|
||||
return result
|
||||
|
||||
except WarRoomMessage.DoesNotExist:
|
||||
return {'error': 'Message not found'}
|
||||
|
||||
def _parse_command_type(self, command_text):
|
||||
"""Parse command type from command text"""
|
||||
command_text = command_text.lower().strip()
|
||||
|
||||
if command_text.startswith('/status'):
|
||||
return 'STATUS'
|
||||
elif command_text.startswith('/runbook'):
|
||||
return 'RUNBOOK'
|
||||
elif command_text.startswith('/escalate'):
|
||||
return 'ESCALATE'
|
||||
elif command_text.startswith('/assign'):
|
||||
return 'ASSIGN'
|
||||
elif command_text.startswith('/update'):
|
||||
return 'UPDATE'
|
||||
else:
|
||||
return 'CUSTOM'
|
||||
|
||||
def _parse_command_parameters(self, command_text):
|
||||
"""Parse command parameters from command text"""
|
||||
parts = command_text.split()
|
||||
if len(parts) > 1:
|
||||
return {'args': parts[1:]}
|
||||
return {}
|
||||
|
||||
@database_sync_to_async
|
||||
def handle_mentions(self, message):
|
||||
"""Handle user mentions in message"""
|
||||
# This would integrate with notification system
|
||||
# For now, just a placeholder
|
||||
pass
|
||||
|
||||
@database_sync_to_async
|
||||
def check_for_commands(self, message):
|
||||
"""Check if message contains commands"""
|
||||
# This would check for command patterns and execute them
|
||||
# For now, just a placeholder
|
||||
pass
|
||||
Reference in New Issue
Block a user