From a1f7ee057d49f46bfc5c4c854fcd36f65cb3cf42 Mon Sep 17 00:00:00 2001 From: JakkrapartXD Date: Wed, 7 Jan 2026 10:42:39 +0700 Subject: [PATCH] feat: Add comprehensive API usage examples for v3, ERD, data dictionary, and course cloning documentation. --- .DS_Store | Bin 0 -> 6148 bytes docs/.DS_Store | Bin 6148 -> 8196 bytes docs/api-docs/.DS_Store | Bin 0 -> 6148 bytes docs/api-docs/ER/.DS_Store | Bin 0 -> 6148 bytes docs/api-docs/ER/ERD_v3_improved.txt | 507 ++++++++++++++++ ...ning_data_dictionary - data_dictionary.csv | 205 +++++++ docs/api-docs/api_course_cloning.md | 360 +++++++++++ docs/api-docs/api_endpoints.md | 36 +- docs/api-docs/api_v3_examples.md | 563 ++++++++++++++++++ 9 files changed, 1661 insertions(+), 10 deletions(-) create mode 100644 .DS_Store create mode 100644 docs/api-docs/.DS_Store create mode 100644 docs/api-docs/ER/.DS_Store create mode 100644 docs/api-docs/ER/ERD_v3_improved.txt create mode 100644 docs/api-docs/ER/e_learning_data_dictionary - data_dictionary.csv create mode 100644 docs/api-docs/api_course_cloning.md create mode 100644 docs/api-docs/api_v3_examples.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..53552f984ebd54d2300e1903e09928af2047ed92 GIT binary patch literal 6148 zcmeHK!AiqG5Z!H~O({YS3OxqA7LC+G#Y>F!;MIs8RBF={4aRI~QgbMUob`wN5`RZ$ zb~gpBo&=FH1G8^4JF{WlhMf#!jC<2?k1>leW`ZJC>M(pGxQ@Cc6>X^ka@^xI7Q-MG z0X*Y|B>$fQJUfGhOt2wKzntF@tnIu@k%=t)f;=7TbAAoRag=6`^TsPxV{N@=*35=^ zIH@CKT56}7!@na&M74;mxODP)~OL&3F zBA+LJlEfmp16%1|`VkTX!~ij{iVWzx&TOn=a+DJ>Kn(nf0lXhr$3jYi-b5P!#kl n6fQzwB1&Tv&^+6 zGg5^Xh$jk2QjfA1vFy!x+X2gfWxz6E8L$jk2L1;I@XqGuT=3r4v$nSkSO%^n1N?k& zA*`E5PqbE#4s@ynfL_6{YOuK+;278FrqL6vF@q*_D6|e0ro<3B9OJI>YZ^V#T8EP` z#fLC=7N$ZG>h8dIl{<;1*0#3{SO(@9;Gc^_9t|j>`^Ea*kEM?}o_`A2`lTAI5`TY` z4UGSHe-KAmUa$WUOUoo6M5qbZ`HCAaKdrYyikd9#eWslWXd0VYG- zy{|Ab^b#y(!0Icc6eFL?_Mvk1Yav(H5Y~ z&o2J{zrYOGtyu;v17fA!X}7?`*&oNVlyI%>LOwvavEM{%6@pHcSoocJGQW%?$PSPpEFcM5Rh7(jc}`2?~*l*o4q7*ft0jfWo}mjyf5KOdd#4q^#i| z_yx9n3IDsl$8 zk=sC_I6NBQ=TSfxYu0waDqt1(+Z5nu_dcH39>vrFUOm5EiYUc(hVh6#VXh0S1pSN- z(GOoHGVl}WgMnWk&ruJ5se&HS{(?0iNki(xF9KHp4hiNM^*op9+X_>8!Nd`Th(aoF zjx8q=QCabSu<+N>XKHqR==h%#7=;Zb>;${(CPBMDR{ruZ2WQ7 zGxzUzzn8?>pjP`K3OmIcH+P*~XU}=xzL4Ygz#9y+me+sFSFfZ@{CVH=U&qn7Q@-~? zrUNfdqrP&8!w4qt-o$Ar$1OQb!%X?M^@LM$N}ck-WYTCfs&2jZbXs*MNA+6OeNsD` zPD{@I!$;3fJC{i}m7fteioofr${mAq;Jj46qjo<{WXgL!x1HN;Mq}yhZ8otpRspNP zf2sh#A6!(%roov;_3A*QJ^_G546A}Z{~2H(*I?7&Oe1DcgmwjLS7AOegm%ZcYwVf^ zXBxFT3G?wG%*w)ip$N4)_+4dAqN&lgRspNPvI0B$vdriI$>#6>WhZ-O6|f5YQwoUU zar?N1l+4+>mK>k8F6swVE^If`s9eyP<5+e0C_X?{1#>QUfK7uljTk|(e*}~awy_HQ HQ3ZYjJtG3b literal 0 HcmV?d00001 diff --git a/docs/api-docs/ER/.DS_Store b/docs/api-docs/ER/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..68214e40e2acd5984fb2ae893abe1754266938a8 GIT binary patch literal 6148 zcmeHKO-sW-5PefCwDeN&;zig$V6gfJVo47kyyy?8_5%r2BU+2za`(sh`#kBJ-9=1V zizi!T24>&xX5Q@NC1kSzr2jPO0bKwsx?-cvW`*f>^(AY>nO&mz9M^cj9di23QnXEM zqXPQujxfa#Gq&HK-w+dy=XCAm7_*_*V)@GrP?CyrsCeF5R5&wT^y8SJSvM m;1`7*zZEl9x8i-eH_kil5YvcdK+e$YN5IQqjVbV_3VZ_3=w;pj literal 0 HcmV?d00001 diff --git a/docs/api-docs/ER/ERD_v3_improved.txt b/docs/api-docs/ER/ERD_v3_improved.txt new file mode 100644 index 00000000..5999ebd1 --- /dev/null +++ b/docs/api-docs/ER/ERD_v3_improved.txt @@ -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) diff --git a/docs/api-docs/ER/e_learning_data_dictionary - data_dictionary.csv b/docs/api-docs/ER/e_learning_data_dictionary - data_dictionary.csv new file mode 100644 index 00000000..cbd60f31 --- /dev/null +++ b/docs/api-docs/ER/e_learning_data_dictionary - data_dictionary.csv @@ -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,ผู้แก้ไขล่าสุด \ No newline at end of file diff --git a/docs/api-docs/api_course_cloning.md b/docs/api-docs/api_course_cloning.md new file mode 100644 index 00000000..59c3a367 --- /dev/null +++ b/docs/api-docs/api_course_cloning.md @@ -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 +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 diff --git a/docs/api-docs/api_endpoints.md b/docs/api-docs/api_endpoints.md index f74fde7e..18b43d24 100644 --- a/docs/api-docs/api_endpoints.md +++ b/docs/api-docs/api_endpoints.md @@ -18,8 +18,8 @@ Authorization: Bearer ### 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 ### 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 | 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 | 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 |--------|----------|------|-------------| | 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 --- -## 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 (อนาคต) diff --git a/docs/api-docs/api_v3_examples.md b/docs/api-docs/api_v3_examples.md new file mode 100644 index 00000000..b8b68b44 --- /dev/null +++ b/docs/api-docs/api_v3_examples.md @@ -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 +``` + +**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 +``` + +**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 +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 +``` + +**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 +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 +``` + +**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 +``` + +**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 +``` + +**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 +``` + +**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 +``` + +**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 +``` + +**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 +``` + +**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 +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)