docs: Add Forgejo and development setup guides, and refactor API documentation structure.
This commit is contained in:
parent
7b1ed6f6cc
commit
a6427b2083
9 changed files with 2446 additions and 7 deletions
520
docs/api-docs/api_attachments_examples.md
Normal file
520
docs/api-docs/api_attachments_examples.md
Normal file
|
|
@ -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: <slides.pdf>
|
||||
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": "<p>เนื้อหาบทเรียน...</p>",
|
||||
"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
|
||||
|
||||
<binary file data>
|
||||
```
|
||||
|
||||
#### 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 (
|
||||
<div className="attachments-list">
|
||||
<h3>📎 ไฟล์แนบ ({attachments.length})</h3>
|
||||
|
||||
{attachments.map(attachment => (
|
||||
<div key={attachment.id} className="attachment-item">
|
||||
<div className="file-icon">
|
||||
{getFileIcon(attachment.mime_type)}
|
||||
</div>
|
||||
|
||||
<div className="file-info">
|
||||
<div className="file-name">{attachment.file_name}</div>
|
||||
<div className="file-description">{attachment.description}</div>
|
||||
<div className="file-size">{formatFileSize(attachment.file_size)}</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={attachment.download_url}
|
||||
download
|
||||
className="download-button"
|
||||
>
|
||||
⬇ ดาวน์โหลด
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<DragDropContext onDragEnd={handleDragEnd}>
|
||||
<Droppable droppableId="attachments">
|
||||
{(provided) => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{items.map((item, index) => (
|
||||
<Draggable key={item.id} draggableId={String(item.id)} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
{item.file_name}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
318
docs/api-docs/api_endpoints.md
Normal file
318
docs/api-docs/api_endpoints.md
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
# API Endpoints Reference
|
||||
|
||||
## Base URL
|
||||
```
|
||||
Development: http://localhost:3000/api
|
||||
Production: https://api.elearning.com/api
|
||||
```
|
||||
|
||||
## Authentication
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Authentication & User Management
|
||||
|
||||
### Authentication
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| POST | `/auth/register` | - | Register new user |
|
||||
| POST | `/auth/login` | - | Login |
|
||||
| POST | `/auth/logout` | ✅ | Logout |
|
||||
| POST | `/auth/refresh` | ✅ | Refresh token |
|
||||
| POST | `/auth/password/reset-request` | - | Request password reset |
|
||||
| POST | `/auth/password/reset` | - | Reset password |
|
||||
|
||||
### User Profile
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/users/me` | ✅ | Get current user profile |
|
||||
| PUT | `/users/me` | ✅ | Update profile |
|
||||
| PUT | `/users/me/password` | ✅ | Change password |
|
||||
|
||||
### User Management (Admin)
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/admin/users` | 🔒 Admin | List all users |
|
||||
| GET | `/admin/users/:userId` | 🔒 Admin | Get user by ID |
|
||||
| PUT | `/admin/users/:userId/role` | 🔒 Admin | Update user role |
|
||||
| DELETE | `/admin/users/:userId` | 🔒 Admin | Deactivate user |
|
||||
|
||||
---
|
||||
|
||||
## 2. Categories
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/categories` | - | List categories |
|
||||
| GET | `/categories/:categoryId` | - | Get category by ID |
|
||||
| POST | `/admin/categories` | 🔒 Admin | Create category |
|
||||
| PUT | `/admin/categories/:categoryId` | 🔒 Admin | Update category |
|
||||
| DELETE | `/admin/categories/:categoryId` | 🔒 Admin | Delete category |
|
||||
|
||||
---
|
||||
|
||||
## 3. Courses
|
||||
|
||||
### Public Endpoints
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/courses` | - | List courses (with filters) |
|
||||
| GET | `/courses/:courseId` | - | Get course details |
|
||||
|
||||
### Student Endpoints
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| 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 (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
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| POST | `/instructor/courses` | 👨🏫 Instructor | Create course |
|
||||
| GET | `/instructor/courses` | 👨🏫 Instructor | Get my courses |
|
||||
| GET | `/instructor/courses/:courseId` | 👨🏫 Instructor | Get course details |
|
||||
| PUT | `/instructor/courses/:courseId` | 👨🏫 Instructor | Update course |
|
||||
| DELETE | `/instructor/courses/:courseId` | 👨🏫 Instructor | Delete course |
|
||||
| POST | `/instructor/courses/:courseId/submit` | 👨🏫 Instructor | Submit for approval |
|
||||
|
||||
---
|
||||
|
||||
## 4. Chapters & Lessons
|
||||
|
||||
### Chapters
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| POST | `/instructor/courses/:courseId/chapters` | 👨🏫 Instructor | Create chapter |
|
||||
| PUT | `/instructor/courses/:courseId/chapters/:chapterId` | 👨🏫 Instructor | Update chapter |
|
||||
| DELETE | `/instructor/courses/:courseId/chapters/:chapterId` | 👨🏫 Instructor | Delete chapter |
|
||||
| PUT | `/instructor/courses/:courseId/chapters/reorder` | 👨🏫 Instructor | Reorder chapters |
|
||||
|
||||
### Lessons
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Quizzes & Assessments
|
||||
|
||||
### Quiz Management (Instructor)
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| POST | `/instructor/courses/:courseId/lessons/:lessonId/quiz` | 👨🏫 Instructor | Create quiz |
|
||||
| PUT | `/instructor/quizzes/:quizId` | 👨🏫 Instructor | Update quiz |
|
||||
| DELETE | `/instructor/quizzes/:quizId` | 👨🏫 Instructor | Delete quiz |
|
||||
| POST | `/instructor/quizzes/:quizId/questions` | 👨🏫 Instructor | Add question |
|
||||
| PUT | `/instructor/quizzes/:quizId/questions/:questionId` | 👨🏫 Instructor | Update question |
|
||||
| DELETE | `/instructor/quizzes/:quizId/questions/:questionId` | 👨🏫 Instructor | Delete question |
|
||||
|
||||
### Taking Quizzes (Student)
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/students/quizzes/:quizId` | 👨🎓 Student | Get quiz |
|
||||
| POST | `/students/quizzes/:quizId/submit` | 👨🎓 Student | Submit quiz |
|
||||
| GET | `/students/quizzes/:quizId/attempts` | 👨🎓 Student | Get quiz results |
|
||||
| GET | `/students/quizzes/:quizId/attempts/:attemptId` | 👨🎓 Student | Get specific attempt |
|
||||
|
||||
---
|
||||
|
||||
## 6. Announcements
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/courses/:courseId/announcements` | ✅ | Get course announcements |
|
||||
| GET | `/courses/:courseId/announcements/:announcementId` | ✅ | Get announcement details |
|
||||
| POST | `/instructor/courses/:courseId/announcements` | 👨🏫 Instructor | Create announcement |
|
||||
| PUT | `/instructor/courses/:courseId/announcements/:announcementId` | 👨🏫 Instructor | Update announcement |
|
||||
| DELETE | `/instructor/courses/:courseId/announcements/:announcementId` | 👨🏫 Instructor | Delete announcement |
|
||||
| GET | `/announcements/:announcementId/attachments/:attachmentId/download` | ✅ | Download attachment |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reports & Analytics
|
||||
|
||||
### Student Reports
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/students/progress` | 👨🎓 Student | Get my overall progress |
|
||||
| GET | `/students/courses/:courseId/progress` | 👨🎓 Student | Get course progress |
|
||||
| GET | `/students/courses/:courseId/certificate` | 👨🎓 Student | Get certificate |
|
||||
| GET | `/students/certificates` | 👨🎓 Student | Get all certificates |
|
||||
|
||||
### Instructor Reports
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/instructor/courses/:courseId/statistics` | 👨🏫 Instructor | Get course statistics |
|
||||
| GET | `/instructor/courses/:courseId/students` | 👨🏫 Instructor | Get student list |
|
||||
| GET | `/instructor/courses/:courseId/students/export` | 👨🏫 Instructor | Export student report |
|
||||
| GET | `/instructor/dashboard` | 👨🏫 Instructor | Get instructor dashboard |
|
||||
|
||||
### Admin Dashboard
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/admin/statistics` | 🔒 Admin | Get system statistics |
|
||||
| GET | `/admin/reports/revenue` | 🔒 Admin | Get revenue report |
|
||||
| GET | `/admin/reports/users` | 🔒 Admin | Get user growth report |
|
||||
| GET | `/admin/reports/courses` | 🔒 Admin | Get course statistics |
|
||||
|
||||
---
|
||||
|
||||
## 8. Orders & Payments
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| POST | `/orders` | 👨🎓 Student | Create order |
|
||||
| GET | `/orders` | ✅ | Get my orders |
|
||||
| GET | `/orders/:orderId` | ✅ | Get order details |
|
||||
| POST | `/orders/:orderId/payment` | 👨🎓 Student | Create payment |
|
||||
| GET | `/orders/:orderId/payment` | ✅ | Get payment status |
|
||||
| POST | `/webhooks/payment/:provider` | - | Payment webhook |
|
||||
|
||||
---
|
||||
|
||||
## 9. Instructor Earnings & Withdrawals
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/instructor/balance` | 👨🏫 Instructor | Get balance |
|
||||
| GET | `/instructor/earnings` | 👨🏫 Instructor | Get earnings history |
|
||||
| POST | `/instructor/withdrawals` | 👨🏫 Instructor | Request withdrawal |
|
||||
| GET | `/instructor/withdrawals` | 👨🏫 Instructor | Get withdrawal history |
|
||||
| GET | `/instructor/withdrawals/:withdrawalId` | 👨🏫 Instructor | Get withdrawal details |
|
||||
|
||||
### Admin - Withdrawal Management
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/admin/withdrawals` | 🔒 Admin | Get all withdrawal requests |
|
||||
| POST | `/admin/withdrawals/:withdrawalId/approve` | 🔒 Admin | Approve withdrawal |
|
||||
| POST | `/admin/withdrawals/:withdrawalId/reject` | 🔒 Admin | Reject withdrawal |
|
||||
|
||||
---
|
||||
|
||||
## 10. Admin - Course Approval
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/admin/courses/pending` | 🔒 Admin | Get pending courses |
|
||||
| GET | `/admin/courses/:courseId` | 🔒 Admin | Get course details |
|
||||
| POST | `/admin/courses/:courseId/approve` | 🔒 Admin | Approve course |
|
||||
| POST | `/admin/courses/:courseId/reject` | 🔒 Admin | Reject course |
|
||||
|
||||
---
|
||||
|
||||
## Query Parameters
|
||||
|
||||
### Common Query Parameters
|
||||
| Parameter | Type | Description | Example |
|
||||
|-----------|------|-------------|---------|
|
||||
| `page` | integer | Page number (default: 1) | `?page=2` |
|
||||
| `limit` | integer | Items per page (default: 20) | `?limit=50` |
|
||||
| `sort` | string | Sort field | `?sort=created_at` |
|
||||
| `order` | string | Sort order (asc/desc) | `?order=desc` |
|
||||
| `search` | string | Search query | `?search=python` |
|
||||
| `lang` | string | Language (th/en) | `?lang=th` |
|
||||
|
||||
### Course Filters
|
||||
| Parameter | Type | Description | Example |
|
||||
|-----------|------|-------------|---------|
|
||||
| `category` | integer | Category ID | `?category=1` |
|
||||
| `is_free` | boolean | Free courses only | `?is_free=true` |
|
||||
| `price_min` | decimal | Minimum price | `?price_min=0` |
|
||||
| `price_max` | decimal | Maximum price | `?price_max=1000` |
|
||||
| `status` | string | Course status | `?status=approved` |
|
||||
| `instructor` | integer | Instructor ID | `?instructor=5` |
|
||||
|
||||
---
|
||||
|
||||
## Response Codes
|
||||
|
||||
| Code | Status | Description |
|
||||
|------|--------|-------------|
|
||||
| 200 | OK | Success |
|
||||
| 201 | Created | Resource created |
|
||||
| 204 | No Content | Success (no response body) |
|
||||
| 400 | Bad Request | Validation error |
|
||||
| 401 | Unauthorized | Invalid/missing token |
|
||||
| 403 | Forbidden | Insufficient permissions |
|
||||
| 404 | Not Found | Resource not found |
|
||||
| 409 | Conflict | Duplicate entry |
|
||||
| 422 | Unprocessable Entity | Business logic error |
|
||||
| 429 | Too Many Requests | Rate limit exceeded |
|
||||
| 500 | Internal Server Error | Server error |
|
||||
|
||||
---
|
||||
|
||||
## Error Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "Human readable message",
|
||||
"details": [
|
||||
{
|
||||
"field": "email",
|
||||
"message": "Email is required"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission Legend
|
||||
|
||||
- `-` = Public (no authentication required)
|
||||
- ✅ = Authenticated (any logged-in user)
|
||||
- 👨🎓 Student = Student role required
|
||||
- 👨🏫 Instructor = Instructor role required
|
||||
- 🔒 Admin = Admin role required
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
| Endpoint Type | Limit |
|
||||
|--------------|-------|
|
||||
| Authentication | 5 requests/minute |
|
||||
| Public | 100 requests/minute |
|
||||
| Authenticated | 1000 requests/hour |
|
||||
|
||||
---
|
||||
|
||||
## Total Endpoints: **93+**
|
||||
|
||||
- Authentication: 6
|
||||
- User Management: 4
|
||||
- Categories: 5
|
||||
- Courses: 12
|
||||
- Chapters & Lessons: 16 (-1)
|
||||
- Quizzes: 10
|
||||
- Announcements: 6
|
||||
- Reports: 11
|
||||
- Orders & Payments: 7
|
||||
- Earnings & Withdrawals: 8
|
||||
- Admin: 8
|
||||
306
docs/api-docs/api_lesson_creation_simplified.md
Normal file
306
docs/api-docs/api_lesson_creation_simplified.md
Normal file
|
|
@ -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 <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": {
|
||||
"th": "บทที่ 1: แนะนำ",
|
||||
"en": "Lesson 1: Introduction"
|
||||
},
|
||||
"content": {
|
||||
"th": "<p>เนื้อหาบทเรียน...</p>",
|
||||
"en": "<p>Lesson content...</p>"
|
||||
},
|
||||
"type": "text",
|
||||
"sort_order": 1,
|
||||
"is_sequential": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Video Lesson (Video Only)
|
||||
|
||||
```http
|
||||
POST /api/instructor/courses/1/chapters/1/lessons
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
title_th: "บทที่ 1: แนะนำ Python"
|
||||
title_en: "Lesson 1: Introduction to Python"
|
||||
content_th: "<p>Python เป็นภาษา...</p>"
|
||||
content_en: "<p>Python is a...</p>"
|
||||
type: "video"
|
||||
sort_order: 1
|
||||
video: <lesson-video.mp4>
|
||||
```
|
||||
|
||||
**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 <token>
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
# Lesson Info
|
||||
title_th: "บทที่ 1: แนะนำ Python"
|
||||
title_en: "Lesson 1: Introduction to Python"
|
||||
content_th: "<p>Python เป็นภาษา...</p>"
|
||||
content_en: "<p>Python is a...</p>"
|
||||
type: "video"
|
||||
sort_order: 1
|
||||
is_sequential: true
|
||||
|
||||
# Video
|
||||
video: <lesson-video.mp4>
|
||||
|
||||
# Attachments
|
||||
attachments[]: <slides.pdf>
|
||||
attachments[]: <code.zip>
|
||||
attachments[]: <exercises.docx>
|
||||
|
||||
# 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": "<p>Python เป็นภาษา...</p>",
|
||||
"en": "<p>Python is a...</p>"
|
||||
},
|
||||
"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 <token>
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
title_th: "บทที่ 2: เอกสารประกอบ"
|
||||
title_en: "Lesson 2: Reading Materials"
|
||||
type: "pdf"
|
||||
sort_order: 2
|
||||
|
||||
attachments[]: <main-document.pdf>
|
||||
attachments[]: <supplementary.pdf>
|
||||
|
||||
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 <token>
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
attachments[]: <additional-file.pdf>
|
||||
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: '<p>เนื้อหา...</p>',
|
||||
en: '<p>Content...</p>'
|
||||
},
|
||||
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 ✨
|
||||
438
docs/api-docs/api_prerequisites_examples.md
Normal file
438
docs/api-docs/api_prerequisites_examples.md
Normal file
|
|
@ -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": "<p>ทดสอบความเข้าใจ</p>",
|
||||
"en": "<p>Test your understanding</p>"
|
||||
},
|
||||
"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 (
|
||||
<div className="lesson-item locked">
|
||||
<LockIcon />
|
||||
<span className="lesson-title">{lesson.title}</span>
|
||||
|
||||
{lesson.lock_reason === 'incomplete_prerequisites' && (
|
||||
<Tooltip>
|
||||
กรุณาเรียนบทเรียนก่อนหน้าให้เสร็จก่อน
|
||||
<ul>
|
||||
{lesson.required_lessons.map(id => (
|
||||
<li key={id}>Lesson {id}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{lesson.lock_reason === 'quiz_not_passed' && (
|
||||
<Tooltip>
|
||||
กรุณาทำแบบทดสอบให้ผ่านก่อน (ต้องได้ 70% ขึ้นไป)
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={`/lessons/${lesson.id}`} className="lesson-item">
|
||||
{lesson.is_completed ? <CheckIcon /> : <PlayIcon />}
|
||||
<span>{lesson.title}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
1193
docs/api-docs/api_usage_examples.md
Normal file
1193
docs/api-docs/api_usage_examples.md
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue