docs: Add edge cases quick reference and permissions matrix.
This commit is contained in:
parent
44f8aea3c1
commit
b6720d5d27
2 changed files with 1059 additions and 0 deletions
792
docs/permissions_matrix.md
Normal file
792
docs/permissions_matrix.md
Normal file
|
|
@ -0,0 +1,792 @@
|
|||
# ตารางสิทธิ์การใช้งานระบบ E-Learning (Permissions Matrix)
|
||||
|
||||
## รายการสิทธิ์ทั้งหมด (Permission List)
|
||||
|
||||
### 1. การจัดการผู้ใช้งาน (User Management)
|
||||
|
||||
| Permission Code | ชื่อสิทธิ์ | คำอธิบาย |
|
||||
|----------------|-----------|---------|
|
||||
| `user.register` | ลงทะเบียนผู้ใช้ใหม่ | สามารถสร้างบัญชีผู้ใช้ใหม่ในระบบ |
|
||||
| `user.login` | เข้าสู่ระบบ | สามารถเข้าสู่ระบบด้วยอีเมล/รหัสผ่าน |
|
||||
| `user.profile.view` | ดูโปรไฟล์ตนเอง | สามารถดูข้อมูลโปรไฟล์ของตนเอง |
|
||||
| `user.profile.edit` | แก้ไขโปรไฟล์ตนเอง | สามารถแก้ไขข้อมูลส่วนตัวของตนเอง |
|
||||
| `user.password.reset` | รีเซ็ตรหัสผ่าน | สามารถรีเซ็ตรหัสผ่านผ่านอีเมล |
|
||||
| `user.list.view` | ดูรายการผู้ใช้ทั้งหมด | สามารถดูรายชื่อผู้ใช้ทั้งหมดในระบบ |
|
||||
| `user.manage` | จัดการผู้ใช้งาน | สามารถสร้าง แก้ไข ลบบัญชีผู้ใช้ |
|
||||
| `user.role.assign` | กำหนดบทบาทผู้ใช้ | สามารถกำหนดและเปลี่ยนบทบาทของผู้ใช้ |
|
||||
|
||||
---
|
||||
|
||||
### 2. การจัดการหลักสูตร (Course Management)
|
||||
|
||||
| Permission Code | ชื่อสิทธิ์ | คำอธิบาย |
|
||||
|----------------|-----------|---------|
|
||||
| `course.list.view` | ดูรายการหลักสูตร | สามารถดูรายการหลักสูตรที่เผยแพร่ |
|
||||
| `course.detail.view` | ดูรายละเอียดหลักสูตร | สามารถดูรายละเอียดของหลักสูตร |
|
||||
| `course.search` | ค้นหาหลักสูตร | สามารถค้นหาหลักสูตรในระบบ |
|
||||
| `course.enroll` | ลงทะเบียนเรียน | สามารถลงทะเบียนเรียนหลักสูตร |
|
||||
| `course.create` | สร้างหลักสูตร | สามารถสร้างหลักสูตรใหม่ |
|
||||
| `course.edit` | แก้ไขหลักสูตร | สามารถแก้ไขรายละเอียดหลักสูตร |
|
||||
| `course.delete` | ลบหลักสูตร | สามารถลบหลักสูตร |
|
||||
| `course.status.manage` | จัดการสถานะหลักสูตร | สามารถเปลี่ยนสถานะเผยแพร่/ซ่อนหลักสูตร |
|
||||
| `course.category.manage` | จัดการหมวดหมู่หลักสูตร | สามารถกำหนดหมวดหมู่หลักสูตร |
|
||||
|
||||
---
|
||||
|
||||
### 3. การจัดการเนื้อหาบทเรียน (Content Management)
|
||||
|
||||
| Permission Code | ชื่อสิทธิ์ | คำอธิบาย |
|
||||
|----------------|-----------|---------|
|
||||
| `content.view` | ดูเนื้อหาบทเรียน | สามารถเข้าถึงและดูเนื้อหาบทเรียน |
|
||||
| `content.video.play` | เล่นวิดีโอ | สามารถเล่นวิดีโอการเรียนรู้ |
|
||||
| `content.document.view` | ดูเอกสาร | สามารถดูเอกสาร PDF |
|
||||
| `content.document.download` | ดาวน์โหลดเอกสาร | สามารถดาวน์โหลดเอกสารประกอบ |
|
||||
| `content.progress.track` | ติดตามความคืบหน้า | ระบบบันทึกความคืบหน้าการเรียน |
|
||||
| `content.chapter.create` | สร้าง Chapter | สามารถสร้าง Chapter ในหลักสูตร |
|
||||
| `content.lesson.create` | สร้าง Lesson | สามารถสร้าง Lesson ในหลักสูตร |
|
||||
| `content.chapter.edit` | แก้ไข Chapter | สามารถแก้ไข Chapter |
|
||||
| `content.lesson.edit` | แก้ไข Lesson | สามารถแก้ไข Lesson |
|
||||
| `content.upload` | อัปโหลดเนื้อหา | สามารถอัปโหลดวิดีโอ, PDF, ไฟล์ประกอบ |
|
||||
| `content.order.manage` | จัดเรียงลำดับบทเรียน | สามารถจัดเรียงลำดับ Chapter และ Lesson |
|
||||
| `content.access.manage` | กำหนดเงื่อนไขการเข้าถึง | สามารถกำหนดเงื่อนไขการเข้าถึงบทเรียน |
|
||||
|
||||
---
|
||||
|
||||
### 4. การจัดการแบบทดสอบ (Quiz & Assessment)
|
||||
|
||||
| Permission Code | ชื่อสิทธิ์ | คำอธิบาย |
|
||||
|----------------|-----------|---------|
|
||||
| `quiz.take` | ทำแบบทดสอบ | สามารถทำแบบทดสอบออนไลน์ |
|
||||
| `quiz.result.view` | ดูผลคะแนนตนเอง | สามารถดูผลคะแนนและประวัติการทำแบบทดสอบของตนเอง |
|
||||
| `quiz.create` | สร้างแบบทดสอบ | สามารถสร้างแบบทดสอบใหม่ |
|
||||
| `quiz.edit` | แก้ไขแบบทดสอบ | สามารถแก้ไขแบบทดสอบ |
|
||||
| `quiz.delete` | ลบแบบทดสอบ | สามารถลบแบบทดสอบ |
|
||||
| `quiz.settings.manage` | กำหนดการตั้งค่าแบบทดสอบ | สามารถกำหนดเวลา คะแนน เกณฑ์ผ่าน |
|
||||
| `quiz.results.view.all` | ดูผลคะแนนผู้เรียนทั้งหมด | สามารถดูผลคะแนนและสถิติของผู้เรียนทั้งหมด |
|
||||
|
||||
---
|
||||
|
||||
### 5. การรายงานและประเมินผล (Reporting)
|
||||
|
||||
| Permission Code | ชื่อสิทธิ์ | คำอธิบาย |
|
||||
|----------------|-----------|---------|
|
||||
| `report.progress.view.own` | ดูรายงานความคืบหน้าตนเอง | สามารถดูรายงานความคืบหน้าการเรียนของตนเอง |
|
||||
| `report.score.view.own` | ดูรายงานคะแนนตนเอง | สามารถดูรายงานคะแนนแบบทดสอบของตนเอง |
|
||||
| `report.certificate.view` | ดูใบประกาศนียบัตร | สามารถดูและดาวน์โหลดใบประกาศนียบัตร |
|
||||
| `report.students.view` | ดูรายงานผู้เรียน | สามารถดูรายงานผู้เรียนในหลักสูตร |
|
||||
| `report.progress.view.all` | ดูรายงานความคืบหน้าผู้เรียนทั้งหมด | สามารถดูความคืบหน้าการเรียนของผู้เรียนทั้งหมด |
|
||||
| `report.export` | ส่งออกรายงาน | สามารถส่งออกรายงานเป็น Excel หรือ PDF |
|
||||
| `report.dashboard.view` | ดู Dashboard | สามารถดู Dashboard ภาพรวมของระบบ |
|
||||
| `report.statistics.view` | ดูสถิติระบบ | สามารถดูสถิติการใช้งานระบบโดยรวม |
|
||||
|
||||
---
|
||||
|
||||
### 6. การจัดการประกาศ (Announcements)
|
||||
|
||||
| Permission Code | ชื่อสิทธิ์ | คำอธิบาย |
|
||||
|----------------|-----------|---------|
|
||||
| `announcement.view` | ดูประกาศ | สามารถดูประกาศภายในหลักสูตร |
|
||||
| `announcement.create` | สร้างประกาศ | สามารถโพสต์ประกาศใหม่ |
|
||||
| `announcement.edit` | แก้ไขประกาศ | สามารถแก้ไขประกาศ |
|
||||
| `announcement.delete` | ลบประกาศ | สามารถลบประกาศ |
|
||||
|
||||
---
|
||||
|
||||
## ตารางสิทธิ์ตามบทบาท (Role-Based Permissions Matrix)
|
||||
|
||||
### สัญลักษณ์
|
||||
- ✅ = มีสิทธิ์
|
||||
- ❌ = ไม่มีสิทธิ์
|
||||
- 🔒 = มีสิทธิ์เฉพาะข้อมูลของตนเอง
|
||||
|
||||
---
|
||||
|
||||
### 1. การจัดการผู้ใช้งาน (User Management)
|
||||
|
||||
| สิทธิ์ | Student | Instructor | Admin |
|
||||
|-------|---------|------------|-------|
|
||||
| `user.register` | ✅ | ✅ | ✅ |
|
||||
| `user.login` | ✅ | ✅ | ✅ |
|
||||
| `user.profile.view` | 🔒 | 🔒 | 🔒 |
|
||||
| `user.profile.edit` | 🔒 | 🔒 | 🔒 |
|
||||
| `user.password.reset` | ✅ | ✅ | ✅ |
|
||||
| `user.list.view` | ❌ | ❌ | ✅ |
|
||||
| `user.manage` | ❌ | ❌ | ✅ |
|
||||
| `user.role.assign` | ❌ | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
### 2. การจัดการหลักสูตร (Course Management)
|
||||
|
||||
| สิทธิ์ | Student | Instructor | Admin |
|
||||
|-------|---------|------------|-------|
|
||||
| `course.list.view` | ✅ | ✅ | ✅ |
|
||||
| `course.detail.view` | ✅ | ✅ | ✅ |
|
||||
| `course.search` | ✅ | ✅ | ✅ |
|
||||
| `course.enroll` | ✅ | ❌ | ❌ |
|
||||
| `course.create` | ❌ | ✅ | ❌ |
|
||||
| `course.edit` | ❌ | ✅ | ❌ |
|
||||
| `course.delete` | ❌ | ✅ | ❌ |
|
||||
| `course.status.manage` | ❌ | ✅ | ❌ |
|
||||
| `course.category.manage` | ❌ | ✅ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
### 3. การจัดการเนื้อหาบทเรียน (Content Management)
|
||||
|
||||
| สิทธิ์ | Student | Instructor | Admin |
|
||||
|-------|---------|------------|-------|
|
||||
| `content.view` | ✅ | ✅ | ❌ |
|
||||
| `content.video.play` | ✅ | ✅ | ❌ |
|
||||
| `content.document.view` | ✅ | ✅ | ❌ |
|
||||
| `content.document.download` | ✅ | ✅ | ❌ |
|
||||
| `content.progress.track` | ✅ | ❌ | ❌ |
|
||||
| `content.chapter.create` | ❌ | ✅ | ❌ |
|
||||
| `content.lesson.create` | ❌ | ✅ | ❌ |
|
||||
| `content.chapter.edit` | ❌ | ✅ | ❌ |
|
||||
| `content.lesson.edit` | ❌ | ✅ | ❌ |
|
||||
| `content.upload` | ❌ | ✅ | ❌ |
|
||||
| `content.order.manage` | ❌ | ✅ | ❌ |
|
||||
| `content.access.manage` | ❌ | ✅ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
### 4. การจัดการแบบทดสอบ (Quiz & Assessment)
|
||||
|
||||
| สิทธิ์ | Student | Instructor | Admin |
|
||||
|-------|---------|------------|-------|
|
||||
| `quiz.take` | ✅ | ❌ | ❌ |
|
||||
| `quiz.result.view` | 🔒 | ❌ | ❌ |
|
||||
| `quiz.create` | ❌ | ✅ | ❌ |
|
||||
| `quiz.edit` | ❌ | ✅ | ❌ |
|
||||
| `quiz.delete` | ❌ | ✅ | ❌ |
|
||||
| `quiz.settings.manage` | ❌ | ✅ | ❌ |
|
||||
| `quiz.results.view.all` | ❌ | ✅ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
### 5. การรายงานและประเมินผล (Reporting)
|
||||
|
||||
| สิทธิ์ | Student | Instructor | Admin |
|
||||
|-------|---------|------------|-------|
|
||||
| `report.progress.view.own` | ✅ | ❌ | ❌ |
|
||||
| `report.score.view.own` | ✅ | ❌ | ❌ |
|
||||
| `report.certificate.view` | ✅ | ❌ | ❌ |
|
||||
| `report.students.view` | ❌ | ✅ | ❌ |
|
||||
| `report.progress.view.all` | ❌ | ✅ | ❌ |
|
||||
| `report.export` | ❌ | ✅ | ❌ |
|
||||
| `report.dashboard.view` | ❌ | ❌ | ✅ |
|
||||
| `report.statistics.view` | ❌ | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
### 6. การจัดการประกาศ (Announcements)
|
||||
|
||||
| สิทธิ์ | Student | Instructor | Admin |
|
||||
|-------|---------|------------|-------|
|
||||
| `announcement.view` | ✅ | ✅ | ❌ |
|
||||
| `announcement.create` | ❌ | ✅ | ❌ |
|
||||
| `announcement.edit` | ❌ | ✅ | ❌ |
|
||||
| `announcement.delete` | ❌ | ✅ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## สรุปจำนวนสิทธิ์ตามบทบาท
|
||||
|
||||
| บทบาท | จำนวนสิทธิ์ทั้งหมด | สิทธิ์เต็ม | สิทธิ์เฉพาะตนเอง | ไม่มีสิทธิ์ |
|
||||
|--------|-------------------|-----------|------------------|------------|
|
||||
| **Student** | 52 | 21 | 5 | 26 |
|
||||
| **Instructor** | 52 | 30 | 3 | 19 |
|
||||
| **Admin** | 52 | 9 | 3 | 40 |
|
||||
|
||||
---
|
||||
|
||||
## หมายเหตุสำคัญ
|
||||
|
||||
### 1. การควบคุมสิทธิ์ระดับข้อมูล (Data-Level Access Control)
|
||||
|
||||
#### Student
|
||||
- สามารถดูและแก้ไขเฉพาะโปรไฟล์ของตนเอง
|
||||
- สามารถดูผลคะแนนและความคืบหน้าเฉพาะของตนเอง
|
||||
- ไม่สามารถเข้าถึงข้อมูลของผู้เรียนคนอื่น
|
||||
|
||||
#### Instructor
|
||||
- สามารถจัดการเฉพาะหลักสูตรที่ตนเองสร้างหรือได้รับมอบหมาย
|
||||
- สามารถดูข้อมูลผู้เรียนเฉพาะในหลักสูตรของตนเอง
|
||||
- ไม่สามารถเข้าถึงข้อมูลระดับระบบโดยรวม
|
||||
|
||||
#### Admin
|
||||
- มีสิทธิ์เข้าถึงข้อมูลผู้ใช้ทั้งหมด
|
||||
- มีสิทธิ์ดูสถิติและรายงานระดับระบบ
|
||||
- ไม่มีสิทธิ์จัดการเนื้อหาการสอนโดยตรง (เป็นหน้าที่ของ Instructor)
|
||||
|
||||
---
|
||||
|
||||
### 2. การนำไปใช้งาน (Implementation Guidelines)
|
||||
|
||||
#### Backend API
|
||||
```javascript
|
||||
// ตัวอย่างการตรวจสอบสิทธิ์
|
||||
const checkPermission = (user, permission) => {
|
||||
const rolePermissions = {
|
||||
STUDENT: ['user.login', 'course.enroll', 'content.view', ...],
|
||||
INSTRUCTOR: ['user.login', 'course.create', 'content.upload', ...],
|
||||
ADMIN: ['user.manage', 'user.role.assign', 'report.dashboard.view', ...]
|
||||
};
|
||||
|
||||
return rolePermissions[user.role]?.includes(permission);
|
||||
};
|
||||
```
|
||||
|
||||
#### Frontend UI
|
||||
```javascript
|
||||
// ตัวอย่างการซ่อน/แสดง UI ตามสิทธิ์
|
||||
const canCreateCourse = hasPermission('course.create'); // true สำหรับ Instructor
|
||||
const canManageUsers = hasPermission('user.manage'); // true สำหรับ Admin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. ความปลอดภัย (Security Considerations)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> - การตรวจสอบสิทธิ์ต้องทำทั้งที่ Frontend และ Backend
|
||||
> - Frontend ใช้สำหรับ UX (ซ่อน/แสดง UI)
|
||||
> - Backend ใช้สำหรับ Security (ป้องกันการเข้าถึงที่ไม่ได้รับอนุญาต)
|
||||
|
||||
> [!WARNING]
|
||||
> - ห้ามพึ่งพาการตรวจสอบสิทธิ์ที่ Frontend เพียงอย่างเดียว
|
||||
> - ต้องมีการตรวจสอบสิทธิ์ที่ API Layer ทุกครั้ง
|
||||
|
||||
---
|
||||
|
||||
### 4. กรณีพิเศษและ Edge Cases ที่ต้องจัดการ
|
||||
|
||||
> [!CAUTION]
|
||||
> กรณีพิเศษเหล่านี้ต้องได้รับการจัดการอย่างถูกต้องเพื่อป้องกันช่องโหว่ด้านความปลอดภัยและปัญหาการใช้งาน
|
||||
|
||||
---
|
||||
|
||||
#### 4.1 การเปลี่ยนแปลงบทบาทผู้ใช้โดย Admin (Admin Role Management)
|
||||
|
||||
**สถานการณ์**: Admin เปลี่ยนบทบาทผู้ใช้ (เช่น ปรับสถานะบัญชี, แก้ไขข้อมูลผิดพลาด)
|
||||
|
||||
> [!NOTE]
|
||||
> ในระบบนี้ **ไม่มีการเปลี่ยนบทบาทระหว่าง Student และ Instructor** ในการใช้งานปกติ
|
||||
> - Student ลงทะเบียนเป็น Student ตั้งแต่แรก
|
||||
> - Instructor สมัครหรือได้รับเชิญเป็น Instructor ตั้งแต่แรก
|
||||
> - การเปลี่ยนบทบาทเกิดขึ้นเฉพาะกรณีพิเศษที่ Admin จัดการ (เช่น แก้ไขข้อมูลผิดพลาด)
|
||||
|
||||
**ปัญหาที่อาจเกิด** (กรณีพิเศษที่ Admin ต้องเปลี่ยนบทบาท):
|
||||
- Session/Token เก่าอาจยังมีสิทธิ์เดิมอยู่
|
||||
- ข้อมูลที่เกี่ยวข้องกับบทบาทเดิม (เช่น หลักสูตรที่สร้าง, การลงทะเบียน)
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
// Admin เปลี่ยนบทบาทผู้ใช้ (กรณีพิเศษเท่านั้น)
|
||||
async function adminChangeUserRole(userId, newRole, adminId, reason) {
|
||||
const user = await getUser(userId);
|
||||
const oldRole = user.role;
|
||||
|
||||
// บันทึก audit log
|
||||
await logRoleChange(userId, oldRole, newRole, adminId, reason);
|
||||
|
||||
// 1. อัปเดตบทบาทในฐานข้อมูล
|
||||
await updateUserRole(userId, newRole);
|
||||
|
||||
// 2. Invalidate all active sessions/tokens
|
||||
await invalidateUserSessions(userId);
|
||||
|
||||
// 3. แจ้งเตือนผู้ใช้ทางอีเมล
|
||||
await notifyUserRoleChange(userId, oldRole, newRole, reason);
|
||||
|
||||
// 4. บังคับให้ Login ใหม่
|
||||
return {
|
||||
success: true,
|
||||
requireRelogin: true,
|
||||
message: 'เปลี่ยนบทบาทเรียบร้อย ผู้ใช้จะต้อง Login ใหม่'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4.2 การเข้าถึงหลักสูตรที่ถูกซ่อน/ลบ (Hidden/Deleted Course Access)
|
||||
|
||||
**สถานการณ์**: Student ลงทะเบียนเรียนหลักสูตรแล้ว แต่ Instructor เปลี่ยนสถานะเป็น "ซ่อน" หรือลบหลักสูตร
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- Student ที่ลงทะเบียนไว้แล้วควรเข้าถึงได้หรือไม่?
|
||||
- ความคืบหน้าการเรียนจะเกิดอะไร?
|
||||
- ใบประกาศนียบัตรที่ได้รับไปแล้วยังใช้ได้หรือไม่?
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
// ตรวจสอบการเข้าถึงหลักสูตร
|
||||
async function canAccessCourse(userId, courseId) {
|
||||
const course = await getCourse(courseId);
|
||||
const enrollment = await getEnrollment(userId, courseId);
|
||||
|
||||
// กรณีหลักสูตรถูกลบ
|
||||
if (course.isDeleted) {
|
||||
// อนุญาตให้ดูเฉพาะ read-only mode (ดูเนื้อหาเก่าได้ แต่ไม่สามารถทำแบบทดสอบ)
|
||||
return enrollment ? { access: 'read-only', reason: 'course_deleted' } : false;
|
||||
}
|
||||
|
||||
// กรณีหลักสูตรถูกซ่อน
|
||||
if (course.status === 'HIDDEN') {
|
||||
// Student ที่ลงทะเบียนไว้แล้วยังเข้าถึงได้ปกติ
|
||||
if (enrollment) return { access: 'full' };
|
||||
|
||||
// Student ใหม่ไม่สามารถลงทะเบียนได้
|
||||
return false;
|
||||
}
|
||||
|
||||
return { access: 'full' };
|
||||
}
|
||||
```
|
||||
|
||||
**นโยบายที่แนะนำ**:
|
||||
- หลักสูตรที่ถูก **ซ่อน**: Student ที่ลงทะเบียนแล้วยังเข้าถึงได้ปกติ
|
||||
- หลักสูตรที่ถูก **ลบ**: เปลี่ยนเป็น Read-Only Mode (ดูเนื้อหาได้ แต่ไม่สามารถทำแบบทดสอบหรือรับใบประกาศนียบัตรใหม่)
|
||||
|
||||
---
|
||||
|
||||
#### 4.3 การลงทะเบียนซ้ำ (Duplicate Enrollment)
|
||||
|
||||
**สถานการณ์**: Student พยายามลงทะเบียนหลักสูตรที่ลงทะเบียนไว้แล้ว
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- ข้อมูลความคืบหน้าเดิมจะเป็นอย่างไร?
|
||||
- ควรรีเซ็ตความคืบหน้าหรือไม่?
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
async function enrollCourse(userId, courseId) {
|
||||
const existingEnrollment = await getEnrollment(userId, courseId);
|
||||
|
||||
if (existingEnrollment) {
|
||||
// ถ้าลงทะเบียนไว้แล้ว ให้ redirect ไปที่หลักสูตร
|
||||
return {
|
||||
success: false,
|
||||
message: 'คุณได้ลงทะเบียนหลักสูตรนี้ไว้แล้ว',
|
||||
redirectTo: `/courses/${courseId}`
|
||||
};
|
||||
}
|
||||
|
||||
// สร้างการลงทะเบียนใหม่
|
||||
return await createEnrollment(userId, courseId);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4.4 การเข้าถึงเนื้อหาที่ยังไม่ถึงลำดับ (Sequential Content Access)
|
||||
|
||||
**สถานการณ์**: Student พยายามเข้าถึง Lesson 5 โดยที่ยังไม่ได้เรียน Lesson 1-4
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- Student อาจใช้ Direct URL เพื่อข้ามลำดับ
|
||||
- ระบบต้องบังคับให้เรียนตามลำดับ
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
async function canAccessLesson(userId, lessonId) {
|
||||
const lesson = await getLesson(lessonId);
|
||||
const course = await getCourse(lesson.courseId);
|
||||
|
||||
// ถ้าหลักสูตรไม่บังคับลำดับ
|
||||
if (!course.requireSequentialAccess) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ตรวจสอบว่าเรียน Lesson ก่อนหน้าครบหรือไม่
|
||||
const previousLessons = await getPreviousLessons(lessonId);
|
||||
const completedLessons = await getCompletedLessons(userId, lesson.courseId);
|
||||
|
||||
const allPreviousCompleted = previousLessons.every(prev =>
|
||||
completedLessons.includes(prev.id)
|
||||
);
|
||||
|
||||
if (!allPreviousCompleted) {
|
||||
return {
|
||||
access: false,
|
||||
reason: 'กรุณาเรียนบทเรียนก่อนหน้าให้เสร็จก่อน',
|
||||
nextAvailableLesson: findNextAvailableLesson(userId, lesson.courseId)
|
||||
};
|
||||
}
|
||||
|
||||
return { access: true };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4.5 การทำแบบทดสอบหลายครั้ง (Multiple Quiz Attempts)
|
||||
|
||||
**สถานการณ์**: Student ทำแบบทดสอบไม่ผ่านและต้องการทำใหม่
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- จำกัดจำนวนครั้งในการทำหรือไม่?
|
||||
- คะแนนที่บันทึกควรเป็นครั้งแรก ครั้งสุดท้าย หรือคะแนนสูงสุด?
|
||||
- ต้องรอระยะเวลาก่อนทำใหม่หรือไม่?
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
async function canTakeQuiz(userId, quizId) {
|
||||
const quiz = await getQuiz(quizId);
|
||||
const attempts = await getQuizAttempts(userId, quizId);
|
||||
|
||||
// ตรวจสอบจำนวนครั้งที่ทำได้
|
||||
if (quiz.maxAttempts && attempts.length >= quiz.maxAttempts) {
|
||||
return {
|
||||
canTake: false,
|
||||
reason: `คุณทำแบบทดสอบครบ ${quiz.maxAttempts} ครั้งแล้ว`
|
||||
};
|
||||
}
|
||||
|
||||
// ตรวจสอบระยะเวลารอระหว่างการทำ (cooldown)
|
||||
if (quiz.cooldownMinutes && attempts.length > 0) {
|
||||
const lastAttempt = attempts[attempts.length - 1];
|
||||
const minutesSinceLastAttempt =
|
||||
(Date.now() - lastAttempt.completedAt) / 1000 / 60;
|
||||
|
||||
if (minutesSinceLastAttempt < quiz.cooldownMinutes) {
|
||||
const remainingMinutes = Math.ceil(
|
||||
quiz.cooldownMinutes - minutesSinceLastAttempt
|
||||
);
|
||||
return {
|
||||
canTake: false,
|
||||
reason: `กรุณารออีก ${remainingMinutes} นาทีก่อนทำใหม่`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { canTake: true };
|
||||
}
|
||||
|
||||
// การบันทึกคะแนน
|
||||
async function recordQuizScore(userId, quizId, score) {
|
||||
const quiz = await getQuiz(quizId);
|
||||
|
||||
// กำหนดว่าจะเก็บคะแนนแบบไหน
|
||||
const scorePolicy = quiz.scorePolicy || 'HIGHEST'; // HIGHEST, LATEST, FIRST, AVERAGE
|
||||
|
||||
await saveQuizAttempt(userId, quizId, score, scorePolicy);
|
||||
}
|
||||
```
|
||||
|
||||
**นโยบายที่แนะนำ**:
|
||||
- **จำนวนครั้ง**: กำหนดได้ต่อแบบทดสอบ (เช่น 3 ครั้ง, ไม่จำกัด)
|
||||
- **คะแนนที่บันทึก**: ใช้คะแนนสูงสุด (HIGHEST) เป็นค่าเริ่มต้น
|
||||
- **Cooldown**: กำหนดได้ต่อแบบทดสอบ (เช่น รอ 30 นาที)
|
||||
|
||||
---
|
||||
|
||||
#### 4.6 Instructor แก้ไขหลักสูตรขณะที่ Student กำลังเรียน
|
||||
|
||||
**สถานการณ์**: Instructor แก้ไขเนื้อหา/ลบ Lesson ขณะที่ Student กำลังดูอยู่
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- Student อาจเห็นข้อผิดพลาด 404 Not Found
|
||||
- ความคืบหน้าอาจไม่ถูกต้อง
|
||||
- Video ที่กำลังเล่นอาจหยุดทำงาน
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
// Soft Delete แทน Hard Delete
|
||||
async function deleteLesson(lessonId, instructorId) {
|
||||
const activeViewers = await getActiveLessonViewers(lessonId);
|
||||
|
||||
if (activeViewers.length > 0) {
|
||||
// ไม่ลบทันที แต่ทำ Soft Delete
|
||||
await markLessonAsDeleted(lessonId);
|
||||
|
||||
// แจ้งเตือน Instructor
|
||||
return {
|
||||
success: true,
|
||||
warning: `มีผู้เรียน ${activeViewers.length} คนกำลังดูบทเรียนนี้อยู่ ระบบจะซ่อนบทเรียนนี้และลบถาวรในอีก 24 ชั่วโมง`
|
||||
};
|
||||
}
|
||||
|
||||
// ถ้าไม่มีคนดู ลบได้เลย
|
||||
await hardDeleteLesson(lessonId);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// ตรวจสอบการเข้าถึง Lesson
|
||||
async function accessLesson(userId, lessonId) {
|
||||
const lesson = await getLesson(lessonId);
|
||||
|
||||
if (lesson.isDeleted) {
|
||||
// ถ้า Student เข้าถึงก่อนที่จะถูกลบ ให้ดูต่อได้
|
||||
const wasAccessedBefore = await hasAccessedBefore(userId, lessonId);
|
||||
|
||||
if (wasAccessedBefore) {
|
||||
return {
|
||||
access: true,
|
||||
warning: 'บทเรียนนี้จะถูกลบในเร็วๆ นี้'
|
||||
};
|
||||
}
|
||||
|
||||
return { access: false, reason: 'บทเรียนนี้ถูกลบแล้ว' };
|
||||
}
|
||||
|
||||
return { access: true };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4.7 Admin ลบบัญชี Instructor ที่มีหลักสูตรอยู่
|
||||
|
||||
**สถานการณ์**: Admin ลบบัญชี Instructor ที่เป็นเจ้าของหลักสูตรหลายรายการ
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- หลักสูตรจะกลายเป็น "ไม่มีเจ้าของ"
|
||||
- Student ที่ลงทะเบียนไว้จะเข้าถึงไม่ได้
|
||||
- ข้อมูลรายงานจะสูญหาย
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
async function deleteInstructor(instructorId, adminId) {
|
||||
const courses = await getCoursesByInstructor(instructorId);
|
||||
|
||||
if (courses.length > 0) {
|
||||
// ห้ามลบถ้ายังมีหลักสูตร
|
||||
return {
|
||||
success: false,
|
||||
error: 'ไม่สามารถลบ Instructor ที่มีหลักสูตรอยู่',
|
||||
suggestion: 'กรุณาโอนหลักสูตรให้ Instructor คนอื่นก่อน',
|
||||
courses: courses.map(c => ({ id: c.id, title: c.title }))
|
||||
};
|
||||
}
|
||||
|
||||
// Soft Delete แทน Hard Delete
|
||||
await deactivateUser(instructorId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'ระงับการใช้งานบัญชีเรียบร้อย'
|
||||
};
|
||||
}
|
||||
|
||||
// ฟังก์ชันโอนหลักสูตร
|
||||
async function transferCourses(fromInstructorId, toInstructorId, adminId) {
|
||||
const courses = await getCoursesByInstructor(fromInstructorId);
|
||||
|
||||
for (const course of courses) {
|
||||
await updateCourseOwner(course.id, toInstructorId);
|
||||
await logOwnershipTransfer(course.id, fromInstructorId, toInstructorId, adminId);
|
||||
}
|
||||
|
||||
return { success: true, transferredCount: courses.length };
|
||||
}
|
||||
```
|
||||
|
||||
**นโยบายที่แนะนำ**:
|
||||
- ใช้ **Soft Delete** (ระงับการใช้งาน) แทน Hard Delete
|
||||
- บังคับให้โอนหลักสูตรก่อนลบบัญชี
|
||||
- เก็บ Audit Log ของการโอนหลักสูตร
|
||||
|
||||
---
|
||||
|
||||
#### 4.8 การเข้าถึงข้อมูลข้ามหลักสูตร (Cross-Course Data Access)
|
||||
|
||||
**สถานการณ์**: Instructor A พยายามดูรายงานผู้เรียนในหลักสูตรของ Instructor B
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- Instructor อาจใช้ API โดยตรงเพื่อเข้าถึงข้อมูลหลักสูตรอื่น
|
||||
- การ Bypass ผ่าน Direct URL
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
// Middleware ตรวจสอบความเป็นเจ้าของ
|
||||
async function checkCourseOwnership(req, res, next) {
|
||||
const { courseId } = req.params;
|
||||
const { userId, role } = req.user;
|
||||
|
||||
// Admin สามารถเข้าถึงได้ทุกหลักสูตร (เฉพาะดู ไม่ใช่แก้ไข)
|
||||
if (role === 'ADMIN') {
|
||||
req.accessLevel = 'read-only';
|
||||
return next();
|
||||
}
|
||||
|
||||
// Instructor ต้องเป็นเจ้าของหลักสูตร
|
||||
if (role === 'INSTRUCTOR') {
|
||||
const course = await getCourse(courseId);
|
||||
|
||||
if (course.instructorId !== userId) {
|
||||
return res.status(403).json({
|
||||
error: 'คุณไม่มีสิทธิ์เข้าถึงหลักสูตรนี้'
|
||||
});
|
||||
}
|
||||
|
||||
req.accessLevel = 'full';
|
||||
return next();
|
||||
}
|
||||
|
||||
// Student ต้องลงทะเบียนหลักสูตรนี้
|
||||
if (role === 'STUDENT') {
|
||||
const enrollment = await getEnrollment(userId, courseId);
|
||||
|
||||
if (!enrollment) {
|
||||
return res.status(403).json({
|
||||
error: 'คุณต้องลงทะเบียนหลักสูตรนี้ก่อน'
|
||||
});
|
||||
}
|
||||
|
||||
req.accessLevel = 'student';
|
||||
return next();
|
||||
}
|
||||
|
||||
return res.status(403).json({ error: 'ไม่มีสิทธิ์เข้าถึง' });
|
||||
}
|
||||
|
||||
// ใช้งาน
|
||||
app.get('/api/courses/:courseId/students',
|
||||
authenticate,
|
||||
checkCourseOwnership,
|
||||
getStudentList
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4.9 Student จบหลักสูตรแล้วแต่ Instructor เพิ่มเนื้อหาใหม่
|
||||
|
||||
**สถานการณ์**: Student ทำครบทุก Lesson และได้ใบประกาศนียบัตรแล้ว แต่ Instructor เพิ่ม Lesson ใหม่
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- ความคืบหน้าจะกลับไปไม่ครบ 100%
|
||||
- ใบประกาศนียบัตรยังใช้ได้หรือไม่?
|
||||
- ต้องเรียนเนื้อหาใหม่หรือไม่?
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
async function calculateProgress(userId, courseId) {
|
||||
const enrollment = await getEnrollment(userId, courseId);
|
||||
const allLessons = await getCourseLessons(courseId);
|
||||
const completedLessons = await getCompletedLessons(userId, courseId);
|
||||
|
||||
// แยกเนื้อหาเก่าและใหม่
|
||||
const lessonsAtEnrollment = allLessons.filter(
|
||||
lesson => lesson.createdAt <= enrollment.enrolledAt
|
||||
);
|
||||
|
||||
const newLessons = allLessons.filter(
|
||||
lesson => lesson.createdAt > enrollment.enrolledAt
|
||||
);
|
||||
|
||||
// คำนวณความคืบหน้าจากเนื้อหาตอนลงทะเบียน
|
||||
const originalProgress =
|
||||
(completedLessons.length / lessonsAtEnrollment.length) * 100;
|
||||
|
||||
// ถ้าจบหลักสูตรแล้ว (100% ของเนื้อหาเดิม)
|
||||
if (originalProgress === 100 && enrollment.certificateIssued) {
|
||||
return {
|
||||
progress: 100,
|
||||
status: 'COMPLETED',
|
||||
certificate: enrollment.certificateId,
|
||||
newContentAvailable: newLessons.length > 0,
|
||||
message: newLessons.length > 0
|
||||
? `มีเนื้อหาใหม่ ${newLessons.length} บทเรียน (ไม่บังคับ)`
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
// ถ้ายังไม่จบ คำนวณจากเนื้อหาทั้งหมด
|
||||
const totalProgress = (completedLessons.length / allLessons.length) * 100;
|
||||
|
||||
return {
|
||||
progress: totalProgress,
|
||||
status: 'IN_PROGRESS',
|
||||
remainingLessons: allLessons.length - completedLessons.length
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**นโยบายที่แนะนำ**:
|
||||
- ใบประกาศนียบัตรที่ออกแล้ว **ยังคงใช้ได้**
|
||||
- เนื้อหาใหม่ถือเป็น **เนื้อหาเสริม** (ไม่บังคับ)
|
||||
- แสดงป้าย "มีเนื้อหาใหม่" ให้ Student ที่จบแล้วทราบ
|
||||
|
||||
---
|
||||
|
||||
#### 4.10 ระบบอยู่ในช่วง Maintenance
|
||||
|
||||
**สถานการณ์**: ระบบต้องปิดปรับปรุงชั่วคราว
|
||||
|
||||
**ปัญหาที่อาจเกิด**:
|
||||
- Student ที่กำลังทำแบบทดสอบจะเกิดอะไร?
|
||||
- ความคืบหน้าจะสูญหายหรือไม่?
|
||||
|
||||
**วิธีจัดการ**:
|
||||
```javascript
|
||||
// Maintenance Mode Middleware
|
||||
async function maintenanceMode(req, res, next) {
|
||||
const isMaintenanceMode = await getSystemSetting('maintenance_mode');
|
||||
|
||||
if (!isMaintenanceMode) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const { role } = req.user || {};
|
||||
|
||||
// อนุญาตให้ Admin เข้าถึงได้
|
||||
if (role === 'ADMIN') {
|
||||
return next();
|
||||
}
|
||||
|
||||
// บันทึกข้อมูลที่กำลังทำอยู่ก่อน
|
||||
if (req.method === 'POST' || req.method === 'PUT') {
|
||||
await savePendingRequest(req);
|
||||
}
|
||||
|
||||
return res.status(503).json({
|
||||
error: 'ระบบอยู่ในช่วงปรับปรุง',
|
||||
message: 'ขออภัยในความไม่สะดวก ระบบจะกลับมาให้บริการเร็วๆ นี้',
|
||||
estimatedTime: await getMaintenanceEndTime(),
|
||||
savedData: req.method !== 'GET' // แจ้งว่าบันทึกข้อมูลไว้แล้ว
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-save สำหรับแบบทดสอบ
|
||||
async function autoSaveQuizProgress(userId, quizId, answers) {
|
||||
// บันทึกทุก 30 วินาที
|
||||
await saveQuizDraft(userId, quizId, answers, Date.now());
|
||||
}
|
||||
|
||||
// กู้คืนข้อมูลหลัง Maintenance
|
||||
async function restoreQuizProgress(userId, quizId) {
|
||||
const draft = await getQuizDraft(userId, quizId);
|
||||
|
||||
if (draft && Date.now() - draft.savedAt < 24 * 60 * 60 * 1000) {
|
||||
return {
|
||||
canRestore: true,
|
||||
answers: draft.answers,
|
||||
savedAt: draft.savedAt
|
||||
};
|
||||
}
|
||||
|
||||
return { canRestore: false };
|
||||
}
|
||||
```
|
||||
|
||||
**นโยบายที่แนะนำ**:
|
||||
- แจ้งเตือนล่วงหน้า 24 ชั่วโมงก่อน Maintenance
|
||||
- Auto-save ข้อมูลแบบทดสอบทุก 30 วินาที
|
||||
- เก็บ Draft ไว้ 24 ชั่วโมงหลัง Maintenance
|
||||
- อนุญาตให้ Admin เข้าถึงระบบได้ระหว่าง Maintenance
|
||||
|
||||
---
|
||||
|
||||
### 5. การขยายสิทธิ์ในอนาคต (Future Extensions)
|
||||
|
||||
ระบบสามารถขยายเพิ่มสิทธิ์ได้ เช่น:
|
||||
- `course.moderate` - สำหรับ Moderator ที่ช่วยตรวจสอบหลักสูตร
|
||||
- `content.review` - สำหรับ Reviewer ที่ตรวจสอบเนื้อหา
|
||||
- `quiz.manual.grade` - สำหรับการให้คะแนนแบบอัตนัย
|
||||
- `discussion.moderate` - สำหรับการดูแลกระดานสนทนา (ถ้ามีในอนาคต)
|
||||
- `course.co-teach` - สำหรับการสอนร่วมกันหลาย Instructor
|
||||
- `content.version.manage` - สำหรับการจัดการเวอร์ชันของเนื้อหา
|
||||
Loading…
Add table
Add a link
Reference in a new issue