mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 15:33:22 +00:00
456 lines
19 KiB
Python
456 lines
19 KiB
Python
from rest_framework.views import APIView
|
|
from rest_framework.response import Response
|
|
from rest_framework import status
|
|
from rest_framework.parsers import MultiPartParser, FormParser
|
|
from django.db import models
|
|
from .models import Conversation, ConversationParticipant, Message, UserPresence
|
|
from Auth.models import Profile, ProfileRole
|
|
from GestionMessagerie.serializers import (
|
|
ConversationSerializer, MessageSerializer,
|
|
ConversationCreateSerializer, UserPresenceSerializer,
|
|
ProfileSimpleSerializer
|
|
)
|
|
from drf_yasg.utils import swagger_auto_schema
|
|
from drf_yasg import openapi
|
|
from django.utils import timezone
|
|
import os
|
|
import uuid
|
|
import logging
|
|
from django.core.files.storage import default_storage
|
|
from django.core.files.base import ContentFile
|
|
from django.db.models import Q
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ====================== MESSAGERIE INSTANTANÉE ======================
|
|
|
|
class InstantConversationListView(APIView):
|
|
"""
|
|
API pour lister les conversations instantanées d'un utilisateur
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Liste les conversations instantanées d'un utilisateur",
|
|
responses={200: ConversationSerializer(many=True)}
|
|
)
|
|
def get(self, request, user_id=None):
|
|
try:
|
|
user = Profile.objects.get(id=user_id)
|
|
|
|
conversations = Conversation.objects.filter(
|
|
participants__participant=user,
|
|
participants__is_active=True,
|
|
is_active=True
|
|
).distinct().order_by('-last_activity')
|
|
|
|
serializer = ConversationSerializer(conversations, many=True, context={'user': user})
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
except Profile.DoesNotExist:
|
|
return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
except Exception as e:
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class InstantConversationCreateView(APIView):
|
|
"""
|
|
API pour créer une nouvelle conversation instantanée
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Crée une nouvelle conversation instantanée",
|
|
request_body=ConversationCreateSerializer,
|
|
responses={201: ConversationSerializer}
|
|
)
|
|
def post(self, request):
|
|
serializer = ConversationCreateSerializer(data=request.data)
|
|
if serializer.is_valid():
|
|
conversation = serializer.save()
|
|
response_serializer = ConversationSerializer(conversation, context={'user': request.user})
|
|
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
class InstantMessageListView(APIView):
|
|
"""
|
|
API pour lister les messages d'une conversation
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Liste les messages d'une conversation",
|
|
responses={200: MessageSerializer(many=True)}
|
|
)
|
|
def get(self, request, conversation_id):
|
|
try:
|
|
conversation = Conversation.objects.get(id=conversation_id)
|
|
messages = conversation.messages.filter(is_deleted=False).order_by('created_at')
|
|
|
|
# Récupérer l'utilisateur actuel depuis les paramètres de requête
|
|
user_id = request.GET.get('user_id')
|
|
user = None
|
|
if user_id:
|
|
try:
|
|
user = Profile.objects.get(id=user_id)
|
|
except Profile.DoesNotExist:
|
|
pass
|
|
|
|
serializer = MessageSerializer(messages, many=True, context={'user': user})
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
except Conversation.DoesNotExist:
|
|
return Response({'error': 'Conversation not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
except Exception as e:
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class InstantMessageCreateView(APIView):
|
|
"""
|
|
API pour envoyer un nouveau message instantané
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Envoie un nouveau message instantané",
|
|
request_body=openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'conversation_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID de la conversation'),
|
|
'sender_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='ID de l\'expéditeur'),
|
|
'content': openapi.Schema(type=openapi.TYPE_STRING, description='Contenu du message'),
|
|
'message_type': openapi.Schema(type=openapi.TYPE_STRING, description='Type de message', default='text')
|
|
},
|
|
required=['conversation_id', 'sender_id', 'content']
|
|
),
|
|
responses={201: MessageSerializer}
|
|
)
|
|
def post(self, request):
|
|
try:
|
|
conversation_id = request.data.get('conversation_id')
|
|
sender_id = request.data.get('sender_id')
|
|
content = request.data.get('content', '').strip()
|
|
message_type = request.data.get('message_type', 'text')
|
|
|
|
if not all([conversation_id, sender_id, content]):
|
|
return Response(
|
|
{'error': 'conversation_id, sender_id, and content are required'},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
# Vérifier que la conversation existe
|
|
conversation = Conversation.objects.get(id=conversation_id)
|
|
|
|
# Vérifier que l'expéditeur existe et peut envoyer dans cette conversation
|
|
sender = Profile.objects.get(id=sender_id)
|
|
participant = ConversationParticipant.objects.filter(
|
|
conversation=conversation,
|
|
participant=sender,
|
|
is_active=True
|
|
).first()
|
|
|
|
if not participant:
|
|
return Response(
|
|
{'error': 'You are not a participant in this conversation'},
|
|
status=status.HTTP_403_FORBIDDEN
|
|
)
|
|
|
|
# Récupérer les données de fichier si disponibles
|
|
file_url = request.data.get('file_url')
|
|
file_name = request.data.get('file_name')
|
|
file_type = request.data.get('file_type')
|
|
file_size = request.data.get('file_size')
|
|
|
|
# Créer le message
|
|
message = Message.objects.create(
|
|
conversation=conversation,
|
|
sender=sender,
|
|
content=content,
|
|
message_type=message_type,
|
|
file_url=file_url,
|
|
file_name=file_name,
|
|
file_type=file_type,
|
|
file_size=file_size
|
|
)
|
|
|
|
# Mettre à jour l'activité de la conversation
|
|
conversation.last_activity = message.created_at
|
|
conversation.save(update_fields=['last_activity'])
|
|
|
|
serializer = MessageSerializer(message)
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
|
|
except Conversation.DoesNotExist:
|
|
return Response({'error': 'Conversation not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
except Profile.DoesNotExist:
|
|
return Response({'error': 'Sender not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
except Exception as e:
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class InstantMarkAsReadView(APIView):
|
|
"""
|
|
API pour marquer une conversation comme lue
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Marque une conversation comme lue pour un utilisateur",
|
|
request_body=openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'user_id': openapi.Schema(type=openapi.TYPE_INTEGER, description='ID de l\'utilisateur')
|
|
},
|
|
required=['user_id']
|
|
),
|
|
responses={200: openapi.Response('Success')}
|
|
)
|
|
def post(self, request, conversation_id):
|
|
try:
|
|
user_id = request.data.get('user_id')
|
|
if not user_id:
|
|
return Response({'error': 'user_id is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
participant = ConversationParticipant.objects.get(
|
|
conversation_id=conversation_id,
|
|
participant_id=user_id,
|
|
is_active=True
|
|
)
|
|
|
|
participant.last_read_at = timezone.now()
|
|
participant.save(update_fields=['last_read_at'])
|
|
|
|
return Response({'status': 'success'}, status=status.HTTP_200_OK)
|
|
|
|
except ConversationParticipant.DoesNotExist:
|
|
return Response({'error': 'Participant not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
except Exception as e:
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class UserPresenceView(APIView):
|
|
"""
|
|
API pour gérer la présence des utilisateurs
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Met à jour le statut de présence d'un utilisateur",
|
|
request_body=openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'status': openapi.Schema(type=openapi.TYPE_STRING, description='Statut de présence')
|
|
},
|
|
required=['status']
|
|
),
|
|
responses={200: UserPresenceSerializer}
|
|
)
|
|
def post(self, request, user_id):
|
|
try:
|
|
user = Profile.objects.get(id=user_id)
|
|
status_value = request.data.get('status')
|
|
|
|
if status_value not in ['online', 'away', 'busy', 'offline']:
|
|
return Response({'error': 'Invalid status'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
presence, created = UserPresence.objects.get_or_create(user=user)
|
|
presence.status = status_value
|
|
presence.last_seen = timezone.now()
|
|
presence.save()
|
|
|
|
serializer = UserPresenceSerializer(presence)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
except Profile.DoesNotExist:
|
|
return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
except Exception as e:
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
@swagger_auto_schema(
|
|
operation_description="Récupère le statut de présence d'un utilisateur",
|
|
responses={200: UserPresenceSerializer}
|
|
)
|
|
def get(self, request, user_id):
|
|
try:
|
|
user = Profile.objects.get(id=user_id)
|
|
presence, created = UserPresence.objects.get_or_create(user=user)
|
|
|
|
if created:
|
|
presence.status = 'offline'
|
|
presence.save()
|
|
|
|
serializer = UserPresenceSerializer(presence)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
except Profile.DoesNotExist:
|
|
return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
except Exception as e:
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class FileUploadView(APIView):
|
|
"""
|
|
API pour l'upload de fichiers dans la messagerie instantanée
|
|
"""
|
|
parser_classes = (MultiPartParser, FormParser)
|
|
|
|
@swagger_auto_schema(
|
|
operation_description="Upload un fichier pour la messagerie",
|
|
manual_parameters=[
|
|
openapi.Parameter('file', openapi.IN_FORM, description="Fichier à uploader", type=openapi.TYPE_FILE, required=True),
|
|
openapi.Parameter('conversation_id', openapi.IN_FORM, description="ID de la conversation", type=openapi.TYPE_INTEGER, required=True),
|
|
openapi.Parameter('sender_id', openapi.IN_FORM, description="ID de l'expéditeur", type=openapi.TYPE_INTEGER, required=True),
|
|
],
|
|
responses={
|
|
200: openapi.Response('Success', openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'fileUrl': openapi.Schema(type=openapi.TYPE_STRING),
|
|
'fileName': openapi.Schema(type=openapi.TYPE_STRING),
|
|
'fileSize': openapi.Schema(type=openapi.TYPE_INTEGER),
|
|
'fileType': openapi.Schema(type=openapi.TYPE_STRING),
|
|
}
|
|
)),
|
|
400: 'Bad Request',
|
|
413: 'File too large',
|
|
415: 'Unsupported file type'
|
|
}
|
|
)
|
|
def post(self, request):
|
|
try:
|
|
file = request.FILES.get('file')
|
|
conversation_id = request.data.get('conversation_id')
|
|
sender_id = request.data.get('sender_id')
|
|
|
|
if not file:
|
|
return Response({'error': 'Aucun fichier fourni'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
if not conversation_id or not sender_id:
|
|
return Response({'error': 'conversation_id et sender_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
# Vérifier que la conversation existe et que l'utilisateur y participe
|
|
try:
|
|
conversation = Conversation.objects.get(id=conversation_id)
|
|
sender = Profile.objects.get(id=sender_id)
|
|
|
|
# Vérifier que l'expéditeur participe à la conversation
|
|
if not ConversationParticipant.objects.filter(
|
|
conversation=conversation,
|
|
participant=sender,
|
|
is_active=True
|
|
).exists():
|
|
return Response({'error': 'Accès non autorisé à cette conversation'}, status=status.HTTP_403_FORBIDDEN)
|
|
|
|
except (Conversation.DoesNotExist, Profile.DoesNotExist):
|
|
return Response({'error': 'Conversation ou utilisateur introuvable'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
# Valider le type de fichier
|
|
allowed_types = [
|
|
'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp',
|
|
'application/pdf',
|
|
'application/msword',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'application/vnd.ms-excel',
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
'text/plain'
|
|
]
|
|
|
|
if file.content_type not in allowed_types:
|
|
return Response({'error': 'Type de fichier non autorisé'}, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
|
|
|
|
# Valider la taille du fichier (10MB max)
|
|
max_size = 10 * 1024 * 1024 # 10MB
|
|
if file.size > max_size:
|
|
return Response({'error': 'Fichier trop volumineux (max 10MB)'}, status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE)
|
|
|
|
# Générer un nom de fichier unique
|
|
file_extension = os.path.splitext(file.name)[1]
|
|
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
|
|
|
# Chemin de stockage : messagerie/conversation_id/
|
|
storage_path = f"messagerie/{conversation_id}/{unique_filename}"
|
|
|
|
# Sauvegarder le fichier
|
|
file_path = default_storage.save(storage_path, ContentFile(file.read()))
|
|
|
|
# Générer l'URL du fichier
|
|
file_url = default_storage.url(file_path)
|
|
if not file_url.startswith('http'):
|
|
# Construire l'URL complète si nécessaire
|
|
file_url = request.build_absolute_uri(file_url)
|
|
|
|
return Response({
|
|
'fileUrl': file_url,
|
|
'fileName': file.name,
|
|
'fileSize': file.size,
|
|
'fileType': file.content_type,
|
|
'filePath': file_path
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
except Exception as e:
|
|
return Response({'error': f'Erreur lors de l\'upload: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class InstantRecipientSearchView(APIView):
|
|
"""
|
|
API pour rechercher des destinataires pour la messagerie instantanée
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Recherche des destinataires pour la messagerie instantanée",
|
|
manual_parameters=[
|
|
openapi.Parameter('establishment_id', openapi.IN_QUERY, description="ID de l'établissement", type=openapi.TYPE_INTEGER, required=True),
|
|
openapi.Parameter('q', openapi.IN_QUERY, description="Terme de recherche", type=openapi.TYPE_STRING, required=True)
|
|
],
|
|
responses={200: ProfileSimpleSerializer(many=True)}
|
|
)
|
|
def get(self, request):
|
|
try:
|
|
establishment_id = request.query_params.get('establishment_id')
|
|
search_query = request.query_params.get('q', '').strip()
|
|
|
|
if not establishment_id:
|
|
return Response({'error': 'establishment_id is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
# Récupérer les IDs des profils actifs dans l'établissement
|
|
profile_roles = ProfileRole.objects.filter(
|
|
establishment_id=establishment_id,
|
|
is_active=True
|
|
).values_list('profile_id', flat=True)
|
|
|
|
# Rechercher les profils correspondants
|
|
users = Profile.objects.filter(id__in=profile_roles)
|
|
|
|
# Appliquer le filtre de recherche si un terme est fourni
|
|
if search_query:
|
|
users = users.filter(
|
|
Q(first_name__icontains=search_query) |
|
|
Q(last_name__icontains=search_query) |
|
|
Q(email__icontains=search_query)
|
|
)
|
|
|
|
# Exclure l'utilisateur actuel des résultats
|
|
if request.user.is_authenticated:
|
|
users = users.exclude(id=request.user.id)
|
|
|
|
serializer = ProfileSimpleSerializer(users[:10], many=True) # Limiter à 10 résultats
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
except Exception as e:
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
class InstantConversationDeleteView(APIView):
|
|
"""
|
|
API pour supprimer (désactiver) une conversation instantanée
|
|
"""
|
|
@swagger_auto_schema(
|
|
operation_description="Supprime une conversation instantanée (désactivation soft)",
|
|
responses={200: openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'success': openapi.Schema(type=openapi.TYPE_BOOLEAN),
|
|
'message': openapi.Schema(type=openapi.TYPE_STRING)
|
|
}
|
|
)}
|
|
)
|
|
def delete(self, request, conversation_id):
|
|
try:
|
|
# Récupérer la conversation par son ID UUID
|
|
conversation = Conversation.objects.filter(id=conversation_id).first()
|
|
|
|
if not conversation:
|
|
return Response({'error': 'Conversation not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
# Suppression simple : désactiver la conversation
|
|
conversation.is_active = False
|
|
conversation.save()
|
|
|
|
return Response({
|
|
'success': True,
|
|
'message': 'Conversation deleted successfully'
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error deleting conversation: {str(e)}")
|
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|