Browse Source

Add Plugnmeet Server API Documentation and Enhance Live Session Verification

master
mortezaei 3 months ago
parent
commit
10a3a1c51c
  1. 1660
      apps/course/services/api.md
  2. 87
      apps/course/views/course.py
  3. 65
      apps/course/views/live_session.py

1660
apps/course/services/api.md
File diff suppressed because it is too large
View File

87
apps/course/views/course.py

@ -602,6 +602,15 @@ class CourseOnlineClassTokenValidateAPIView(GenericAPIView):
return metadata
def _build_live_session_context(self, course: Course) -> dict:
"""
Build live session context with real-time PlugNMeet verification.
This method:
1. Finds the latest session for the course
2. Verifies with PlugNMeet if the room is actually active
3. Auto-closes sessions if PlugNMeet reports room is inactive
4. Returns accurate session state independent of webhook delays
"""
latest_session = (
CourseLiveSession.objects.filter(course=course)
.order_by('-started_at', '-id')
@ -609,6 +618,7 @@ class CourseOnlineClassTokenValidateAPIView(GenericAPIView):
)
if not latest_session:
logger.debug(f"[Live Session Context] No session found for course={course.slug}")
return {
'is_online': False,
'live_session': None,
@ -620,6 +630,13 @@ class CourseOnlineClassTokenValidateAPIView(GenericAPIView):
started_at = latest_session.started_at
ended_at = latest_session.ended_at
is_online = bool(started_at and not ended_at)
# CRITICAL: Verify room status with PlugNMeet if session appears online
# This ensures we don't rely solely on webhooks which may fail or be delayed
if is_online and latest_session.room_id:
is_online = self._verify_and_sync_room_status(latest_session)
# Refresh ended_at in case session was closed
ended_at = latest_session.ended_at
live_session_data = {
'id': latest_session.id,
@ -629,13 +646,78 @@ class CourseOnlineClassTokenValidateAPIView(GenericAPIView):
'ended_at': self._format_datetime(ended_at),
}
return {
context = {
'is_online': is_online,
'live_session': live_session_data,
'active_room_id': live_session_data['room_id'] if is_online and live_session_data['room_id'] else None,
'livesession_started_at': live_session_data['started_at'],
'livesession_ended_at': live_session_data['ended_at'],
}
logger.debug(f"[Live Session Context] course={course.slug} is_online={is_online} room_id={live_session_data['room_id']}")
return context
def _verify_and_sync_room_status(self, session: CourseLiveSession) -> bool:
"""
Verify room status with PlugNMeet and sync local database.
Args:
session: The CourseLiveSession to verify
Returns:
bool: True if room is active, False if inactive or verification failed
Side effects:
- Closes session in database if PlugNMeet reports room is inactive
- Updates LiveSessionUser records accordingly
"""
if not session.room_id:
logger.warning(f"[Room Sync] Session has no room_id - session_id={session.id}")
return False
try:
client = PlugNMeetClient()
response = client.is_room_active(session.room_id)
# PlugNMeet returns: {"status": true, "msg": "...", "isActive": true/false}
is_active = response.get('isActive', False)
response_msg = response.get('msg', 'unknown')
if is_active:
logger.debug(f"[Room Sync] ✓ Room verified active - room_id={session.room_id} session_id={session.id} msg={response_msg}")
return True
else:
# Room is not active in PlugNMeet but active in our database
# This happens when:
# 1. Webhook failed to fire
# 2. Room was ended externally
# 3. Room crashed or timed out
logger.warning(f"[Room Sync] ✗ Room inactive in PlugNMeet - auto-closing session_id={session.id} room_id={session.room_id} msg={response_msg}")
self._close_live_session(session)
return False
except PlugNMeetError as e:
# PlugNMeet API returned an error
error_msg = str(e)
logger.error(f"[Room Sync] PlugNMeet API error - room_id={session.room_id} session_id={session.id} error={error_msg}")
# Check if error message indicates room doesn't exist
if 'not found' in error_msg.lower() or 'does not exist' in error_msg.lower():
logger.warning(f"[Room Sync] Room not found in PlugNMeet - closing session_id={session.id}")
self._close_live_session(session)
return False
# For other API errors, assume room might still be active (fail-safe)
logger.warning(f"[Room Sync] Cannot verify room status, assuming inactive for safety - room_id={session.room_id}")
return False
except Exception as e:
# Network error or unexpected exception
logger.error(f"[Room Sync] Unexpected error verifying room - room_id={session.room_id} session_id={session.id} error={type(e).__name__}: {str(e)}")
# For network errors, fail-safe: assume room might still be active
# but log a warning for monitoring
logger.warning(f"[Room Sync] Network/system error, assuming room inactive for safety")
return False
@staticmethod
def _user_can_join_live_session(user, course: Course) -> bool:
@ -686,8 +768,7 @@ class CourseOnlineClassTokenValidateAPIView(GenericAPIView):
# except (PlugNMeetError, Exception) as e:
# logger.warning(f"[Room Sync] Failed to check room status - room_id={active_session.room_id} error={str(e)}")
@staticmethod
def _close_live_session(session: CourseLiveSession):
def _close_live_session(self, session: CourseLiveSession):
"""
Close a live session and all related user entries.
Sets ended_at for session and exited_at/is_online for users.

65
apps/course/views/live_session.py

@ -295,12 +295,21 @@ class CourseLiveSessionTokenAPIView(GenericAPIView):
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}")
logger.info(f"[LiveSession Token] Active session found in DB - 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)
# 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)
is_admin = user.can_manage_course(course)
user_role = "professor" if is_admin else "student"
@ -368,6 +377,60 @@ class CourseLiveSessionTokenAPIView(GenericAPIView):
'plugnmeet': plugnmeet_response,
})
@staticmethod
def _verify_room_is_active(session: CourseLiveSession) -> bool:
"""
Verify that the room is actually active in PlugNMeet.
Args:
session: The CourseLiveSession to verify
Returns:
bool: True if room is active in PlugNMeet, False otherwise
Side effects:
- Closes session in database if PlugNMeet reports room is inactive
"""
if not session.room_id:
logger.warning(f"[Room Verify] Session has no room_id - session_id={session.id}")
return False
try:
client = PlugNMeetClient()
response = client.is_room_active(session.room_id)
is_active = response.get('isActive', False)
response_msg = response.get('msg', 'unknown')
if is_active:
logger.debug(f"[Room Verify] ✓ Room is active - room_id={session.room_id} session_id={session.id}")
return True
else:
logger.warning(f"[Room Verify] ✗ Room is NOT active - room_id={session.room_id} session_id={session.id} msg={response_msg}")
# Auto-close the session since room is not active
now = timezone.now()
session.ended_at = now
session.save(update_fields=['ended_at', 'updated_at'])
logger.info(f"[Room Verify] Session auto-closed - session_id={session.id} room_id={session.room_id}")
return False
except PlugNMeetError as e:
error_msg = str(e)
logger.error(f"[Room Verify] PlugNMeet API error - room_id={session.room_id} error={error_msg}")
# If room not found, close the session
if 'not found' in error_msg.lower() or 'does not exist' in error_msg.lower():
now = timezone.now()
session.ended_at = now
session.save(update_fields=['ended_at', 'updated_at'])
logger.warning(f"[Room Verify] Room not found - session closed - session_id={session.id}")
return False
except Exception as e:
logger.error(f"[Room Verify] Unexpected error - room_id={session.room_id} error={type(e).__name__}: {str(e)}")
return False
@staticmethod
def _build_profile_url(request, user):
avatar = getattr(user, 'avatar', None)

Loading…
Cancel
Save