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
|
### Authentication
|
||||||
| Method | Endpoint | Auth | Description |
|
| Method | Endpoint | Auth | Description |
|
||||||
|--------|----------|------|-------------|
|
|--------|----------|------|-------------|
|
||||||
| POST | `/auth/register` | - | Register new user |
|
| POST | `/auth/register` | - | Register new user (username + email) |
|
||||||
| POST | `/auth/login` | - | Login |
|
| POST | `/auth/login` | - | Login (username OR email) |
|
||||||
| POST | `/auth/logout` | ✅ | Logout |
|
| POST | `/auth/logout` | ✅ | Logout |
|
||||||
| POST | `/auth/refresh` | ✅ | Refresh token |
|
| POST | `/auth/refresh` | ✅ | Refresh token |
|
||||||
| POST | `/auth/password/reset-request` | - | Request password reset |
|
| POST | `/auth/password/reset-request` | - | Request password reset |
|
||||||
|
|
@ -28,8 +28,9 @@ Authorization: Bearer <token>
|
||||||
### User Profile
|
### User Profile
|
||||||
| Method | Endpoint | Auth | Description |
|
| Method | Endpoint | Auth | Description |
|
||||||
|--------|----------|------|-------------|
|
|--------|----------|------|-------------|
|
||||||
| GET | `/users/me` | ✅ | Get current user profile |
|
| GET | `/users/me` | ✅ | Get current user info |
|
||||||
| PUT | `/users/me` | ✅ | Update profile |
|
| GET | `/users/me/profile` | ✅ | Get user profile details |
|
||||||
|
| PUT | `/users/me/profile` | ✅ | Update profile (name, phone, avatar) |
|
||||||
| PUT | `/users/me/password` | ✅ | Change password |
|
| PUT | `/users/me/password` | ✅ | Change password |
|
||||||
|
|
||||||
### User Management (Admin)
|
### User Management (Admin)
|
||||||
|
|
@ -40,6 +41,12 @@ Authorization: Bearer <token>
|
||||||
| PUT | `/admin/users/:userId/role` | 🔒 Admin | Update user role |
|
| PUT | `/admin/users/:userId/role` | 🔒 Admin | Update user role |
|
||||||
| DELETE | `/admin/users/:userId` | 🔒 Admin | Deactivate user |
|
| 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
|
## 2. Categories
|
||||||
|
|
@ -83,6 +90,15 @@ Authorization: Bearer <token>
|
||||||
| PUT | `/instructor/courses/:courseId` | 👨🏫 Instructor | Update course |
|
| PUT | `/instructor/courses/:courseId` | 👨🏫 Instructor | Update course |
|
||||||
| DELETE | `/instructor/courses/:courseId` | 👨🏫 Instructor | Delete course |
|
| DELETE | `/instructor/courses/:courseId` | 👨🏫 Instructor | Delete course |
|
||||||
| POST | `/instructor/courses/:courseId/submit` | 👨🏫 Instructor | Submit for approval |
|
| 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/progress` | 👨🎓 Student | Get my overall progress |
|
||||||
| GET | `/students/courses/:courseId/progress` | 👨🎓 Student | Get course progress |
|
| GET | `/students/courses/:courseId/progress` | 👨🎓 Student | Get course progress |
|
||||||
| GET | `/students/courses/:courseId/certificate` | 👨🎓 Student | Get certificate |
|
| GET | `/students/courses/:courseId/certificate` | 👨🎓 Student | Get certificate (PDF file_path) |
|
||||||
| GET | `/students/certificates` | 👨🎓 Student | Get all certificates |
|
| GET | `/students/certificates` | 👨🎓 Student | Get all my certificates |
|
||||||
|
|
||||||
### Instructor Reports
|
### Instructor Reports
|
||||||
| Method | Endpoint | Auth | Description |
|
| Method | Endpoint | Auth | Description |
|
||||||
|
|
@ -311,15 +327,15 @@ Authorization: Bearer <token>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Total Endpoints: **96+**
|
## Total Endpoints: **107+**
|
||||||
|
|
||||||
- Authentication: 6
|
- Authentication: 6
|
||||||
- User Management: 4
|
- User Management: 6 (+2 profile, +2 roles)
|
||||||
- Categories: 5
|
- Categories: 5
|
||||||
- Courses: 14 (+2)
|
- Courses: 19 (+4 instructors, +1 clone)
|
||||||
- Chapters & Lessons: 16
|
- Chapters & Lessons: 16
|
||||||
- Quizzes: 10
|
- Quizzes: 10
|
||||||
- Announcements: 7 (+1)
|
- Announcements: 7
|
||||||
- Reports: 11
|
- Reports: 11
|
||||||
- Orders & Payments: 7 (อนาคต)
|
- Orders & Payments: 7 (อนาคต)
|
||||||
- Earnings & Withdrawals: 8 (อนาคต)
|
- 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