elearning/docs/permissions_matrix.md

38 KiB

ตารางสิทธิ์การใช้งานระบบ 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

// ตัวอย่างการตรวจสอบสิทธิ์
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

// ตัวอย่างการซ่อน/แสดง 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 เก่าอาจยังมีสิทธิ์เดิมอยู่
  • ข้อมูลที่เกี่ยวข้องกับบทบาทเดิม (เช่น หลักสูตรที่สร้าง, การลงทะเบียน)

วิธีจัดการ:

// 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 ที่ลงทะเบียนไว้แล้วควรเข้าถึงได้หรือไม่?
  • ความคืบหน้าการเรียนจะเกิดอะไร?
  • ใบประกาศนียบัตรที่ได้รับไปแล้วยังใช้ได้หรือไม่?

วิธีจัดการ:

// ตรวจสอบการเข้าถึงหลักสูตร
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 พยายามลงทะเบียนหลักสูตรที่ลงทะเบียนไว้แล้ว

ปัญหาที่อาจเกิด:

  • ข้อมูลความคืบหน้าเดิมจะเป็นอย่างไร?
  • ควรรีเซ็ตความคืบหน้าหรือไม่?

วิธีจัดการ:

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 เพื่อข้ามลำดับ
  • ระบบต้องบังคับให้เรียนตามลำดับ

วิธีจัดการ:

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 ทำแบบทดสอบไม่ผ่านและต้องการทำใหม่

ปัญหาที่อาจเกิด:

  • จำกัดจำนวนครั้งในการทำหรือไม่?
  • คะแนนที่บันทึกควรเป็นครั้งแรก ครั้งสุดท้าย หรือคะแนนสูงสุด?
  • ต้องรอระยะเวลาก่อนทำใหม่หรือไม่?

วิธีจัดการ:

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 ที่กำลังเล่นอาจหยุดทำงาน

วิธีจัดการ:

// 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 ที่ลงทะเบียนไว้จะเข้าถึงไม่ได้
  • ข้อมูลรายงานจะสูญหาย

วิธีจัดการ:

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

วิธีจัดการ:

// 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%
  • ใบประกาศนียบัตรยังใช้ได้หรือไม่?
  • ต้องเรียนเนื้อหาใหม่หรือไม่?

วิธีจัดการ:

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 ที่กำลังทำแบบทดสอบจะเกิดอะไร?
  • ความคืบหน้าจะสูญหายหรือไม่?

วิธีจัดการ:

// 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 - สำหรับการจัดการเวอร์ชันของเนื้อหา