diff --git a/.gitignore b/.gitignore index 2b3aa6c7..163ba712 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ Learner Learner/* +.env +.agent +.agent/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Backend/agent_skills_backend.md b/Backend/agent_skills_backend.md new file mode 100644 index 00000000..426995af --- /dev/null +++ b/Backend/agent_skills_backend.md @@ -0,0 +1,685 @@ +# Agent Skills - E-Learning Backend Development + +> คู่มือสำหรับ AI Agent ในการพัฒนา Backend ของระบบ E-Learning Platform +> **Tech Stack**: Node.js + Express + Prisma + PostgreSQL + Redis + MinIO (S3) + +--- + +## 🎯 Overview + +ระบบ E-Learning Platform เป็น REST API ที่รองรับ: +- **3 บทบาทหลัก**: Admin, Instructor, Student +- **107+ endpoints** ครอบคลุมการจัดการคอร์ส, บทเรียน, แบบทดสอบ, ประกาศ, รายงาน +- **Multi-language support**: Thai (th) และ English (en) +- **File management**: Video streaming, attachments, certificates +- **Real-time features**: Video progress tracking, quiz attempts + +--- + +## 📚 Core Concepts + +### 1. Multi-Language Data Structure +ข้อมูลที่แสดงผลต้องรองรับ 2 ภาษา: +```javascript +{ + "title": { + "th": "Python สำหรับผู้เริ่มต้น", + "en": "Python for Beginners" + } +} +``` + +### 2. Role-Based Access Control (RBAC) +```javascript +// 3 บทบาทหลัก +- ADMIN: จัดการระบบทั้งหมด +- INSTRUCTOR: สร้างและจัดการคอร์สของตนเอง +- STUDENT: ลงทะเบียนและเรียนคอร์ส +``` + +### 3. Course Hierarchy +``` +Course + └─ Chapters (บท) + └─ Lessons (บทเรียน) + ├─ Video (optional) + ├─ Attachments (0-10 files) + └─ Quiz (optional) +``` + +### 4. Lesson Prerequisites +- Sequential lessons: ต้องเรียนตามลำดับ +- Prerequisite lessons: ระบุบทเรียนที่ต้องจบก่อน +- Quiz requirements: ต้องผ่านแบบทดสอบก่อน + +--- + +## 🔐 Authentication & Authorization + +### JWT Token Structure +```javascript +{ + userId: 123, + username: "john_doe", + email: "john@example.com", + role: "STUDENT", + iat: 1234567890, + exp: 1234654290 +} +``` + +### Login Methods +- **Username + Password** +- **Email + Password** + +### Key Endpoints +``` +POST /api/auth/register +POST /api/auth/login +POST /api/auth/logout +POST /api/auth/refresh +POST /api/auth/password/reset-request +POST /api/auth/password/reset +``` + +--- + +## 👥 User Management + +### User Profile Structure +```javascript +{ + id: 1, + username: "john_doe", + email: "john@example.com", + role: { code: "STUDENT", name: {...} }, + profile: { + prefix: "Mr.", + first_name: "John", + last_name: "Doe", + phone: "0812345678", + avatar_url: "https://..." + } +} +``` + +### Key Endpoints +``` +GET /api/users/me +GET /api/users/me/profile +PUT /api/users/me/profile +PUT /api/users/me/password +``` + +--- + +## 📚 Course Management + +### Course Status Flow +``` +DRAFT → PENDING → APPROVED/REJECTED → PUBLISHED +``` + +### Course Structure +```javascript +{ + id: 1, + title: { th: "...", en: "..." }, + description: { th: "...", en: "..." }, + thumbnail: "https://...", + price: 990, + is_free: false, + have_certificate: true, + status: "APPROVED", + category_id: 1, + instructors: [ + { user_id: 5, is_primary: true }, + { user_id: 10, is_primary: false } + ] +} +``` + +### Multi-Instructor Support +- **Primary Instructor**: สามารถจัดการทุกอย่างในคอร์ส +- **Co-Instructors**: ช่วยสอนและจัดการเนื้อหา +- ห้ามลบ instructor คนสุดท้าย + +### Key Endpoints + +**Public:** +``` +GET /api/courses +GET /api/courses/:courseId +``` + +**Student:** +``` +POST /api/students/courses/:courseId/enroll +GET /api/students/courses +GET /api/students/courses/:courseId/learn +``` + +**Instructor:** +``` +POST /api/instructor/courses +GET /api/instructor/courses +PUT /api/instructor/courses/:courseId +DELETE /api/instructor/courses/:courseId +POST /api/instructor/courses/:courseId/submit +POST /api/instructor/courses/:courseId/clone +``` + +**Instructors Management:** +``` +GET /api/instructor/courses/:courseId/instructors +POST /api/instructor/courses/:courseId/instructors +DELETE /api/instructor/courses/:courseId/instructors/:userId +PUT /api/instructor/courses/:courseId/instructors/:userId/primary +``` + +--- + +## 📖 Chapters & Lessons + +### Lesson Types +- **video**: วีดีโอบทเรียน +- **text**: เนื้อหาข้อความ +- **pdf**: เอกสาร PDF +- **quiz**: แบบทดสอบ + +### Create Lesson (One Request) +สามารถสร้าง lesson พร้อม video และ attachments ในครั้งเดียว: + +```http +POST /api/instructor/courses/:courseId/chapters/:chapterId/lessons +Content-Type: multipart/form-data + +title_th: "บทที่ 1" +title_en: "Lesson 1" +type: "video" +video: +attachments[]: +attachments[]: +descriptions[0][th]: "สไลด์" +descriptions[0][en]: "Slides" +``` + +### Lesson Prerequisites +```javascript +{ + is_sequential: true, // ต้องเรียนตามลำดับ + prerequisite_lesson_ids: [1, 2], // ต้องจบบทเรียนนี้ก่อน + require_pass_quiz: true // ต้องผ่านแบบทดสอบ +} +``` + +### Key Endpoints + +**Chapters:** +``` +POST /api/instructor/courses/:courseId/chapters +PUT /api/instructor/courses/:courseId/chapters/:chapterId +DELETE /api/instructor/courses/:courseId/chapters/:chapterId +PUT /api/instructor/courses/:courseId/chapters/reorder +``` + +**Lessons:** +``` +POST /api/instructor/courses/:courseId/chapters/:chapterId/lessons +PUT /api/instructor/courses/:courseId/lessons/:lessonId +DELETE /api/instructor/courses/:courseId/lessons/:lessonId +PUT /api/instructor/courses/:courseId/lessons/reorder +``` + +**Prerequisites:** +``` +GET /api/instructor/courses/:courseId/lessons/:lessonId/prerequisites +POST /api/instructor/courses/:courseId/lessons/:lessonId/prerequisites +``` + +--- + +## 📎 Attachments + +### File Limits +- **Max file size**: 100 MB per file +- **Max attachments**: 10 files per lesson +- **Total size**: 500 MB per lesson + +### Allowed File Types +- **Documents**: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX +- **Archives**: ZIP, RAR, 7Z +- **Images**: JPG, PNG, GIF +- **Text**: TXT, CSV + +### Key Endpoints +``` +POST /api/instructor/courses/:courseId/lessons/:lessonId/attachments +GET /api/instructor/courses/:courseId/lessons/:lessonId/attachments +PUT /api/instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId +DELETE /api/instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId +PUT /api/instructor/courses/:courseId/lessons/:lessonId/attachments/reorder +GET /api/students/lessons/:lessonId/attachments/:attachmentId/download +``` + +--- + +## 📹 Video Progress Tracking + +### Auto-Save Progress +- บันทึกทุก **5 วินาที** ขณะดูวีดีโอ +- Auto-complete เมื่อดู **≥ 90%** +- Resume จากตำแหน่งล่าสุด + +### Data Structure +```javascript +{ + lesson_id: 5, + video_progress_seconds: 450, + video_duration_seconds: 900, + video_progress_percentage: 50.00, + is_completed: false, + last_watched_at: "2024-12-24T14:00:00Z" +} +``` + +### Key Endpoints +``` +POST /api/students/lessons/:lessonId/progress +GET /api/students/lessons/:lessonId/progress +``` + +--- + +## 🎯 Quizzes & Assessments + +### Quiz Configuration +```javascript +{ + title: { th: "...", en: "..." }, + passing_score: 70, // คะแนนผ่าน (%) + time_limit: 30, // นาที + max_attempts: 3, // จำนวนครั้งที่ทำได้ + cooldown_minutes: 60, // ระยะเวลารอระหว่างครั้ง + score_policy: "HIGHEST", // HIGHEST, LATEST, FIRST, AVERAGE + shuffle_questions: true, + shuffle_choices: true, + show_answers_after_completion: true +} +``` + +### Question Types +- **multiple_choice**: เลือกตอบ (1 คำตอบ) +- **true_false**: จริง/เท็จ +- **multiple_select**: เลือกหลายคำตอบ + +### Key Endpoints + +**Instructor:** +``` +POST /api/instructor/courses/:courseId/lessons/:lessonId/quiz +PUT /api/instructor/quizzes/:quizId +DELETE /api/instructor/quizzes/:quizId +POST /api/instructor/quizzes/:quizId/questions +PUT /api/instructor/quizzes/:quizId/questions/:questionId +DELETE /api/instructor/quizzes/:quizId/questions/:questionId +``` + +**Student:** +``` +GET /api/students/quizzes/:quizId +POST /api/students/quizzes/:quizId/submit +GET /api/students/quizzes/:quizId/attempts +GET /api/students/quizzes/:quizId/attempts/:attemptId +``` + +--- + +## 📢 Announcements + +### Announcement Structure +```javascript +{ + id: 1, + course_id: 1, + title: { th: "...", en: "..." }, + content: { th: "...", en: "..." }, + is_pinned: true, + attachments: [...], + created_at: "2024-12-23T10:00:00Z" +} +``` + +### Key Endpoints + +**Student:** +``` +GET /api/students/courses/:courseId/announcements +GET /api/students/courses/:courseId/announcements/:announcementId +``` + +**Instructor:** +``` +GET /api/instructor/courses/:courseId/announcements +POST /api/instructor/courses/:courseId/announcements +PUT /api/instructor/courses/:courseId/announcements/:announcementId +DELETE /api/instructor/courses/:courseId/announcements/:announcementId +``` + +--- + +## 📊 Progress & Certificates + +### Course Progress Calculation +```javascript +progress_percentage = (completed_lessons / total_lessons) * 100 +``` + +### Certificate Issuance +- ออกอัตโนมัติเมื่อจบคอร์ส 100% +- เฉพาะคอร์สที่ `have_certificate = true` +- เก็บไฟล์ PDF ใน S3 + +### Key Endpoints +``` +GET /api/students/progress +GET /api/students/courses/:courseId/progress +GET /api/students/courses/:courseId/certificate +GET /api/students/certificates +``` + +--- + +## 🔄 Course Cloning + +### What Gets Cloned +✅ **Copied:** +- Course info (title, description, price) +- All chapters and lessons +- All quizzes and questions +- All attachments (files copied to new location) +- Lesson prerequisites +- Sort orders + +❌ **NOT Copied:** +- Enrollments +- Student progress +- Reviews/ratings +- Approval status (new course = DRAFT) + +### Key Endpoint +``` +POST /api/instructor/courses/:courseId/clone +``` + +--- + +## 🔒 Access Control & Permissions + +### Lesson Access Check +``` +GET /api/students/courses/:courseId/lessons/:lessonId/access-check +``` + +**Response (Locked):** +```javascript +{ + can_access: false, + reason: "incomplete_prerequisites", + required_lessons: [1, 2], + missing_lessons: [2], + next_available_lesson: { id: 2, title: "..." } +} +``` + +### Ownership Validation +```javascript +// Middleware ตรวจสอบ +if (role === 'INSTRUCTOR' && course.instructorId !== userId) { + return 403; +} +``` + +--- + +## 📈 Reports & Analytics + +### Student Reports +``` +GET /api/students/progress +GET /api/students/courses/:courseId/progress +``` + +### Instructor Reports +``` +GET /api/instructor/courses/:courseId/statistics +GET /api/instructor/courses/:courseId/students +GET /api/instructor/courses/:courseId/students/export +GET /api/instructor/dashboard +``` + +### Admin Dashboard +``` +GET /api/admin/statistics +GET /api/admin/reports/revenue +GET /api/admin/reports/users +GET /api/admin/reports/courses +``` + +--- + +## ⚠️ Edge Cases & Best Practices + +### 1. Soft Delete > Hard Delete +```javascript +// ใช้ flag แทนการลบจริง +{ isDeleted: true, deletedAt: "..." } +``` + +### 2. Concurrent Video Progress +```javascript +// ใช้ last_watched_at ตัดสินว่า session ไหนล่าสุด +if (newProgress.last_watched_at > currentProgress.last_watched_at) { + updateProgress(newProgress); +} +``` + +### 3. Quiz Attempt Validation +```javascript +// ตรวจสอบก่อนให้ทำแบบทดสอบ +if (attempts >= maxAttempts) return error; +if (lastAttempt + cooldown > now) return error; +``` + +### 4. Lesson Lock Check +```javascript +// ตรวจสอบที่ Backend เสมอ +if (!allPreviousCompleted) { + return { access: false, nextAvailableLesson }; +} +``` + +### 5. Multi-Instructor Constraints +```javascript +// ห้ามลบ instructor คนสุดท้าย +if (instructors.length === 1) { + return error("Cannot remove last instructor"); +} +``` + +--- + +## 🗄️ Database Schema (Prisma) + +### Key Models +```prisma +model User { + id Int @id @default(autoincrement()) + username String @unique + email String @unique + password String + role Role @relation(...) + profile Profile? +} + +model Course { + id Int @id @default(autoincrement()) + title Json // { th: "", en: "" } + description Json + price Decimal + is_free Boolean + have_certificate Boolean + status CourseStatus + chapters Chapter[] + instructors CourseInstructor[] +} + +model Lesson { + id Int @id @default(autoincrement()) + title Json + content Json + type LessonType + video_url String? + is_sequential Boolean + prerequisite_lessons LessonPrerequisite[] + attachments Attachment[] + quiz Quiz? +} + +model Quiz { + id Int @id @default(autoincrement()) + title Json + passing_score Int + time_limit Int? + max_attempts Int? + cooldown_minutes Int? + score_policy ScorePolicy + show_answers_after_completion Boolean + questions Question[] +} +``` + +--- + +## 🔧 Technical Implementation + +### File Upload (MinIO/S3) +```javascript +// Video upload +const videoPath = `courses/${courseId}/lessons/${lessonId}/video.mp4`; +await s3.upload(videoPath, videoFile); + +// Attachment upload +const attachmentPath = `lessons/${lessonId}/attachments/${filename}`; +await s3.upload(attachmentPath, file); +``` + +### Video Progress Auto-Save +```javascript +// Frontend: Save ทุก 5 วินาที +setInterval(() => { + if (!video.paused) { + saveProgress(video.currentTime, video.duration); + } +}, 5000); +``` + +### JWT Middleware +```javascript +const authMiddleware = async (req, res, next) => { + const token = req.headers.authorization?.split(' ')[1]; + const decoded = jwt.verify(token, JWT_SECRET); + req.user = await prisma.user.findUnique({ where: { id: decoded.userId } }); + next(); +}; +``` + +### Role-Based Middleware +```javascript +const requireRole = (roles) => (req, res, next) => { + if (!roles.includes(req.user.role.code)) { + return res.status(403).json({ error: "Forbidden" }); + } + next(); +}; +``` + +--- + +## 📋 Error Codes + +| Code | Description | +|------|-------------| +| `INVALID_CREDENTIALS` | อีเมล/รหัสผ่านไม่ถูกต้อง | +| `ALREADY_ENROLLED` | ลงทะเบียนซ้ำ | +| `LESSON_LOCKED` | บทเรียนถูกล็อค | +| `INCOMPLETE_PREREQUISITES` | ยังไม่จบบทเรียนที่ต้องเรียนก่อน | +| `QUIZ_NOT_PASSED` | ยังไม่ผ่านแบบทดสอบ | +| `MAX_ATTEMPTS_EXCEEDED` | ทำแบบทดสอบเกินจำนวนครั้ง | +| `COOLDOWN_ACTIVE` | ต้องรอก่อนทำแบบทดสอบอีกครั้ง | +| `FILE_TOO_LARGE` | ไฟล์ใหญ่เกินไป | +| `INVALID_FILE_TYPE` | ประเภทไฟล์ไม่รองรับ | +| `COURSE_NOT_FOUND` | ไม่พบคอร์ส | +| `UNAUTHORIZED` | ไม่มีสิทธิ์เข้าถึง | + +--- + +## 🎯 Development Checklist + +### สำหรับทุก Endpoint +- [ ] Validate input data (Joi/Zod) +- [ ] Check authentication (JWT) +- [ ] Check authorization (Role/Ownership) +- [ ] Handle errors properly +- [ ] Return consistent response format +- [ ] Log important actions +- [ ] Add rate limiting + +### สำหรับ File Upload +- [ ] Validate file type +- [ ] Validate file size +- [ ] Generate unique filename +- [ ] Upload to S3/MinIO +- [ ] Save metadata to database +- [ ] Handle upload errors + +### สำหรับ Multi-Language +- [ ] Accept `lang` query parameter +- [ ] Return appropriate language +- [ ] Fallback to default language +- [ ] Validate both languages exist + +--- + +## 📚 Related Documentation + +- [API Endpoints Reference](../docs/api-docs/api_endpoints.md) +- [API Usage Examples](../docs/api-docs/api_usage_examples.md) +- [Edge Cases Quick Reference](../docs/api-docs/edge_cases_quick_reference.md) +- [Development Setup](../docs/development_setup.md) + +--- + +## 🚀 Quick Start Commands + +```bash +# Install dependencies +npm install + +# Setup database +npx prisma migrate dev + +# Seed database +npx prisma db seed + +# Start development server +npm run dev + +# Run tests +npm test +``` + +--- + +**Last Updated**: 2026-01-07 +**Version**: 1.0.0 diff --git a/Backend/compose.yaml b/Backend/compose.yaml new file mode 100644 index 00000000..14ff30f8 --- /dev/null +++ b/Backend/compose.yaml @@ -0,0 +1,126 @@ +services: + # PostgreSQL Database + postgres: + image: postgres:16-alpine + container_name: elearning-postgres + restart: unless-stopped + security_opt: + - apparmor=unconfined + ports: + - "5432:5432" + environment: + POSTGRES_DB: elearning_dev + POSTGRES_PASSWORD: 12345678 + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - elearning-network + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U elearning" ] + interval: 10s + timeout: 5s + retries: 5 + + # MinIO - S3 Compatible Storage + minio: + image: minio/minio:latest + deploy: + resources: + limits: + memory: 1G + container_name: elearning-minio + restart: unless-stopped + security_opt: + - apparmor=unconfined + ports: + - "9000:9000" # API + - "9001:9001" # Console + environment: + - TZ=Asia/Bangkok + - MINIO_ROOT_USER=admin + - MINIO_ROOT_PASSWORD=12345678 + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + networks: + - elearning-network + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ] + interval: 30s + timeout: 20s + retries: 3 + + # MinIO Client - Create buckets on startup + minio-init: + image: minio/mc:latest + container_name: elearning-minio-init + security_opt: + - apparmor=unconfined + depends_on: + - minio + entrypoint: > + /bin/sh -c " sleep 5; /usr/bin/mc alias set myminio http://minio:9000 admin 12345678; /usr/bin/mc mb myminio/courses --ignore-existing; /usr/bin/mc mb myminio/videos --ignore-existing; /usr/bin/mc mb myminio/documents --ignore-existing; /usr/bin/mc mb myminio/images --ignore-existing; /usr/bin/mc mb myminio/attachments --ignore-existing; /usr/bin/mc anonymous set download myminio/images; echo 'MinIO buckets created successfully'; exit 0; " + networks: + - elearning-network + + # Redis - Cache & Session Store + redis: + image: redis:7-alpine + container_name: elearning-redis + restart: unless-stopped + security_opt: + - apparmor=unconfined + ports: + - "6379:6379" + command: redis-server --appendonly yes --requirepass dev_redis_password + volumes: + - redis_data:/data + networks: + - elearning-network + healthcheck: + test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] + interval: 10s + timeout: 3s + retries: 5 + + # Mailhog - Email Testing + mailhog: + image: mailhog/mailhog:latest + container_name: elearning-mailhog + restart: unless-stopped + security_opt: + - apparmor=unconfined + ports: + - "1025:1025" # SMTP + - "8025:8025" # Web UI + networks: + - elearning-network + + # Adminer - Database Management UI + adminer: + image: adminer:latest + container_name: elearning-adminer + restart: unless-stopped + security_opt: + - apparmor=unconfined + ports: + - "8080:8080" + environment: + ADMINER_DEFAULT_SERVER: postgres + ADMINER_DESIGN: dracula + networks: + - elearning-network + depends_on: + - postgres + +volumes: + postgres_data: + driver: local + minio_data: + driver: local + redis_data: + driver: local + +networks: + elearning-network: + driver: bridge