bug fix
Deploy to Production / deploy-production (push) Successful in 49s
Details
Deploy to Production / deploy-production (push) Successful in 49s
Details
This commit is contained in:
parent
9382eab7b2
commit
a167683bd9
|
|
@ -497,11 +497,13 @@ def _format_lesson_datetime_ru(dt, user_timezone='UTC'):
|
|||
|
||||
|
||||
@shared_task
|
||||
def send_lesson_completion_confirmation_telegram(lesson_id):
|
||||
def send_lesson_completion_confirmation_telegram(lesson_id, only_if_someone_not_connected=False):
|
||||
"""
|
||||
Отправить ментору в Telegram сообщение о завершённом по времени занятии
|
||||
Отправить ментору в Telegram сообщение о завершённом занятии
|
||||
с кнопками «Занятие состоялось» / «Занятие отменилось».
|
||||
Вызывается при авто-завершении занятия Celery-задачей.
|
||||
|
||||
only_if_someone_not_connected: при True — отправить только если ментор или ученик не подключались
|
||||
(при авто-завершении Celery). При False — всегда отправить (ручное завершение, сигнал).
|
||||
"""
|
||||
import asyncio
|
||||
from apps.schedule.models import Lesson
|
||||
|
|
@ -509,6 +511,7 @@ def send_lesson_completion_confirmation_telegram(lesson_id):
|
|||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from .telegram_bot import send_telegram_message_with_buttons
|
||||
|
||||
logger.info(f'send_lesson_completion_confirmation_telegram: lesson_id={lesson_id}')
|
||||
try:
|
||||
lesson = Lesson.objects.select_related('mentor', 'client', 'client__user').get(id=lesson_id)
|
||||
except Lesson.DoesNotExist:
|
||||
|
|
@ -516,7 +519,13 @@ def send_lesson_completion_confirmation_telegram(lesson_id):
|
|||
return
|
||||
|
||||
mentor = lesson.mentor
|
||||
if not mentor or not mentor.telegram_id:
|
||||
if not mentor:
|
||||
logger.warning(f'Lesson {lesson_id}: mentor is None, skipping completion confirmation')
|
||||
return
|
||||
if not mentor.telegram_id:
|
||||
logger.warning(
|
||||
f'Lesson {lesson_id}: mentor {mentor.id} has no telegram_id linked, skipping completion confirmation'
|
||||
)
|
||||
return
|
||||
|
||||
tz = mentor.timezone or 'UTC'
|
||||
|
|
@ -545,6 +554,11 @@ def send_lesson_completion_confirmation_telegram(lesson_id):
|
|||
mentor_status = '✅ Подключился' if mentor_connected else '❌ Не подключался'
|
||||
client_status = '✅ Подключился' if client_connected else '❌ Не подключался'
|
||||
|
||||
someone_not_connected = not mentor_connected or not client_connected
|
||||
if only_if_someone_not_connected and not someone_not_connected:
|
||||
logger.info(f'Lesson {lesson_id}: both participants connected, skipping completion confirmation')
|
||||
return
|
||||
|
||||
message = (
|
||||
f"⏱ <b>Занятие завершилось по времени</b>\n\n"
|
||||
f"📚 <b>{lesson.title}</b>\n"
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
# Generated manually
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("schedule", "0010_lesson_livekit_access_token_lesson_livekit_room_name_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="lesson",
|
||||
name="mentor_connected_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
help_text="Время подключения ментора к видеокомнате",
|
||||
null=True,
|
||||
verbose_name="Ментор подключился",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="lesson",
|
||||
name="client_connected_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
help_text="Время подключения студента к видеокомнате",
|
||||
null=True,
|
||||
verbose_name="Студент подключился",
|
||||
),
|
||||
),
|
||||
]
|
||||
# Generated manually
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("schedule", "0010_lesson_livekit_access_token_lesson_livekit_room_name_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="lesson",
|
||||
name="mentor_connected_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
help_text="Время подключения ментора к видеокомнате",
|
||||
null=True,
|
||||
verbose_name="Ментор подключился",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="lesson",
|
||||
name="client_connected_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
help_text="Время подключения студента к видеокомнате",
|
||||
null=True,
|
||||
verbose_name="Студент подключился",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -633,12 +633,12 @@ class LessonCalendarSerializer(serializers.Serializer):
|
|||
'end_date': 'Дата окончания должна быть позже даты начала'
|
||||
})
|
||||
|
||||
# Ограничение диапазона (не более 3 месяцев)
|
||||
# Ограничение диапазона (не более 6 месяцев — для календаря, смена месяцев)
|
||||
if start_date and end_date:
|
||||
delta = end_date - start_date
|
||||
if delta.days > 90:
|
||||
if delta.days > 180:
|
||||
raise serializers.ValidationError(
|
||||
'Диапазон не может превышать 90 дней'
|
||||
'Диапазон не может превышать 180 дней'
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from django.utils import timezone
|
|||
from datetime import timedelta
|
||||
|
||||
from .models import Lesson
|
||||
from apps.notifications.tasks import send_lesson_notification
|
||||
from apps.notifications.tasks import send_lesson_notification, send_lesson_completion_confirmation_telegram
|
||||
|
||||
|
||||
@receiver(post_save, sender=Lesson)
|
||||
|
|
@ -30,6 +30,9 @@ def lesson_saved(sender, instance, created, **kwargs):
|
|||
lesson_id=instance.id,
|
||||
notification_type='lesson_created'
|
||||
)
|
||||
# Если занятие создано сразу в статусе completed (задним числом) — отправляем подтверждение в Telegram
|
||||
if instance.status == 'completed':
|
||||
send_lesson_completion_confirmation_telegram.delay(instance.id)
|
||||
# Напоминания отправляются периодической задачей send_lesson_reminders
|
||||
# (проверяет флаги reminder_24h_sent, reminder_1h_sent, reminder_15m_sent)
|
||||
else:
|
||||
|
|
@ -43,11 +46,16 @@ def lesson_saved(sender, instance, created, **kwargs):
|
|||
)
|
||||
|
||||
if instance.tracker.has_changed('status'):
|
||||
# Статус изменился — не уведомляем, если занятие уже в прошлом
|
||||
# При переводе в completed — всегда отправляем подтверждение в Telegram (с кнопками «состоялось»/«отменилось»)
|
||||
# Работает для ручного завершения и для занятий, созданных/завершённых задним числом
|
||||
if instance.status == 'completed':
|
||||
send_lesson_completion_confirmation_telegram.delay(instance.id)
|
||||
|
||||
# Общие уведомления — не отправляем, если занятие уже в прошлом
|
||||
# (коррекция статуса/стоимости после факта, например отмена задним числом)
|
||||
ref_time = instance.end_time or instance.start_time
|
||||
if ref_time and ref_time < timezone.now():
|
||||
return # Занятие уже прошло — уведомления не отправляем
|
||||
return # Занятие уже прошло — пропускаем lesson_cancelled / lesson_completed
|
||||
if instance.status == 'cancelled':
|
||||
send_lesson_notification.delay(
|
||||
lesson_id=instance.id,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from celery import shared_task
|
|||
import logging
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from django.db.models import Count
|
||||
from django.db.models import Count, Q
|
||||
from .models import Lesson, Subject, MentorSubject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -380,7 +380,8 @@ def start_lessons_automatically():
|
|||
now = timezone.now()
|
||||
started_count = 0
|
||||
completed_count = 0
|
||||
|
||||
logger.info(f'[start_lessons_automatically] Запуск')
|
||||
|
||||
try:
|
||||
# Находим все запланированные занятия, которые должны начаться
|
||||
# start_time <= now (время начала уже наступило)
|
||||
|
|
@ -402,17 +403,22 @@ def start_lessons_automatically():
|
|||
for lesson in lessons_to_start_list:
|
||||
logger.info(f'Занятие {lesson.id} автоматически переведено в статус "in_progress"')
|
||||
|
||||
# Находим занятия, которые уже прошли и должны быть завершены
|
||||
# end_time < now - 5 минут (время окончания прошло более 5 минут назад - даём время на завершение)
|
||||
# status in ['scheduled', 'in_progress'] (еще не завершены)
|
||||
five_minutes_ago = now - timedelta(minutes=5)
|
||||
# Находим занятия, которые уже прошли и должны быть завершены:
|
||||
# end_time < now - 15 минут (всегда ждём 15 мин после конца, даже если занятие создали задним числом)
|
||||
fifteen_minutes_ago = now - timedelta(minutes=15)
|
||||
lessons_to_complete = Lesson.objects.filter(
|
||||
status__in=['scheduled', 'in_progress'],
|
||||
end_time__lt=five_minutes_ago
|
||||
end_time__lt=fifteen_minutes_ago
|
||||
).select_related('mentor', 'client')
|
||||
|
||||
lessons_to_complete_list = list(lessons_to_complete)
|
||||
if lessons_to_complete_list:
|
||||
logger.info(
|
||||
f'[start_lessons_automatically] Найдено {len(lessons_to_complete_list)} занятий '
|
||||
f'для завершения (end_time < {fifteen_minutes_ago})'
|
||||
)
|
||||
|
||||
# Оптимизация: используем bulk_update вместо цикла с save()
|
||||
lessons_to_complete_list = list(lessons_to_complete)
|
||||
for lesson in lessons_to_complete_list:
|
||||
lesson.status = 'completed'
|
||||
lesson.completed_at = now
|
||||
|
|
@ -439,9 +445,10 @@ def start_lessons_automatically():
|
|||
logger.error(f'Ошибка закрытия LiveKit комнаты для урока {lesson.id}: {str(e)}', exc_info=True)
|
||||
|
||||
# Отправить ментору в Telegram сообщение с кнопками подтверждения
|
||||
# (только если кто-то не подключался — при обоих подключённых ментор подтверждает при выходе)
|
||||
try:
|
||||
from apps.notifications.tasks import send_lesson_completion_confirmation_telegram
|
||||
send_lesson_completion_confirmation_telegram.delay(lesson.id)
|
||||
send_lesson_completion_confirmation_telegram.delay(lesson.id, only_if_someone_not_connected=True)
|
||||
except Exception as e:
|
||||
logger.warning(f'Не удалось отправить подтверждение занятия в Telegram: {e}')
|
||||
|
||||
|
|
|
|||
|
|
@ -283,20 +283,36 @@ def participant_connected(request):
|
|||
Вызывается фронтендом при успешном подключении к LiveKit комнате.
|
||||
|
||||
POST /api/video/livekit/participant-connected/
|
||||
Body: { "room_name": "uuid" }
|
||||
Body: { "room_name": "uuid", "lesson_id": 123 } — lesson_id опционален, резерв при 404 по room_name
|
||||
"""
|
||||
room_name = request.data.get('room_name')
|
||||
if not room_name:
|
||||
lesson_id = request.data.get('lesson_id')
|
||||
|
||||
if not room_name and not lesson_id:
|
||||
return Response(
|
||||
{'error': 'room_name обязателен'},
|
||||
{'error': 'Укажите room_name или lesson_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
import uuid as uuid_module
|
||||
room_uuid = uuid_module.UUID(str(room_name))
|
||||
video_room = VideoRoom.objects.get(room_id=room_uuid)
|
||||
except (ValueError, VideoRoom.DoesNotExist):
|
||||
video_room = None
|
||||
if room_name:
|
||||
try:
|
||||
import uuid as uuid_module
|
||||
room_uuid = uuid_module.UUID(str(room_name).strip())
|
||||
video_room = VideoRoom.objects.get(room_id=room_uuid)
|
||||
except (ValueError, VideoRoom.DoesNotExist):
|
||||
pass
|
||||
if not video_room and lesson_id:
|
||||
try:
|
||||
video_room = VideoRoom.objects.get(lesson_id=lesson_id)
|
||||
except VideoRoom.DoesNotExist:
|
||||
pass
|
||||
|
||||
if not video_room:
|
||||
logger.warning(
|
||||
'participant_connected: VideoRoom not found (room_name=%s, lesson_id=%s, user=%s)',
|
||||
room_name, lesson_id, getattr(request.user, 'pk', request.user)
|
||||
)
|
||||
return Response(
|
||||
{'error': 'Видеокомната не найдена'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
|
|
@ -304,7 +320,10 @@ def participant_connected(request):
|
|||
|
||||
user = request.user
|
||||
client_user = video_room.client.user if hasattr(video_room.client, 'user') else video_room.client
|
||||
if user != video_room.mentor and user != client_user:
|
||||
is_mentor = user.pk == video_room.mentor_id
|
||||
client_pk = getattr(client_user, 'pk', None) or getattr(client_user, 'id', None)
|
||||
is_client = client_pk is not None and user.pk == client_pk
|
||||
if not is_mentor and not is_client:
|
||||
return Response(
|
||||
{'error': 'Нет доступа к этой видеокомнате'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
|
|
@ -325,7 +344,12 @@ def participant_connected(request):
|
|||
participant.save(update_fields=['is_connected'])
|
||||
|
||||
video_room.mark_participant_joined(user)
|
||||
|
||||
logger.info(
|
||||
'participant_connected: marked %s for lesson %s (room %s)',
|
||||
'mentor' if is_mentor else 'client',
|
||||
video_room.lesson_id,
|
||||
video_room.room_id,
|
||||
)
|
||||
return Response({'success': True}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,10 +26,11 @@ def create_video_room_for_lesson(sender, instance, created, **kwargs):
|
|||
video_room = None
|
||||
|
||||
if not video_room:
|
||||
client_user = instance.client.user if hasattr(instance.client, 'user') else instance.client
|
||||
video_room = VideoRoom.objects.create(
|
||||
lesson=instance,
|
||||
mentor=instance.mentor,
|
||||
client=instance.client,
|
||||
client=client_user,
|
||||
is_recording=True, # По умолчанию включаем запись
|
||||
max_participants=2
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
"""
|
||||
URL routing для видео API.
|
||||
"""
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
VideoRoomViewSet,
|
||||
VideoParticipantViewSet,
|
||||
VideoCallLogViewSet,
|
||||
ScreenRecordingViewSet
|
||||
)
|
||||
from .janus_views import JanusVideoRoomViewSet
|
||||
from .token_views import VideoRoomTokenViewSet
|
||||
from .livekit_views import create_livekit_room, get_livekit_config, delete_livekit_room_by_lesson, update_livekit_participant_media_state, participant_connected
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'rooms', VideoRoomViewSet, basename='videoroom')
|
||||
router.register(r'participants', VideoParticipantViewSet, basename='videoparticipant')
|
||||
router.register(r'logs', VideoCallLogViewSet, basename='videocalllog')
|
||||
router.register(r'recordings', ScreenRecordingViewSet, basename='screenrecording')
|
||||
|
||||
# Janus Gateway endpoints (параллельно с ion-sfu)
|
||||
router.register(r'janus', JanusVideoRoomViewSet, basename='janus-videoroom')
|
||||
|
||||
# Token-based access (публичный доступ по токену)
|
||||
router.register(r'token', VideoRoomTokenViewSet, basename='videoroom-token')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
# LiveKit endpoints
|
||||
path('livekit/create-room/', create_livekit_room, name='livekit-create-room'),
|
||||
path('livekit/config/', get_livekit_config, name='livekit-config'),
|
||||
path('livekit/rooms/lesson/<int:lesson_id>/', delete_livekit_room_by_lesson, name='livekit-delete-room-by-lesson'),
|
||||
path('livekit/update-media-state/', update_livekit_participant_media_state, name='livekit-update-media-state'),
|
||||
path('livekit/participant-connected/', participant_connected, name='livekit-participant-connected'),
|
||||
]
|
||||
"""
|
||||
URL routing для видео API.
|
||||
"""
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
VideoRoomViewSet,
|
||||
VideoParticipantViewSet,
|
||||
VideoCallLogViewSet,
|
||||
ScreenRecordingViewSet
|
||||
)
|
||||
from .janus_views import JanusVideoRoomViewSet
|
||||
from .token_views import VideoRoomTokenViewSet
|
||||
from .livekit_views import create_livekit_room, get_livekit_config, delete_livekit_room_by_lesson, update_livekit_participant_media_state, participant_connected
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'rooms', VideoRoomViewSet, basename='videoroom')
|
||||
router.register(r'participants', VideoParticipantViewSet, basename='videoparticipant')
|
||||
router.register(r'logs', VideoCallLogViewSet, basename='videocalllog')
|
||||
router.register(r'recordings', ScreenRecordingViewSet, basename='screenrecording')
|
||||
|
||||
# Janus Gateway endpoints (параллельно с ion-sfu)
|
||||
router.register(r'janus', JanusVideoRoomViewSet, basename='janus-videoroom')
|
||||
|
||||
# Token-based access (публичный доступ по токену)
|
||||
router.register(r'token', VideoRoomTokenViewSet, basename='videoroom-token')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
# LiveKit endpoints
|
||||
path('livekit/create-room/', create_livekit_room, name='livekit-create-room'),
|
||||
path('livekit/config/', get_livekit_config, name='livekit-config'),
|
||||
path('livekit/rooms/lesson/<int:lesson_id>/', delete_livekit_room_by_lesson, name='livekit-delete-room-by-lesson'),
|
||||
path('livekit/update-media-state/', update_livekit_participant_media_state, name='livekit-update-media-state'),
|
||||
path('livekit/participant-connected/', participant_connected, name='livekit-participant-connected'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -81,6 +81,12 @@ app.conf.beat_schedule = {
|
|||
# ============================================
|
||||
# ЗАДАЧИ РАСПИСАНИЯ
|
||||
# ============================================
|
||||
|
||||
# Автоматическое начало и завершение занятий по времени (каждую минуту)
|
||||
'start-lessons-automatically': {
|
||||
'task': 'apps.schedule.tasks.start_lessons_automatically',
|
||||
'schedule': 60.0, # каждые 60 секунд
|
||||
},
|
||||
|
||||
# Отправка напоминаний о занятиях за 1 час
|
||||
'send-lesson-reminders': {
|
||||
|
|
|
|||
|
|
@ -1,59 +1,66 @@
|
|||
/**
|
||||
* API для работы с LiveKit
|
||||
*/
|
||||
|
||||
import apiClient from '@/lib/api-client';
|
||||
|
||||
export interface LiveKitRoomResponse {
|
||||
room_name: string;
|
||||
ws_url: string;
|
||||
access_token: string;
|
||||
server_url: string;
|
||||
ice_servers?: Array<{
|
||||
urls: string[];
|
||||
username?: string;
|
||||
credential?: string;
|
||||
}>;
|
||||
video_room_id?: number;
|
||||
is_admin?: boolean;
|
||||
lesson?: {
|
||||
id: number;
|
||||
title: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LiveKitConfig {
|
||||
server_url: string;
|
||||
ice_servers?: Array<{
|
||||
urls: string[];
|
||||
username?: string;
|
||||
credential?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать LiveKit комнату для занятия
|
||||
*/
|
||||
export async function createLiveKitRoom(lessonId: number): Promise<LiveKitRoomResponse> {
|
||||
const res = await apiClient.post<LiveKitRoomResponse>('/video/livekit/create-room/', {
|
||||
lesson_id: lessonId,
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конфигурацию LiveKit
|
||||
*/
|
||||
export async function getLiveKitConfig(): Promise<LiveKitConfig> {
|
||||
const res = await apiClient.get<LiveKitConfig>('/video/livekit/config/');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отметить подключение участника к видеокомнате (для метрик)
|
||||
*/
|
||||
export async function participantConnected(roomName: string): Promise<void> {
|
||||
await apiClient.post('/video/livekit/participant-connected/', { room_name: roomName });
|
||||
}
|
||||
/**
|
||||
* API для работы с LiveKit
|
||||
*/
|
||||
|
||||
import apiClient from '@/lib/api-client';
|
||||
|
||||
export interface LiveKitRoomResponse {
|
||||
room_name: string;
|
||||
ws_url: string;
|
||||
access_token: string;
|
||||
server_url: string;
|
||||
ice_servers?: Array<{
|
||||
urls: string[];
|
||||
username?: string;
|
||||
credential?: string;
|
||||
}>;
|
||||
video_room_id?: number;
|
||||
is_admin?: boolean;
|
||||
lesson?: {
|
||||
id: number;
|
||||
title: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LiveKitConfig {
|
||||
server_url: string;
|
||||
ice_servers?: Array<{
|
||||
urls: string[];
|
||||
username?: string;
|
||||
credential?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать LiveKit комнату для занятия
|
||||
*/
|
||||
export async function createLiveKitRoom(lessonId: number): Promise<LiveKitRoomResponse> {
|
||||
const res = await apiClient.post<LiveKitRoomResponse>('/video/livekit/create-room/', {
|
||||
lesson_id: lessonId,
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конфигурацию LiveKit
|
||||
*/
|
||||
export async function getLiveKitConfig(): Promise<LiveKitConfig> {
|
||||
const res = await apiClient.get<LiveKitConfig>('/video/livekit/config/');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отметить подключение участника к видеокомнате (для метрик).
|
||||
* lessonId — резерв при 404 по room_name (например, если room.name отличается от БД).
|
||||
*/
|
||||
export async function participantConnected(params: {
|
||||
roomName: string;
|
||||
lessonId?: number | null;
|
||||
}): Promise<void> {
|
||||
const { roomName, lessonId } = params;
|
||||
const body: { room_name: string; lesson_id?: number } = { room_name: roomName };
|
||||
if (lessonId != null) body.lesson_id = lessonId;
|
||||
await apiClient.post('/video/livekit/participant-connected/', body);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -447,13 +447,15 @@ function RoomContent({ lessonId, boardId, boardLoading, showBoard, setShowBoard,
|
|||
getNavBadges().then(setNavBadges).catch(() => setNavBadges(null));
|
||||
}, [user]);
|
||||
|
||||
// Фиксируем подключение ментора/студента для метрик
|
||||
// Фиксируем подключение ментора/студента для метрик (lessonId — резервный поиск комнаты)
|
||||
useEffect(() => {
|
||||
const onConnected = () => {
|
||||
if (room.name) participantConnected(room.name).catch(() => {});
|
||||
if (room.name || lessonId)
|
||||
participantConnected({ roomName: room.name || '', lessonId: lessonId ?? undefined }).catch(() => {});
|
||||
};
|
||||
room.on(RoomEvent.Connected, onConnected);
|
||||
if (room.state === 'connected' && room.name) participantConnected(room.name).catch(() => {});
|
||||
if (room.state === 'connected' && (room.name || lessonId))
|
||||
participantConnected({ roomName: room.name || '', lessonId: lessonId ?? undefined }).catch(() => {});
|
||||
return () => {
|
||||
room.off(RoomEvent.Connected, onConnected);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue