feat: Add comprehensive API usage examples for v3, ERD, data dictionary, and course cloning documentation.
This commit is contained in:
parent
6c4d10963e
commit
a1f7ee057d
9 changed files with 1661 additions and 10 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
BIN
docs/.DS_Store
vendored
BIN
docs/.DS_Store
vendored
Binary file not shown.
BIN
docs/api-docs/.DS_Store
vendored
Normal file
BIN
docs/api-docs/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
docs/api-docs/ER/.DS_Store
vendored
Normal file
BIN
docs/api-docs/ER/.DS_Store
vendored
Normal file
Binary file not shown.
507
docs/api-docs/ER/ERD_v3_improved.txt
Normal file
507
docs/api-docs/ER/ERD_v3_improved.txt
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
// E-Learning Platform - Enhanced ERD v3.0
|
||||
// Major improvements:
|
||||
// 1. Separated roles table with display values
|
||||
// 2. Separated user_profiles table
|
||||
// 3. Added created_by/updated_by audit trails
|
||||
// 4. Made updated_at/updated_by nullable
|
||||
// 5. Used ENUM for status fields
|
||||
// 6. Optimized audit trail placement
|
||||
// 7. Added announcement status and multi-instructor support
|
||||
|
||||
// ============================================
|
||||
// CORE TABLES
|
||||
// ============================================
|
||||
|
||||
Table roles {
|
||||
id int [pk, increment]
|
||||
code varchar [unique, not null, note: 'ADMIN | INSTRUCTOR | STUDENT']
|
||||
name jsonb [not null, note: '{"th": "ผู้ดูแลระบบ", "en": "Administrator"}']
|
||||
description jsonb [note: 'role description']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
code [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Table users {
|
||||
id int [pk, increment]
|
||||
username varchar [unique, not null]
|
||||
email varchar [unique, not null]
|
||||
password varchar [not null]
|
||||
role_id int [not null, ref: > roles.id]
|
||||
email_verified_at datetime
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
indexes {
|
||||
username [unique]
|
||||
email [unique]
|
||||
role_id
|
||||
}
|
||||
}
|
||||
|
||||
Table user_profiles {
|
||||
id int [pk, increment]
|
||||
user_id int [unique, not null, ref: > users.id]
|
||||
prefix varchar [note: 'Mr. | Mrs. | Ms. | Dr. etc.']
|
||||
first_name varchar [not null]
|
||||
last_name varchar [not null]
|
||||
phone varchar
|
||||
avatar_url varchar
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
updated_by int [ref: > users.id]
|
||||
|
||||
indexes {
|
||||
user_id [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Table categories {
|
||||
id int [pk, increment]
|
||||
name jsonb [not null, note: 'multi-language category name']
|
||||
slug varchar [unique, not null]
|
||||
description jsonb
|
||||
icon varchar [note: 'icon identifier']
|
||||
sort_order int [not null, default: 0]
|
||||
is_active boolean [not null, default: true]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
created_by int [not null, ref: > users.id]
|
||||
updated_at datetime
|
||||
updated_by int [ref: > users.id]
|
||||
|
||||
indexes {
|
||||
slug [unique]
|
||||
is_active
|
||||
sort_order
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// COURSE STRUCTURE
|
||||
// ============================================
|
||||
|
||||
Table courses {
|
||||
id int [pk, increment]
|
||||
category_id int [ref: > categories.id]
|
||||
title jsonb [not null, note: '{"th": "...", "en": "..."}']
|
||||
slug varchar [unique, not null]
|
||||
description jsonb
|
||||
thumbnail_url varchar
|
||||
price decimal [not null, default: 0, note: 'must be >= 0']
|
||||
is_free boolean [not null, default: false]
|
||||
have_certificate boolean [not null, default: false, note: 'issue certificate upon completion']
|
||||
status varchar [not null, default: 'DRAFT', note: 'ENUM: DRAFT | PENDING | APPROVED | REJECTED | ARCHIVED']
|
||||
approved_by int [ref: > users.id, note: 'admin user id']
|
||||
approved_at datetime
|
||||
rejection_reason varchar
|
||||
created_at datetime [not null, default: `now()`]
|
||||
created_by int [not null, ref: > users.id, note: 'primary instructor']
|
||||
updated_at datetime
|
||||
updated_by int [ref: > users.id]
|
||||
|
||||
indexes {
|
||||
category_id
|
||||
slug [unique]
|
||||
status
|
||||
created_by
|
||||
(status, is_free)
|
||||
(category_id, status)
|
||||
}
|
||||
}
|
||||
|
||||
Table course_instructors {
|
||||
id int [pk, increment]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
user_id int [not null, ref: > users.id]
|
||||
is_primary boolean [not null, default: false, note: 'primary instructor']
|
||||
joined_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
(course_id, user_id) [unique]
|
||||
course_id
|
||||
user_id
|
||||
is_primary
|
||||
}
|
||||
}
|
||||
|
||||
Table chapters {
|
||||
id int [pk, increment]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
title jsonb [not null, note: 'multi-language']
|
||||
description jsonb
|
||||
sort_order int [not null, default: 0, note: 'must be >= 0']
|
||||
is_published boolean [not null, default: false]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
Note: 'No created_by/updated_by - use course audit trail'
|
||||
|
||||
indexes {
|
||||
course_id
|
||||
(course_id, sort_order)
|
||||
}
|
||||
}
|
||||
|
||||
Table lessons {
|
||||
id int [pk, increment]
|
||||
chapter_id int [not null, ref: > chapters.id]
|
||||
title jsonb [not null, note: 'multi-language']
|
||||
content jsonb [note: 'multi-language lesson content']
|
||||
type varchar [not null, note: 'ENUM: VIDEO QUIZ']
|
||||
duration_minutes int [note: 'estimated duration']
|
||||
sort_order int [not null, default: 0, note: 'must be >= 0']
|
||||
is_sequential boolean [not null, default: true]
|
||||
prerequisite_lesson_ids jsonb [note: 'array of lesson IDs [1, 2, 3]']
|
||||
require_pass_quiz boolean [default: false]
|
||||
is_published boolean [not null, default: false]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
Note: 'No created_by/updated_by - use course audit trail'
|
||||
|
||||
indexes {
|
||||
chapter_id
|
||||
(chapter_id, sort_order)
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// QUIZ SYSTEM
|
||||
// ============================================
|
||||
|
||||
Table quizzes {
|
||||
id int [pk, increment]
|
||||
lesson_id int [not null, ref: > lessons.id]
|
||||
title jsonb [not null, note: 'multi-language']
|
||||
description jsonb
|
||||
passing_score int [not null, default: 60, note: 'must be 0-100']
|
||||
time_limit int [note: 'in minutes, must be > 0 if set']
|
||||
shuffle_questions boolean [not null, default: false]
|
||||
shuffle_choices boolean [not null, default: false]
|
||||
show_answers_after_completion boolean [not null, default: true, note: 'show correct answers after quiz completion']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
created_by int [not null, ref: > users.id]
|
||||
updated_at datetime
|
||||
updated_by int [ref: > users.id]
|
||||
|
||||
indexes {
|
||||
lesson_id
|
||||
}
|
||||
}
|
||||
|
||||
Table questions {
|
||||
id int [pk, increment]
|
||||
quiz_id int [not null, ref: > quizzes.id]
|
||||
question jsonb [not null, note: 'multi-language']
|
||||
explanation jsonb [note: 'answer explanation']
|
||||
question_type varchar [not null, default: 'MULTIPLE_CHOICE', note: 'ENUM: MULTIPLE_CHOICE | TRUE_FALSE | SHORT_ANSWER']
|
||||
score int [not null, default: 1, note: 'must be > 0']
|
||||
sort_order int [not null, default: 0]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
Note: 'No created_by/updated_by - saved with quiz'
|
||||
|
||||
indexes {
|
||||
quiz_id
|
||||
(quiz_id, sort_order)
|
||||
}
|
||||
}
|
||||
|
||||
Table choices {
|
||||
id int [pk, increment]
|
||||
question_id int [not null, ref: > questions.id]
|
||||
text jsonb [not null, note: 'multi-language']
|
||||
is_correct boolean [not null, default: false]
|
||||
sort_order int [not null, default: 0]
|
||||
indexes {
|
||||
question_id
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// STUDENT PROGRESS
|
||||
// ============================================
|
||||
|
||||
Table enrollments {
|
||||
id int [pk, increment]
|
||||
user_id int [not null, ref: > users.id]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
status varchar [not null, default: 'ENROLLED', note: 'ENUM: ENROLLED | IN_PROGRESS | COMPLETED | DROPPED']
|
||||
progress_percentage int [not null, default: 0, note: '0-100']
|
||||
enrolled_at datetime [not null, default: `now()`]
|
||||
started_at datetime [note: 'first lesson access']
|
||||
completed_at datetime
|
||||
last_accessed_at datetime
|
||||
|
||||
Note: 'No created_by/updated_by - system managed'
|
||||
|
||||
indexes {
|
||||
(user_id, course_id) [unique, name: 'unique_enrollment']
|
||||
user_id
|
||||
course_id
|
||||
status
|
||||
last_accessed_at
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Table certificates {
|
||||
id int [pk, increment]
|
||||
user_id int [not null, ref: > users.id]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
enrollment_id int [unique, not null, ref: > enrollments.id]
|
||||
file_path varchar [not null, note: 'S3 path to certificate PDF']
|
||||
issued_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
user_id
|
||||
course_id
|
||||
enrollment_id [unique]
|
||||
(user_id, course_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Table lesson_progress {
|
||||
id int [pk, increment]
|
||||
user_id int [not null, ref: > users.id]
|
||||
lesson_id int [not null, ref: > lessons.id]
|
||||
is_completed boolean [not null, default: false]
|
||||
completed_at datetime
|
||||
video_progress_seconds int [default: 0]
|
||||
video_duration_seconds int
|
||||
video_progress_percentage decimal(5,2)
|
||||
last_watched_at datetime
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
Note: 'No created_by/updated_by - student action only'
|
||||
|
||||
indexes {
|
||||
(user_id, lesson_id) [unique]
|
||||
user_id
|
||||
lesson_id
|
||||
last_watched_at
|
||||
}
|
||||
}
|
||||
|
||||
Table quiz_attempts {
|
||||
id int [pk, increment]
|
||||
user_id int [not null, ref: > users.id]
|
||||
quiz_id int [not null, ref: > quizzes.id]
|
||||
score int [not null, default: 0, note: '0-100']
|
||||
total_questions int [not null]
|
||||
correct_answers int [not null, default: 0]
|
||||
is_passed boolean [not null, default: false]
|
||||
attempt_number int [not null, default: 1]
|
||||
answers jsonb [note: 'student answers for review']
|
||||
started_at datetime [not null, default: `now()`]
|
||||
completed_at datetime
|
||||
|
||||
Note: 'No updated_at - attempts are immutable after completion'
|
||||
|
||||
indexes {
|
||||
user_id
|
||||
quiz_id
|
||||
(user_id, quiz_id)
|
||||
(user_id, quiz_id, attempt_number)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// COMMUNICATION
|
||||
// ============================================
|
||||
|
||||
Table announcements {
|
||||
id int [pk, increment]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
title jsonb [not null, note: 'multi-language']
|
||||
content jsonb [not null, note: 'multi-language']
|
||||
status varchar [not null, default: 'DRAFT', note: 'ENUM: DRAFT | PUBLISHED | ARCHIVED']
|
||||
is_pinned boolean [not null, default: false]
|
||||
published_at datetime [note: 'scheduled publish date']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
created_by int [not null, ref: > users.id]
|
||||
updated_at datetime
|
||||
updated_by int [ref: > users.id]
|
||||
|
||||
indexes {
|
||||
course_id
|
||||
created_by
|
||||
status
|
||||
(course_id, status, is_pinned, published_at)
|
||||
}
|
||||
}
|
||||
|
||||
Table announcement_attachments {
|
||||
id int [pk, increment]
|
||||
announcement_id int [not null, ref: > announcements.id]
|
||||
file_name varchar [not null]
|
||||
file_path varchar [not null, note: 'S3 key']
|
||||
file_size int [not null, note: 'bytes']
|
||||
mime_type varchar [not null]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
|
||||
Note: 'No updated_at - attachments are immutable'
|
||||
|
||||
indexes {
|
||||
announcement_id
|
||||
}
|
||||
}
|
||||
|
||||
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']
|
||||
file_size int [not null, note: 'bytes']
|
||||
mime_type varchar [not null]
|
||||
description jsonb [note: 'multi-language']
|
||||
sort_order int [not null, default: 0]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
|
||||
Note: 'No updated_at - use lesson audit trail'
|
||||
|
||||
indexes {
|
||||
lesson_id
|
||||
(lesson_id, sort_order)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// PAYMENT SYSTEM (Future)
|
||||
// ============================================
|
||||
|
||||
Table orders {
|
||||
id int [pk, increment]
|
||||
user_id int [not null, ref: > users.id]
|
||||
total_amount decimal [not null, default: 0]
|
||||
status varchar [not null, default: 'PENDING', note: 'ENUM: PENDING | PAID | CANCELLED | REFUNDED']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
indexes {
|
||||
user_id
|
||||
status
|
||||
(user_id, status)
|
||||
}
|
||||
}
|
||||
|
||||
Table order_items {
|
||||
id int [pk, increment]
|
||||
order_id int [not null, ref: > orders.id]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
price decimal [not null]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
order_id
|
||||
course_id
|
||||
}
|
||||
}
|
||||
|
||||
Table payments {
|
||||
id int [pk, increment]
|
||||
order_id int [not null, ref: > orders.id]
|
||||
provider varchar [not null, note: 'stripe | paypal | promptpay']
|
||||
transaction_id varchar [unique]
|
||||
amount decimal [not null]
|
||||
status varchar [not null, default: 'PENDING', note: 'ENUM: PENDING | SUCCESS | FAILED']
|
||||
paid_at datetime
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
indexes {
|
||||
order_id
|
||||
transaction_id [unique]
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
Table instructor_balances {
|
||||
id int [pk, increment]
|
||||
instructor_id int [not null, unique, ref: > users.id]
|
||||
available_amount decimal [not null, default: 0]
|
||||
withdrawn_amount decimal [not null, default: 0]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
|
||||
indexes {
|
||||
instructor_id [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Table withdrawal_requests {
|
||||
id int [pk, increment]
|
||||
instructor_id int [not null, ref: > users.id]
|
||||
amount decimal [not null]
|
||||
status varchar [not null, default: 'PENDING', note: 'ENUM: PENDING | APPROVED | REJECTED | PAID']
|
||||
approved_by int [ref: > users.id]
|
||||
approved_at datetime
|
||||
rejected_reason varchar
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime
|
||||
updated_by int [ref: > users.id]
|
||||
|
||||
indexes {
|
||||
instructor_id
|
||||
status
|
||||
(instructor_id, status)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// NOTES & BEST PRACTICES
|
||||
// ============================================
|
||||
|
||||
// 1. ENUM Status Values:
|
||||
// - courses: DRAFT | PENDING | APPROVED | REJECTED | ARCHIVED
|
||||
// - enrollments: ENROLLED | IN_PROGRESS | COMPLETED | DROPPED
|
||||
// - announcements: DRAFT | PUBLISHED | ARCHIVED
|
||||
// - orders: PENDING | PAID | CANCELLED | REFUNDED
|
||||
// - payments: PENDING | SUCCESS | FAILED
|
||||
// - withdrawals: PENDING | APPROVED | REJECTED | PAID
|
||||
// - score_policy: HIGHEST | LATEST | FIRST | AVERAGE
|
||||
// - lesson_type: VIDEO | PDF | TEXT | QUIZ
|
||||
// - question_type: MULTIPLE_CHOICE | TRUE_FALSE | SHORT_ANSWER
|
||||
|
||||
// 2. Audit Trail Strategy:
|
||||
// - Full audit (created_by, updated_by): courses, quizzes, categories, announcements
|
||||
// - Partial audit (created_by only): user_profiles
|
||||
// - No audit: student actions (enrollments, progress, attempts)
|
||||
// - No audit: child records saved with parent (questions, choices, chapters, lessons)
|
||||
|
||||
// 3. Updated Fields:
|
||||
// - updated_at is NULL on creation, only set when actually updated
|
||||
// - updated_by is NULL on creation, only set when updated by someone
|
||||
|
||||
// 4. Multi-Instructor Support:
|
||||
// - course_instructors table allows multiple instructors per course
|
||||
// - is_primary flag identifies main instructor
|
||||
// - created_by in courses table is the original creator
|
||||
|
||||
// 5. Role System:
|
||||
// - roles table stores code (ADMIN) and display names ({"th": "ผู้ดูแลระบบ"})
|
||||
// - Allows easy translation and role management
|
||||
// - users.role_id references roles.id
|
||||
|
||||
// 6. User Profiles:
|
||||
// - Separated from users table for cleaner authentication
|
||||
// - Contains display information (name, bio, avatar)
|
||||
// - prefix field for Thai/international name prefixes
|
||||
|
||||
// 7. Announcement Status:
|
||||
// - DRAFT: not visible to students
|
||||
// - PUBLISHED: visible to enrolled students
|
||||
// - ARCHIVED: hidden but kept for history
|
||||
|
||||
// 8. Foreign Key Actions:
|
||||
// - users.role_id: RESTRICT (don't delete roles in use)
|
||||
// - courses.created_by: RESTRICT (don't delete instructors with courses)
|
||||
// - enrollments: CASCADE (delete with user/course)
|
||||
// - announcements: CASCADE (delete with course)
|
||||
// - quiz_attempts: CASCADE (delete with user)
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
table_name,column_name,data_type,size,is_nullable,is_primary_key,is_foreign_key,description
|
||||
roles,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
roles,code,VARCHAR,50,NO,NO,NO,รหัสบทบาท (ADMIN | INSTRUCTOR | STUDENT)
|
||||
roles,name,JSONB,,NO,NO,NO,ชื่อบทบาทหลายภาษา
|
||||
roles,description,JSONB,,YES,NO,NO,คำอธิบายบทบาท
|
||||
roles,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
users,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
users,username,VARCHAR,100,NO,NO,NO,ชื่อผู้ใช้สำหรับเข้าสู่ระบบ (ไม่ซ้ำ)
|
||||
users,email,VARCHAR,255,NO,NO,NO,อีเมล (ไม่ซ้ำ)
|
||||
users,password,VARCHAR,255,NO,NO,NO,รหัสผ่านที่เข้ารหัสแล้ว
|
||||
users,role_id,INTEGER,10,NO,NO,YES,อ้างอิงบทบาทผู้ใช้
|
||||
users,email_verified_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่ยืนยันอีเมล
|
||||
users,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
users,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
user_profiles,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
user_profiles,user_id,INTEGER,10,NO,NO,YES,อ้างอิงผู้ใช้ (ไม่ซ้ำ)
|
||||
user_profiles,prefix,VARCHAR,20,YES,NO,NO,"คำนำหน้าชื่อ enum(นาย, นาง, นางสาว, ดร.)"
|
||||
user_profiles,first_name,VARCHAR,100,NO,NO,NO,ชื่อจริง
|
||||
user_profiles,last_name,VARCHAR,100,NO,NO,NO,นามสกุล
|
||||
user_profiles,phone,VARCHAR,20,YES,NO,NO,เบอร์โทรศัพท์
|
||||
user_profiles,avatar_url,VARCHAR,500,YES,NO,NO,URL รูปโปรไฟล์
|
||||
user_profiles,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
user_profiles,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
user_profiles,updated_by,INTEGER,10,YES,NO,YES,ผู้ใช้ที่แก้ไขล่าสุด
|
||||
categories,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
categories,name,JSONB,,NO,NO,NO,ชื่อหมวดหมู่หลายภาษา
|
||||
categories,slug,VARCHAR,100,NO,NO,NO,ตัวระบุสำหรับ URL (ไม่ซ้ำ)
|
||||
categories,description,JSONB,,YES,NO,NO,คำอธิบายหลายภาษา
|
||||
categories,icon,VARCHAR,100,YES,NO,NO,ตัวระบุไอคอน
|
||||
categories,sort_order,INTEGER,10,NO,NO,NO,ลำดับการแสดงผล
|
||||
categories,is_active,BOOLEAN,,NO,NO,NO,สถานะเปิดใช้งาน
|
||||
categories,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
categories,created_by,INTEGER,10,NO,NO,YES,ผู้สร้าง
|
||||
categories,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
categories,updated_by,INTEGER,10,YES,NO,YES,ผู้แก้ไขล่าสุด
|
||||
courses,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
courses,category_id,INTEGER,10,YES,NO,YES,อ้างอิงหมวดหมู่
|
||||
courses,title,JSONB,,NO,NO,NO,ชื่อคอร์สหลายภาษา
|
||||
courses,slug,VARCHAR,200,NO,NO,NO,ตัวระบุสำหรับ URL (ไม่ซ้ำ)
|
||||
courses,description,JSONB,,YES,NO,NO,คำอธิบายคอร์สหลายภาษา
|
||||
courses,thumbnail_url,VARCHAR,500,YES,NO,NO,URL รูปปกคอร์ส
|
||||
courses,price,"DECIMAL(10,2)",,NO,NO,NO,ราคาคอร์ส (ต้อง >= 0)
|
||||
courses,is_free,BOOLEAN,,NO,NO,NO,คอร์สฟรี
|
||||
courses,have_certificate,BOOLEAN,,NO,NO,NO,ออกใบประกาศนียบัตรเมื่อจบ
|
||||
courses,status,VARCHAR,20,NO,NO,NO,"สถานะ enum(DRAFT, PENDING, APPROVED, REJECTED, ARCHIVED)"
|
||||
courses,approved_by,INTEGER,10,YES,NO,YES,ผู้ดูแลที่อนุมัติ
|
||||
courses,approved_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่อนุมัติ
|
||||
courses,rejection_reason,TEXT,,YES,NO,NO,เหตุผลที่ปฏิเสธ
|
||||
courses,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
courses,created_by,INTEGER,10,NO,NO,YES,ผู้สอนหลัก
|
||||
courses,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
courses,updated_by,INTEGER,10,YES,NO,YES,ผู้แก้ไขล่าสุด
|
||||
course_instructors,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
course_instructors,course_id,INTEGER,10,NO,NO,YES,อ้างอิงคอร์ส
|
||||
course_instructors,user_id,INTEGER,10,NO,NO,YES,อ้างอิงผู้สอน
|
||||
course_instructors,is_primary,BOOLEAN,,NO,NO,NO,ผู้สอนหลัก
|
||||
course_instructors,joined_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่เข้าร่วม
|
||||
chapters,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
chapters,course_id,INTEGER,10,NO,NO,YES,อ้างอิงคอร์ส
|
||||
chapters,title,JSONB,,NO,NO,NO,ชื่อบทหลายภาษา
|
||||
chapters,description,JSONB,,YES,NO,NO,คำอธิบายบทหลายภาษา
|
||||
chapters,sort_order,INTEGER,10,NO,NO,NO,ลำดับการแสดงผล (ต้อง >= 0)
|
||||
chapters,is_published,BOOLEAN,,NO,NO,NO,สถานะเผยแพร่
|
||||
chapters,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
chapters,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
lessons,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
lessons,chapter_id,INTEGER,10,NO,NO,YES,อ้างอิงบท
|
||||
lessons,title,JSONB,,NO,NO,NO,ชื่อบทเรียนหลายภาษา
|
||||
lessons,content,JSONB,,YES,NO,NO,เนื้อหาบทเรียนหลายภาษา
|
||||
lessons,type,VARCHAR,20,NO,NO,NO,"ประเภทบทเรียน (VIDEO, QUIZ)"
|
||||
lessons,duration_minutes,INTEGER,10,YES,NO,NO,ระยะเวลาโดยประมาณ (นาที)
|
||||
lessons,sort_order,INTEGER,10,NO,NO,NO,ลำดับการแสดงผล (ต้อง >= 0)
|
||||
lessons,is_sequential,BOOLEAN,,NO,NO,NO,ต้องเรียนตามลำดับ
|
||||
lessons,prerequisite_lesson_ids,JSONB,,YES,NO,NO,รายการ ID บทเรียนที่ต้องเรียนก่อน
|
||||
lessons,require_pass_quiz,BOOLEAN,,YES,NO,NO,ต้องผ่านแบบทดสอบก่อนดำเนินการต่อ
|
||||
lessons,is_published,BOOLEAN,,NO,NO,NO,สถานะเผยแพร่
|
||||
lessons,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
lessons,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
quizzes,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
quizzes,lesson_id,INTEGER,10,NO,NO,YES,อ้างอิงบทเรียน
|
||||
quizzes,title,JSONB,,NO,NO,NO,ชื่อแบบทดสอบหลายภาษา
|
||||
quizzes,description,JSONB,,YES,NO,NO,คำอธิบายแบบทดสอบหลายภาษา
|
||||
quizzes,passing_score,INTEGER,10,NO,NO,NO,คะแนนผ่าน (0-100)
|
||||
quizzes,time_limit,INTEGER,10,YES,NO,NO,เวลาจำกัด (นาที ต้อง > 0 ถ้ากำหนด)
|
||||
quizzes,shuffle_questions,BOOLEAN,,NO,NO,NO,สุ่มลำดับคำถาม
|
||||
quizzes,shuffle_choices,BOOLEAN,,NO,NO,NO,สุ่มลำดับตัวเลือก
|
||||
quizzes,show_answers_after_completion,BOOLEAN,,NO,NO,NO,แสดงเฉลยหลังทำเสร็จ
|
||||
quizzes,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
quizzes,created_by,INTEGER,10,NO,NO,YES,ผู้สร้าง
|
||||
quizzes,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
quizzes,updated_by,INTEGER,10,YES,NO,YES,ผู้แก้ไขล่าสุด
|
||||
questions,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
questions,quiz_id,INTEGER,10,NO,NO,YES,อ้างอิงแบบทดสอบ
|
||||
questions,question,JSONB,,NO,NO,NO,ข้อความคำถามหลายภาษา
|
||||
questions,explanation,JSONB,,YES,NO,NO,คำอธิบายเฉลยหลายภาษา
|
||||
questions,question_type,VARCHAR,20,NO,NO,NO,"ประเภทคำถาม enum(MULTIPLE_CHOICE, TRUE_FALSE, SHORT_ANSWER)"
|
||||
questions,score,INTEGER,10,NO,NO,NO,คะแนนสำหรับคำตอบที่ถูก (ต้อง > 0)
|
||||
questions,sort_order,INTEGER,10,NO,NO,NO,ลำดับการแสดงผล
|
||||
questions,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
questions,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
choices,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
choices,question_id,INTEGER,10,NO,NO,YES,อ้างอิงคำถาม
|
||||
choices,text,JSONB,,NO,NO,NO,ข้อความตัวเลือกหลายภาษา
|
||||
choices,is_correct,BOOLEAN,,NO,NO,NO,คำตอบที่ถูกต้อง
|
||||
choices,sort_order,INTEGER,10,NO,NO,NO,ลำดับการแสดงผล
|
||||
enrollments,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
enrollments,user_id,INTEGER,10,NO,NO,YES,อ้างอิงนักเรียน
|
||||
enrollments,course_id,INTEGER,10,NO,NO,YES,อ้างอิงคอร์ส
|
||||
enrollments,status,VARCHAR,20,NO,NO,NO,"สถานะ enum(ENROLLED, IN_PROGRESS, COMPLETED, DROPPED)"
|
||||
enrollments,progress_percentage,INTEGER,10,NO,NO,NO,เปอร์เซ็นต์ความคืบหน้า (0-100)
|
||||
enrollments,enrolled_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่ลงทะเบียน
|
||||
enrollments,started_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่เข้าเรียนครั้งแรก
|
||||
enrollments,completed_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่เรียนจบ
|
||||
enrollments,last_accessed_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่เข้าใช้ล่าสุด
|
||||
certificates,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
certificates,user_id,INTEGER,10,NO,NO,YES,อ้างอิงนักเรียน
|
||||
certificates,course_id,INTEGER,10,NO,NO,YES,อ้างอิงคอร์ส
|
||||
certificates,enrollment_id,INTEGER,10,NO,NO,YES,อ้างอิงการลงทะเบียน (ไม่ซ้ำ)
|
||||
certificates,file_path,VARCHAR,500,NO,NO,NO,เส้นทาง S3 ไฟล์ PDF ใบประกาศนียบัตร
|
||||
certificates,issued_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่ออกใบประกาศนียบัตร
|
||||
lesson_progress,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
lesson_progress,user_id,INTEGER,10,NO,NO,YES,อ้างอิงนักเรียน
|
||||
lesson_progress,lesson_id,INTEGER,10,NO,NO,YES,อ้างอิงบทเรียน
|
||||
lesson_progress,is_completed,BOOLEAN,,NO,NO,NO,สถานะเรียนจบ
|
||||
lesson_progress,completed_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่เรียนจบ
|
||||
lesson_progress,video_progress_seconds,INTEGER,10,YES,NO,NO,ความคืบหน้าวีดีโอ (วินาที)
|
||||
lesson_progress,video_duration_seconds,INTEGER,10,YES,NO,NO,ความยาววีดีโอทั้งหมด (วินาที)
|
||||
lesson_progress,video_progress_percentage,"DECIMAL(5,2)",,YES,NO,NO,เปอร์เซ็นต์ความคืบหน้าวีดีโอ
|
||||
lesson_progress,last_watched_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่ดูล่าสุด
|
||||
lesson_progress,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
lesson_progress,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
quiz_attempts,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
quiz_attempts,user_id,INTEGER,10,NO,NO,YES,อ้างอิงนักเรียน
|
||||
quiz_attempts,quiz_id,INTEGER,10,NO,NO,YES,อ้างอิงแบบทดสอบ
|
||||
quiz_attempts,score,INTEGER,10,NO,NO,NO,คะแนนที่ได้ (0-100)
|
||||
quiz_attempts,total_questions,INTEGER,10,NO,NO,NO,จำนวนคำถามทั้งหมด
|
||||
quiz_attempts,correct_answers,INTEGER,10,NO,NO,NO,จำนวนคำตอบที่ถูก
|
||||
quiz_attempts,is_passed,BOOLEAN,,NO,NO,NO,สถานะผ่าน
|
||||
quiz_attempts,attempt_number,INTEGER,10,NO,NO,NO,ครั้งที่ทำ
|
||||
quiz_attempts,answers,JSONB,,YES,NO,NO,คำตอบของนักเรียนสำหรับตรวจสอบ
|
||||
quiz_attempts,started_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่เริ่มทำ
|
||||
quiz_attempts,completed_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่ทำเสร็จ
|
||||
announcements,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
announcements,course_id,INTEGER,10,NO,NO,YES,อ้างอิงคอร์ส
|
||||
announcements,title,JSONB,,NO,NO,NO,ชื่อประกาศหลายภาษา
|
||||
announcements,content,JSONB,,NO,NO,NO,เนื้อหาประกาศหลายภาษา
|
||||
announcements,status,VARCHAR,20,NO,NO,NO,"สถานะ enum(DRAFT, PUBLISHED, ARCHIVED)"
|
||||
announcements,is_pinned,BOOLEAN,,NO,NO,NO,ปักหมุด
|
||||
announcements,published_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่กำหนดเผยแพร่
|
||||
announcements,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
announcements,created_by,INTEGER,10,NO,NO,YES,ผู้สร้าง
|
||||
announcements,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
announcements,updated_by,INTEGER,10,YES,NO,YES,ผู้แก้ไขล่าสุด
|
||||
announcement_attachments,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
announcement_attachments,announcement_id,INTEGER,10,NO,NO,YES,อ้างอิงประกาศ
|
||||
announcement_attachments,file_name,VARCHAR,255,NO,NO,NO,ชื่อไฟล์
|
||||
announcement_attachments,file_path,VARCHAR,500,NO,NO,NO,เส้นทาง/คีย์ S3
|
||||
announcement_attachments,file_size,INTEGER,10,NO,NO,NO,ขนาดไฟล์ (ไบต์)
|
||||
announcement_attachments,mime_type,VARCHAR,100,NO,NO,NO,ประเภท MIME
|
||||
announcement_attachments,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
lesson_attachments,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
lesson_attachments,lesson_id,INTEGER,10,NO,NO,YES,อ้างอิงบทเรียน
|
||||
lesson_attachments,file_name,VARCHAR,255,NO,NO,NO,ชื่อไฟล์
|
||||
lesson_attachments,file_path,VARCHAR,500,NO,NO,NO,เส้นทาง/คีย์ S3
|
||||
lesson_attachments,file_size,INTEGER,10,NO,NO,NO,ขนาดไฟล์ (ไบต์)
|
||||
lesson_attachments,mime_type,VARCHAR,100,NO,NO,NO,ประเภท MIME
|
||||
lesson_attachments,description,JSONB,,YES,NO,NO,คำอธิบายไฟล์หลายภาษา
|
||||
lesson_attachments,sort_order,INTEGER,10,NO,NO,NO,ลำดับการแสดงผล
|
||||
lesson_attachments,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
orders,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
orders,user_id,INTEGER,10,NO,NO,YES,อ้างอิงลูกค้า
|
||||
orders,total_amount,"DECIMAL(10,2)",,NO,NO,NO,ยอดรวมคำสั่งซื้อ
|
||||
orders,status,VARCHAR,20,NO,NO,NO,"สถานะ enum(PENDING, PAID, CANCELLED, REFUNDED)"
|
||||
orders,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
orders,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
order_items,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
order_items,order_id,INTEGER,10,NO,NO,YES,อ้างอิงคำสั่งซื้อ
|
||||
order_items,course_id,INTEGER,10,NO,NO,YES,อ้างอิงคอร์ส
|
||||
order_items,price,"DECIMAL(10,2)",,NO,NO,NO,ราคาคอร์ส ณ เวลาที่ซื้อ
|
||||
order_items,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
payments,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
payments,order_id,INTEGER,10,NO,NO,YES,อ้างอิงคำสั่งซื้อ
|
||||
payments,provider,VARCHAR,50,NO,NO,NO,"ผู้ให้บริการชำระเงิน enum(stripe, paypal, promptpay)"
|
||||
payments,transaction_id,VARCHAR,100,YES,NO,NO,รหัสธุรกรรมจากผู้ให้บริการ (ไม่ซ้ำ)
|
||||
payments,amount,"DECIMAL(10,2)",,NO,NO,NO,จำนวนเงินที่ชำระ
|
||||
payments,status,VARCHAR,20,NO,NO,NO,"สถานะ enum(PENDING, SUCCESS, FAILED)"
|
||||
payments,paid_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่ชำระเงิน
|
||||
payments,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
payments,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
instructor_balances,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
instructor_balances,instructor_id,INTEGER,10,NO,NO,YES,อ้างอิงผู้สอน (ไม่ซ้ำ)
|
||||
instructor_balances,available_amount,"DECIMAL(10,2)",,NO,NO,NO,ยอดเงินคงเหลือ
|
||||
instructor_balances,withdrawn_amount,"DECIMAL(10,2)",,NO,NO,NO,ยอดเงินที่ถอนแล้วทั้งหมด
|
||||
instructor_balances,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
instructor_balances,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
withdrawal_requests,id,INTEGER,10,NO,YES,NO,คีย์หลัก เพิ่มค่าอัตโนมัติ
|
||||
withdrawal_requests,instructor_id,INTEGER,10,NO,NO,YES,อ้างอิงผู้สอน
|
||||
withdrawal_requests,amount,"DECIMAL(10,2)",,NO,NO,NO,จำนวนเงินที่ขอถอน
|
||||
withdrawal_requests,status,VARCHAR,20,NO,NO,NO,"สถานะ enum(PENDING, APPROVED, REJECTED, PAID)"
|
||||
withdrawal_requests,approved_by,INTEGER,10,YES,NO,YES,ผู้ดูแลที่อนุมัติ
|
||||
withdrawal_requests,approved_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่อนุมัติ
|
||||
withdrawal_requests,rejected_reason,TEXT,,YES,NO,NO,เหตุผลที่ปฏิเสธ
|
||||
withdrawal_requests,created_at,TIMESTAMP,,NO,NO,NO,วันเวลาที่สร้าง
|
||||
withdrawal_requests,updated_at,TIMESTAMP,,YES,NO,NO,วันเวลาที่แก้ไขล่าสุด
|
||||
withdrawal_requests,updated_by,INTEGER,10,YES,NO,YES,ผู้แก้ไขล่าสุด
|
||||
|
360
docs/api-docs/api_course_cloning.md
Normal file
360
docs/api-docs/api_course_cloning.md
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
# Course Cloning API
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
API endpoint for cloning an entire course including all content, lessons, quizzes, and attachments.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Clone Course
|
||||
|
||||
### Endpoint
|
||||
```http
|
||||
POST /api/instructor/courses/:courseId/clone
|
||||
Authorization: Bearer <instructor-token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
### Request Body
|
||||
```json
|
||||
{
|
||||
"new_title": {
|
||||
"th": "Python สำหรับผู้เริ่มต้น (ปรับปรุง 2026)",
|
||||
"en": "Python for Beginners (Updated 2026)"
|
||||
},
|
||||
"new_slug": "python-beginners-2026",
|
||||
"copy_settings": {
|
||||
"copy_chapters": true,
|
||||
"copy_lessons": true,
|
||||
"copy_quizzes": true,
|
||||
"copy_attachments": true,
|
||||
"copy_announcements": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response 201
|
||||
```json
|
||||
{
|
||||
"new_course_id": 25,
|
||||
"cloned_from_course_id": 1,
|
||||
"status": "DRAFT",
|
||||
"message": "Course cloned successfully",
|
||||
"cloned_content": {
|
||||
"chapters": 10,
|
||||
"lessons": 45,
|
||||
"quizzes": 10,
|
||||
"questions": 150,
|
||||
"attachments": 30,
|
||||
"total_duration_minutes": 1200
|
||||
},
|
||||
"next_steps": [
|
||||
"Edit content as needed",
|
||||
"Update videos if necessary",
|
||||
"Submit for approval when ready"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 What Gets Cloned
|
||||
|
||||
### ✅ Copied:
|
||||
- Course info (title, description, price, thumbnail)
|
||||
- All chapters
|
||||
- All lessons (including video references)
|
||||
- All quizzes and questions
|
||||
- All attachments (files copied to new location)
|
||||
- Course settings (is_free, have_certificate)
|
||||
- Lesson prerequisites
|
||||
- Sort orders
|
||||
|
||||
### ❌ NOT Copied:
|
||||
- Enrollments
|
||||
- Student progress
|
||||
- Reviews/ratings
|
||||
- Announcements (optional)
|
||||
- Approval status (new course = DRAFT)
|
||||
- Statistics
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Use Cases
|
||||
|
||||
### 1. Update Videos
|
||||
```
|
||||
1. Clone course
|
||||
2. Replace videos in new course
|
||||
3. Submit for approval
|
||||
4. Publish
|
||||
```
|
||||
|
||||
### 2. Add Lessons
|
||||
```
|
||||
1. Clone course
|
||||
2. Add new lessons/chapters
|
||||
3. Submit for approval
|
||||
4. Publish
|
||||
```
|
||||
|
||||
### 3. Major Restructure
|
||||
```
|
||||
1. Clone course
|
||||
2. Reorganize chapters/lessons
|
||||
3. Update content
|
||||
4. Submit for approval
|
||||
5. Publish
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Permissions
|
||||
|
||||
**Who can clone**:
|
||||
- ✅ Course owner (created_by)
|
||||
- ✅ Primary instructor (is_primary = true)
|
||||
- ✅ Admin
|
||||
|
||||
**Restrictions**:
|
||||
- ❌ Cannot clone archived courses
|
||||
- ❌ Cannot clone rejected courses
|
||||
- ✅ Can clone approved/published courses
|
||||
|
||||
---
|
||||
|
||||
## 📊 Clone Process
|
||||
|
||||
### Backend Process:
|
||||
```javascript
|
||||
async function cloneCourse(originalCourseId, newData) {
|
||||
// 1. Create new course
|
||||
const newCourse = await createCourse({
|
||||
...originalCourse,
|
||||
title: newData.new_title,
|
||||
slug: newData.new_slug,
|
||||
status: 'DRAFT',
|
||||
created_by: currentUser.id
|
||||
});
|
||||
|
||||
// 2. Clone chapters
|
||||
for (const chapter of originalChapters) {
|
||||
const newChapter = await createChapter({
|
||||
...chapter,
|
||||
course_id: newCourse.id
|
||||
});
|
||||
|
||||
// 3. Clone lessons
|
||||
for (const lesson of chapter.lessons) {
|
||||
const newLesson = await createLesson({
|
||||
...lesson,
|
||||
chapter_id: newChapter.id
|
||||
});
|
||||
|
||||
// 4. Clone attachments (copy files)
|
||||
for (const attachment of lesson.attachments) {
|
||||
await copyFileToS3(attachment.file_path, newPath);
|
||||
await createAttachment({
|
||||
...attachment,
|
||||
lesson_id: newLesson.id,
|
||||
file_path: newPath
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Clone quizzes
|
||||
if (lesson.quiz) {
|
||||
const newQuiz = await createQuiz({
|
||||
...lesson.quiz,
|
||||
lesson_id: newLesson.id
|
||||
});
|
||||
|
||||
// 6. Clone questions
|
||||
for (const question of lesson.quiz.questions) {
|
||||
const newQuestion = await createQuestion({
|
||||
...question,
|
||||
quiz_id: newQuiz.id
|
||||
});
|
||||
|
||||
// 7. Clone choices
|
||||
for (const choice of question.choices) {
|
||||
await createChoice({
|
||||
...choice,
|
||||
question_id: newQuestion.id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newCourse;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Error Handling
|
||||
|
||||
### Error: Course Not Found
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "COURSE_NOT_FOUND",
|
||||
"message": "Course not found or you don't have permission"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error: Slug Already Exists
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "SLUG_EXISTS",
|
||||
"message": "Course slug already exists. Please use a different slug.",
|
||||
"suggestion": "python-beginners-2026-v2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error: Clone Failed
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "CLONE_FAILED",
|
||||
"message": "Failed to clone course. Please try again.",
|
||||
"details": "Error copying attachment: file.pdf"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Frontend Integration
|
||||
|
||||
### Clone Button
|
||||
```javascript
|
||||
async function cloneCourse(courseId) {
|
||||
const response = await fetch(`/api/instructor/courses/${courseId}/clone`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
new_title: {
|
||||
th: `${originalTitle.th} (คัดลอก)`,
|
||||
en: `${originalTitle.en} (Copy)`
|
||||
},
|
||||
new_slug: `${originalSlug}-copy-${Date.now()}`
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
// Redirect to edit new course
|
||||
window.location.href = `/instructor/courses/${data.new_course_id}/edit`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance Considerations
|
||||
|
||||
### Large Courses:
|
||||
- Use background job for cloning
|
||||
- Show progress indicator
|
||||
- Send notification when complete
|
||||
|
||||
```http
|
||||
POST /api/instructor/courses/:courseId/clone
|
||||
{
|
||||
"async": true
|
||||
}
|
||||
|
||||
Response 202:
|
||||
{
|
||||
"job_id": "clone-job-123",
|
||||
"status": "PROCESSING",
|
||||
"message": "Clone in progress. You will be notified when complete.",
|
||||
"estimated_time_minutes": 5
|
||||
}
|
||||
```
|
||||
|
||||
### Check Job Status:
|
||||
```http
|
||||
GET /api/instructor/clone-jobs/:jobId
|
||||
|
||||
Response:
|
||||
{
|
||||
"job_id": "clone-job-123",
|
||||
"status": "COMPLETED",
|
||||
"new_course_id": 25,
|
||||
"progress": {
|
||||
"chapters": "10/10",
|
||||
"lessons": "45/45",
|
||||
"quizzes": "10/10"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Endpoints
|
||||
|
||||
### Get Clone History
|
||||
```http
|
||||
GET /api/instructor/courses/:courseId/clones
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"original_course_id": 1,
|
||||
"clones": [
|
||||
{
|
||||
"id": 25,
|
||||
"title": "Python for Beginners (Updated 2026)",
|
||||
"status": "PUBLISHED",
|
||||
"cloned_at": "2026-01-06T15:00:00Z",
|
||||
"cloned_by": "Prof. John Smith"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"title": "Python for Beginners (Advanced)",
|
||||
"status": "DRAFT",
|
||||
"cloned_at": "2026-01-05T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Summary
|
||||
|
||||
**Endpoint**: `POST /instructor/courses/:courseId/clone`
|
||||
|
||||
**Copies**:
|
||||
- ✅ All chapters, lessons, quizzes
|
||||
- ✅ All attachments (files copied)
|
||||
- ✅ All settings and configurations
|
||||
|
||||
**Does NOT copy**:
|
||||
- ❌ Enrollments or student data
|
||||
- ❌ Reviews or ratings
|
||||
- ❌ Approval status
|
||||
|
||||
**Result**: New DRAFT course ready for editing
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
After cloning:
|
||||
1. Edit new course as needed
|
||||
2. Update videos if necessary
|
||||
3. Add/remove lessons
|
||||
4. Submit for approval
|
||||
5. Publish when approved
|
||||
|
|
@ -18,8 +18,8 @@ Authorization: Bearer <token>
|
|||
### Authentication
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| POST | `/auth/register` | - | Register new user |
|
||||
| POST | `/auth/login` | - | Login |
|
||||
| POST | `/auth/register` | - | Register new user (username + email) |
|
||||
| POST | `/auth/login` | - | Login (username OR email) |
|
||||
| POST | `/auth/logout` | ✅ | Logout |
|
||||
| POST | `/auth/refresh` | ✅ | Refresh token |
|
||||
| POST | `/auth/password/reset-request` | - | Request password reset |
|
||||
|
|
@ -28,8 +28,9 @@ Authorization: Bearer <token>
|
|||
### User Profile
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/users/me` | ✅ | Get current user profile |
|
||||
| PUT | `/users/me` | ✅ | Update profile |
|
||||
| GET | `/users/me` | ✅ | Get current user info |
|
||||
| GET | `/users/me/profile` | ✅ | Get user profile details |
|
||||
| PUT | `/users/me/profile` | ✅ | Update profile (name, phone, avatar) |
|
||||
| PUT | `/users/me/password` | ✅ | Change password |
|
||||
|
||||
### User Management (Admin)
|
||||
|
|
@ -40,6 +41,12 @@ Authorization: Bearer <token>
|
|||
| PUT | `/admin/users/:userId/role` | 🔒 Admin | Update user role |
|
||||
| DELETE | `/admin/users/:userId` | 🔒 Admin | Deactivate user |
|
||||
|
||||
### Role Management (Admin)
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/admin/roles` | 🔒 Admin | List all roles |
|
||||
| GET | `/admin/roles/:roleId` | 🔒 Admin | Get role details |
|
||||
|
||||
---
|
||||
|
||||
## 2. Categories
|
||||
|
|
@ -83,6 +90,15 @@ Authorization: Bearer <token>
|
|||
| PUT | `/instructor/courses/:courseId` | 👨🏫 Instructor | Update course |
|
||||
| DELETE | `/instructor/courses/:courseId` | 👨🏫 Instructor | Delete course |
|
||||
| POST | `/instructor/courses/:courseId/submit` | 👨🏫 Instructor | Submit for approval |
|
||||
| POST | `/instructor/courses/:courseId/clone` | 👨🏫 Instructor | Clone course (copy all content) |
|
||||
|
||||
### Course Instructors
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/instructor/courses/:courseId/instructors` | 👨🏫 Instructor | List course instructors |
|
||||
| POST | `/instructor/courses/:courseId/instructors` | 👨🏫 Instructor | Add instructor to course |
|
||||
| DELETE | `/instructor/courses/:courseId/instructors/:userId` | 👨🏫 Instructor | Remove instructor |
|
||||
| PUT | `/instructor/courses/:courseId/instructors/:userId/primary` | 👨🏫 Instructor | Set as primary instructor |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -166,8 +182,8 @@ Authorization: Bearer <token>
|
|||
|--------|----------|------|-------------|
|
||||
| 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 |
|
||||
| GET | `/students/courses/:courseId/certificate` | 👨🎓 Student | Get certificate (PDF file_path) |
|
||||
| GET | `/students/certificates` | 👨🎓 Student | Get all my certificates |
|
||||
|
||||
### Instructor Reports
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|
|
@ -311,15 +327,15 @@ Authorization: Bearer <token>
|
|||
|
||||
---
|
||||
|
||||
## Total Endpoints: **96+**
|
||||
## Total Endpoints: **107+**
|
||||
|
||||
- Authentication: 6
|
||||
- User Management: 4
|
||||
- User Management: 6 (+2 profile, +2 roles)
|
||||
- Categories: 5
|
||||
- Courses: 14 (+2)
|
||||
- Courses: 19 (+4 instructors, +1 clone)
|
||||
- Chapters & Lessons: 16
|
||||
- Quizzes: 10
|
||||
- Announcements: 7 (+1)
|
||||
- Announcements: 7
|
||||
- Reports: 11
|
||||
- Orders & Payments: 7 (อนาคต)
|
||||
- Earnings & Withdrawals: 8 (อนาคต)
|
||||
|
|
|
|||
563
docs/api-docs/api_v3_examples.md
Normal file
563
docs/api-docs/api_v3_examples.md
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
# API Usage Examples for ERD v3
|
||||
|
||||
## 🔐 Authentication
|
||||
|
||||
### Register with Username
|
||||
```http
|
||||
POST /api/auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"password": "SecurePass123!",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"prefix": "Mr."
|
||||
}
|
||||
```
|
||||
|
||||
**Response 201**:
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"role": {
|
||||
"id": 3,
|
||||
"code": "STUDENT",
|
||||
"name": {
|
||||
"th": "นักเรียน",
|
||||
"en": "Student"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"prefix": "Mr.",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
},
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Login with Username
|
||||
```http
|
||||
POST /api/auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "john_doe",
|
||||
"password": "SecurePass123!"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"role": {
|
||||
"code": "STUDENT",
|
||||
"name": {"th": "นักเรียน", "en": "Student"}
|
||||
}
|
||||
},
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Login with Email
|
||||
```http
|
||||
POST /api/auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "john@example.com",
|
||||
"password": "SecurePass123!"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**: Same as username login
|
||||
|
||||
---
|
||||
|
||||
## 👤 User Profile
|
||||
|
||||
### Get User Info
|
||||
```http
|
||||
GET /api/users/me
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"email_verified_at": "2026-01-01T10:00:00Z",
|
||||
"role": {
|
||||
"id": 3,
|
||||
"code": "STUDENT",
|
||||
"name": {"th": "นักเรียน", "en": "Student"}
|
||||
},
|
||||
"created_at": "2026-01-01T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get User Profile Details
|
||||
```http
|
||||
GET /api/users/me/profile
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"prefix": "Mr.",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"phone": "0812345678",
|
||||
"avatar_url": "https://s3.../avatars/john.jpg",
|
||||
"created_at": "2026-01-01T10:00:00Z",
|
||||
"updated_at": "2026-01-05T15:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Update Profile
|
||||
```http
|
||||
PUT /api/users/me/profile
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"prefix": "Dr.",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"phone": "0898765432",
|
||||
"avatar_url": "https://s3.../avatars/john-new.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"prefix": "Dr.",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"phone": "0898765432",
|
||||
"avatar_url": "https://s3.../avatars/john-new.jpg",
|
||||
"updated_at": "2026-01-06T11:00:00Z",
|
||||
"updated_by": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Multi-Instructor Support
|
||||
|
||||
### List Course Instructors
|
||||
```http
|
||||
GET /api/instructor/courses/1/instructors
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"instructors": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 5,
|
||||
"username": "prof_smith",
|
||||
"name": "Prof. John Smith",
|
||||
"avatar_url": "https://s3.../avatars/smith.jpg",
|
||||
"is_primary": true,
|
||||
"joined_at": "2026-01-01T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"user_id": 10,
|
||||
"username": "dr_jane",
|
||||
"name": "Dr. Jane Doe",
|
||||
"avatar_url": "https://s3.../avatars/jane.jpg",
|
||||
"is_primary": false,
|
||||
"joined_at": "2026-01-05T14:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Add Instructor to Course
|
||||
```http
|
||||
POST /api/instructor/courses/1/instructors
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"user_id": 15,
|
||||
"is_primary": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response 201**:
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"course_id": 1,
|
||||
"user_id": 15,
|
||||
"username": "ta_mike",
|
||||
"name": "Mike Johnson",
|
||||
"is_primary": false,
|
||||
"joined_at": "2026-01-06T11:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Set Primary Instructor
|
||||
```http
|
||||
PUT /api/instructor/courses/1/instructors/10/primary
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"message": "Primary instructor updated",
|
||||
"instructors": [
|
||||
{
|
||||
"user_id": 5,
|
||||
"is_primary": false
|
||||
},
|
||||
{
|
||||
"user_id": 10,
|
||||
"is_primary": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Remove Instructor
|
||||
```http
|
||||
DELETE /api/instructor/courses/1/instructors/15
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"message": "Instructor removed successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Error 400** (Last instructor):
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "LAST_INSTRUCTOR",
|
||||
"message": "Cannot remove the last instructor from course"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Certificates
|
||||
|
||||
### Get My Certificates
|
||||
```http
|
||||
GET /api/students/certificates
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"certificates": [
|
||||
{
|
||||
"id": 1,
|
||||
"course_id": 1,
|
||||
"course_title": "Python for Beginners",
|
||||
"completion_date": "2026-01-05",
|
||||
"file_path": "https://s3.../certificates/cert-user1-course1.pdf",
|
||||
"issued_at": "2026-01-05T16:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"course_id": 3,
|
||||
"course_title": "Web Development Bootcamp",
|
||||
"completion_date": "2025-12-20",
|
||||
"file_path": "https://s3.../certificates/cert-user1-course3.pdf",
|
||||
"issued_at": "2025-12-20T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Course Certificate
|
||||
```http
|
||||
GET /api/students/courses/1/certificate
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"course_id": 1,
|
||||
"course_title": "Python for Beginners",
|
||||
"student_name": "John Doe",
|
||||
"completion_date": "2026-01-05",
|
||||
"file_path": "https://s3.../certificates/cert-user1-course1.pdf",
|
||||
"issued_at": "2026-01-05T16:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error 404** (Not completed):
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "CERTIFICATE_NOT_FOUND",
|
||||
"message": "Certificate not available. Complete the course first."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error 400** (Course doesn't issue certificates):
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "NO_CERTIFICATE",
|
||||
"message": "This course does not issue certificates"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Role Management (Admin)
|
||||
|
||||
### List All Roles
|
||||
```http
|
||||
GET /api/admin/roles
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"code": "ADMIN",
|
||||
"name": {
|
||||
"th": "ผู้ดูแลระบบ",
|
||||
"en": "Administrator"
|
||||
},
|
||||
"description": {
|
||||
"th": "มีสิทธิ์เต็มในการจัดการระบบ",
|
||||
"en": "Full system access"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"code": "INSTRUCTOR",
|
||||
"name": {
|
||||
"th": "ผู้สอน",
|
||||
"en": "Instructor"
|
||||
},
|
||||
"description": {
|
||||
"th": "สามารถสร้างและจัดการคอร์สเรียน",
|
||||
"en": "Can create and manage courses"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"code": "STUDENT",
|
||||
"name": {
|
||||
"th": "นักเรียน",
|
||||
"en": "Student"
|
||||
},
|
||||
"description": {
|
||||
"th": "สามารถลงทะเบียนและเรียนคอร์ส",
|
||||
"en": "Can enroll and learn courses"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Role Details
|
||||
```http
|
||||
GET /api/admin/roles/2
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"code": "INSTRUCTOR",
|
||||
"name": {
|
||||
"th": "ผู้สอน",
|
||||
"en": "Instructor"
|
||||
},
|
||||
"description": {
|
||||
"th": "สามารถสร้างและจัดการคอร์สเรียน",
|
||||
"en": "Can create and manage courses"
|
||||
},
|
||||
"user_count": 45,
|
||||
"created_at": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Course with Certificate
|
||||
|
||||
### Get Course Details (with have_certificate)
|
||||
```http
|
||||
GET /api/courses/1
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"title": {
|
||||
"th": "Python สำหรับผู้เริ่มต้น",
|
||||
"en": "Python for Beginners"
|
||||
},
|
||||
"description": {...},
|
||||
"price": 990,
|
||||
"is_free": false,
|
||||
"have_certificate": true,
|
||||
"instructors": [
|
||||
{
|
||||
"user_id": 5,
|
||||
"name": "Prof. John Smith",
|
||||
"is_primary": true
|
||||
},
|
||||
{
|
||||
"user_id": 10,
|
||||
"name": "Dr. Jane Doe",
|
||||
"is_primary": false
|
||||
}
|
||||
],
|
||||
"total_lessons": 50,
|
||||
"total_duration": 1200,
|
||||
"status": "APPROVED"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quiz with show_answers_after_completion
|
||||
|
||||
### Get Quiz
|
||||
```http
|
||||
GET /api/students/quizzes/1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"title": {"th": "แบบทดสอบท้ายบท", "en": "Chapter Quiz"},
|
||||
"description": {...},
|
||||
"passing_score": 60,
|
||||
"time_limit": 30,
|
||||
"shuffle_questions": true,
|
||||
"shuffle_choices": true,
|
||||
"show_answers_after_completion": true,
|
||||
"questions": [...]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Submit Quiz (with answers shown)
|
||||
```http
|
||||
POST /api/students/quizzes/1/submit
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"answers": [
|
||||
{"question_id": 1, "choice_id": 3},
|
||||
{"question_id": 2, "choice_id": 7}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200** (show_answers_after_completion = true):
|
||||
```json
|
||||
{
|
||||
"attempt_id": 1,
|
||||
"score": 75,
|
||||
"total_questions": 10,
|
||||
"correct_answers": 8,
|
||||
"is_passed": true,
|
||||
"answers": [
|
||||
{
|
||||
"question_id": 1,
|
||||
"question": {"th": "Python คืออะไร?"},
|
||||
"your_answer": 3,
|
||||
"correct_answer": 3,
|
||||
"is_correct": true,
|
||||
"explanation": {"th": "Python เป็นภาษาโปรแกรมมิ่ง..."}
|
||||
},
|
||||
{
|
||||
"question_id": 2,
|
||||
"question": {"th": "ตัวแปรใน Python..."},
|
||||
"your_answer": 7,
|
||||
"correct_answer": 8,
|
||||
"is_correct": false,
|
||||
"explanation": {"th": "ตัวแปรใน Python ไม่ต้องประกาศชนิดข้อมูล"}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200** (show_answers_after_completion = false):
|
||||
```json
|
||||
{
|
||||
"attempt_id": 1,
|
||||
"score": 75,
|
||||
"total_questions": 10,
|
||||
"correct_answers": 8,
|
||||
"is_passed": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [API Endpoints Reference](./api_endpoints.md)
|
||||
- [ERD v3 Schema](./ER/ERD_v3_improved.txt)
|
||||
- [Edge Cases](./edge_cases_quick_reference.md)
|
||||
Loading…
Add table
Add a link
Reference in a new issue