feat: Document video progress tracking APIs and update the main API endpoint list.
This commit is contained in:
parent
4b8f524eac
commit
abac1c2d8c
5 changed files with 230 additions and 3 deletions
|
|
@ -153,6 +153,10 @@ Table lesson_progress {
|
||||||
lesson_id int [not null, ref: > lessons.id]
|
lesson_id int [not null, ref: > lessons.id]
|
||||||
is_completed boolean [not null, default: false]
|
is_completed boolean [not null, default: false]
|
||||||
completed_at datetime
|
completed_at datetime
|
||||||
|
video_progress_seconds int [default: 0, note: 'current playback position in seconds']
|
||||||
|
video_duration_seconds int [note: 'total video duration in seconds']
|
||||||
|
video_progress_percentage decimal(5,2) [note: 'calculated: (progress/duration)*100']
|
||||||
|
last_watched_at datetime [note: 'last time user watched this video']
|
||||||
created_at datetime [not null, default: `now()`]
|
created_at datetime [not null, default: `now()`]
|
||||||
updated_at datetime [not null, default: `now()`]
|
updated_at datetime [not null, default: `now()`]
|
||||||
|
|
||||||
|
|
@ -160,6 +164,7 @@ Table lesson_progress {
|
||||||
(user_id, lesson_id) [unique]
|
(user_id, lesson_id) [unique]
|
||||||
user_id
|
user_id
|
||||||
lesson_id
|
lesson_id
|
||||||
|
last_watched_at
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,6 +70,8 @@ Authorization: Bearer <token>
|
||||||
| GET | `/students/courses/:courseId/learn` | 👨🎓 Student | Get course learning page (with lock status) |
|
| 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` | 👨🎓 Student | Get lesson content (checks prerequisites) |
|
||||||
| GET | `/students/courses/:courseId/lessons/:lessonId/access-check` | 👨🎓 Student | Check lesson access without loading content |
|
| GET | `/students/courses/:courseId/lessons/:lessonId/access-check` | 👨🎓 Student | Check lesson access without loading content |
|
||||||
|
| POST | `/students/lessons/:lessonId/progress` | 👨🎓 Student | Save video progress |
|
||||||
|
| GET | `/students/lessons/:lessonId/progress` | 👨🎓 Student | Get video progress |
|
||||||
| POST | `/students/courses/:courseId/lessons/:lessonId/complete` | 👨🎓 Student | Mark lesson as complete |
|
| POST | `/students/courses/:courseId/lessons/:lessonId/complete` | 👨🎓 Student | Mark lesson as complete |
|
||||||
|
|
||||||
### Instructor Endpoints
|
### Instructor Endpoints
|
||||||
|
|
@ -303,13 +305,13 @@ Authorization: Bearer <token>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Total Endpoints: **93+**
|
## Total Endpoints: **95+**
|
||||||
|
|
||||||
- Authentication: 6
|
- Authentication: 6
|
||||||
- User Management: 4
|
- User Management: 4
|
||||||
- Categories: 5
|
- Categories: 5
|
||||||
- Courses: 12
|
- Courses: 14 (+2)
|
||||||
- Chapters & Lessons: 16 (-1)
|
- Chapters & Lessons: 16
|
||||||
- Quizzes: 10
|
- Quizzes: 10
|
||||||
- Announcements: 6
|
- Announcements: 6
|
||||||
- Reports: 11
|
- Reports: 11
|
||||||
|
|
|
||||||
191
docs/api-docs/api_video_progress.md
Normal file
191
docs/api-docs/api_video_progress.md
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
# Video Progress Tracking - API Usage Examples
|
||||||
|
|
||||||
|
## Save Video Progress
|
||||||
|
|
||||||
|
### POST `/students/lessons/:lessonId/progress`
|
||||||
|
|
||||||
|
**Purpose**: บันทึกตำแหน่งการดูวีดีโอ (เรียกทุก 5 วินาที)
|
||||||
|
|
||||||
|
#### Request:
|
||||||
|
```http
|
||||||
|
POST /api/students/lessons/5/progress
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"progress_seconds": 450,
|
||||||
|
"duration_seconds": 900
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response 200:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lesson_id": 5,
|
||||||
|
"video_progress_seconds": 450,
|
||||||
|
"video_duration_seconds": 900,
|
||||||
|
"video_progress_percentage": 50.00,
|
||||||
|
"is_completed": false,
|
||||||
|
"last_watched_at": "2024-12-24T14:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Auto-Complete (90%+):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"progress_seconds": 810,
|
||||||
|
"duration_seconds": 900
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lesson_id": 5,
|
||||||
|
"video_progress_seconds": 810,
|
||||||
|
"video_duration_seconds": 900,
|
||||||
|
"video_progress_percentage": 90.00,
|
||||||
|
"is_completed": true,
|
||||||
|
"completed_at": "2024-12-24T14:05:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Get Video Progress
|
||||||
|
|
||||||
|
### GET `/students/lessons/:lessonId/progress`
|
||||||
|
|
||||||
|
**Purpose**: ดึงตำแหน่งการดูวีดีโอเพื่อเล่นต่อ
|
||||||
|
|
||||||
|
#### Request:
|
||||||
|
```http
|
||||||
|
GET /api/students/lessons/5/progress
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response 200 (Has Progress):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lesson_id": 5,
|
||||||
|
"video_progress_seconds": 450,
|
||||||
|
"video_duration_seconds": 900,
|
||||||
|
"video_progress_percentage": 50.00,
|
||||||
|
"is_completed": false,
|
||||||
|
"last_watched_at": "2024-12-24T13:30:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response 200 (No Progress):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lesson_id": 5,
|
||||||
|
"video_progress_seconds": 0,
|
||||||
|
"video_duration_seconds": null,
|
||||||
|
"video_progress_percentage": 0,
|
||||||
|
"is_completed": false,
|
||||||
|
"last_watched_at": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Frontend Integration
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const VideoPlayer = ({ lessonId, videoUrl }) => {
|
||||||
|
const videoRef = useRef(null);
|
||||||
|
|
||||||
|
// Load saved progress
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadProgress() {
|
||||||
|
const res = await fetch(`/api/students/lessons/${lessonId}/progress`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.video_progress_seconds > 0) {
|
||||||
|
videoRef.current.currentTime = data.video_progress_seconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadProgress();
|
||||||
|
}, [lessonId]);
|
||||||
|
|
||||||
|
// Save progress every 5 seconds
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (videoRef.current && !videoRef.current.paused) {
|
||||||
|
saveProgress(
|
||||||
|
Math.floor(videoRef.current.currentTime),
|
||||||
|
Math.floor(videoRef.current.duration)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function saveProgress(currentTime, duration) {
|
||||||
|
await fetch(`/api/students/lessons/${lessonId}/progress`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
progress_seconds: currentTime,
|
||||||
|
duration_seconds: duration
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return <video ref={videoRef} src={videoUrl} controls />;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modified Endpoints
|
||||||
|
|
||||||
|
### GET `/students/courses/:courseId/learn`
|
||||||
|
|
||||||
|
**Added**: `video_progress_percentage` และ `resume_from` ในแต่ละ lesson
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Lesson 1",
|
||||||
|
"type": "video",
|
||||||
|
"is_completed": true,
|
||||||
|
"video_progress_percentage": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Lesson 2",
|
||||||
|
"type": "video",
|
||||||
|
"is_completed": false,
|
||||||
|
"video_progress_percentage": 50,
|
||||||
|
"resume_from": 450
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET `/students/courses/:courseId/progress`
|
||||||
|
|
||||||
|
**Added**: `recently_watched` section
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"course_id": 1,
|
||||||
|
"progress_percentage": 30,
|
||||||
|
"recently_watched": [
|
||||||
|
{
|
||||||
|
"lesson_id": 2,
|
||||||
|
"title": "Lesson 2",
|
||||||
|
"video_progress_percentage": 50,
|
||||||
|
"last_watched_at": "2024-12-24T14:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
| 8 | [เข้าถึงข้อมูลข้ามหลักสูตร](#8-เข้าถึงข้อมูลข้ามหลักสูตร) | 🔴 สูง | Middleware ตรวจสอบ ownership |
|
| 8 | [เข้าถึงข้อมูลข้ามหลักสูตร](#8-เข้าถึงข้อมูลข้ามหลักสูตร) | 🔴 สูง | Middleware ตรวจสอบ ownership |
|
||||||
| 9 | [เพิ่มเนื้อหาหลังจบหลักสูตร](#9-เพิ่มเนื้อหาหลังจบหลักสูตร) | 🟡 ปานกลาง | เนื้อหาใหม่ = เสริม (ไม่บังคับ) |
|
| 9 | [เพิ่มเนื้อหาหลังจบหลักสูตร](#9-เพิ่มเนื้อหาหลังจบหลักสูตร) | 🟡 ปานกลาง | เนื้อหาใหม่ = เสริม (ไม่บังคับ) |
|
||||||
| 10 | [Maintenance Mode](#10-maintenance-mode) | 🟡 ปานกลาง | Auto-save + แจ้งเตือนล่วงหน้า |
|
| 10 | [Maintenance Mode](#10-maintenance-mode) | 🟡 ปานกลาง | Auto-save + แจ้งเตือนล่วงหน้า |
|
||||||
|
| 11 | [Video Progress Tracking](#11-video-progress-tracking) | 🟡 ปานกลาง | Save ทุก 5 วินาที + last_watched_at |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -211,6 +212,34 @@ if (originalProgress === 100 && certificateIssued) {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 11. Video Progress Tracking
|
||||||
|
|
||||||
|
**ปัญหา**: Student ดูวีดีโอค้างไว้หลายแท็บ หรือปิดเบราว์เซอร์กลางคัน
|
||||||
|
|
||||||
|
**วิธีแก้**:
|
||||||
|
```javascript
|
||||||
|
// 1. Save progress ทุก 5 วินาที
|
||||||
|
// 2. ใช้ last_watched_at เป็นตัวตัดสิน
|
||||||
|
// 3. Auto-complete เมื่อดู 90%+
|
||||||
|
// 4. Resume จากตำแหน่งล่าสุด
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points**:
|
||||||
|
- ✅ Save progress ทุก 5 วินาที (ไม่บ่อยเกินไป)
|
||||||
|
- ✅ ใช้ `last_watched_at` ตัดสินว่า session ไหนล่าสุด
|
||||||
|
- ✅ Auto-complete เมื่อดู ≥ 90%
|
||||||
|
- ✅ Resume จากตำแหน่งที่บันทึกล่าสุด
|
||||||
|
- ❌ ไม่ save เมื่อ video pause หรือ buffering
|
||||||
|
|
||||||
|
**Concurrent Updates**:
|
||||||
|
```javascript
|
||||||
|
// แท็บ A: บันทึกที่ 5:00 (14:00:00)
|
||||||
|
// แท็บ B: บันทึกที่ 3:00 (14:00:05)
|
||||||
|
// → ใช้แท็บ B (last_watched_at ใหม่กว่า)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🔒 หลักการสำคัญ (Best Practices)
|
## 🔒 หลักการสำคัญ (Best Practices)
|
||||||
|
|
||||||
### 1. Soft Delete > Hard Delete
|
### 1. Soft Delete > Hard Delete
|
||||||
Loading…
Add table
Add a link
Reference in a new issue