diff --git a/apps/course/migrations/0012_courselivesession_recording_title.py b/apps/course/migrations/0012_courselivesession_recording_title.py new file mode 100644 index 0000000..aee701c --- /dev/null +++ b/apps/course/migrations/0012_courselivesession_recording_title.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.12 on 2026-06-10 12:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('course', '0011_coursecategory_icon'), + ] + + operations = [ + migrations.AddField( + model_name='courselivesession', + name='recording_title', + field=models.CharField(blank=True, help_text='Custom title specified by the professor for the recording/lesson.', max_length=255, null=True, verbose_name='Recording Title'), + ), + ] diff --git a/apps/course/models/live_session.py b/apps/course/models/live_session.py index c58f0bf..4157e97 100644 --- a/apps/course/models/live_session.py +++ b/apps/course/models/live_session.py @@ -36,6 +36,13 @@ class CourseLiveSession(models.Model): null=True, blank=True, ) + recording_title = models.CharField( + max_length=255, + verbose_name=_("Recording Title"), + help_text=_("Custom title specified by the professor for the recording/lesson."), + null=True, + blank=True, + ) recorded_file = models.FileField( upload_to="live_session_recordings/", verbose_name=_("Recorded File"), diff --git a/apps/course/signals.py b/apps/course/signals.py index b37e686..817ab02 100644 --- a/apps/course/signals.py +++ b/apps/course/signals.py @@ -167,10 +167,13 @@ def create_course_lesson_from_recording(sender, instance, **kwargs): is_active=True ) - # 3. Calculate next part number based on total lessons in the chapter - lessons_count = CourseLesson.objects.filter(chapter=chapter).count() - part_num = lessons_count + 1 - lesson_title = f"Part {part_num}" + # 3. Determine lesson title: use custom title if available, otherwise Part [n] + if instance.title and not instance.title.endswith(" - Recording"): + lesson_title = instance.title + else: + lessons_count = CourseLesson.objects.filter(chapter=chapter).count() + part_num = lessons_count + 1 + lesson_title = f"Part {part_num}" # 4. Calculate duration in minutes duration_minutes = 0 diff --git a/apps/course/urls.py b/apps/course/urls.py index 89bdb87..b7f8ef1 100644 --- a/apps/course/urls.py +++ b/apps/course/urls.py @@ -31,6 +31,7 @@ urlpatterns = [ path('online/token/validate/', views.CourseOnlineClassTokenValidateAPIView.as_view(), name='course-online-token-validate'), re_path(r'(?P[\w-]+)/online/room/create/$', views.CourseLiveSessionRoomCreateAPIView.as_view(), name='course-live-session-room-create'), path('online/room/token/', views.CourseLiveSessionTokenAPIView.as_view(), name='course-live-session-token'), + path('online/room/set-recording-title/', views.CourseLiveSessionSetRecordingTitleAPIView.as_view(), name='course-live-session-set-recording-title'), path('live-sessions//recorded-file/', views.CourseLiveSessionRecordedFileAPIView.as_view(), name='course-live-session-recorded-file'), # PlugNMeet webhook endpoint diff --git a/apps/course/views/live_session.py b/apps/course/views/live_session.py index c6aff88..38cee36 100644 --- a/apps/course/views/live_session.py +++ b/apps/course/views/live_session.py @@ -728,3 +728,48 @@ class CourseLiveSessionRecordedFileAPIView(GenericAPIView): 'file_time': recording.file_time, 'is_active': recording.is_active, }, status=status.HTTP_201_CREATED) + + +class CourseLiveSessionSetRecordingTitleAPIView(GenericAPIView): + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + + @swagger_auto_schema( + operation_description="Set the recording title for the active live session", + tags=["Imam-Javad - Course"], + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['room_id', 'title'], + properties={ + 'room_id': openapi.Schema(type=openapi.TYPE_STRING, description='Room ID'), + 'title': openapi.Schema(type=openapi.TYPE_STRING, description='Custom Recording Title'), + } + ), + responses={ + 200: openapi.Response( + description="Recording title updated successfully" + ) + } + ) + def post(self, request, *args, **kwargs): + room_id = request.data.get('room_id') + title = request.data.get('title') + + if not room_id or not title: + raise AppAPIException({'message': 'room_id and title are required.'}, status_code=status.HTTP_400_BAD_REQUEST) + + try: + session = CourseLiveSession.objects.get(room_id=room_id, ended_at__isnull=True) + except CourseLiveSession.DoesNotExist: + raise AppAPIException({'message': 'Active session not found.'}, status_code=status.HTTP_404_NOT_FOUND) + + # Check if user has permission to manage course associated with session + if not request.user.can_manage_course(session.course): + raise AppAPIException({'message': 'Permission denied.'}, status_code=status.HTTP_403_FORBIDDEN) + + session.recording_title = title.strip() + session.save(update_fields=['recording_title', 'updated_at']) + + logger.info(f"[LiveSession Title] Set recording title for room_id={room_id} to '{title}'") + return Response({'success': True, 'message': 'Recording title updated successfully.'}, status=status.HTTP_200_OK) + diff --git a/apps/course/views/webhook.py b/apps/course/views/webhook.py index 5fe0a5d..3a672e0 100644 --- a/apps/course/views/webhook.py +++ b/apps/course/views/webhook.py @@ -345,7 +345,7 @@ class PlugNMeetWebhookAPIView(APIView): recording = LiveSessionRecording.objects.create( session=session, - title=f"{session.subject} - Recording", + title=session.recording_title or f"{session.subject} - Recording", file_time=duration if duration.total_seconds() > 0 else None, recording_type='video' if file_name.lower().endswith('.mp4') else 'voice' )