@ -300,20 +300,39 @@ class CourseLiveSessionTokenAPIView(GenericAPIView):
logger . warning ( f " [LiveSession Token] No active session found - course={course_slug} user_id={user.id} " )
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 )
raise AppAPIException ( { ' message ' : ' No active live session found for this course. ' } , status_code = status . HTTP_404_NOT_FOUND )
# CRITICAL: Verify the room is actually active in PlugNMeet before issuing token
# This prevents issuing tokens for rooms that have crashed or ended without webhook notification
room_id = session . room_id
if not self . _verify_room_is_active ( session ) :
logger . error ( f " [LiveSession Token] Room not active in PlugNMeet - refusing token - room_id={room_id} session_id={session.id} " )
raise AppAPIException ( {
' status ' : ' False ' ,
' message ' : ' room is not active. create room first ' ,
' msg ' : ' room is not active. create room first '
} , status_code = status . HTTP_400_BAD_REQUEST )
# Check user role first to determine permissions
is_admin = user . can_manage_course ( course )
is_admin = user . can_manage_course ( course )
user_role = " professor " if is_admin else " student "
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} " )
logger . info ( f " [LiveSession Token] User role determined - user_id={user.id} role={user_role} course={course_slug} " )
# CRITICAL: Verify the room is actually active in PlugNMeet before issuing token
# This prevents issuing tokens for rooms that have crashed or ended without webhook notification
room_id = session . room_id
room_is_active = self . _verify_room_is_active ( session )
if not room_is_active :
# Room is not active in PlugNMeet but we have a session record
if is_admin :
# For professors: Auto-recreate the room in PlugNMeet
logger . info ( f " [LiveSession Token] Room inactive but professor requesting - recreating room - room_id={room_id} session_id={session.id} " )
try :
self . _recreate_room_in_plugnmeet ( course , session )
logger . info ( f " [LiveSession Token] Room recreated successfully - room_id={room_id} " )
except Exception as e :
logger . error ( f " [LiveSession Token] Failed to recreate room - room_id={room_id} error={str(e)} " )
raise AppAPIException ( {
' status ' : ' False ' ,
' message ' : f ' Failed to recreate room: {str(e)} ' ,
' msg ' : f ' Failed to recreate room: {str(e)} '
} , status_code = status . HTTP_500_INTERNAL_SERVER_ERROR )
else :
# For students: Refuse token - they cannot create rooms
logger . error ( f " [LiveSession Token] Room not active and user is student - refusing token - room_id={room_id} user_id={user.id} " )
raise AppAPIException ( {
' status ' : ' False ' ,
' message ' : ' room is not active. Please wait for the professor to start the class. ' ,
' msg ' : ' room is not active. Please wait for the professor to start the class. '
} , status_code = status . HTTP_400_BAD_REQUEST )
if not is_admin and not Participant . objects . filter ( course = course , student_id = user . id , is_active = True ) . exists ( ) :
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} " )
logger . warning ( f " [LiveSession Token] Access denied - user_id={user.id} not enrolled in course={course_slug} " )
@ -399,8 +418,18 @@ class CourseLiveSessionTokenAPIView(GenericAPIView):
client = PlugNMeetClient ( )
client = PlugNMeetClient ( )
response = client . is_room_active ( session . room_id )
response = client . is_room_active ( session . room_id )
is_active = response . get ( ' isActive ' , False )
# Debug: Log full response
logger . debug ( f " [Room Verify] PlugNMeet response - room_id={session.room_id} response={response} " )
# Handle isActive as boolean or string
is_active_raw = response . get ( ' isActive ' , False )
is_active = is_active_raw if isinstance ( is_active_raw , bool ) else str ( is_active_raw ) . lower ( ) == ' true '
response_msg = response . get ( ' msg ' , ' unknown ' )
response_msg = response . get ( ' msg ' , ' unknown ' )
response_status = response . get ( ' status ' , False )
# Trust status and msg if they indicate active room
if response_status and ' active ' in response_msg . lower ( ) and ' not ' not in response_msg . lower ( ) :
is_active = True
if is_active :
if is_active :
logger . debug ( f " [Room Verify] ✓ Room is active - room_id={session.room_id} session_id={session.id} " )
logger . debug ( f " [Room Verify] ✓ Room is active - room_id={session.room_id} session_id={session.id} " )
@ -431,6 +460,47 @@ class CourseLiveSessionTokenAPIView(GenericAPIView):
logger . error ( f " [Room Verify] Unexpected error - room_id={session.room_id} error={type(e).__name__}: {str(e)} " )
logger . error ( f " [Room Verify] Unexpected error - room_id={session.room_id} error={type(e).__name__}: {str(e)} " )
return False
return False
def _recreate_room_in_plugnmeet ( self , course : Course , session : CourseLiveSession ) - > None :
"""
Recreate a room in PlugNMeet when session exists but room is inactive .
This happens when :
- Webhook failed to notify us of room closure
- PlugNMeet server restarted
- Room was manually ended
Args :
course : The course for which to recreate the room
session : The existing session to reactivate
Raises :
PlugNMeetError : If room creation fails
"""
subject = session . subject or f " {course.title} Live Session "
room_id = session . room_id
metadata = self . _build_metadata ( subject )
payload = {
' room_id ' : room_id ,
' metadata ' : metadata ,
}
logger . info ( f " [Room Recreate] Recreating room in PlugNMeet - room_id={room_id} session_id={session.id} " )
try :
client = PlugNMeetClient ( )
plugnmeet_response = client . create_room ( payload )
logger . info ( f " [Room Recreate] Room recreated successfully - room_id={room_id} response={plugnmeet_response} " )
# Reset session ended_at to mark it as active again
session . ended_at = None
session . save ( update_fields = [ ' ended_at ' , ' updated_at ' ] )
logger . info ( f " [Room Recreate] Session reactivated - session_id={session.id} " )
except PlugNMeetError as exc :
logger . error ( f " [Room Recreate] Failed to recreate room - room_id={room_id} error={str(exc)} " )
raise
@staticmethod
@staticmethod
def _build_profile_url ( request , user ) :
def _build_profile_url ( request , user ) :
avatar = getattr ( user , ' avatar ' , None )
avatar = getattr ( user , ' avatar ' , None )