Browse Source

Refactor CourseLiveSessionRoomCreateAPIView to enhance live session creation process

- Implemented validation and permission checks for course management.
- Updated room ID generation to comply with NATS rules by prefixing with 'room-'.
- Integrated JWT token generation for user access to live sessions.
- Improved error handling for PlugNMeet API interactions.
- Enhanced response structure to include access token for frontend connectivity.
master
Mohsen Taba 4 months ago
parent
commit
7f0a036238
  1. 188
      apps/course/views/live_session.py

188
apps/course/views/live_session.py

@ -11,7 +11,8 @@ from rest_framework.authentication import TokenAuthentication
from rest_framework.response import Response from rest_framework.response import Response
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi from drf_yasg import openapi
from datetime import time
import jwt
from apps.course.models import Course, CourseLiveSession, Participant, LiveSessionRecording from apps.course.models import Course, CourseLiveSession, Participant, LiveSessionRecording
from apps.course.serializers import LiveSessionRoomCreateSerializer, LiveSessionTokenSerializer, LiveSessionRecordedFileSerializer, LiveSessionRecordingSerializer from apps.course.serializers import LiveSessionRoomCreateSerializer, LiveSessionTokenSerializer, LiveSessionRecordedFileSerializer, LiveSessionRecordingSerializer
from apps.course.services.plugnmeet import PlugNMeetClient, PlugNMeetError from apps.course.services.plugnmeet import PlugNMeetClient, PlugNMeetError
@ -42,91 +43,156 @@ class CourseLiveSessionRoomCreateAPIView(GenericAPIView):
) )
} }
) )
def post(self, request, slug, *args, **kwargs):
logger.info(f"[LiveSession Create] Request from user_id={request.user.id} for course={slug}")
data = dict(request.data or {})
if 'metadata' in data:
logger.warning("[LiveSession Create] 'metadata' provided by client will be ignored for security reasons.")
data.pop('metadata', None)
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
def post(self, request, slug, *args, **kwargs):
# 1. Validation and Permission Logic
course = get_object_or_404(Course, slug=slug) course = get_object_or_404(Course, slug=slug)
if not request.user.can_manage_course(course): 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)
raise AppAPIException({'message': 'Permission denied'}, status_code=status.HTTP_403_FORBIDDEN)
logger.info(f"[LiveSession Create] Permission granted for user_id={request.user.id} course={slug}")
# 2. Build Safe Room ID (Prefix with 'room-' to satisfy NATS rules)
# OLD: f"{course.id}-imamjavad" -> "9-imamjavad" (Risk of NATS errors)
# NEW: f"room-{course.id}-imamjavad" -> "room-9-imamjavad" (Safe)
room_id = f"room-{course.id}-imamjavad"
subject = serializer.validated_data.get('subject') or f"{course.title} Live Session"
room_id = self._build_room_id(course)
subject = f"{course.title} Live Session"
metadata = self._build_metadata(subject) metadata = self._build_metadata(subject)
payload = {
'room_id': room_id,
'metadata': metadata,
}
logger.info(f"[LiveSession Create] Calling PlugNMeet API - room_id={room_id} course={slug}")
# 3. Create Room via PlugNMeet API
try: try:
client = PlugNMeetClient() 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)
# Note: client.create_room only CREATES the room.
# It does not give you a token to join it.
plugnmeet_response = client.create_room({
'room_id': room_id,
'metadata': metadata,
})
except Exception as exc:
logger.error(f"PlugNMeet Error: {exc}")
raise AppAPIException({'message': str(exc)}, status_code=500)
# 4. Save to Database
session, created = CourseLiveSession.objects.get_or_create( session, created = CourseLiveSession.objects.get_or_create(
course=course, course=course,
room_id=room_id, room_id=room_id,
defaults={
'subject': subject,
'started_at': timezone.now(),
},
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}")
# 5. GENERATE JOIN TOKEN (Critical for User Access)
# You must fetch these exactly as they are in your config.yaml
PNM_API_KEY = "habibmeet_api_key_2024"
PNM_SECRET = "habibmeet_secret_zumyyYWqv7KR2kUqvYdq4z4sXg7XTBD2ljT6_2024"
token_payload = {
"iss": PNM_API_KEY, # Issuer must be the API Key
"exp": int(time.time()) + 3600, # 1 hour expiry
"sub": str(request.user.id),
"room_id": room_id,
"name": f"{request.user.first_name} {request.user.last_name}",
"is_admin": True, # Gives moderator privileges
"user_id": str(request.user.id),
}
# SIGN with the SECRET, not the key
token = jwt.encode(token_payload, PNM_SECRET, algorithm="HS256")
return Response({ return Response({
'success': True,
'session': { 'session': {
'id': session.id, 'id': session.id,
'room_id': session.room_id, 'room_id': session.room_id,
'subject': session.subject,
'started_at': session.started_at, 'started_at': session.started_at,
}, },
'plugnmeet': plugnmeet_response,
}, status=status.HTTP_201_CREATED if created else status.HTTP_200_OK)
# Frontend uses this to connect:
# https://meet.newhorizonco.uk/?access_token={access_token}
'access_token': token
}, status=status.HTTP_201_CREATED)
# def post(self, request, slug, *args, **kwargs):
# logger.info(f"[LiveSession Create] Request from user_id={request.user.id} for course={slug}")
# data = dict(request.data or {})
# if 'metadata' in data:
# logger.warning("[LiveSession Create] 'metadata' provided by client will be ignored for security reasons.")
# data.pop('metadata', None)
# serializer = self.get_serializer(data=data)
# 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 = self._build_room_id(course)
# metadata = self._build_metadata(subject)
# 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 @staticmethod
def _build_room_id(course: Course) -> str: def _build_room_id(course: Course) -> str:
return f"{course.id}-imamjavad"
return f"room-{course.id}-imamjavad"
def _build_metadata(self, subject: str) -> dict: def _build_metadata(self, subject: str) -> dict:
# Build secured, centralized metadata. Client overrides are NOT allowed. # Build secured, centralized metadata. Client overrides are NOT allowed.

Loading…
Cancel
Save