""" WebSocket Consumers für Live-Chat """ import json from channels.generic.websocket import AsyncWebsocketConsumer from channels.db import database_sync_to_async from django.utils import timezone from .models import ChatRoom, ChatMessage, UserOnlineStatus, ChatNotification from django.contrib.auth.models import User class ChatConsumer(AsyncWebsocketConsumer): """WebSocket Consumer für Chat-Funktionalität""" async def connect(self): """WebSocket-Verbindung herstellen""" self.room_id = self.scope['url_route']['kwargs']['room_id'] self.room_group_name = f'chat_{self.room_id}' self.user = self.scope['user'] # Prüfen ob User Zugriff auf Chat-Raum hat if await self.can_access_room(): # Zur Chat-Gruppe beitreten await self.channel_layer.group_add( self.room_group_name, self.channel_name ) # Online-Status aktualisieren await self.update_online_status(True) await self.accept() # System-Nachricht senden await self.send_system_message(f"{self.user.username} ist dem Chat beigetreten") # Ungelesene Nachrichten als gelesen markieren await self.mark_messages_as_read() else: await self.close() async def disconnect(self, close_code): """WebSocket-Verbindung trennen""" # Online-Status aktualisieren await self.update_online_status(False) # System-Nachricht senden await self.send_system_message(f"{self.user.username} hat den Chat verlassen") # Aus Chat-Gruppe austreten await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) async def receive(self, text_data): """Nachricht vom Client empfangen""" try: data = json.loads(text_data) message_type = data.get('type', 'message') if message_type == 'message': await self.handle_message(data) elif message_type == 'typing': await self.handle_typing(data) elif message_type == 'read': await self.handle_read_messages() elif message_type == 'file_upload': await self.handle_file_upload(data) except json.JSONDecodeError: await self.send_error_message("Ungültiges JSON-Format") except Exception as e: await self.send_error_message(f"Fehler: {str(e)}") async def handle_message(self, data): """Chat-Nachricht verarbeiten""" content = data.get('content', '').strip() message_type = data.get('message_type', 'text') if not content and message_type == 'text': return # Nachricht in Datenbank speichern message = await self.save_message(content, message_type, data.get('file_url')) # Nachricht an alle im Chat senden await self.channel_layer.group_send( self.room_group_name, { 'type': 'chat_message', 'message': { 'id': str(message.id), 'sender': message.sender.username, 'sender_id': message.sender.id, 'content': message.content, 'message_type': message.message_type, 'file_url': message.file.url if message.file else None, 'image_url': message.image.url if message.image else None, 'created_at': message.created_at.isoformat(), 'is_system': message.is_system_message, } } ) # Benachrichtigung an andere User senden await self.send_notification_to_others(message) async def handle_typing(self, data): """Typing-Indicator verarbeiten""" is_typing = data.get('typing', False) await self.channel_layer.group_send( self.room_group_name, { 'type': 'typing_indicator', 'user': self.user.username, 'user_id': self.user.id, 'typing': is_typing } ) async def handle_read_messages(self): """Nachrichten als gelesen markieren""" await self.mark_messages_as_read() await self.channel_layer.group_send( self.room_group_name, { 'type': 'messages_read', 'user_id': self.user.id } ) async def handle_file_upload(self, data): """Datei-Upload verarbeiten""" file_url = data.get('file_url') file_type = data.get('file_type', 'file') if file_type == 'image': message_type = 'image' else: message_type = 'file' message = await self.save_message( f"Datei hochgeladen: {data.get('filename', 'Unbekannt')}", message_type, file_url ) await self.channel_layer.group_send( self.room_group_name, { 'type': 'chat_message', 'message': { 'id': str(message.id), 'sender': message.sender.username, 'sender_id': message.sender.id, 'content': message.content, 'message_type': message.message_type, 'file_url': file_url, 'filename': data.get('filename'), 'created_at': message.created_at.isoformat(), 'is_system': False, } } ) async def chat_message(self, event): """Chat-Nachricht an Client senden""" await self.send(text_data=json.dumps({ 'type': 'message', 'message': event['message'] })) async def typing_indicator(self, event): """Typing-Indicator an Client senden""" await self.send(text_data=json.dumps({ 'type': 'typing', 'user': event['user'], 'user_id': event['user_id'], 'typing': event['typing'] })) async def messages_read(self, event): """Nachrichten-als-gelesen-Status senden""" await self.send(text_data=json.dumps({ 'type': 'read', 'user_id': event['user_id'] })) async def system_message(self, event): """System-Nachricht an Client senden""" await self.send(text_data=json.dumps({ 'type': 'system', 'message': event['message'] })) async def error_message(self, event): """Fehler-Nachricht an Client senden""" await self.send(text_data=json.dumps({ 'type': 'error', 'message': event['message'] })) # Database Operations @database_sync_to_async def can_access_room(self): """Prüfen ob User Zugriff auf Chat-Raum hat""" try: room = ChatRoom.objects.get(id=self.room_id) return self.user == room.customer or self.user == room.admin or self.user.is_staff except ChatRoom.DoesNotExist: return False @database_sync_to_async def save_message(self, content, message_type, file_url=None): """Nachricht in Datenbank speichern""" room = ChatRoom.objects.get(id=self.room_id) message = ChatMessage.objects.create( room=room, sender=self.user, content=content, message_type=message_type, is_system_message=False ) return message @database_sync_to_async def update_online_status(self, is_online): """Online-Status aktualisieren""" status, created = UserOnlineStatus.objects.get_or_create(user=self.user) status.is_online = is_online status.current_room = ChatRoom.objects.get(id=self.room_id) if is_online else None status.save() @database_sync_to_async def mark_messages_as_read(self): """Nachrichten als gelesen markieren""" room = ChatRoom.objects.get(id=self.room_id) room.mark_messages_as_read(self.user) @database_sync_to_async def send_system_message(self, content): """System-Nachricht senden""" room = ChatRoom.objects.get(id=self.room_id) message = ChatMessage.objects.create( room=room, sender=self.user, content=content, message_type='system', is_system_message=True ) return message @database_sync_to_async def send_notification_to_others(self, message): """Benachrichtigung an andere User senden""" room = message.room other_users = room.get_other_users(message.sender) for user in other_users: ChatNotification.objects.create( user=user, room=room, notification_type='new_message', title=f'Neue Nachricht von {message.sender.username}', message=f'{message.content[:100]}...' if len(message.content) > 100 else message.content ) class NotificationConsumer(AsyncWebsocketConsumer): """WebSocket Consumer für Benachrichtigungen""" async def connect(self): """WebSocket-Verbindung herstellen""" self.user = self.scope['user'] if self.user.is_authenticated: self.notification_group_name = f'notifications_{self.user.id}' # Zur Benachrichtigungs-Gruppe beitreten await self.channel_layer.group_add( self.notification_group_name, self.channel_name ) await self.accept() else: await self.close() async def disconnect(self, close_code): """WebSocket-Verbindung trennen""" if hasattr(self, 'notification_group_name'): await self.channel_layer.group_discard( self.notification_group_name, self.channel_name ) async def receive(self, text_data): """Nachricht vom Client empfangen""" try: data = json.loads(text_data) action = data.get('action') if action == 'mark_read': await self.mark_notification_read(data.get('notification_id')) except json.JSONDecodeError: await self.send_error_message("Ungültiges JSON-Format") async def notification_message(self, event): """Benachrichtigung an Client senden""" await self.send(text_data=json.dumps({ 'type': 'notification', 'notification': event['notification'] })) @database_sync_to_async def mark_notification_read(self, notification_id): """Benachrichtigung als gelesen markieren""" try: notification = ChatNotification.objects.get( id=notification_id, user=self.user ) notification.read_at = timezone.now() notification.save() except ChatNotification.DoesNotExist: pass