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