elearning/docs/permissions_matrix.md

793 lines
38 KiB
Markdown
Raw Permalink Normal View History

# ตารางสิทธิ์การใช้งานระบบ 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` - สำหรับการจัดการเวอร์ชันของเนื้อหา