521 lines
13 KiB
Markdown
521 lines
13 KiB
Markdown
|
|
# 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
|