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
-
55apps/course/views/live_session.py
-
643docs/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 |
|||
{ |
|||
"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 |
|||
{ |
|||
"course": { "id": 42, "title": "کلاس جبر" }, |
|||
"user": { "id": 27, "fullname": "دانشجو نمونه" }, |
|||
"room_id": "algebra-1402", |
|||
"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." } |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## ۲. ساخت یا فعال کردن روم (استاد) |
|||
## گام ۲: گرفتن توکن ورود |
|||
|
|||
### API Endpoint برای Django Backend: |
|||
``` |
|||
POST /api/courses/<course-slug>/online/room/create/ |
|||
Headers: |
|||
Authorization: Token <USER_TOKEN> |
|||
Content-Type: application/json |
|||
|
|||
Body (نمونه): |
|||
{ |
|||
"subject": "کلاس جبر فصل ۱" // اختیاری؛ پیشفرض عنوان دوره + "Live Session" |
|||
} |
|||
``` |
|||
|
|||
**⚠️ نکات مهم:** |
|||
- **فرانت نباید `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: |
|||
Authorization: Token <USER_TOKEN> |
|||
Content-Type: application/json |
|||
@ -226,22 +121,122 @@ Body: |
|||
|
|||
**⚠️ نکات مهم:** |
|||
- **فرانت فقط `course_slug` ارسال میکند!** |
|||
- بکاند از `Authorization` header کاربر را شناسایی میکند |
|||
- بکاند Django از `Authorization` header کاربر را شناسایی میکند |
|||
- بکاند خودش live session فعال دوره را پیدا میکند: |
|||
```python |
|||
# 1. پیدا کردن دوره |
|||
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 |
|||
``` |
|||
- بکاند خودش همه اطلاعات کاربر را میسازد: |
|||
- `user_id` از `request.user.id` |
|||
- `user_id` از `request.user` |
|||
- `name` از `user.get_full_name()` یا `user.email` |
|||
- `is_admin` از `user.can_manage_course(course)` - تشخیص خودکار استاد/دانشجو |
|||
- `is_admin` از `user.can_manage_course(course)` |
|||
- `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 |
|||
{ |
|||
"room_id": "algebra-1402", |
|||
@ -249,85 +244,229 @@ Body: |
|||
"plugnmeet": { |
|||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", |
|||
"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 |
|||
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` → دکمه «ورود به کلاس» را نمایش دهید. |
|||
5. **بررسی `defaultLockSettings`:** |
|||
- اگر `lockWhiteboard: true` باشد، فقط admin میتواند ویرایش کند |
|||
- اگر `lockSharedNotepad: true` باشد، فقط admin میتواند ویرایش کند |
|||
|
|||
2. **گرفتن توکن و ورود**: |
|||
6. **بررسی user `lock_settings` در توکن:** |
|||
```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