You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
10 KiB
231 lines
10 KiB
import logging
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils import timezone
|
|
|
|
from rest_framework import status
|
|
from rest_framework.generics import GenericAPIView
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
|
|
from apps.course.models import Course, CourseLiveSession, Participant
|
|
from apps.course.serializers import LiveSessionRoomCreateSerializer, LiveSessionTokenSerializer
|
|
from apps.course.services.plugnmeet import PlugNMeetClient, PlugNMeetError
|
|
from utils.exceptions import AppAPIException
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CourseLiveSessionRoomCreateAPIView(GenericAPIView):
|
|
permission_classes = [IsAuthenticated]
|
|
serializer_class = LiveSessionRoomCreateSerializer
|
|
|
|
def post(self, request, slug, *args, **kwargs):
|
|
logger.info(f"[LiveSession Create] Request from user_id={request.user.id} for course={slug}")
|
|
|
|
serializer = self.get_serializer(data=request.data or {})
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
course = get_object_or_404(Course, slug=slug)
|
|
|
|
if not request.user.can_manage_course(course):
|
|
logger.warning(f"[LiveSession Create] Permission denied - user_id={request.user.id} course={slug}")
|
|
raise AppAPIException({'message': 'You do not have permission to create a live session for this course.'}, status_code=status.HTTP_403_FORBIDDEN)
|
|
|
|
logger.info(f"[LiveSession Create] Permission granted for user_id={request.user.id} course={slug}")
|
|
|
|
subject = serializer.validated_data.get('subject') or f"{course.title} Live Session"
|
|
room_id = serializer.validated_data.get('room_id') or self._build_room_id(course)
|
|
metadata = self._merge_metadata(subject, serializer.validated_data.get('metadata') or {})
|
|
|
|
payload = {
|
|
'room_id': room_id,
|
|
'metadata': metadata,
|
|
}
|
|
|
|
logger.info(f"[LiveSession Create] Calling PlugNMeet API - room_id={room_id} course={slug}")
|
|
|
|
try:
|
|
client = PlugNMeetClient()
|
|
plugnmeet_response = client.create_room(payload)
|
|
logger.info(f"[LiveSession Create] PlugNMeet room created successfully - room_id={room_id}")
|
|
except ImproperlyConfigured as exc:
|
|
logger.error(f"[LiveSession Create] Configuration error - {str(exc)}")
|
|
raise AppAPIException({'message': str(exc)}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
except PlugNMeetError as exc:
|
|
logger.error(f"[LiveSession Create] PlugNMeet API error - room_id={room_id} error={str(exc)}")
|
|
detail = exc.response_data or {'message': str(exc)}
|
|
status_code = exc.status_code or status.HTTP_502_BAD_GATEWAY
|
|
raise AppAPIException(detail, status_code=status_code)
|
|
|
|
session, created = CourseLiveSession.objects.get_or_create(
|
|
course=course,
|
|
room_id=room_id,
|
|
defaults={
|
|
'subject': subject,
|
|
'started_at': timezone.now(),
|
|
},
|
|
)
|
|
|
|
if created:
|
|
logger.info(f"[LiveSession Create] New session created - session_id={session.id} room_id={room_id} course={slug}")
|
|
else:
|
|
logger.info(f"[LiveSession Create] Existing session reactivated - session_id={session.id} room_id={room_id} course={slug}")
|
|
updates = {}
|
|
if session.subject != subject:
|
|
session.subject = subject
|
|
updates['subject'] = subject
|
|
if session.room_id != room_id:
|
|
session.room_id = room_id
|
|
updates['room_id'] = room_id
|
|
if session.started_at is None:
|
|
session.started_at = timezone.now()
|
|
updates['started_at'] = session.started_at
|
|
if updates:
|
|
session.save(update_fields=list(updates.keys()))
|
|
logger.info(f"[LiveSession Create] Session updated - session_id={session.id} fields={list(updates.keys())}")
|
|
|
|
logger.info(f"[LiveSession Create] Success - session_id={session.id} room_id={room_id} course={slug} user_id={request.user.id}")
|
|
|
|
return Response({
|
|
'session': {
|
|
'id': session.id,
|
|
'room_id': session.room_id,
|
|
'subject': session.subject,
|
|
'started_at': session.started_at,
|
|
},
|
|
'plugnmeet': plugnmeet_response,
|
|
}, status=status.HTTP_201_CREATED if created else status.HTTP_200_OK)
|
|
|
|
@staticmethod
|
|
def _build_room_id(course: Course) -> str:
|
|
timestamp = timezone.now().strftime('%Y%m%d%H%M%S')
|
|
return f"{course.slug}-{timestamp}"
|
|
|
|
def _merge_metadata(self, subject: str, overrides: dict) -> dict:
|
|
base = {
|
|
'room_title': subject,
|
|
'default_lock_settings': {
|
|
'lock_microphone': True,
|
|
'lock_webcam': True,
|
|
'lock_screen_sharing': True,
|
|
},
|
|
'room_features': {
|
|
'mute_on_start': True,
|
|
'waiting_room_features': {
|
|
'is_active': False,
|
|
},
|
|
},
|
|
}
|
|
return self._deep_update(base, overrides)
|
|
|
|
def _deep_update(self, base: dict, overrides: dict) -> dict:
|
|
for key, value in overrides.items():
|
|
if isinstance(value, dict) and isinstance(base.get(key), dict):
|
|
base[key] = self._deep_update(base.get(key, {}), value)
|
|
else:
|
|
base[key] = value
|
|
return base
|
|
|
|
|
|
class CourseLiveSessionTokenAPIView(GenericAPIView):
|
|
permission_classes = [IsAuthenticated]
|
|
serializer_class = LiveSessionTokenSerializer
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
course_slug = serializer.validated_data['course_slug']
|
|
user = request.user
|
|
|
|
logger.info(f"[LiveSession Token] Request from user_id={user.id} for course={course_slug}")
|
|
|
|
try:
|
|
course = Course.objects.get(slug=course_slug)
|
|
except Course.DoesNotExist:
|
|
logger.warning(f"[LiveSession Token] Course not found - course={course_slug} user_id={user.id}")
|
|
raise AppAPIException({'message': 'Course not found.'}, status_code=status.HTTP_404_NOT_FOUND)
|
|
|
|
if not course.is_online:
|
|
logger.warning(f"[LiveSession Token] Course not configured for online - course={course_slug} user_id={user.id}")
|
|
raise AppAPIException({'message': 'Course is not configured for online sessions.'}, status_code=status.HTTP_400_BAD_REQUEST)
|
|
|
|
try:
|
|
session = CourseLiveSession.objects.select_related('course').get(
|
|
course=course,
|
|
ended_at__isnull=True
|
|
)
|
|
logger.info(f"[LiveSession Token] Active session found - session_id={session.id} room_id={session.room_id} course={course_slug}")
|
|
except CourseLiveSession.DoesNotExist:
|
|
logger.warning(f"[LiveSession Token] No active session found - course={course_slug} user_id={user.id}")
|
|
raise AppAPIException({'message': 'No active live session found for this course.'}, status_code=status.HTTP_404_NOT_FOUND)
|
|
|
|
room_id = session.room_id
|
|
|
|
is_admin = user.can_manage_course(course)
|
|
user_role = "professor" if is_admin else "student"
|
|
logger.info(f"[LiveSession Token] User role determined - user_id={user.id} role={user_role} course={course_slug}")
|
|
|
|
if not is_admin and not Participant.objects.filter(course=course, student_id=user.id, is_active=True).exists():
|
|
logger.warning(f"[LiveSession Token] Access denied - user_id={user.id} not enrolled in course={course_slug}")
|
|
raise AppAPIException({'message': 'You do not have access to this live session.'}, status_code=status.HTTP_403_FORBIDDEN)
|
|
|
|
user_info = {
|
|
'user_id': str(user.id),
|
|
'name': user.get_full_name() or user.email or user.username or f"user-{user.id}",
|
|
'is_admin': is_admin,
|
|
}
|
|
|
|
user_metadata = {}
|
|
profile_pic = self._build_profile_url(request, user)
|
|
if profile_pic:
|
|
user_metadata['profilePic'] = profile_pic
|
|
|
|
if not is_admin:
|
|
user_metadata['lock_settings'] = {
|
|
'lock_microphone': True,
|
|
'lock_screen_sharing': True,
|
|
'lock_webcam': True,
|
|
}
|
|
else:
|
|
user_metadata['is_hidden'] = False
|
|
|
|
if user_metadata:
|
|
user_info['user_metadata'] = user_metadata
|
|
|
|
payload = {
|
|
'room_id': room_id,
|
|
'user_info': user_info,
|
|
}
|
|
|
|
logger.info(f"[LiveSession Token] Requesting token from PlugNMeet - room_id={room_id} user_id={user.id} role={user_role}")
|
|
|
|
try:
|
|
client = PlugNMeetClient()
|
|
plugnmeet_response = client.get_join_token(payload)
|
|
logger.info(f"[LiveSession Token] Token generated successfully - room_id={room_id} user_id={user.id}")
|
|
except ImproperlyConfigured as exc:
|
|
logger.error(f"[LiveSession Token] Configuration error - {str(exc)}")
|
|
raise AppAPIException({'message': str(exc)}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
except PlugNMeetError as exc:
|
|
logger.error(f"[LiveSession Token] PlugNMeet API error - room_id={room_id} user_id={user.id} error={str(exc)}")
|
|
detail = exc.response_data or {'message': str(exc)}
|
|
status_code = exc.status_code or status.HTTP_502_BAD_GATEWAY
|
|
raise AppAPIException(detail, status_code=status_code)
|
|
|
|
logger.info(f"[LiveSession Token] Success - room_id={room_id} user_id={user.id} role={user_role} course={course_slug}")
|
|
|
|
return Response({
|
|
'room_id': room_id,
|
|
'token': plugnmeet_response.get('token'),
|
|
'plugnmeet': plugnmeet_response,
|
|
})
|
|
|
|
@staticmethod
|
|
def _build_profile_url(request, user):
|
|
avatar = getattr(user, 'avatar', None)
|
|
if avatar and getattr(avatar, 'url', None):
|
|
return request.build_absolute_uri(avatar.url)
|
|
return None
|