@ -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": "prof@example.com",
"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
```