diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 00000000..60a78faf Binary files /dev/null and b/docs/.DS_Store differ diff --git a/docs/ERD_v2_improved.txt b/docs/ERD_v2_improved.txt index 9552844f..03d6d546 100644 --- a/docs/ERD_v2_improved.txt +++ b/docs/ERD_v2_improved.txt @@ -66,6 +66,9 @@ Table lessons { content jsonb [note: 'multi-language lesson content'] type varchar [not null, note: 'video | pdf | text | quiz'] sort_order int [not null, default: 0, note: 'must be >= 0'] + is_sequential boolean [not null, default: true, note: 'require previous lessons to be completed'] + prerequisite_lesson_ids jsonb [note: 'array of lesson IDs that must be completed first, e.g. [1, 2, 3]'] + require_pass_quiz boolean [default: false, note: 'require passing quiz to unlock next lessons'] created_at datetime [not null, default: `now()`] updated_at datetime [not null, default: `now()`] @@ -214,6 +217,23 @@ Table announcement_attachments { } } +Table lesson_attachments { + id int [pk, increment] + lesson_id int [not null, ref: > lessons.id] + file_name varchar [not null] + file_path varchar [not null, note: 'S3 key or file path'] + file_size int [not null, note: 'in bytes'] + mime_type varchar [not null] + description jsonb [note: 'multi-language description'] + sort_order int [not null, default: 0] + created_at datetime [not null, default: `now()`] + + indexes { + lesson_id + (lesson_id, sort_order) + } +} + Table orders { id int [pk, increment] user_id int [not null, ref: > users.id] diff --git a/docs/api-docs/api_attachments_examples.md b/docs/api-docs/api_attachments_examples.md new file mode 100644 index 00000000..303f5704 --- /dev/null +++ b/docs/api-docs/api_attachments_examples.md @@ -0,0 +1,520 @@ +# API Usage Examples - Lesson Attachments + +## 📎 Lesson Attachments Endpoints + +--- + +## 1. Upload Attachment (Instructor) + +### POST `/instructor/courses/:courseId/lessons/:lessonId/attachments` + +#### Request: +```http +POST /api/instructor/courses/1/lessons/5/attachments +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Content-Type: multipart/form-data + +file: +description_th: "สไลด์ประกอบการสอน" +description_en: "Lecture slides" +sort_order: 1 +``` + +#### Response 201: +```json +{ + "id": 10, + "lesson_id": 5, + "file_name": "slides.pdf", + "file_path": "lessons/5/slides.pdf", + "file_size": 2048576, + "mime_type": "application/pdf", + "description": { + "th": "สไลด์ประกอบการสอน", + "en": "Lecture slides" + }, + "sort_order": 1, + "download_url": "/api/lessons/5/attachments/10/download", + "created_at": "2024-12-23T15:00:00Z" +} +``` + +#### Response 422 (File Too Large): +```json +{ + "error": { + "code": "FILE_TOO_LARGE", + "message": "File size exceeds maximum limit of 100 MB", + "file_size": 104857600, + "max_size": 104857600 + } +} +``` + +#### Response 422 (Too Many Attachments): +```json +{ + "error": { + "code": "MAX_ATTACHMENTS_EXCEEDED", + "message": "Maximum 10 attachments per lesson", + "current_count": 10, + "max_count": 10 + } +} +``` + +#### Response 422 (Invalid File Type): +```json +{ + "error": { + "code": "INVALID_FILE_TYPE", + "message": "File type not allowed", + "mime_type": "application/x-executable", + "allowed_types": ["application/pdf", "application/zip", "image/jpeg", "..."] + } +} +``` + +--- + +## 2. List Attachments (Instructor) + +### GET `/instructor/courses/:courseId/lessons/:lessonId/attachments` + +#### Request: +```http +GET /api/instructor/courses/1/lessons/5/attachments +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 200: +```json +{ + "lesson_id": 5, + "attachments": [ + { + "id": 10, + "file_name": "slides.pdf", + "file_size": 2048576, + "mime_type": "application/pdf", + "description": { + "th": "สไลด์ประกอบการสอน", + "en": "Lecture slides" + }, + "sort_order": 1, + "download_url": "/api/lessons/5/attachments/10/download", + "created_at": "2024-12-23T15:00:00Z" + }, + { + "id": 11, + "file_name": "code.zip", + "file_size": 512000, + "mime_type": "application/zip", + "description": { + "th": "โค้ดตัวอย่าง", + "en": "Sample code" + }, + "sort_order": 2, + "download_url": "/api/lessons/5/attachments/11/download", + "created_at": "2024-12-23T15:05:00Z" + }, + { + "id": 12, + "file_name": "exercises.docx", + "file_size": 256000, + "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "description": { + "th": "แบบฝึกหัด", + "en": "Exercises" + }, + "sort_order": 3, + "download_url": "/api/lessons/5/attachments/12/download", + "created_at": "2024-12-23T15:10:00Z" + } + ], + "total": 3 +} +``` + +--- + +## 3. Update Attachment Info (Instructor) + +### PUT `/instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId` + +#### Request: +```http +PUT /api/instructor/courses/1/lessons/5/attachments/10 +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Content-Type: application/json + +{ + "description": { + "th": "สไลด์ประกอบการสอน (ฉบับแก้ไข)", + "en": "Lecture slides (revised)" + }, + "sort_order": 2 +} +``` + +#### Response 200: +```json +{ + "id": 10, + "lesson_id": 5, + "file_name": "slides.pdf", + "file_size": 2048576, + "mime_type": "application/pdf", + "description": { + "th": "สไลด์ประกอบการสอน (ฉบับแก้ไข)", + "en": "Lecture slides (revised)" + }, + "sort_order": 2, + "updated_at": "2024-12-23T15:20:00Z" +} +``` + +--- + +## 4. Delete Attachment (Instructor) + +### DELETE `/instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId` + +#### Request: +```http +DELETE /api/instructor/courses/1/lessons/5/attachments/10 +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 204: +``` +No Content +``` + +--- + +## 5. Reorder Attachments (Instructor) + +### PUT `/instructor/courses/:courseId/lessons/:lessonId/attachments/reorder` + +#### Request: +```http +PUT /api/instructor/courses/1/lessons/5/attachments/reorder +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Content-Type: application/json + +{ + "attachments": [ + { "id": 11, "sort_order": 1 }, + { "id": 12, "sort_order": 2 }, + { "id": 10, "sort_order": 3 } + ] +} +``` + +#### Response 200: +```json +{ + "message": "Attachments reordered successfully", + "attachments": [ + { + "id": 11, + "file_name": "code.zip", + "sort_order": 1 + }, + { + "id": 12, + "file_name": "exercises.docx", + "sort_order": 2 + }, + { + "id": 10, + "file_name": "slides.pdf", + "sort_order": 3 + } + ] +} +``` + +--- + +## 6. Get Lesson with Attachments (Student) + +### GET `/students/courses/:courseId/lessons/:lessonId` + +**Changes**: เพิ่ม `attachments` array + +#### Request: +```http +GET /api/students/courses/1/lessons/5 +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 200: +```json +{ + "id": 5, + "title": "บทที่ 3: ทฤษฎี", + "content": "

เนื้อหาบทเรียน...

", + "type": "video", + "video_url": "https://cdn.example.com/videos/lesson-5.m3u8", + "duration": "00:25:00", + "is_completed": false, + "can_access": true, + + "attachments": [ + { + "id": 11, + "file_name": "code.zip", + "file_size": 512000, + "mime_type": "application/zip", + "description": "โค้ดตัวอย่าง", + "download_url": "/api/lessons/5/attachments/11/download" + }, + { + "id": 12, + "file_name": "exercises.docx", + "file_size": 256000, + "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "description": "แบบฝึกหัด", + "download_url": "/api/lessons/5/attachments/12/download" + }, + { + "id": 10, + "file_name": "slides.pdf", + "file_size": 2048576, + "mime_type": "application/pdf", + "description": "สไลด์ประกอบการสอน", + "download_url": "/api/lessons/5/attachments/10/download" + } + ] +} +``` + +--- + +## 7. Download Attachment + +### GET `/lessons/:lessonId/attachments/:attachmentId/download` + +#### Request: +```http +GET /api/lessons/5/attachments/10/download +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 200: +```http +HTTP/1.1 200 OK +Content-Type: application/pdf +Content-Disposition: attachment; filename="slides.pdf" +Content-Length: 2048576 + + +``` + +#### Response 403 (Not Enrolled): +```json +{ + "error": { + "code": "NOT_ENROLLED", + "message": "You must enroll in this course to download attachments", + "course_id": 1 + } +} +``` + +--- + +## Frontend Integration Examples + +### 1. Upload Attachment Component + +```javascript +async function uploadAttachment(courseId, lessonId, file, description) { + const formData = new FormData(); + formData.append('file', file); + formData.append('description_th', description.th); + formData.append('description_en', description.en); + formData.append('sort_order', 1); + + try { + const response = await fetch( + `/api/instructor/courses/${courseId}/lessons/${lessonId}/attachments`, + { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}` + }, + body: formData + } + ); + + if (!response.ok) { + const error = await response.json(); + + if (error.error.code === 'FILE_TOO_LARGE') { + showAlert('ไฟล์ใหญ่เกินไป (สูงสุด 100 MB)'); + } else if (error.error.code === 'MAX_ATTACHMENTS_EXCEEDED') { + showAlert('แนบไฟล์ได้สูงสุด 10 ไฟล์'); + } else if (error.error.code === 'INVALID_FILE_TYPE') { + showAlert('ประเภทไฟล์ไม่รองรับ'); + } + return; + } + + const data = await response.json(); + showSuccess('อัปโหลดไฟล์สำเร็จ'); + return data; + } catch (error) { + console.error('Upload error:', error); + showAlert('เกิดข้อผิดพลาดในการอัปโหลด'); + } +} +``` + +### 2. Display Attachments List + +```javascript +function AttachmentsList({ attachments }) { + return ( +
+

📎 ไฟล์แนบ ({attachments.length})

+ + {attachments.map(attachment => ( +
+
+ {getFileIcon(attachment.mime_type)} +
+ +
+
{attachment.file_name}
+
{attachment.description}
+
{formatFileSize(attachment.file_size)}
+
+ + + ⬇ ดาวน์โหลด + +
+ ))} +
+ ); +} + +function getFileIcon(mimeType) { + if (mimeType.includes('pdf')) return '📄'; + if (mimeType.includes('zip')) return '📦'; + if (mimeType.includes('word')) return '📝'; + if (mimeType.includes('excel')) return '📊'; + if (mimeType.includes('powerpoint')) return '📊'; + if (mimeType.includes('image')) return '🖼️'; + return '📎'; +} + +function formatFileSize(bytes) { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; +} +``` + +### 3. Drag & Drop Reorder + +```javascript +function ReorderAttachments({ attachments, onReorder }) { + const [items, setItems] = useState(attachments); + + const handleDragEnd = (result) => { + if (!result.destination) return; + + const reordered = Array.from(items); + const [removed] = reordered.splice(result.source.index, 1); + reordered.splice(result.destination.index, 0, removed); + + // Update sort_order + const updated = reordered.map((item, index) => ({ + id: item.id, + sort_order: index + 1 + })); + + setItems(reordered); + onReorder(updated); + }; + + return ( + + + {(provided) => ( +
+ {items.map((item, index) => ( + + {(provided) => ( +
+ {item.file_name} +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+ ); +} +``` + +--- + +## Allowed File Types + +| Category | MIME Type | Extension | +|----------|-----------|-----------| +| **Documents** | `application/pdf` | .pdf | +| | `application/msword` | .doc | +| | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | .docx | +| | `application/vnd.ms-excel` | .xls | +| | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | .xlsx | +| | `application/vnd.ms-powerpoint` | .ppt | +| | `application/vnd.openxmlformats-officedocument.presentationml.presentation` | .pptx | +| **Archives** | `application/zip` | .zip | +| | `application/x-rar-compressed` | .rar | +| | `application/x-7z-compressed` | .7z | +| **Images** | `image/jpeg` | .jpg | +| | `image/png` | .png | +| | `image/gif` | .gif | +| **Text** | `text/plain` | .txt | +| | `text/csv` | .csv | + +--- + +## File Limits + +- **Max file size**: 100 MB per file +- **Max attachments**: 10 files per lesson +- **Total size**: 500 MB per lesson + +--- + +## Summary + +**New Endpoints**: 6 +- `POST /instructor/courses/:courseId/lessons/:lessonId/attachments` +- `GET /instructor/courses/:courseId/lessons/:lessonId/attachments` +- `PUT /instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId` +- `DELETE /instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId` +- `PUT /instructor/courses/:courseId/lessons/:lessonId/attachments/reorder` +- `GET /lessons/:lessonId/attachments/:attachmentId/download` + +**Modified Endpoints**: 2 +- `GET /students/courses/:courseId/lessons/:lessonId` - เพิ่ม attachments array +- `GET /students/courses/:courseId/learn` - เพิ่ม attachments_count diff --git a/docs/api_endpoints.md b/docs/api-docs/api_endpoints.md similarity index 88% rename from docs/api_endpoints.md rename to docs/api-docs/api_endpoints.md index 521573d8..5703d818 100644 --- a/docs/api_endpoints.md +++ b/docs/api-docs/api_endpoints.md @@ -67,8 +67,9 @@ Authorization: Bearer |--------|----------|------|-------------| | POST | `/courses/:courseId/enroll` | 👨‍🎓 Student | Enroll in course | | GET | `/students/courses` | 👨‍🎓 Student | Get my enrolled courses | -| GET | `/students/courses/:courseId/learn` | 👨‍🎓 Student | Get course learning page | -| GET | `/students/courses/:courseId/lessons/:lessonId` | 👨‍🎓 Student | Get lesson content | +| GET | `/students/courses/:courseId/learn` | 👨‍🎓 Student | Get course learning page (with lock status) | +| GET | `/students/courses/:courseId/lessons/:lessonId` | 👨‍🎓 Student | Get lesson content (checks prerequisites) | +| GET | `/students/courses/:courseId/lessons/:lessonId/access-check` | 👨‍🎓 Student | Check lesson access without loading content | | POST | `/students/courses/:courseId/lessons/:lessonId/complete` | 👨‍🎓 Student | Mark lesson as complete | ### Instructor Endpoints @@ -96,11 +97,22 @@ Authorization: Bearer ### Lessons | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| -| POST | `/instructor/courses/:courseId/chapters/:chapterId/lessons` | 👨‍🏫 Instructor | Create lesson | +| POST | `/instructor/courses/:courseId/chapters/:chapterId/lessons` | 👨‍🏫 Instructor | Create lesson (with video & attachments) | | PUT | `/instructor/courses/:courseId/lessons/:lessonId` | 👨‍🏫 Instructor | Update lesson | | DELETE | `/instructor/courses/:courseId/lessons/:lessonId` | 👨‍🏫 Instructor | Delete lesson | -| POST | `/instructor/courses/:courseId/lessons/:lessonId/video` | 👨‍🏫 Instructor | Upload video | | PUT | `/instructor/courses/:courseId/lessons/reorder` | 👨‍🏫 Instructor | Reorder lessons | +| GET | `/instructor/courses/:courseId/lessons/:lessonId/prerequisites` | 👨‍🏫 Instructor | View lesson prerequisites | +| POST | `/instructor/courses/:courseId/lessons/:lessonId/prerequisites` | 👨‍🏫 Instructor | Update lesson prerequisites | + +### Lesson Attachments +| Method | Endpoint | Auth | Description | +|--------|----------|------|-------------| +| POST | `/instructor/courses/:courseId/lessons/:lessonId/attachments` | 👨‍🏫 Instructor | Add more attachments | +| GET | `/instructor/courses/:courseId/lessons/:lessonId/attachments` | 👨‍🏫 Instructor | List attachments | +| PUT | `/instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId` | 👨‍🏫 Instructor | Update attachment info | +| DELETE | `/instructor/courses/:courseId/lessons/:lessonId/attachments/:attachmentId` | 👨‍🏫 Instructor | Delete attachment | +| PUT | `/instructor/courses/:courseId/lessons/:lessonId/attachments/reorder` | 👨‍🏫 Instructor | Reorder attachments | +| GET | `/lessons/:lessonId/attachments/:attachmentId/download` | ✅ | Download attachment | --- @@ -291,13 +303,13 @@ Authorization: Bearer --- -## Total Endpoints: **85+** +## Total Endpoints: **93+** - Authentication: 6 - User Management: 4 - Categories: 5 -- Courses: 11 -- Chapters & Lessons: 9 +- Courses: 12 +- Chapters & Lessons: 16 (-1) - Quizzes: 10 - Announcements: 6 - Reports: 11 diff --git a/docs/api-docs/api_lesson_creation_simplified.md b/docs/api-docs/api_lesson_creation_simplified.md new file mode 100644 index 00000000..8bef304c --- /dev/null +++ b/docs/api-docs/api_lesson_creation_simplified.md @@ -0,0 +1,306 @@ +# Create Lesson with Files - Simplified API + +## 📝 Create Lesson (One Request) + +### POST `/instructor/courses/:courseId/chapters/:chapterId/lessons` + +สร้าง lesson พร้อม video และ attachments ในครั้งเดียว + +--- + +## Request Examples + +### 1. Text Lesson (No Files) + +```http +POST /api/instructor/courses/1/chapters/1/lessons +Authorization: Bearer +Content-Type: application/json + +{ + "title": { + "th": "บทที่ 1: แนะนำ", + "en": "Lesson 1: Introduction" + }, + "content": { + "th": "

เนื้อหาบทเรียน...

", + "en": "

Lesson content...

" + }, + "type": "text", + "sort_order": 1, + "is_sequential": true +} +``` + +--- + +### 2. Video Lesson (Video Only) + +```http +POST /api/instructor/courses/1/chapters/1/lessons +Authorization: Bearer +Content-Type: multipart/form-data + +title_th: "บทที่ 1: แนะนำ Python" +title_en: "Lesson 1: Introduction to Python" +content_th: "

Python เป็นภาษา...

" +content_en: "

Python is a...

" +type: "video" +sort_order: 1 +video: +``` + +**Response 201**: +```json +{ + "id": 1, + "chapter_id": 1, + "title": { + "th": "บทที่ 1: แนะนำ Python", + "en": "Lesson 1: Introduction to Python" + }, + "type": "video", + "video": { + "url": "https://cdn.example.com/videos/lesson-1.m3u8", + "duration": "00:15:30", + "file_size": 52428800 + }, + "attachments": [], + "created_at": "2024-12-23T15:00:00Z" +} +``` + +--- + +### 3. Video + Attachments (Complete) + +```http +POST /api/instructor/courses/1/chapters/1/lessons +Authorization: Bearer +Content-Type: multipart/form-data + +# Lesson Info +title_th: "บทที่ 1: แนะนำ Python" +title_en: "Lesson 1: Introduction to Python" +content_th: "

Python เป็นภาษา...

" +content_en: "

Python is a...

" +type: "video" +sort_order: 1 +is_sequential: true + +# Video +video: + +# Attachments +attachments[]: +attachments[]: +attachments[]: + +# Attachment Descriptions +descriptions[0][th]: "สไลด์ประกอบการสอน" +descriptions[0][en]: "Lecture slides" +descriptions[1][th]: "โค้ดตัวอย่าง" +descriptions[1][en]: "Sample code" +descriptions[2][th]: "แบบฝึกหัด" +descriptions[2][en]: "Exercises" +``` + +**Response 201**: +```json +{ + "id": 1, + "chapter_id": 1, + "title": { + "th": "บทที่ 1: แนะนำ Python", + "en": "Lesson 1: Introduction to Python" + }, + "content": { + "th": "

Python เป็นภาษา...

", + "en": "

Python is a...

" + }, + "type": "video", + "sort_order": 1, + "is_sequential": true, + + "video": { + "url": "https://cdn.example.com/videos/lesson-1.m3u8", + "duration": "00:15:30", + "file_size": 52428800, + "uploaded_at": "2024-12-23T15:00:00Z" + }, + + "attachments": [ + { + "id": 1, + "file_name": "slides.pdf", + "file_size": 2048576, + "mime_type": "application/pdf", + "description": { + "th": "สไลด์ประกอบการสอน", + "en": "Lecture slides" + }, + "sort_order": 1, + "download_url": "/api/lessons/1/attachments/1/download" + }, + { + "id": 2, + "file_name": "code.zip", + "file_size": 512000, + "mime_type": "application/zip", + "description": { + "th": "โค้ดตัวอย่าง", + "en": "Sample code" + }, + "sort_order": 2, + "download_url": "/api/lessons/1/attachments/2/download" + }, + { + "id": 3, + "file_name": "exercises.docx", + "file_size": 256000, + "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "description": { + "th": "แบบฝึกหัด", + "en": "Exercises" + }, + "sort_order": 3, + "download_url": "/api/lessons/1/attachments/3/download" + } + ], + + "created_at": "2024-12-23T15:00:00Z" +} +``` + +--- + +### 4. PDF Lesson (Attachments Only) + +```http +POST /api/instructor/courses/1/chapters/1/lessons +Authorization: Bearer +Content-Type: multipart/form-data + +title_th: "บทที่ 2: เอกสารประกอบ" +title_en: "Lesson 2: Reading Materials" +type: "pdf" +sort_order: 2 + +attachments[]: +attachments[]: + +descriptions[0][th]: "เอกสารหลัก" +descriptions[0][en]: "Main document" +descriptions[1][th]: "เอกสารเพิ่มเติม" +descriptions[1][en]: "Supplementary material" +``` + +--- + +## Add More Files Later + +ถ้าต้องการเพิ่มไฟล์ทีหลัง: + +### POST `/instructor/courses/:courseId/lessons/:lessonId/attachments` + +```http +POST /api/instructor/courses/1/lessons/1/attachments +Authorization: Bearer +Content-Type: multipart/form-data + +attachments[]: +descriptions[0][th]: "ไฟล์เพิ่มเติม" +descriptions[0][en]: "Additional file" +``` + +--- + +## Frontend Example + +```javascript +async function createLesson(courseId, chapterId, lessonData) { + const formData = new FormData(); + + // Basic info + formData.append('title_th', lessonData.title.th); + formData.append('title_en', lessonData.title.en); + formData.append('content_th', lessonData.content.th); + formData.append('content_en', lessonData.content.en); + formData.append('type', lessonData.type); + formData.append('sort_order', lessonData.sortOrder); + + // Video (if exists) + if (lessonData.video) { + formData.append('video', lessonData.video); + } + + // Attachments (if exists) + if (lessonData.attachments && lessonData.attachments.length > 0) { + lessonData.attachments.forEach((file, index) => { + formData.append('attachments[]', file.file); + formData.append(`descriptions[${index}][th]`, file.description.th); + formData.append(`descriptions[${index}][en]`, file.description.en); + }); + } + + const response = await fetch( + `/api/instructor/courses/${courseId}/chapters/${chapterId}/lessons`, + { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}` + }, + body: formData + } + ); + + return await response.json(); +} + +// Usage +const lesson = await createLesson(1, 1, { + title: { + th: 'บทที่ 1: แนะนำ', + en: 'Lesson 1: Introduction' + }, + content: { + th: '

เนื้อหา...

', + en: '

Content...

' + }, + type: 'video', + sortOrder: 1, + video: videoFile, + attachments: [ + { + file: pdfFile, + description: { th: 'สไลด์', en: 'Slides' } + }, + { + file: zipFile, + description: { th: 'โค้ด', en: 'Code' } + } + ] +}); +``` + +--- + +## Benefits + +✅ **One Request** - ส่งครั้งเดียว ไม่ต้องส่งหลายรอบ +✅ **Atomic** - สร้างสำเร็จหรือ fail ทั้งหมด +✅ **Faster** - ลด HTTP requests +✅ **Better UX** - Instructor ไม่ต้องรอหลายขั้นตอน +✅ **Flexible** - เพิ่มไฟล์ทีหลังได้ + +--- + +## Summary + +**Before**: 3 requests +1. Create lesson +2. Upload video +3. Upload attachments + +**After**: 1 request +1. Create lesson with everything ✨ diff --git a/docs/api-docs/api_prerequisites_examples.md b/docs/api-docs/api_prerequisites_examples.md new file mode 100644 index 00000000..b32ce406 --- /dev/null +++ b/docs/api-docs/api_prerequisites_examples.md @@ -0,0 +1,438 @@ +# API Usage Examples - Lesson Prerequisites + +## New Endpoints Usage Examples + +--- + +## 1. Check Lesson Access (Student) + +### GET `/students/courses/:courseId/lessons/:lessonId/access-check` + +**Purpose**: ตรวจสอบว่าสามารถเข้าถึง lesson ได้หรือไม่ โดยไม่ต้อง load content + +#### Request: +```http +GET /api/students/courses/1/lessons/3/access-check +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 200 (Can Access): +```json +{ + "can_access": true, + "lesson": { + "id": 3, + "title": "แบบทดสอบท้ายบท", + "type": "quiz", + "sort_order": 3 + } +} +``` + +#### Response 200 (Locked - Incomplete Prerequisites): +```json +{ + "can_access": false, + "reason": "incomplete_prerequisites", + "message": "กรุณาเรียนบทเรียนก่อนหน้าให้เสร็จก่อน", + "required_lessons": [ + { + "id": 1, + "title": "บทที่ 1: แนะนำ", + "type": "video", + "is_completed": true + }, + { + "id": 2, + "title": "บทที่ 2: ตัวแปร", + "type": "video", + "is_completed": false + } + ], + "missing_lessons": [2], + "next_available_lesson": { + "id": 2, + "title": "บทที่ 2: ตัวแปร" + } +} +``` + +#### Response 200 (Locked - Quiz Not Passed): +```json +{ + "can_access": false, + "reason": "quiz_not_passed", + "message": "กรุณาทำแบบทดสอบให้ผ่านก่อน", + "required_quiz": { + "lesson_id": 3, + "title": "แบบทดสอบท้ายบท", + "passing_score": 70, + "your_best_score": 55, + "attempts_used": 2, + "max_attempts": 3 + } +} +``` + +--- + +## 2. Get Learning Page with Lock Status (Student) + +### GET `/students/courses/:courseId/learn` + +**Changes**: เพิ่ม `is_locked` และ `lock_reason` ในแต่ละ lesson + +#### Request: +```http +GET /api/students/courses/1/learn +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 200: +```json +{ + "course": { + "id": 1, + "title": "Python สำหรับผู้เริ่มต้น" + }, + "enrollment": { + "status": "enrolled", + "progress_percentage": 35 + }, + "chapters": [ + { + "id": 1, + "title": "บทที่ 1: พื้นฐาน", + "sort_order": 1, + "lessons": [ + { + "id": 1, + "title": "1.1 แนะนำ Python", + "type": "video", + "duration": "00:15:30", + "is_completed": true, + "is_locked": false, + "completed_at": "2024-12-23T10:00:00Z" + }, + { + "id": 2, + "title": "1.2 ตัวแปร", + "type": "video", + "duration": "00:20:00", + "is_completed": false, + "is_locked": false + }, + { + "id": 3, + "title": "1.3 แบบทดสอบ", + "type": "quiz", + "is_completed": false, + "is_locked": true, + "lock_reason": "incomplete_prerequisites", + "required_lessons": [1, 2] + }, + { + "id": 4, + "title": "1.4 ขั้นสูง", + "type": "video", + "duration": "00:25:00", + "is_completed": false, + "is_locked": true, + "lock_reason": "quiz_not_passed", + "required_quiz": 3 + } + ] + } + ] +} +``` + +--- + +## 3. Get Lesson Content with Prerequisites Check (Student) + +### GET `/students/courses/:courseId/lessons/:lessonId` + +**Changes**: ตรวจสอบ prerequisites ก่อน return content + +#### Request: +```http +GET /api/students/courses/1/lessons/3 +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 403 (Locked): +```json +{ + "error": { + "code": "LESSON_LOCKED", + "message": "Cannot access this lesson", + "reason": "incomplete_prerequisites", + "required_lessons": [1, 2], + "completed_lessons": [1], + "missing_lessons": [2], + "next_available_lesson": { + "id": 2, + "title": "บทที่ 2: ตัวแปร", + "type": "video" + } + } +} +``` + +--- + +## 4. Create Lesson with Prerequisites (Instructor) + +### POST `/instructor/courses/:courseId/chapters/:chapterId/lessons` + +**Changes**: เพิ่ม fields สำหรับ prerequisites + +#### Request: +```http +POST /api/instructor/courses/1/chapters/1/lessons +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Content-Type: application/json + +{ + "title": { + "th": "แบบทดสอบท้ายบท", + "en": "Chapter Quiz" + }, + "content": { + "th": "

ทดสอบความเข้าใจ

", + "en": "

Test your understanding

" + }, + "type": "quiz", + "sort_order": 3, + "is_sequential": true, + "prerequisite_lesson_ids": [1, 2], + "require_pass_quiz": true +} +``` + +#### Response 201: +```json +{ + "id": 3, + "chapter_id": 1, + "title": { + "th": "แบบทดสอบท้ายบท", + "en": "Chapter Quiz" + }, + "type": "quiz", + "sort_order": 3, + "is_sequential": true, + "prerequisite_lesson_ids": [1, 2], + "require_pass_quiz": true, + "created_at": "2024-12-23T14:00:00Z" +} +``` + +#### Response 422 (Validation Error): +```json +{ + "error": { + "code": "VALIDATION_ERROR", + "message": "Invalid prerequisites", + "details": [ + { + "field": "prerequisite_lesson_ids", + "message": "Lesson ID 99 does not exist" + } + ] + } +} +``` + +--- + +## 5. View Lesson Prerequisites (Instructor) + +### GET `/instructor/courses/:courseId/lessons/:lessonId/prerequisites` + +#### Request: +```http +GET /api/instructor/courses/1/lessons/4/prerequisites +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +#### Response 200: +```json +{ + "lesson": { + "id": 4, + "title": "บทที่ 3: ขั้นสูง", + "type": "video", + "sort_order": 4 + }, + "is_sequential": true, + "prerequisites": [ + { + "id": 3, + "title": "แบบทดสอบท้ายบท", + "type": "quiz", + "require_pass": true, + "passing_score": 70 + } + ], + "affected_students": { + "total_enrolled": 150, + "can_access": 85, + "locked": 65, + "breakdown": { + "incomplete_prerequisites": 45, + "quiz_not_passed": 20 + } + } +} +``` + +--- + +## 6. Update Lesson Prerequisites (Instructor) + +### POST `/instructor/courses/:courseId/lessons/:lessonId/prerequisites` + +#### Request: +```http +POST /api/instructor/courses/1/lessons/4/prerequisites +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +Content-Type: application/json + +{ + "is_sequential": true, + "prerequisite_lesson_ids": [3], + "require_pass_quiz": false +} +``` + +#### Response 200: +```json +{ + "lesson_id": 4, + "is_sequential": true, + "prerequisites": [ + { + "id": 3, + "title": "แบบทดสอบท้ายบท", + "type": "quiz", + "require_pass": false + } + ], + "message": "Prerequisites updated successfully", + "affected_students": { + "newly_unlocked": 20, + "still_locked": 45 + } +} +``` + +--- + +## Error Codes + +### New Error Codes for Prerequisites + +| Code | Description | +|------|-------------| +| `LESSON_LOCKED` | Cannot access lesson due to prerequisites | +| `INCOMPLETE_PREREQUISITES` | Required lessons not completed | +| `QUIZ_NOT_PASSED` | Required quiz not passed | +| `INVALID_PREREQUISITES` | Invalid prerequisite configuration | +| `CIRCULAR_DEPENDENCY` | Circular dependency detected | + +--- + +## Frontend Integration Examples + +### 1. Display Locked Lesson + +```javascript +function LessonItem({ lesson }) { + if (lesson.is_locked) { + return ( +
+ + {lesson.title} + + {lesson.lock_reason === 'incomplete_prerequisites' && ( + + กรุณาเรียนบทเรียนก่อนหน้าให้เสร็จก่อน +
    + {lesson.required_lessons.map(id => ( +
  • Lesson {id}
  • + ))} +
+
+ )} + + {lesson.lock_reason === 'quiz_not_passed' && ( + + กรุณาทำแบบทดสอบให้ผ่านก่อน (ต้องได้ 70% ขึ้นไป) + + )} +
+ ); + } + + return ( + + {lesson.is_completed ? : } + {lesson.title} + + ); +} +``` + +### 2. Check Access Before Navigation + +```javascript +async function navigateToLesson(courseId, lessonId) { + try { + const response = await fetch( + `/api/students/courses/${courseId}/lessons/${lessonId}/access-check`, + { + headers: { + 'Authorization': `Bearer ${token}` + } + } + ); + + const data = await response.json(); + + if (data.can_access) { + // Navigate to lesson + router.push(`/courses/${courseId}/lessons/${lessonId}`); + } else { + // Show lock message + if (data.reason === 'incomplete_prerequisites') { + showAlert(`กรุณาเรียนบทเรียนก่อนหน้าให้เสร็จก่อน`); + // Highlight next available lesson + highlightLesson(data.next_available_lesson.id); + } else if (data.reason === 'quiz_not_passed') { + showAlert(`กรุณาทำแบบทดสอบให้ผ่านก่อน`); + // Navigate to quiz + router.push(`/courses/${courseId}/lessons/${data.required_quiz.lesson_id}`); + } + } + } catch (error) { + console.error('Error checking lesson access:', error); + } +} +``` + +--- + +## Summary + +**New Endpoints**: 3 +- `GET /students/courses/:courseId/lessons/:lessonId/access-check` +- `GET /instructor/courses/:courseId/lessons/:lessonId/prerequisites` +- `POST /instructor/courses/:courseId/lessons/:lessonId/prerequisites` + +**Modified Endpoints**: 4 +- `GET /students/courses/:courseId/learn` - เพิ่ม lock status +- `GET /students/courses/:courseId/lessons/:lessonId` - เช็ค prerequisites +- `POST /instructor/courses/:courseId/chapters/:chapterId/lessons` - รองรับ prerequisites +- `PUT /instructor/courses/:courseId/lessons/:lessonId` - รองรับ prerequisites diff --git a/docs/api_usage_examples.md b/docs/api-docs/api_usage_examples.md similarity index 100% rename from docs/api_usage_examples.md rename to docs/api-docs/api_usage_examples.md diff --git a/docs/development_setup.md b/docs/development_setup.md new file mode 100644 index 00000000..34ff79bc --- /dev/null +++ b/docs/development_setup.md @@ -0,0 +1,650 @@ +# Development Environment Setup Guide + +## 🏗️ Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Developer Machines │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Frontend │ │ Frontend │ │ Backend │ │ +│ │ (Student) │ │ (Admin) │ │ (API) │ │ +│ │ Port: 3000 │ │ Port: 3001 │ │ Port: 4000 │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ └──────────────────┴──────────────────┘ │ +│ │ │ +└────────────────────────────┼─────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Proxmox Server (Shared Services) │ +│ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Docker Compose Stack │ │ +│ │ │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ +│ │ │ PostgreSQL │ │ MinIO │ │ Redis │ │ │ +│ │ │ Port: 5432 │ │ Port: 9000 │ │ Port: 6379 │ │ │ +│ │ └────────────┘ └────────────┘ └────────────┘ │ │ +│ │ │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ +│ │ │ Mailhog │ │ Adminer │ │ Forgejo │ │ │ +│ │ │ Port: 1025 │ │ Port: 8080 │ │ Port: 3030 │ │ │ +│ │ │ Port: 8025 │ │ │ │ Port: 2222 │ │ │ +│ │ └────────────┘ └────────────┘ └────────────┘ │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ │ +│ IP: 192.168.1.100 (example) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 📦 Services on Proxmox Server + +### 1. PostgreSQL - Database +- **Port**: 5432 +- **Purpose**: Main application database +- **Access**: All developers + +### 2. MinIO - S3-compatible Storage +- **Port**: 9000 (API), 9001 (Console) +- **Purpose**: File storage (videos, documents, images) +- **Access**: Backend + Developers + +### 3. Redis - Cache & Session +- **Port**: 6379 +- **Purpose**: Caching, session storage, queue +- **Access**: Backend only + +### 4. Mailhog - Email Testing +- **Port**: 1025 (SMTP), 8025 (Web UI) +- **Purpose**: Catch all emails in development +- **Access**: All developers + +### 5. Adminer - Database Management +- **Port**: 8080 +- **Purpose**: Web-based database management +- **Access**: All developers + +### 6. Forgejo - Git Server +- **Port**: 3030 (HTTP), 2222 (SSH) +- **Purpose**: Self-hosted Git repository (like GitHub/GitLab) +- **Access**: All developers + +--- + +## 🐳 Docker Compose Configuration + +Create this file on your Proxmox server: + +### `/opt/elearning-dev/docker-compose.yml` + +```yaml +version: '3.8' + +services: + # PostgreSQL Database + postgres: + image: postgres:16-alpine + container_name: elearning-postgres + restart: unless-stopped + ports: + - "5432:5432" + environment: + POSTGRES_DB: elearning_dev + POSTGRES_USER: elearning + POSTGRES_PASSWORD: dev_password_change_in_prod + POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init-scripts:/docker-entrypoint-initdb.d + 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 + container_name: elearning-minio + restart: unless-stopped + ports: + - "9000:9000" # API + - "9001:9001" # Console + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin123 + MINIO_BROWSER_REDIRECT_URL: http://192.168.1.100:9001 + 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 + depends_on: + - minio + entrypoint: > + /bin/sh -c " + sleep 5; + /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin123; + /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 + 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 + 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 + ports: + - "8080:8080" + environment: + ADMINER_DEFAULT_SERVER: postgres + ADMINER_DESIGN: dracula + networks: + - elearning-network + depends_on: + - postgres + + # Forgejo - Self-hosted Git Server + forgejo: + image: codeberg.org/forgejo/forgejo:1.21 + container_name: elearning-forgejo + restart: unless-stopped + ports: + - "3030:3000" # HTTP + - "2222:22" # SSH + environment: + - USER_UID=1000 + - USER_GID=1000 + - FORGEJO__database__DB_TYPE=postgres + - FORGEJO__database__HOST=postgres:5432 + - FORGEJO__database__NAME=forgejo + - FORGEJO__database__USER=elearning + - FORGEJO__database__PASSWD=dev_password_change_in_prod + volumes: + - forgejo_data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + networks: + - elearning-network + depends_on: + - postgres + +volumes: + postgres_data: + driver: local + minio_data: + driver: local + redis_data: + driver: local + forgejo_data: + driver: local + +networks: + elearning-network: + driver: bridge +``` + +--- + +## 🚀 Setup Instructions + +### Step 1: Prepare Proxmox Server + +```bash +# SSH to Proxmox server +ssh root@192.168.1.100 + +# Create directory +mkdir -p /opt/elearning-dev +cd /opt/elearning-dev + +# Create docker-compose.yml (paste content above) +nano docker-compose.yml + +# Create init scripts directory +mkdir init-scripts +``` + +### Step 2: Create Database Init Script (Optional) + +Create `/opt/elearning-dev/init-scripts/01-create-extensions.sql`: + +```sql +-- Enable required PostgreSQL extensions +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pg_trgm"; + +-- Create Forgejo database +CREATE DATABASE forgejo; +GRANT ALL PRIVILEGES ON DATABASE forgejo TO elearning; + +-- Create additional schemas if needed +-- CREATE SCHEMA IF NOT EXISTS analytics; +``` + +### Step 3: Start Services + +```bash +# Start all services +docker-compose up -d + +# Check status +docker-compose ps + +# View logs +docker-compose logs -f + +# Check specific service +docker-compose logs -f postgres +``` + +### Step 4: Verify Services + +```bash +# Test PostgreSQL +docker exec -it elearning-postgres psql -U elearning -d elearning_dev -c "SELECT version();" + +# Test Redis +docker exec -it elearning-redis redis-cli -a dev_redis_password ping + +# Test MinIO +curl http://192.168.1.100:9000/minio/health/live +``` + +--- + +## 💻 Developer Machine Setup + +### Frontend (Student) - Port 3000 + +**Directory**: `/path/to/frontend-student` + +**`.env.local`**: +```env +# API Configuration +NEXT_PUBLIC_API_URL=http://localhost:4000/api +NEXT_PUBLIC_WS_URL=ws://localhost:4000 + +# MinIO/S3 Configuration +NEXT_PUBLIC_S3_ENDPOINT=http://192.168.1.100:9000 +NEXT_PUBLIC_S3_BUCKET=courses + +# App Configuration +NEXT_PUBLIC_APP_NAME=E-Learning Platform +NEXT_PUBLIC_APP_URL=http://localhost:3000 +``` + +**Start Command**: +```bash +npm run dev +# or +yarn dev +``` + +--- + +### Frontend (Admin) - Port 3001 + +**Directory**: `/path/to/frontend-admin` + +**`.env.local`**: +```env +# API Configuration +NEXT_PUBLIC_API_URL=http://localhost:4000/api +NEXT_PUBLIC_WS_URL=ws://localhost:4000 + +# MinIO/S3 Configuration +NEXT_PUBLIC_S3_ENDPOINT=http://192.168.1.100:9000 +NEXT_PUBLIC_S3_BUCKET=courses + +# App Configuration +NEXT_PUBLIC_APP_NAME=E-Learning Admin +NEXT_PUBLIC_APP_URL=http://localhost:3001 +``` + +**package.json** (modify dev script): +```json +{ + "scripts": { + "dev": "next dev -p 3001" + } +} +``` + +**Start Command**: +```bash +npm run dev +# or +yarn dev +``` + +--- + +### Backend (API) - Port 4000 + +**Directory**: `/path/to/backend` + +**`.env`**: +```env +# Application +NODE_ENV=development +PORT=4000 +APP_URL=http://localhost:4000 + +# Database +DATABASE_URL=postgresql://elearning:dev_password_change_in_prod@192.168.1.100:5432/elearning_dev +DB_HOST=192.168.1.100 +DB_PORT=5432 +DB_NAME=elearning_dev +DB_USER=elearning +DB_PASSWORD=dev_password_change_in_prod + +# Redis +REDIS_URL=redis://:dev_redis_password@192.168.1.100:6379 +REDIS_HOST=192.168.1.100 +REDIS_PORT=6379 +REDIS_PASSWORD=dev_redis_password + +# MinIO/S3 +S3_ENDPOINT=http://192.168.1.100:9000 +S3_ACCESS_KEY=minioadmin +S3_SECRET_KEY=minioadmin123 +S3_BUCKET_COURSES=courses +S3_BUCKET_VIDEOS=videos +S3_BUCKET_DOCUMENTS=documents +S3_BUCKET_IMAGES=images +S3_BUCKET_ATTACHMENTS=attachments +S3_REGION=us-east-1 +S3_USE_SSL=false + +# Email (Mailhog) +SMTP_HOST=192.168.1.100 +SMTP_PORT=1025 +SMTP_USER= +SMTP_PASSWORD= +SMTP_FROM=noreply@elearning.local +SMTP_FROM_NAME=E-Learning Platform + +# JWT +JWT_SECRET=your-super-secret-jwt-key-change-in-production +JWT_EXPIRES_IN=24h +JWT_REFRESH_EXPIRES_IN=7d + +# CORS +CORS_ORIGIN=http://localhost:3000,http://localhost:3001 + +# File Upload +MAX_FILE_SIZE=104857600 # 100MB +ALLOWED_VIDEO_TYPES=video/mp4,video/webm +ALLOWED_DOCUMENT_TYPES=application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document +ALLOWED_IMAGE_TYPES=image/jpeg,image/png,image/gif +``` + +**Start Command**: +```bash +npm run dev +# or +yarn dev +``` + +--- + +## 🔧 Useful Commands + +### Docker Management + +```bash +# Stop all services +docker-compose down + +# Stop and remove volumes (⚠️ deletes data) +docker-compose down -v + +# Restart specific service +docker-compose restart postgres + +# View logs +docker-compose logs -f postgres + +# Execute command in container +docker exec -it elearning-postgres psql -U elearning -d elearning_dev +``` + +### Database Management + +```bash +# Backup database +docker exec elearning-postgres pg_dump -U elearning elearning_dev > backup.sql + +# Restore database +cat backup.sql | docker exec -i elearning-postgres psql -U elearning -d elearning_dev + +# Access database shell +docker exec -it elearning-postgres psql -U elearning -d elearning_dev +``` + +### MinIO Management + +```bash +# Access MinIO Console +# Open browser: http://192.168.1.100:9001 +# Username: minioadmin +# Password: minioadmin123 + +# Using MinIO Client (mc) +docker exec -it elearning-minio mc alias set local http://localhost:9000 minioadmin minioadmin123 +docker exec -it elearning-minio mc ls local/ +``` + +--- + +## 🌐 Access URLs + +| Service | URL | Credentials | +|---------|-----|-------------| +| **Frontend (Student)** | http://localhost:3000 | - | +| **Frontend (Admin)** | http://localhost:3001 | - | +| **Backend API** | http://localhost:4000 | - | +| **PostgreSQL** | 192.168.1.100:5432 | elearning / dev_password_change_in_prod | +| **MinIO Console** | http://192.168.1.100:9001 | minioadmin / minioadmin123 | +| **MinIO API** | http://192.168.1.100:9000 | - | +| **Redis** | 192.168.1.100:6379 | Password: dev_redis_password | +| **Mailhog UI** | http://192.168.1.100:8025 | - | +| **Adminer** | http://192.168.1.100:8080 | - | +| **Forgejo** | http://192.168.1.100:3030 | Setup on first visit | +| **Forgejo SSH** | ssh://git@192.168.1.100:2222 | SSH key required | + +--- + +## 📊 Resource Requirements + +### Proxmox Server (Recommended) +- **CPU**: 4 cores +- **RAM**: 8 GB +- **Storage**: 50 GB (SSD recommended) +- **Network**: 1 Gbps + +### Developer Machines +- **CPU**: 2+ cores +- **RAM**: 8 GB +- **Storage**: 20 GB free space + +--- + +## 🔒 Security Notes + +> [!WARNING] +> **Development Environment Only!** +> - These credentials are for development only +> - Change all passwords in production +> - Never commit `.env` files to git +> - Use `.env.example` for templates + +--- + +## 🐛 Troubleshooting + +### Cannot connect to PostgreSQL + +```bash +# Check if container is running +docker ps | grep postgres + +# Check logs +docker logs elearning-postgres + +# Test connection +docker exec -it elearning-postgres pg_isready -U elearning +``` + +### Cannot connect to MinIO + +```bash +# Check if container is running +docker ps | grep minio + +# Check logs +docker logs elearning-minio + +# Test health +curl http://192.168.1.100:9000/minio/health/live +``` + +### Port already in use + +```bash +# Find process using port +lsof -i :5432 +# or +netstat -tulpn | grep 5432 + +# Kill process +kill -9 +``` + +### Docker network issues + +```bash +# Recreate network +docker-compose down +docker network prune +docker-compose up -d +``` + +--- + +## 📝 Development Workflow + +### Daily Workflow + +1. **Start Proxmox Services** (if not running) + ```bash + ssh root@192.168.1.100 + cd /opt/elearning-dev + docker-compose up -d + ``` + +2. **Start Backend** + ```bash + cd /path/to/backend + npm run dev + ``` + +3. **Start Frontend (Student)** + ```bash + cd /path/to/frontend-student + npm run dev + ``` + +4. **Start Frontend (Admin)** + ```bash + cd /path/to/frontend-admin + npm run dev + ``` + +5. **Develop & Test** + - Student app: http://localhost:3000 + - Admin app: http://localhost:3001 + - API: http://localhost:4000 + - Check emails: http://192.168.1.100:8025 + +### End of Day + +```bash +# Optional: Stop services to save resources +ssh root@192.168.1.100 +cd /opt/elearning-dev +docker-compose stop +``` + +--- + +## 🎯 Next Steps + +1. ✅ Setup Proxmox server with Docker Compose +2. ✅ Configure developer machines with `.env` files +3. ✅ Test connectivity to all services +4. ✅ Run database migrations +5. ✅ Seed initial data +6. ✅ Start development! + +--- + +## 📚 Additional Resources + +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [PostgreSQL Documentation](https://www.postgresql.org/docs/) +- [MinIO Documentation](https://min.io/docs/) +- [Redis Documentation](https://redis.io/documentation) diff --git a/docs/forgejo_setup.md b/docs/forgejo_setup.md new file mode 100644 index 00000000..93dc35b0 --- /dev/null +++ b/docs/forgejo_setup.md @@ -0,0 +1,493 @@ +# Forgejo Setup Guide + +## 🎯 What is Forgejo? + +**Forgejo** is a self-hosted Git server (like GitHub or GitLab) that you can run on your own server. It's perfect for: +- 📦 Hosting your code repositories privately +- 👥 Team collaboration with pull requests +- 🔍 Code review +- 📋 Issue tracking +- 🔄 CI/CD integration + +--- + +## 🚀 Initial Setup + +### Step 1: Access Forgejo + +After starting Docker Compose, open your browser: + +``` +http://192.168.1.100:3030 +``` + +You'll see the **Initial Configuration** page. + +--- + +### Step 2: Configure Forgejo + +Fill in the following settings: + +#### **Database Settings** (Already configured via Docker) +- ✅ Database Type: `PostgreSQL` +- ✅ Host: `postgres:5432` +- ✅ Username: `elearning` +- ✅ Password: `dev_password_change_in_prod` +- ✅ Database Name: `forgejo` + +#### **General Settings** +- **Site Title**: `E-Learning Dev` +- **Repository Root Path**: `/data/git/repositories` (default) +- **Git LFS Root Path**: `/data/git/lfs` (default) +- **Run As Username**: `git` (default) + +#### **Server and Third-Party Service Settings** +- **SSH Server Domain**: `192.168.1.100` +- **SSH Port**: `2222` +- **Forgejo HTTP Listen Port**: `3000` (internal) +- **Forgejo Base URL**: `http://192.168.1.100:3030` +- **Log Path**: `/data/forgejo/log` (default) + +#### **Email Settings** (Optional - use Mailhog) +- **SMTP Host**: `mailhog:1025` +- **Send Email As**: `forgejo@elearning.local` +- **Require Sign-In to View Pages**: ✅ (recommended) + +#### **Administrator Account Settings** +Create your admin account: +- **Username**: `admin` +- **Password**: `admin123` (change in production!) +- **Email**: `admin@elearning.local` + +Click **Install Forgejo** + +--- + +## 👥 Creating Developer Accounts + +### Option 1: Self-Registration (if enabled) + +Users can register at: `http://192.168.1.100:3030/user/sign_up` + +### Option 2: Admin Creates Users + +1. Login as admin +2. Go to **Site Administration** (top right) +3. Click **User Accounts** +4. Click **Create User Account** +5. Fill in: + - Username + - Email + - Password +6. Click **Create User Account** + +--- + +## 📦 Creating Your First Repository + +### Via Web Interface + +1. Login to Forgejo +2. Click **+** (top right) → **New Repository** +3. Fill in: + - **Owner**: Your username + - **Repository Name**: `elearning-backend` + - **Description**: Backend API for E-Learning Platform + - **Visibility**: Private + - **Initialize Repository**: ✅ Add README + - **Add .gitignore**: Node + - **Add License**: MIT (optional) +4. Click **Create Repository** + +--- + +## 🔑 Setting Up SSH Access + +### Step 1: Generate SSH Key (if you don't have one) + +```bash +# On your developer machine +ssh-keygen -t ed25519 -C "your_email@example.com" + +# Press Enter to accept default location (~/.ssh/id_ed25519) +# Enter a passphrase (optional but recommended) +``` + +### Step 2: Add SSH Key to Forgejo + +```bash +# Copy your public key +cat ~/.ssh/id_ed25519.pub +``` + +1. Login to Forgejo +2. Click your **avatar** (top right) → **Settings** +3. Click **SSH / GPG Keys** +4. Click **Add Key** +5. Paste your public key +6. Give it a name (e.g., "MacBook Pro") +7. Click **Add Key** + +### Step 3: Test SSH Connection + +```bash +ssh -T git@192.168.1.100 -p 2222 +``` + +You should see: +``` +Hi there, ! You've successfully authenticated, but Forgejo does not provide shell access. +``` + +--- + +## 💻 Using Git with Forgejo + +### Clone Repository + +```bash +# Via SSH (recommended) +git clone ssh://git@192.168.1.100:2222//elearning-backend.git + +# Via HTTP +git clone http://192.168.1.100:3030//elearning-backend.git +``` + +### Push Existing Project + +```bash +cd /path/to/your/project + +# Initialize git (if not already) +git init + +# Add Forgejo as remote +git remote add origin ssh://git@192.168.1.100:2222//elearning-backend.git + +# Add files +git add . +git commit -m "Initial commit" + +# Push to Forgejo +git push -u origin main +``` + +--- + +## 🏢 Organizing Repositories + +### Create Organization + +1. Click **+** (top right) → **New Organization** +2. Fill in: + - **Organization Name**: `elearning-team` + - **Visibility**: Private +3. Click **Create Organization** + +### Add Team Members + +1. Go to Organization → **Teams** +2. Click **Create New Team** +3. Fill in: + - **Team Name**: `Developers` + - **Permission**: Write +4. Click **Create Team** +5. Click **Add Team Member** and select users + +### Create Repository in Organization + +1. Click **+** → **New Repository** +2. **Owner**: Select `elearning-team` +3. Fill in repository details +4. Click **Create Repository** + +--- + +## 🔄 Recommended Repository Structure + +``` +elearning-team/ +├── elearning-backend # Backend API +├── elearning-frontend-student # Student Frontend +├── elearning-frontend-admin # Admin Frontend +├── elearning-docs # Documentation +└── elearning-infrastructure # Docker, CI/CD configs +``` + +--- + +## 🎯 Workflow Example + +### 1. Clone Repository + +```bash +git clone ssh://git@192.168.1.100:2222/elearning-team/elearning-backend.git +cd elearning-backend +``` + +### 2. Create Feature Branch + +```bash +git checkout -b feature/add-quiz-api +``` + +### 3. Make Changes + +```bash +# Edit files +nano src/controllers/quizController.js + +# Commit changes +git add . +git commit -m "feat: add quiz submission endpoint" +``` + +### 4. Push Branch + +```bash +git push origin feature/add-quiz-api +``` + +### 5. Create Pull Request + +1. Go to repository in Forgejo +2. Click **Pull Requests** +3. Click **New Pull Request** +4. Select: + - **Base**: `main` + - **Compare**: `feature/add-quiz-api` +5. Fill in title and description +6. Click **Create Pull Request** + +### 6. Code Review + +Team members can: +- Review code changes +- Add comments +- Approve or request changes + +### 7. Merge + +Once approved: +1. Click **Merge Pull Request** +2. Select merge method (Merge / Squash / Rebase) +3. Click **Confirm Merge** + +--- + +## 🔧 Git Configuration + +### Set Git Config for Forgejo + +```bash +# Set your identity +git config --global user.name "Your Name" +git config --global user.email "your.email@example.com" + +# Use SSH by default +git config --global url."ssh://git@192.168.1.100:2222/".insteadOf "http://192.168.1.100:3030/" + +# Set default branch name +git config --global init.defaultBranch main +``` + +### SSH Config (Optional) + +Create `~/.ssh/config`: + +``` +Host forgejo + HostName 192.168.1.100 + Port 2222 + User git + IdentityFile ~/.ssh/id_ed25519 +``` + +Now you can clone with: +```bash +git clone forgejo:/elearning-backend.git +``` + +--- + +## 📋 Common Tasks + +### Create .gitignore + +```bash +# Create .gitignore for Node.js +cat > .gitignore << 'EOF' +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment variables +.env +.env.local +.env.*.local + +# Build output +dist/ +build/ +.next/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log + +# Database +*.sqlite +*.db +EOF + +git add .gitignore +git commit -m "chore: add .gitignore" +git push +``` + +### Protect Main Branch + +1. Go to Repository → **Settings** +2. Click **Branches** +3. Click **Add Rule** +4. Fill in: + - **Branch Name Pattern**: `main` + - **Enable Branch Protection**: ✅ + - **Require Pull Request**: ✅ + - **Require Approvals**: 1 + - **Dismiss Stale Approvals**: ✅ +5. Click **Save** + +--- + +## 🐛 Troubleshooting + +### Cannot Connect via SSH + +```bash +# Check if Forgejo is running +docker ps | grep forgejo + +# Test SSH connection +ssh -T git@192.168.1.100 -p 2222 -v + +# Check if port 2222 is open +telnet 192.168.1.100 2222 +``` + +### Permission Denied (publickey) + +```bash +# Make sure your SSH key is added to Forgejo +# Check SSH agent +ssh-add -l + +# Add your key to agent +ssh-add ~/.ssh/id_ed25519 +``` + +### Cannot Push to Repository + +```bash +# Check remote URL +git remote -v + +# Update remote URL +git remote set-url origin ssh://git@192.168.1.100:2222//repo.git + +# Check your permissions in Forgejo +# Make sure you have write access to the repository +``` + +--- + +## 🎯 Best Practices + +### 1. **Use SSH for Authentication** +- More secure than HTTP +- No need to enter password every time + +### 2. **Protect Main Branch** +- Require pull requests +- Require code reviews +- Prevent force pushes + +### 3. **Use Meaningful Commit Messages** +```bash +# Good +git commit -m "feat: add user authentication endpoint" +git commit -m "fix: resolve quiz submission bug" +git commit -m "docs: update API documentation" + +# Bad +git commit -m "update" +git commit -m "fix bug" +git commit -m "changes" +``` + +### 4. **Create Feature Branches** +```bash +# Feature +git checkout -b feature/user-profile + +# Bug fix +git checkout -b fix/login-error + +# Hotfix +git checkout -b hotfix/security-patch +``` + +### 5. **Regular Commits** +- Commit often +- Keep commits focused +- One logical change per commit + +--- + +## 📚 Additional Resources + +- [Forgejo Documentation](https://forgejo.org/docs/) +- [Git Documentation](https://git-scm.com/doc) +- [GitHub Flow Guide](https://guides.github.com/introduction/flow/) + +--- + +## ✅ Quick Reference + +| Action | Command | +|--------|---------| +| Clone repo | `git clone ssh://git@192.168.1.100:2222//.git` | +| Create branch | `git checkout -b feature/name` | +| Stage changes | `git add .` | +| Commit | `git commit -m "message"` | +| Push | `git push origin branch-name` | +| Pull latest | `git pull origin main` | +| Check status | `git status` | +| View history | `git log --oneline` | +| Switch branch | `git checkout branch-name` | +| Delete branch | `git branch -d branch-name` | + +--- + +## 🎊 You're Ready! + +Now you have a fully functional Git server for your E-Learning project! + +**Next Steps**: +1. Create repositories for each component (backend, frontend-student, frontend-admin) +2. Invite team members +3. Set up branch protection +4. Start collaborating! 🚀