Browse Source
feat(api)!: centralize metadata and camelCase payloads
feat(api)!: centralize metadata and camelCase payloads
- Remove `metadata` from LiveSessionRoomCreateSerializer and ignore any client-provided `metadata` for security (with warning log) - Build secured room metadata server-side with explicit default lock settings (mic/webcam/screen share locked; whiteboard/notepad/chat unlocked) and comprehensive room features (chat, recording, breakout, waiting room, etc.) - Convert all request payload keys to camelCase before calling PlugNMeet for both room creation and join token to match protocol requirements - Extend non-admin user lock_settings in join token to include whiteboard/notepad/chat controls - Update live-session docs and add comprehensive PlugNMeet API docs BREAKING CHANGE: Clients must no longer send `metadata` when creating a room; all room settings are now enforced server-side and cannot be overridden by the client. The serializer no longer accepts `metadata`.shokhmgar
5 changed files with 2123 additions and 262 deletions
-
1apps/course/serializers/online.py
-
22apps/course/services/plugnmeet.py
-
57apps/course/views/live_session.py
-
645docs/live-session-api.md
-
1660docs/plugnmeet_api.md
@ -1,219 +1,114 @@ |
|||||
# راهنمای اتصال فرانتاند به API لایو کلاس |
|
||||
|
# راهنمای گرفتن توکن و ورود کلاینت به کلاسهای plugNmeet |
||||
|
|
||||
این مستند جریان کامل «ایجاد روم لایو»، «گرفتن توکن ورود» و «مدیریت ورود استاد و دانشجو» را توضیح میدهد. |
|
||||
|
این راهنما خلاصه میکند که برای سناریوی استاد/دانشجو چگونه از سرویس plugNmeet توکن بگیریم و کلاینت فرانتاند (`client/`) با آن وارد کلاس شود. |
||||
|
|
||||
## ۱. اعتبارسنجی وضعیت دوره |
|
||||
|
## پیشنیازها |
||||
|
- آدرس سرویس: `window.PLUG_N_MEET_SERVER_URL = "https://meet.newhorizonco.uk"` (در `config.js`). |
||||
|
- `api_key` و `secret` از فایل پیکربندی بکاند (`services/plugnmeet-server/config.yaml`). |
||||
|
- بدنهٔ درخواستها باید با پروتکل JSON متناظر با پیامهای پروتوباف (`plugnmeet-protocol`) ارسال شود؛ سرور طبق `HandleAuthHeaderCheck` هدرهای امنیتی را بررسی میکند. |
||||
|
|
||||
|
## گام ۱: ایجاد یا فعال بودن اتاق |
||||
|
|
||||
|
### API Endpoint برای Django Backend: |
||||
``` |
``` |
||||
GET /api/courses/<course-slug>/online/validate/ |
|
||||
Headers: |
|
||||
Authorization: Token <USER_TOKEN> |
|
||||
|
POST /api/courses/<course-slug>/online/room/create/ |
||||
``` |
``` |
||||
|
|
||||
### پاسخ نمونه (استاد، کلاس آنلاین در حال اجرا) |
|
||||
|
|
||||
|
### بدنه درخواست از فرانت به Django: |
||||
```json |
```json |
||||
{ |
{ |
||||
"course": { |
|
||||
"id": 42, |
|
||||
"slug": "algebra-10", |
|
||||
"title": "کلاس جبر", |
|
||||
"is_online": true, |
|
||||
"online_link": "https://imamjavad.app/courses/algebra-10/live", |
|
||||
"status": "ongoing", |
|
||||
"professor": { |
|
||||
"id": 10, |
|
||||
"fullname": "استاد نمونه", |
|
||||
"slug": "ostad-nemoone", |
|
||||
"avatar": "https://imamjavad.app/media/users/avatars/2025/10/ostad.jpg" |
|
||||
} |
|
||||
}, |
|
||||
"user": { |
|
||||
"id": 10, |
|
||||
"email": "[email protected]", |
|
||||
"fullname": "استاد نمونه", |
|
||||
"avatar": "https://imamjavad.app/media/users/avatars/2025/10/ostad.jpg", |
|
||||
"roles": ["professor"], |
|
||||
"is_staff": false |
|
||||
}, |
|
||||
"metadata": { |
|
||||
"status": "ongoing", |
|
||||
"has_started": true, |
|
||||
"has_finished": false, |
|
||||
"professor_in_class": false, |
|
||||
"can_create_live_session": false, |
|
||||
"can_join_live_session": true, |
|
||||
"scheduled_times": { |
|
||||
"day": "monday", |
|
||||
"start_time": "09:00", |
|
||||
"timezone": "Asia/Tehran" |
|
||||
}, |
|
||||
"generated_at": "2025-10-14T01:32:45+03:30", |
|
||||
"validated_at": "2025-10-14T01:33:10+03:30", |
|
||||
"redirect_path": null, |
|
||||
"is_online": true, |
|
||||
"active_room_id": "algebra-1402", |
|
||||
"livesession_started_at": "2025-10-14T01:15:00+03:30", |
|
||||
"livesession_ended_at": null, |
|
||||
"live_session": { |
|
||||
"id": 7, |
|
||||
"room_id": "algebra-1402", |
|
||||
"subject": "کلاس جبر فصل ۱", |
|
||||
"started_at": "2025-10-14T01:15:00+03:30", |
|
||||
"ended_at": null |
|
||||
} |
|
||||
} |
|
||||
|
"subject": "کلاس جبر فصل ۱" // اختیاری - عنوان روم |
||||
} |
} |
||||
``` |
``` |
||||
|
|
||||
- `can_create_live_session`: اگر `true` → استاد میتواند روم جدید بسازد (فقط وقتی کلاس آفلاین است) |
|
||||
- `can_join_live_session`: اگر `true` → کاربر میتواند به کلاس فعال بپیوندد (استاد یا دانشجو) |
|
||||
- `active_room_id`: room_id کلاس فعال (برای نمایش در UI) |
|
||||
- `livesession_started_at`: زمان شروع - برای محاسبه مدت سپریشده |
|
||||
|
**⚠️ نکات مهم:** |
||||
|
- **فرانت نباید `metadata` ارسال کند!** |
||||
|
- بکاند Django (در `apps/course/views/live_session.py`) بهطور خودکار تنظیمات امنیتی را اعمال میکند |
||||
|
- این تضمین میکند که تنظیمات امنیتی بهصورت متمرکز و یکسان اعمال شود |
||||
|
|
||||
### پاسخ نمونه (استاد، کلاس آفلاین) |
|
||||
|
**🎯 تنظیمات ضروری برای نمایش فیچرها:** |
||||
|
- برای نمایش **Whiteboard**: باید `whiteboardFeatures.allowedWhiteboard: true` باشد |
||||
|
- برای نمایش **SharedNotePad**: باید `sharedNotePadFeatures.allowedSharedNotePad: true` باشد و Etherpad service فعال باشد |
||||
|
- برای نمایش **BreakoutRoom**: باید `breakoutRoomFeatures.isAllow: true` باشد (فقط در منوی admin) |
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"course": { "id": 42, "title": "کلاس جبر" }, |
|
||||
"user": { "id": 10, "fullname": "استاد نمونه" }, |
|
||||
"metadata": { |
|
||||
"status": "ongoing", |
|
||||
"has_started": true, |
|
||||
"has_finished": false, |
|
||||
"professor_in_class": false, |
|
||||
"can_create_live_session": true, |
|
||||
"can_join_live_session": false, |
|
||||
"scheduled_times": { "day": "monday", "time": "09:00" }, |
|
||||
"generated_at": "2025-10-14T01:32:45+03:30", |
|
||||
"validated_at": "2025-10-14T01:33:10+03:30", |
|
||||
"redirect_path": null, |
|
||||
"is_online": false, |
|
||||
"active_room_id": null, |
|
||||
"livesession_started_at": null, |
|
||||
"livesession_ended_at": null, |
|
||||
"live_session": null |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
### بدنه درخواست از Django به PlugNMeet (خودکار): |
||||
|
بکاند Django این بدنه را خودش به PlugNMeet ارسال میکند: |
||||
|
|
||||
### پاسخ نمونه (دانشجو ثبتنامکرده، کلاس آنلاین) |
|
||||
|
**⚠️ توجه به نامگذاری:** |
||||
|
- در Python میتوانید از `snake_case` استفاده کنید |
||||
|
- اما **حتماً قبل از ارسال به PlugNMeet API** باید به `camelCase` تبدیل شود |
||||
|
- مثال: `default_lock_settings` → `defaultLockSettings` |
||||
|
- مثال: `room_features` → `roomFeatures` |
||||
|
|
||||
```json |
```json |
||||
{ |
{ |
||||
"course": { "id": 42, "title": "کلاس جبر" }, |
|
||||
"user": { "id": 27, "fullname": "دانشجو نمونه" }, |
|
||||
|
"room_id": "algebra-1402", |
||||
"metadata": { |
"metadata": { |
||||
"status": "ongoing", |
|
||||
"has_started": true, |
|
||||
"has_finished": false, |
|
||||
"professor_in_class": false, |
|
||||
"can_create_live_session": false, |
|
||||
"can_join_live_session": true, |
|
||||
"scheduled_times": { "day": "monday", "time": "09:00" }, |
|
||||
"generated_at": "2025-10-14T01:32:45+03:30", |
|
||||
"validated_at": "2025-10-14T01:33:10+03:30", |
|
||||
"redirect_path": null, |
|
||||
"is_online": true, |
|
||||
"active_room_id": "algebra-1402", |
|
||||
"livesession_started_at": "2025-10-14T01:15:00+03:30", |
|
||||
"livesession_ended_at": null, |
|
||||
"live_session": { |
|
||||
"id": 7, |
|
||||
"room_id": "algebra-1402", |
|
||||
"subject": "کلاس جبر فصل ۱", |
|
||||
"started_at": "2025-10-14T01:15:00+03:30", |
|
||||
"ended_at": null |
|
||||
|
"room_title": "کلاس جبر فصل ۱", |
||||
|
"defaultLockSettings": { |
||||
|
"lockMicrophone": true, // 🔒 قفل - فقط میزبان میتواند باز کند |
||||
|
"lockWebcam": true, // 🔒 قفل - فقط میزبان میتواند باز کند |
||||
|
"lockScreenSharing": true, // 🔒 قفل - فقط میزبان میتواند باز کند |
||||
|
"lockWhiteboard": false, // ✅ همه میتوانند ویرایش کنند |
||||
|
"lockSharedNotepad": false, // ✅ همه میتوانند ویرایش کنند |
||||
|
"lockChat": false, |
||||
|
"lockChatSendMessage": false, |
||||
|
"lockChatFileShare": false, |
||||
|
"lockPrivateChat": false |
||||
|
}, |
||||
|
"roomFeatures": { |
||||
|
"allowWebcams": true, |
||||
|
"muteOnStart": true, // 🔇 همه با میک خاموش وارد میشوند |
||||
|
"allowScreenSharing": true, |
||||
|
"allowRecording": true, |
||||
|
"allowRtmp": false, |
||||
|
"allowViewOtherWebcams": true, |
||||
|
"allowViewOtherParticipantsList": true, |
||||
|
"adminOnlyWebcams": false, |
||||
|
"allowPolls": true, |
||||
|
"roomDuration": 0, |
||||
|
"chatFeatures": { |
||||
|
"allowChat": true, |
||||
|
"allowFileUpload": true |
||||
|
}, |
||||
|
"sharedNotePadFeatures": { |
||||
|
"allowedSharedNotePad": true |
||||
|
}, |
||||
|
"whiteboardFeatures": { |
||||
|
"allowedWhiteboard": true |
||||
|
}, |
||||
|
"breakoutRoomFeatures": { |
||||
|
"isAllow": true, |
||||
|
"allowedNumberRooms": 6 |
||||
|
}, |
||||
|
"waitingRoomFeatures": { |
||||
|
"isActive": false |
||||
|
}, |
||||
|
"recordingFeatures": { |
||||
|
"isAllow": true, |
||||
|
"isAllowCloud": true, |
||||
|
"enableAutoCloudRecording": false |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
``` |
``` |
||||
|
|
||||
### پاسخ نمونه (دانشجو ثبتنامکرده، کلاس آفلاین) |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"course": { "id": 42, "title": "کلاس جبر" }, |
|
||||
"user": { "id": 27, "fullname": "دانشجو نمونه" }, |
|
||||
"metadata": { |
|
||||
"status": "ongoing", |
|
||||
"has_started": true, |
|
||||
"has_finished": false, |
|
||||
"professor_in_class": false, |
|
||||
"can_create_live_session": false, |
|
||||
"can_join_live_session": false, |
|
||||
"scheduled_times": { "day": "monday", "time": "09:00" }, |
|
||||
"generated_at": "2025-10-14T01:32:45+03:30", |
|
||||
"validated_at": "2025-10-14T01:33:10+03:30", |
|
||||
"redirect_path": null, |
|
||||
"is_online": false, |
|
||||
"active_room_id": null, |
|
||||
"livesession_started_at": null, |
|
||||
"livesession_ended_at": null, |
|
||||
"live_session": null |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
> **چرا بکاند این کار را میکند؟** |
||||
|
> - ✅ **امنیت متمرکز**: تنظیمات امنیتی در یک جا کنترل میشود |
||||
|
> - ✅ **جلوگیری از دستکاری**: فرانت نمیتواند تنظیمات را تغییر دهد |
||||
|
> - ✅ **یکپارچگی**: همه کلاسها با تنظیمات یکسان ساخته میشوند |
||||
|
> - 🔒 طبق تابع `AssignLockSettingsToUser` در `pkg/models/user_lock.go` این مقادیر برای کاربران غیر-admin اعمال میشود |
||||
|
|
||||
### پاسخ نمونه (کاربر بدون دسترسی) |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"status": "error", |
|
||||
"code": "app_api_error", |
|
||||
"status_code": 403, |
|
||||
"message": "An error occurred while processing the request.", |
|
||||
"errors": [ |
|
||||
{ "message": "You do not have access to this course." } |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## ۲. ساخت یا فعال کردن روم (استاد) |
|
||||
|
|
||||
``` |
|
||||
POST /api/courses/<course-slug>/online/room/create/ |
|
||||
Headers: |
|
||||
Authorization: Token <USER_TOKEN> |
|
||||
Content-Type: application/json |
|
||||
|
## گام ۲: گرفتن توکن ورود |
||||
|
|
||||
Body (نمونه): |
|
||||
{ |
|
||||
"subject": "کلاس جبر فصل ۱" // اختیاری؛ پیشفرض عنوان دوره + "Live Session" |
|
||||
} |
|
||||
|
### API Endpoint برای Django Backend: |
||||
``` |
``` |
||||
|
|
||||
**⚠️ نکات مهم:** |
|
||||
- **فرانت نباید `metadata` ارسال کند!** |
|
||||
- بکاند بهطور خودکار تنظیمات امنیتی را اعمال میکند: |
|
||||
- `lock_microphone: true` - میکروفون برای همه قفل است |
|
||||
- `lock_webcam: true` - وبکم برای همه قفل است |
|
||||
- `lock_screen_sharing: true` - اشتراک صفحه برای همه قفل است |
|
||||
- `mute_on_start: true` - همه با میکروفون خاموش وارد میشوند |
|
||||
- **فقط میزبان (استاد)** میتواند این محدودیتها را برداشته و به دانشجو اجازه دهد |
|
||||
|
|
||||
### پاسخ موفق (۲۰۱ یا ۲۰۰) |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"session": { |
|
||||
"id": 7, |
|
||||
"room_id": "algebra-1402", |
|
||||
"subject": "کلاس جبر فصل ۱", |
|
||||
"started_at": "2025-10-14T01:32:45+03:30" |
|
||||
}, |
|
||||
"plugnmeet": { |
|
||||
"status": "success", |
|
||||
"room_id": "algebra-1402", |
|
||||
"...": "پاسخ کامل PlugNMeet" |
|
||||
} |
|
||||
} |
|
||||
|
POST /api/courses/online/room/token/ |
||||
``` |
``` |
||||
|
|
||||
## ۳. گرفتن توکن ورود به روم |
|
||||
|
|
||||
|
### درخواست از فرانت به Django: |
||||
``` |
``` |
||||
POST /api/courses/online/room/token/ |
|
||||
Headers: |
Headers: |
||||
Authorization: Token <USER_TOKEN> |
Authorization: Token <USER_TOKEN> |
||||
Content-Type: application/json |
Content-Type: application/json |
||||
@ -226,22 +121,122 @@ Body: |
|||||
|
|
||||
**⚠️ نکات مهم:** |
**⚠️ نکات مهم:** |
||||
- **فرانت فقط `course_slug` ارسال میکند!** |
- **فرانت فقط `course_slug` ارسال میکند!** |
||||
- بکاند از `Authorization` header کاربر را شناسایی میکند |
|
||||
|
- بکاند Django از `Authorization` header کاربر را شناسایی میکند |
||||
- بکاند خودش live session فعال دوره را پیدا میکند: |
- بکاند خودش live session فعال دوره را پیدا میکند: |
||||
```python |
```python |
||||
|
# 1. پیدا کردن دوره |
||||
course = Course.objects.get(slug=course_slug) |
course = Course.objects.get(slug=course_slug) |
||||
session = CourseLiveSession.objects.get(course=course, ended_at__isnull=True) |
|
||||
|
|
||||
|
# 2. پیدا کردن live session فعال |
||||
|
session = CourseLiveSession.objects.get( |
||||
|
course=course, |
||||
|
ended_at__isnull=True # session هایی که هنوز به پایان نرسیدهاند |
||||
|
) |
||||
|
|
||||
|
# 3. گرفتن room_id |
||||
room_id = session.room_id |
room_id = session.room_id |
||||
``` |
``` |
||||
- بکاند خودش همه اطلاعات کاربر را میسازد: |
- بکاند خودش همه اطلاعات کاربر را میسازد: |
||||
- `user_id` از `request.user.id` |
|
||||
|
- `user_id` از `request.user` |
||||
- `name` از `user.get_full_name()` یا `user.email` |
- `name` از `user.get_full_name()` یا `user.email` |
||||
- `is_admin` از `user.can_manage_course(course)` - تشخیص خودکار استاد/دانشجو |
|
||||
|
- `is_admin` از `user.can_manage_course(course)` |
||||
- `profilePic` از `user.avatar` |
- `profilePic` از `user.avatar` |
||||
- `lock_settings` خودکار برای دانشجو (همه قفل) |
|
||||
|
- `lock_settings` برای غیر-admin |
||||
|
|
||||
|
### بدنه درخواست از Django به PlugNMeet (خودکار): |
||||
|
|
||||
|
بکاند Django این payload را خودش میسازد و به PlugNMeet میفرستد: |
||||
|
|
||||
|
**برای استاد:** |
||||
|
```json |
||||
|
{ |
||||
|
"room_id": "algebra-1402", |
||||
|
"user_info": { |
||||
|
"user_id": "10", // 🔐 از request.user |
||||
|
"name": "استاد نمونه", // 🔐 از user.get_full_name() |
||||
|
"is_admin": true, // 🔐 از user.can_manage_course() |
||||
|
"user_metadata": { |
||||
|
"is_hidden": false, |
||||
|
"profilePic": "https://..." // 🔐 از user.avatar |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**برای دانشجو:** |
||||
|
```json |
||||
|
{ |
||||
|
"room_id": "algebra-1402", |
||||
|
"user_info": { |
||||
|
"user_id": "27", // 🔐 از request.user |
||||
|
"name": "دانشجو نمونه", // 🔐 از user.get_full_name() |
||||
|
"is_admin": false, // 🔐 از user.can_manage_course() |
||||
|
"user_metadata": { |
||||
|
"profilePic": "https://...", // 🔐 از user.avatar |
||||
|
"lock_settings": { // 🔒 خودکار برای غیر-admin |
||||
|
"lock_microphone": true, |
||||
|
"lock_screen_sharing": true, |
||||
|
"lock_webcam": true, |
||||
|
"lock_whiteboard": false, // ✅ میتواند روی whiteboard بنویسد |
||||
|
"lock_shared_notepad": false, // ✅ میتواند در notepad بنویسد |
||||
|
"lock_chat": false, |
||||
|
"lock_chat_send_message": false, |
||||
|
"lock_chat_file_share": false, |
||||
|
"lock_private_chat": false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### نحوه کار بکاند Django: |
||||
|
```python |
||||
|
# 1. شناسایی کاربر از token |
||||
|
user = request.user # از Authorization header |
||||
|
|
||||
|
# 2. پیدا کردن دوره و session فعال |
||||
|
course = Course.objects.get(slug=course_slug) |
||||
|
session = CourseLiveSession.objects.get(course=course, ended_at__isnull=True) |
||||
|
room_id = session.room_id |
||||
|
|
||||
|
# 3. تشخیص نقش |
||||
|
is_admin = user.can_manage_course(course) # استاد یا مالک دوره |
||||
|
|
||||
|
# 4. ساخت user_info |
||||
|
user_info = { |
||||
|
'user_id': str(user.id), |
||||
|
'name': user.get_full_name() or user.email, |
||||
|
'is_admin': is_admin, |
||||
|
} |
||||
|
|
||||
|
# 4. اضافه کردن profilePic |
||||
|
profile_pic = request.build_absolute_uri(user.avatar.url) |
||||
|
user_metadata['profilePic'] = profile_pic |
||||
|
|
||||
|
# 5. اضافه کردن lock_settings برای غیر-admin |
||||
|
if not is_admin: |
||||
|
user_metadata['lock_settings'] = { |
||||
|
'lock_microphone': True, |
||||
|
'lock_screen_sharing': True, |
||||
|
'lock_webcam': True, |
||||
|
'lock_whiteboard': False, # دانشجو میتواند روی whiteboard بنویسد |
||||
|
'lock_shared_notepad': False, # دانشجو میتواند در notepad بنویسد |
||||
|
'lock_chat': False, |
||||
|
'lock_chat_send_message': False, |
||||
|
'lock_chat_file_share': False, |
||||
|
'lock_private_chat': False, |
||||
|
} |
||||
|
``` |
||||
|
|
||||
### پاسخ موفق |
|
||||
|
### ارسال به PlugNMeet: |
||||
|
بکاند Django با هدرهای امنیتی به PlugNMeet ارسال میکند: |
||||
|
- `API-KEY`: از settings |
||||
|
- `HASH-SIGNATURE`: `HMAC_SHA256(body, secret)` |
||||
|
- این توکن JWT اختصاصی plugNmeet است که در `GeneratePNMJoinToken` ساخته میشود |
||||
|
- `is_admin: true` باعث میشود در `GetPNMJoinToken` کاربر به عنوان presenter با تمام دسترسیها ثبت شود |
||||
|
- `lock_settings` باعث میشود در فرانتاند PlugNMeet دکمههای میکروفون/وبکم غیرفعال شوند |
||||
|
|
||||
|
### پاسخ Django به فرانت: |
||||
```json |
```json |
||||
{ |
{ |
||||
"room_id": "algebra-1402", |
"room_id": "algebra-1402", |
||||
@ -249,85 +244,229 @@ Body: |
|||||
"plugnmeet": { |
"plugnmeet": { |
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", |
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", |
||||
"expires": 300, |
"expires": 300, |
||||
"...": "پاسخ کامل PlugNMeet" |
|
||||
|
... |
||||
} |
} |
||||
} |
} |
||||
``` |
``` |
||||
|
|
||||
### نحوه استفاده: |
|
||||
- `token` باید در URL سرویس PlugNMeet استفاده شود: |
|
||||
|
فرانت با این `token` میتواند کاربر را به PlugNMeet وارد کند: |
||||
|
``` |
||||
|
https://meet.newhorizonco.uk/?access_token=<TOKEN> |
||||
|
``` |
||||
|
|
||||
|
## گام ۳: ورود کلاینت با توکن |
||||
|
۱. توکن را در URL یا کوکی قرار دهید؛ کلاینت مقدار را از `access_token` در کوئریاسترینگ یا از کوکی `pnm_access_token` میخواند (`getAccessToken` در `client/src/helpers/utils.ts`). |
||||
|
۲. آدرس ورود: `https://meet.newhorizonco.uk/?access_token=<TOKEN>`. |
||||
|
۳. اپلیکیشن React موجود در `client/src/components/app/index.tsx` پس از بارگذاری: |
||||
|
- درخواست `POST /api/verifyToken` را با هدر `Authorization: <TOKEN>` میفرستد (`HandleVerifyToken`). |
||||
|
- اگر توکن معتبر باشد، لیست آدرسهای NATS و موضوعات لازم را میگیرد و اتصال را آغاز میکند (`startNatsConn`). |
||||
|
۴. پس از اتصال، وضعیت کاربر و اتاق در Redux ذخیره میشود (`sessionSlice`). اگر کاربر ادمین باشد، تمام امکانات بدون محدودیت فعال است؛ در غیر این صورت مقدارهای `lock_settings` تعیین میکنند چه دکمههایی فعال باشند. |
||||
|
|
||||
|
## کنترل حالت صحبت/شنیدن برای استاد و دانشجو |
||||
|
|
||||
|
### استاد (Moderator/Host): |
||||
|
- ✅ در توکن `is_admin: true` ارسال میشود |
||||
|
- ✅ بکاند Django در `apps/course/views/live_session.py` این را تشخیص میدهد: |
||||
|
```python |
||||
|
is_admin = user.can_manage_course(course) # استاد یا مالک دوره |
||||
``` |
``` |
||||
https://meet.newhorizonco.uk/?access_token=<token> |
|
||||
|
- ✅ سرور PlugNMeet در `GetPNMJoinToken` رول presenter را فعال میکند |
||||
|
- ✅ **هیچ قفلی** روی میکروفون، وبکم یا اشتراک صفحه اعمال نمیشود |
||||
|
- 🎤 استاد میتواند بلافاصله صحبت کند و به دانشجو **اجازه صحبت** دهد |
||||
|
|
||||
|
### دانشجو (Participant): |
||||
|
- 🔒 در توکن `is_admin: false` ارسال میشود |
||||
|
- 🔒 بکاند Django خودکار lock_settings را اضافه میکند: |
||||
|
```python |
||||
|
if not is_admin: |
||||
|
user_metadata['lock_settings'] = { |
||||
|
'lock_microphone': True, |
||||
|
'lock_screen_sharing': True, |
||||
|
'lock_webcam': True, |
||||
|
'lock_whiteboard': False, # میتواند روی whiteboard بنویسد |
||||
|
'lock_shared_notepad': False, # میتواند در notepad بنویسد |
||||
|
'lock_chat': False, |
||||
|
'lock_chat_send_message': False, |
||||
|
'lock_chat_file_share': False, |
||||
|
'lock_private_chat': False, |
||||
|
} |
||||
``` |
``` |
||||
- بکاند خودکار تشخیص میدهد: |
|
||||
- **استاد**: `is_admin: true` → همه دسترسیها بدون محدودیت |
|
||||
- **دانشجو**: `is_admin: false` + `lock_settings` → میکروفون، وبکم و اشتراک صفحه قفل است |
|
||||
|
- 🔇 دکمههای میکروفون، وبکم و اشتراک صفحه **غیرفعال** هستند |
||||
|
- 👂 فقط میتواند **گوش دهد** تا میزبان اجازه دهد |
||||
|
- ✅ اما میتواند در **Whiteboard** و **SharedNotePad** بنویسد و چت کند |
||||
|
- این منطق در `joinModal.tsx` با متغیر `isMicLock` پیادهسازی شده است |
||||
|
|
||||
|
### نحوه دادن اجازه به دانشجو: |
||||
|
- میزبان باید از داخل کلاس از طریق UI کنترل کند |
||||
|
- یا از API `/api/updateLockSettings` یا `switchPresenter` استفاده کند |
||||
|
|
||||
|
## نکات تکمیلی |
||||
|
|
||||
|
### توکنها و انقضا: |
||||
|
- توکنها زمان انقضای مفهومی دارند (`client.token_validity` در YAML) |
||||
|
- در صورت نزدیک شدن به انقضا، کلاینت خودکار با `REQ_RENEW_PNM_TOKEN` درخواست تمدید میدهد |
||||
|
|
||||
|
### Authorization: |
||||
|
- برای درخواستهای بعدی به `/api/...` همان هدر `Authorization` را ست کنید |
||||
|
- کلاینت این کار را در `helpers/api/plugNmeetAPI.ts` انجام میدهد |
||||
|
|
||||
|
### مدیریت دسترسیها: |
||||
|
- اگر میخواهید دانشجو را به صحبتکننده ارتقا دهید: `/api/updateLockSettings` یا `switchPresenter` |
||||
|
- این کار فقط توسط **میزبان** امکانپذیر است |
||||
|
|
||||
|
## 🔐 جمعبندی امنیت |
||||
|
|
||||
|
### ❌ چیزهایی که فرانت نباید انجام دهد: |
||||
|
|
||||
|
#### موقع ساخت روم: |
||||
|
- ❌ ارسال `metadata` |
||||
|
- ❌ ارسال `default_lock_settings` |
||||
|
- ❌ ارسال `room_features` |
||||
|
|
||||
|
#### موقع گرفتن توکن: |
||||
|
- ❌ ارسال `room_id` (بکاند خودش از session فعال میگیرد) |
||||
|
- ❌ ارسال `user_info` |
||||
|
- ❌ ارسال `is_admin` |
||||
|
- ❌ ارسال `lock_settings` |
||||
|
- ❌ ارسال `user_id` یا `name` |
||||
|
|
||||
|
### ✅ چیزهایی که فرانت فقط ارسال میکند: |
||||
|
|
||||
|
#### موقع ساخت روم: |
||||
|
```json |
||||
|
{ |
||||
|
"room_id": "algebra-1402", // اختیاری |
||||
|
"subject": "کلاس جبر" // اختیاری |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### موقع گرفتن توکن: |
||||
|
```json |
||||
|
{ |
||||
|
"course_slug": "algebra-10" // فقط این! |
||||
|
} |
||||
|
``` |
||||
|
+ `Authorization: Token <USER_TOKEN>` در header |
||||
|
|
||||
|
### ✅ چیزهایی که بکاند Django خودش انجام میدهد: |
||||
|
|
||||
|
#### برای همه درخواستها: |
||||
|
- ✅ شناسایی کاربر از `Authorization` header |
||||
|
- ✅ بررسی دسترسی با `user.can_manage_course()` یا `Participant.objects.filter()` |
||||
|
|
||||
|
#### موقع ساخت روم: |
||||
|
- ✅ تعیین `defaultLockSettings` (همه `true` به جز whiteboard/notepad) |
||||
|
- ✅ تعیین `roomFeatures` **کامل** شامل: |
||||
|
- ✅ `sharedNotePadFeatures.allowedSharedNotePad: true` |
||||
|
- ✅ `whiteboardFeatures.allowedWhiteboard: true` |
||||
|
- ✅ `breakoutRoomFeatures.isAllow: true` |
||||
|
- ✅ `chatFeatures`, `recordingFeatures`, و سایر فیچرها |
||||
|
- ✅ تبدیل نامهای `snake_case` به `camelCase` قبل از ارسال به PlugNMeet |
||||
|
- ✅ ساخت `metadata` کامل برای PlugNMeet |
||||
|
|
||||
|
#### موقع گرفتن توکن: |
||||
|
- ✅ پیدا کردن live session فعال از `course_slug` |
||||
|
- ✅ گرفتن `room_id` از session |
||||
|
- ✅ ساخت `user_id` از `request.user.id` |
||||
|
- ✅ ساخت `name` از `user.get_full_name()` یا `user.email` |
||||
|
- ✅ تشخیص `is_admin` از `user.can_manage_course(course)` |
||||
|
- ✅ گرفتن `profilePic` از `user.avatar` |
||||
|
- ✅ اضافه کردن `lock_settings` کامل برای غیر-admin شامل: |
||||
|
- ✅ `lock_microphone`, `lock_webcam`, `lock_screen_sharing` (همه `True`) |
||||
|
- ✅ `lock_whiteboard`, `lock_shared_notepad` (همه `False` - میتوانند بنویسند) |
||||
|
- ✅ `lock_chat`, `lock_chat_send_message`, `lock_private_chat` (همه `False`) |
||||
|
- ✅ تبدیل نامهای `snake_case` به `camelCase` قبل از ارسال |
||||
|
- ✅ ساخت `user_info` کامل برای PlugNMeet |
||||
|
|
||||
|
**نتیجه:** |
||||
|
- 🔒 **امنیت کامل**: فرانت نمیتواند هیچ تنظیمات امنیتی را دستکاری کند |
||||
|
- ✅ **متمرکز**: همه logic در بکاند Django است |
||||
|
- 🎯 **ساده**: فرانت فقط `course_slug` و `Authorization` header ارسال میکند |
||||
|
- 🔐 **قابل کنترل**: بکاند تعیین میکند کدام session فعال است |
||||
|
|
||||
|
--- |
||||
|
|
||||
## سناریوهای پیشنهادی برای پیادهسازی فرانت |
|
||||
|
## 🐛 عیبیابی |
||||
|
|
||||
### سناریوی استاد |
|
||||
1. **دریافت وضعیت**: با `GET /online/validate/` وضعیت را بگیرید. |
|
||||
- اگر `can_create_live_session = true` است → دکمه «ساخت کلاس» را نشان دهید. |
|
||||
- اگر `can_join_live_session = true` است → دکمه «ورود به کلاس» را نشان دهید. |
|
||||
2. **ساخت روم** (در صورت نیاز): |
|
||||
|
### مشکل: Whiteboard/SharedNotePad نمایش داده نمیشود |
||||
|
|
||||
|
**علائم:** |
||||
|
- آیکون Whiteboard در footer نمایش داده نمیشود |
||||
|
- گزینه Enable/Disable SharedNotePad در منوی admin نیست |
||||
|
- گزینه Manage Breakout Room در منوی admin نیست |
||||
|
|
||||
|
**راه حلها:** |
||||
|
|
||||
|
1. **بررسی `roomFeatures` در room creation:** |
||||
```json |
```json |
||||
POST /online/room/create/ |
|
||||
Body: { |
|
||||
"room_id": "algebra-10-1704880365000", // اختیاری |
|
||||
"subject": "کلاس جبر" // اختیاری |
|
||||
|
"roomFeatures": { |
||||
|
"sharedNotePadFeatures": { |
||||
|
"allowedSharedNotePad": true // ✅ باید true باشد |
||||
|
}, |
||||
|
"whiteboardFeatures": { |
||||
|
"allowedWhiteboard": true // ✅ باید true باشد |
||||
|
}, |
||||
|
"breakoutRoomFeatures": { |
||||
|
"isAllow": true // ✅ باید true باشد |
||||
|
} |
||||
} |
} |
||||
``` |
``` |
||||
**نکته**: فقط این دو فیلد! بکاند خودش `metadata` و تنظیمات امنیتی را اعمال میکند. |
|
||||
|
|
||||
3. **گرفتن توکن**: |
|
||||
```json |
|
||||
POST /online/room/token/ |
|
||||
Body: { |
|
||||
"course_slug": "algebra-10" // فقط این! |
|
||||
} |
|
||||
|
2. **بررسی نامگذاری فیلدها:** |
||||
|
- ❌ `shared_note_pad_features` (snake_case) - اشتباه |
||||
|
- ✅ `sharedNotePadFeatures` (camelCase) - صحیح |
||||
|
|
||||
|
3. **بررسی `config.yaml` در plugnmeet-server:** |
||||
|
```yaml |
||||
|
shared_notepad: |
||||
|
enabled: true # ✅ باید true باشد |
||||
|
etherpad_hosts: |
||||
|
- id: "etherpad_node_01" |
||||
|
host: "http://plugnmeet-etherpad:9001" |
||||
|
client_id: "plugNmeet" |
||||
|
client_secret: "..." |
||||
``` |
``` |
||||
**نکته**: بکاند خودش live session فعال را پیدا میکند و `room_id` را میگیرد. |
|
||||
|
|
||||
4. **ورود به کلاس**: |
|
||||
```javascript |
|
||||
window.open(`https://meet.newhorizonco.uk/?access_token=${token}`, "_blank"); |
|
||||
|
4. **بررسی Etherpad service:** |
||||
|
```bash |
||||
|
docker ps | grep etherpad |
||||
|
# باید یک container با نام plugnmeet-etherpad اجرا باشد |
||||
``` |
``` |
||||
|
|
||||
### سناریوی دانشجو |
|
||||
1. **دریافت وضعیت**: با `GET /online/validate/` وضعیت را بگیرید. |
|
||||
- اگر `is_online = true` و `can_join_live_session = true` → دکمه «ورود به کلاس» را نمایش دهید. |
|
||||
|
|
||||
2. **گرفتن توکن و ورود**: |
|
||||
|
5. **بررسی `defaultLockSettings`:** |
||||
|
- اگر `lockWhiteboard: true` باشد، فقط admin میتواند ویرایش کند |
||||
|
- اگر `lockSharedNotepad: true` باشد، فقط admin میتواند ویرایش کند |
||||
|
|
||||
|
6. **بررسی user `lock_settings` در توکن:** |
||||
```json |
```json |
||||
POST /online/room/token/ |
|
||||
Body: { |
|
||||
"course_slug": "algebra-10" // فقط این! |
|
||||
|
"lock_settings": { |
||||
|
"lock_whiteboard": false, // false = میتواند ویرایش کند |
||||
|
"lock_shared_notepad": false // false = میتواند ویرایش کند |
||||
} |
} |
||||
``` |
``` |
||||
سپس با `token` دریافتی به PlugNMeet وارد شوید. |
|
||||
|
|
||||
### ✅ آنچه بکاند خودکار انجام میدهد: |
|
||||
|
### مشکل: دانشجو نمیتواند در Whiteboard بنویسد |
||||
|
|
||||
#### موقع ساخت روم: |
|
||||
- ✅ تعیین `default_lock_settings` (همه `true`) |
|
||||
- ✅ تعیین `room_features.mute_on_start: true` |
|
||||
- ✅ ساخت `metadata` کامل برای PlugNMeet |
|
||||
|
**علت:** |
||||
|
- `lock_whiteboard: true` در توکن کاربر |
||||
|
|
||||
#### موقع گرفتن توکن: |
|
||||
- ✅ پیدا کردن live session فعال از `course_slug` |
|
||||
- ✅ گرفتن `room_id` از session |
|
||||
- ✅ تشخیص `is_admin` با `user.can_manage_course(course)` |
|
||||
- ✅ ساخت `user_info` کامل (user_id, name, profilePic) |
|
||||
- ✅ اضافه کردن `lock_settings` برای دانشجو |
|
||||
|
|
||||
### ❌ آنچه فرانت نباید ارسال کند: |
|
||||
- ❌ `metadata` موقع ساخت روم |
|
||||
- ❌ `room_id` موقع گرفتن توکن |
|
||||
- ❌ `user_info`, `is_admin`, `lock_settings` |
|
||||
|
|
||||
### 🔐 نکات امنیتی: |
|
||||
- همه تنظیمات امنیتی در سمت سرور کنترل میشود |
|
||||
- فرانت نمیتواند تنظیمات را دستکاری کند |
|
||||
- بکاند تعیین میکند چه کسی استاد است و چه کسی دانشجو |
|
||||
- زمانی که استاد وارد لایو شده است، `can_create_live_session` برابر `false` میشود |
|
||||
- برای نمایش مدت سپریشده، از `livesession_started_at` استفاده کرده و در فرانت اختلاف با زمان فعلی را محاسبه کنید |
|
||||
|
**راه حل:** |
||||
|
- در هنگام ساخت توکن برای دانشجو، `lock_whiteboard` را `false` کنید |
||||
|
- یا از منوی admin، lock را برای آن کاربر خاص باز کنید |
||||
|
|
||||
|
### مشکل: SharedNotePad آیکون دارد اما باز نمیشود |
||||
|
|
||||
|
**علت:** |
||||
|
- Etherpad service اجرا نیست یا در دسترس نیست |
||||
|
|
||||
|
**راه حل:** |
||||
|
```bash |
||||
|
# بررسی وضعیت Etherpad |
||||
|
docker-compose -f docker-compose.plugnmeet.yml ps etherpad |
||||
|
|
||||
|
# اگر اجرا نیست، راهاندازی کنید |
||||
|
docker-compose -f docker-compose.plugnmeet.yml up -d plugnmeet-etherpad |
||||
|
|
||||
|
# بررسی logs |
||||
|
docker-compose -f docker-compose.plugnmeet.yml logs -f plugnmeet-etherpad |
||||
|
``` |
||||
1660
docs/plugnmeet_api.md
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue