docs: Relocate ERD documentation to a new ER/ subdirectory and update its README.
This commit is contained in:
parent
7ebe33d920
commit
9f385ed997
3 changed files with 0 additions and 0 deletions
204
docs/api-docs/ER/ERD v.1.txt
Normal file
204
docs/api-docs/ER/ERD v.1.txt
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
Table users {
|
||||
id int [pk
|
||||
]
|
||||
name varchar
|
||||
email varchar [unique
|
||||
]
|
||||
password varchar
|
||||
role varchar // admin | instructor | learner
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table courses {
|
||||
id int [pk
|
||||
]
|
||||
categories_id int
|
||||
title jsonb // { "th": "...", "en": "..." }
|
||||
description jsonb // { "th": "...", "en": "..." }
|
||||
price decimal
|
||||
is_free boolean
|
||||
status varchar // draft | pending | approved | rejected
|
||||
instructor_id int
|
||||
approved_by int // admin user id
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table categories {
|
||||
id int [pk
|
||||
]
|
||||
name jsonb // multi-language category name
|
||||
description jsonb // optional
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table chapters {
|
||||
id int [pk
|
||||
]
|
||||
course_id int
|
||||
title jsonb // multi-language
|
||||
sort_order int
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table lessons {
|
||||
id int [pk
|
||||
]
|
||||
chapter_id int
|
||||
title jsonb // multi-language
|
||||
content jsonb // multi-language lesson content
|
||||
type varchar // video | pdf | text | quiz
|
||||
sort_order int
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table quizzes {
|
||||
id int [pk
|
||||
]
|
||||
lesson_id int
|
||||
title jsonb // multi-language
|
||||
passing_score int
|
||||
time_limit int
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table questions {
|
||||
id int [pk
|
||||
]
|
||||
quiz_id int
|
||||
question jsonb // multi-language
|
||||
score int
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table choices {
|
||||
id int [pk
|
||||
]
|
||||
question_id int
|
||||
text jsonb // multi-language
|
||||
is_correct boolean
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table enrollments {
|
||||
id int [pk
|
||||
]
|
||||
user_id int
|
||||
course_id int
|
||||
status varchar // enrolled | completed
|
||||
enrolled_at datetime
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table announcements {
|
||||
id int [pk
|
||||
]
|
||||
course_id int
|
||||
instructor_id int
|
||||
title jsonb // multi-language
|
||||
content jsonb // multi-language
|
||||
is_pinned boolean // pin important announcements to top
|
||||
published_at datetime // scheduled publish date
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table announcement_attachments {
|
||||
id int [pk
|
||||
]
|
||||
announcement_id int
|
||||
file_name varchar
|
||||
file_path varchar
|
||||
file_size int
|
||||
mime_type varchar
|
||||
created_at datetime
|
||||
}
|
||||
|
||||
Table orders {
|
||||
id int [pk
|
||||
]
|
||||
user_id int
|
||||
total_amount decimal
|
||||
status varchar // pending | paid | cancelled
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table order_items {
|
||||
id int [pk
|
||||
]
|
||||
order_id int
|
||||
course_id int
|
||||
price decimal
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table payments {
|
||||
id int [pk
|
||||
]
|
||||
order_id int
|
||||
provider varchar
|
||||
transaction_id varchar
|
||||
amount decimal
|
||||
status varchar // success | failed
|
||||
paid_at datetime
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table instructor_balances {
|
||||
id int [pk
|
||||
]
|
||||
instructor_id int
|
||||
available_amount decimal
|
||||
withdrawn_amount decimal
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Table withdrawal_requests {
|
||||
id int [pk
|
||||
]
|
||||
instructor_id int
|
||||
amount decimal
|
||||
status varchar // pending | approved | rejected | paid
|
||||
approved_by int
|
||||
created_at datetime
|
||||
updated_at datetime
|
||||
}
|
||||
|
||||
Ref: courses.instructor_id > users.id
|
||||
Ref: courses.approved_by > users.id
|
||||
|
||||
Ref: courses.categories_id > categories.id
|
||||
|
||||
Ref: chapters.course_id > courses.id
|
||||
Ref: lessons.chapter_id > chapters.id
|
||||
Ref: quizzes.lesson_id > lessons.id
|
||||
Ref: questions.quiz_id > quizzes.id
|
||||
Ref: choices.question_id > questions.id
|
||||
|
||||
Ref: enrollments.user_id > users.id
|
||||
Ref: enrollments.course_id > courses.id
|
||||
|
||||
Ref: announcements.course_id > courses.id
|
||||
Ref: announcements.instructor_id > users.id
|
||||
Ref: announcement_attachments.announcement_id > announcements.id
|
||||
|
||||
Ref: orders.user_id > users.id
|
||||
Ref: order_items.order_id > orders.id
|
||||
Ref: order_items.course_id > courses.id
|
||||
Ref: payments.order_id > orders.id
|
||||
|
||||
Ref: instructor_balances.instructor_id > users.id
|
||||
Ref: withdrawal_requests.instructor_id > users.id
|
||||
Ref: withdrawal_requests.approved_by > users.id
|
||||
328
docs/api-docs/ER/ERD_v2_improved.txt
Normal file
328
docs/api-docs/ER/ERD_v2_improved.txt
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
// E-Learning Platform - Enhanced ERD with Constraints
|
||||
// Version 2.0 - Improved with NOT NULL, CHECK, DEFAULT, and UNIQUE constraints
|
||||
|
||||
Table users {
|
||||
id int [pk, increment]
|
||||
name varchar [not null]
|
||||
email varchar [unique, not null]
|
||||
password varchar [not null]
|
||||
role varchar [not null, default: 'student', note: 'admin | instructor | student']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
email [unique]
|
||||
role
|
||||
}
|
||||
}
|
||||
|
||||
Table courses {
|
||||
id int [pk, increment]
|
||||
categories_id int [ref: > categories.id]
|
||||
title jsonb [not null, note: '{ "th": "...", "en": "..." }']
|
||||
description jsonb [note: '{ "th": "...", "en": "..." }']
|
||||
price decimal [not null, default: 0, note: 'must be >= 0']
|
||||
is_free boolean [not null, default: false]
|
||||
status varchar [not null, default: 'draft', note: 'draft | pending | approved | rejected']
|
||||
instructor_id int [not null, ref: > users.id]
|
||||
approved_by int [ref: > users.id, note: 'admin user id']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
instructor_id
|
||||
categories_id
|
||||
status
|
||||
(instructor_id, status)
|
||||
}
|
||||
}
|
||||
|
||||
Table categories {
|
||||
id int [pk, increment]
|
||||
name jsonb [not null, note: 'multi-language category name']
|
||||
description jsonb [note: 'optional']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
}
|
||||
|
||||
Table chapters {
|
||||
id int [pk, increment]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
title jsonb [not null, note: 'multi-language']
|
||||
sort_order int [not null, default: 0, note: 'must be >= 0']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
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: 'video | pdf | text | quiz']
|
||||
sort_order int [not null, default: 0, note: 'must be >= 0']
|
||||
is_sequential boolean [not null, default: true, note: 'require previous lessons to be completed']
|
||||
prerequisite_lesson_ids jsonb [note: 'array of lesson IDs that must be completed first, e.g. [1, 2, 3]']
|
||||
require_pass_quiz boolean [default: false, note: 'require passing quiz to unlock next lessons']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
chapter_id
|
||||
(chapter_id, sort_order)
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
Table quizzes {
|
||||
id int [pk, increment]
|
||||
lesson_id int [not null, ref: > lessons.id]
|
||||
title jsonb [not null, note: 'multi-language']
|
||||
passing_score int [not null, default: 60, note: 'must be 0-100']
|
||||
time_limit int [note: 'in minutes, must be > 0 if set']
|
||||
max_attempts int [note: 'null = unlimited']
|
||||
cooldown_minutes int [note: 'waiting time between attempts']
|
||||
score_policy varchar [default: 'highest', note: 'highest | latest | first | average']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
lesson_id
|
||||
}
|
||||
}
|
||||
|
||||
Table questions {
|
||||
id int [pk, increment]
|
||||
quiz_id int [not null, ref: > quizzes.id]
|
||||
question jsonb [not null, note: 'multi-language']
|
||||
question_type varchar [not null, default: 'multiple_choice', note: 'multiple_choice | true_false']
|
||||
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 [not null, default: `now()`]
|
||||
|
||||
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]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
question_id
|
||||
}
|
||||
}
|
||||
|
||||
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: 'enrolled | completed']
|
||||
progress_percentage int [not null, default: 0, note: '0-100']
|
||||
certificate_issued boolean [not null, default: false]
|
||||
certificate_id varchar [unique, note: 'unique certificate identifier']
|
||||
enrolled_at datetime [not null, default: `now()`]
|
||||
completed_at datetime [note: 'when status changed to completed']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
(user_id, course_id) [unique, name: 'unique_enrollment']
|
||||
user_id
|
||||
course_id
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
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, note: 'current playback position in seconds']
|
||||
video_duration_seconds int [note: 'total video duration in seconds']
|
||||
video_progress_percentage decimal(5,2) [note: 'calculated: (progress/duration)*100']
|
||||
last_watched_at datetime [note: 'last time user watched this video']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
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]
|
||||
started_at datetime [not null, default: `now()`]
|
||||
completed_at datetime
|
||||
created_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
user_id
|
||||
quiz_id
|
||||
(user_id, quiz_id)
|
||||
(user_id, quiz_id, attempt_number)
|
||||
}
|
||||
}
|
||||
|
||||
Table announcements {
|
||||
id int [pk, increment]
|
||||
course_id int [not null, ref: > courses.id]
|
||||
instructor_id int [not null, ref: > users.id]
|
||||
title jsonb [not null, note: 'multi-language']
|
||||
content jsonb [not null, note: 'multi-language']
|
||||
is_pinned boolean [not null, default: false, note: 'pin important announcements to top']
|
||||
published_at datetime [note: 'scheduled publish date, null = publish immediately']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
course_id
|
||||
instructor_id
|
||||
(course_id, is_pinned, published_at)
|
||||
(course_id, 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 or file path']
|
||||
file_size int [not null, note: 'in bytes']
|
||||
mime_type varchar [not null]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
|
||||
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 or file path']
|
||||
file_size int [not null, note: 'in bytes']
|
||||
mime_type varchar [not null]
|
||||
description jsonb [note: 'multi-language description']
|
||||
sort_order int [not null, default: 0]
|
||||
created_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
lesson_id
|
||||
(lesson_id, sort_order)
|
||||
}
|
||||
}
|
||||
|
||||
Table orders {
|
||||
id int [pk, increment]
|
||||
user_id int [not null, ref: > users.id]
|
||||
total_amount decimal [not null, default: 0, note: 'must be >= 0']
|
||||
status varchar [not null, default: 'pending', note: 'pending | paid | cancelled']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
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, note: 'must be >= 0']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_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 etc.']
|
||||
transaction_id varchar [unique, note: 'unique transaction ID from payment provider']
|
||||
amount decimal [not null, note: 'must be > 0']
|
||||
status varchar [not null, default: 'pending', note: 'pending | success | failed']
|
||||
paid_at datetime
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
order_id
|
||||
transaction_id [unique]
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
Table instructor_balances {
|
||||
id int [pk, increment]
|
||||
instructor_id int [not null, unique, ref: > users.id, note: 'one balance record per instructor']
|
||||
available_amount decimal [not null, default: 0, note: 'must be >= 0']
|
||||
withdrawn_amount decimal [not null, default: 0, note: 'must be >= 0']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
instructor_id [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Table withdrawal_requests {
|
||||
id int [pk, increment]
|
||||
instructor_id int [not null, ref: > users.id]
|
||||
amount decimal [not null, note: 'must be > 0']
|
||||
status varchar [not null, default: 'pending', note: 'pending | approved | rejected | paid']
|
||||
approved_by int [ref: > users.id, note: 'admin user id']
|
||||
rejected_reason varchar [note: 'reason if rejected']
|
||||
created_at datetime [not null, default: `now()`]
|
||||
updated_at datetime [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
instructor_id
|
||||
status
|
||||
(instructor_id, status)
|
||||
}
|
||||
}
|
||||
|
||||
// Additional helpful notes:
|
||||
// 1. All foreign keys should have ON DELETE actions defined during implementation
|
||||
// 2. Suggested ON DELETE actions:
|
||||
// - courses.instructor_id: RESTRICT (prevent deleting instructor with courses)
|
||||
// - enrollments: CASCADE (delete enrollments when user/course deleted)
|
||||
// - announcements: CASCADE (delete announcements when course deleted)
|
||||
// - quiz_attempts: CASCADE (delete attempts when user deleted)
|
||||
// 3. All jsonb fields support multi-language: { "th": "...", "en": "..." }
|
||||
// 4. Indexes are suggested for common query patterns
|
||||
// 5. Consider adding soft delete (deleted_at) for important tables
|
||||
312
docs/api-docs/ER/README.md
Normal file
312
docs/api-docs/ER/README.md
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
# Database Schema (ERD) Documentation
|
||||
|
||||
## 📊 Entity-Relationship Diagram
|
||||
|
||||
### Online Visualization
|
||||
|
||||
View the interactive ERD diagram:
|
||||
- **dbdiagram.io**: [dbdiagram.io](https://dbdiagram.io/d/e-lerning-6943c64be4bb1dd3a9896479)
|
||||
|
||||
## 📋 Database Overview
|
||||
|
||||
### Total Tables: **17**
|
||||
|
||||
| Category | Tables | Count |
|
||||
|----------|--------|-------|
|
||||
| **Core** | users, categories, courses, chapters, lessons | 5 |
|
||||
| **Content** | quizzes, questions, choices, lesson_attachments | 4 |
|
||||
| **Progress** | enrollments, lesson_progress, quiz_attempts | 3 |
|
||||
| **Communication** | announcements, announcement_attachments | 2 |
|
||||
| **Commerce** (อนาคต) | orders, payments, withdrawals | 3 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Features
|
||||
|
||||
### 1. **Multi-language Support**
|
||||
- Uses `jsonb` fields for Thai/English content
|
||||
- Fields: `title`, `content`, `description`
|
||||
|
||||
### 2. **Video Progress Tracking**
|
||||
- Track playback position per student
|
||||
- Auto-complete at 90% watched
|
||||
- Resume from last position
|
||||
|
||||
### 3. **Lesson Prerequisites**
|
||||
- Sequential learning control
|
||||
- Quiz pass requirements
|
||||
- Flexible ordering
|
||||
|
||||
### 4. **File Attachments**
|
||||
- Multiple files per lesson
|
||||
- Support for PDF, DOCX, ZIP, images
|
||||
- Sortable with descriptions
|
||||
|
||||
### 5. **Quiz System**
|
||||
- Multiple attempts with cooldown
|
||||
- Score policies (HIGHEST/LATEST/FIRST/AVERAGE)
|
||||
- Detailed attempt history
|
||||
|
||||
---
|
||||
|
||||
## 📊 Main Entities
|
||||
|
||||
### Users
|
||||
- Roles: STUDENT, INSTRUCTOR, ADMIN
|
||||
- Profile information
|
||||
- Authentication data
|
||||
|
||||
### Courses
|
||||
- Multi-language titles and descriptions
|
||||
- Categories and pricing
|
||||
- Instructor ownership
|
||||
- Approval workflow
|
||||
|
||||
### Chapters & Lessons
|
||||
- Hierarchical structure
|
||||
- Sort ordering
|
||||
- Multiple lesson types (video, pdf, text, quiz)
|
||||
- Prerequisites support
|
||||
|
||||
### Progress Tracking
|
||||
- Enrollment status
|
||||
- Lesson completion
|
||||
- Video playback position
|
||||
- Quiz attempts and scores
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Relationships
|
||||
|
||||
```
|
||||
users (1) ──── (many) courses [instructor]
|
||||
users (1) ──── (many) enrollments
|
||||
users (1) ──── (many) lesson_progress
|
||||
users (1) ──── (many) quiz_attempts
|
||||
|
||||
courses (1) ──── (many) chapters
|
||||
courses (1) ──── (many) enrollments
|
||||
courses (1) ──── (many) announcements
|
||||
|
||||
chapters (1) ──── (many) lessons
|
||||
|
||||
lessons (1) ──── (many) lesson_attachments
|
||||
lessons (1) ──── (many) lesson_progress
|
||||
lessons (1) ──── (many) quizzes
|
||||
|
||||
quizzes (1) ──── (many) questions
|
||||
quizzes (1) ──── (many) quiz_attempts
|
||||
|
||||
questions (1) ──── (many) choices
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 How to Visualize
|
||||
|
||||
### Using dbdiagram.io
|
||||
|
||||
1. **Open dbdiagram.io**
|
||||
- Go to [https://dbdiagram.io/d](https://dbdiagram.io/d)
|
||||
|
||||
2. **Import Schema**
|
||||
- Click "Import" button (top right)
|
||||
- Select "From DBML"
|
||||
- Copy contents from [`ERD_v2_improved.txt`](./ERD_v2_improved.txt)
|
||||
- Paste into the editor
|
||||
- Click "Import"
|
||||
|
||||
3. **View Diagram**
|
||||
- Zoom in/out with mouse wheel
|
||||
- Drag to pan
|
||||
- Click tables to highlight relationships
|
||||
- Export as PNG/PDF/SQL
|
||||
|
||||
### Alternative Tools
|
||||
|
||||
- **DBeaver**: Import SQL and generate ER diagram
|
||||
- **MySQL Workbench**: Reverse engineer from database
|
||||
- **pgAdmin**: PostgreSQL schema visualization
|
||||
- **draw.io**: Manual diagram creation
|
||||
|
||||
---
|
||||
|
||||
## 📝 Table Descriptions
|
||||
|
||||
### Core Tables
|
||||
|
||||
#### `users`
|
||||
- User accounts and authentication
|
||||
- Roles: STUDENT, INSTRUCTOR, ADMIN
|
||||
- Profile information
|
||||
|
||||
#### `categories`
|
||||
- Course categorization
|
||||
- Multi-language names
|
||||
|
||||
#### `courses`
|
||||
- Course information and metadata
|
||||
- Pricing and approval status
|
||||
- Instructor ownership
|
||||
|
||||
#### `chapters`
|
||||
- Course structure organization
|
||||
- Sortable within courses
|
||||
|
||||
#### `lessons`
|
||||
- Learning content units
|
||||
- Types: video, pdf, text, quiz
|
||||
- Prerequisites and sequencing
|
||||
|
||||
### Content Tables
|
||||
|
||||
#### `quizzes`
|
||||
- Assessment configuration
|
||||
- Attempt limits and cooldown
|
||||
- Passing score requirements
|
||||
|
||||
#### `questions`
|
||||
- Quiz questions
|
||||
- Types: multiple_choice, true_false
|
||||
- Point values
|
||||
|
||||
#### `choices`
|
||||
- Answer options for questions
|
||||
- Correct answer marking
|
||||
|
||||
#### `lesson_attachments`
|
||||
- Supplementary files
|
||||
- PDFs, documents, code samples
|
||||
- Multi-language descriptions
|
||||
|
||||
### Progress Tables
|
||||
|
||||
#### `enrollments`
|
||||
- Student course registrations
|
||||
- Progress tracking
|
||||
- Certificate issuance
|
||||
|
||||
#### `lesson_progress`
|
||||
- Lesson completion status
|
||||
- Video playback position
|
||||
- Watch history
|
||||
|
||||
#### `quiz_attempts`
|
||||
- Quiz submission records
|
||||
- Scores and answers
|
||||
- Attempt history
|
||||
|
||||
### Communication Tables
|
||||
|
||||
#### `announcements`
|
||||
- Course announcements
|
||||
- Pinning and scheduling
|
||||
- Multi-language content
|
||||
|
||||
#### `announcement_attachments`
|
||||
- Announcement file attachments
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Database Constraints
|
||||
|
||||
### Primary Keys
|
||||
- All tables use auto-incrementing integer PKs
|
||||
|
||||
### Foreign Keys
|
||||
- Maintain referential integrity
|
||||
- Cascade deletes where appropriate
|
||||
|
||||
### Unique Constraints
|
||||
- `(user_id, course_id)` in enrollments
|
||||
- `(user_id, lesson_id)` in lesson_progress
|
||||
- Email in users
|
||||
|
||||
### Check Constraints (via notes)
|
||||
- Progress percentages: 0-100
|
||||
- Scores: 0-100
|
||||
- Sort orders: >= 0
|
||||
- Prices: >= 0
|
||||
|
||||
### Indexes
|
||||
- Single column indexes on FKs
|
||||
- Composite indexes for common queries
|
||||
- JSONB GIN indexes for multi-language fields
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Migration Notes
|
||||
|
||||
### PostgreSQL Extensions Required
|
||||
```sql
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- for text search
|
||||
```
|
||||
|
||||
### JSONB Usage
|
||||
```sql
|
||||
-- Multi-language fields
|
||||
title JSONB -- {"th": "...", "en": "..."}
|
||||
|
||||
-- Query examples
|
||||
WHERE title->>'th' LIKE '%Python%'
|
||||
WHERE title @> '{"en": "Introduction"}'
|
||||
```
|
||||
|
||||
### Soft Delete Pattern
|
||||
```sql
|
||||
-- Recommended for important tables
|
||||
ALTER TABLE courses ADD COLUMN deleted_at TIMESTAMP;
|
||||
CREATE INDEX idx_courses_deleted ON courses(deleted_at) WHERE deleted_at IS NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance Considerations
|
||||
|
||||
### Indexes Created
|
||||
- All foreign keys
|
||||
- Composite indexes for (course_id, sort_order)
|
||||
- User email for login
|
||||
- Course status for filtering
|
||||
- Last watched timestamp for recent videos
|
||||
|
||||
### Query Optimization
|
||||
- Use `SELECT` specific columns
|
||||
- Implement pagination
|
||||
- Cache course lists in Redis
|
||||
- Use database views for complex queries
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [API Endpoints](./api_endpoints.md) - REST API reference
|
||||
- [Edge Cases](../edge_cases_quick_reference.md) - Special scenarios
|
||||
- [Development Setup](../development_setup.md) - Environment configuration
|
||||
|
||||
---
|
||||
|
||||
## 📊 Statistics
|
||||
|
||||
- **Total Tables**: 17
|
||||
- **Total Relationships**: 20+
|
||||
- **Multi-language Fields**: 15+
|
||||
- **Indexed Columns**: 40+
|
||||
- **JSONB Fields**: 15+
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. ✅ Review ERD schema
|
||||
2. ⏳ Create PostgreSQL database
|
||||
3. ⏳ Run migrations
|
||||
4. ⏳ Seed initial data
|
||||
5. ⏳ Implement API endpoints
|
||||
6. ⏳ Test with sample data
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2024-12-24
|
||||
**Version**: 2.0 (Improved)
|
||||
Loading…
Add table
Add a link
Reference in a new issue