// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============================================ // User Management // ============================================ model Role { id Int @id @default(autoincrement()) code String @unique @db.VarChar(50) name Json // { th: "", en: "" } description Json? created_at DateTime @default(now()) updated_at DateTime @updatedAt users User[] @@map("roles") } model User { id Int @id @default(autoincrement()) username String @unique @db.VarChar(50) email String @unique @db.VarChar(255) password String @db.VarChar(255) role_id Int is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt role Role @relation(fields: [role_id], references: [id]) profile Profile? // Relations created_courses Course[] @relation("CourseCreator") instructor_courses CourseInstructor[] enrollments Enrollment[] lesson_progress LessonProgress[] quiz_attempts QuizAttempt[] certificates Certificate[] @@index([email]) @@index([role_id]) @@map("users") } model Profile { id Int @id @default(autoincrement()) user_id Int @unique first_name String? @db.VarChar(100) last_name String? @db.VarChar(100) phone String? @db.VarChar(20) avatar_url String? @db.VarChar(500) bio Json? // { th: "", en: "" } birth_date DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt user User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@map("profiles") } // ============================================ // Course Management // ============================================ enum CourseStatus { DRAFT PENDING_APPROVAL APPROVED REJECTED ARCHIVED } model Category { id Int @id @default(autoincrement()) code String @unique @db.VarChar(50) name Json // { th: "", en: "" } description Json? icon_url String? @db.VarChar(500) is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt courses Course[] @@map("categories") } model Course { id Int @id @default(autoincrement()) title Json // { th: "", en: "" } description Json thumbnail_url String? @db.VarChar(500) price Decimal @default(0) @db.Decimal(10, 2) is_free Boolean @default(false) have_certificate Boolean @default(false) status CourseStatus @default(DRAFT) category_id Int created_by Int rejection_reason String? @db.Text is_deleted Boolean @default(false) deleted_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt category Category @relation(fields: [category_id], references: [id]) creator User @relation("CourseCreator", fields: [created_by], references: [id]) chapters Chapter[] instructors CourseInstructor[] enrollments Enrollment[] @@index([category_id]) @@index([status]) @@index([created_by]) @@index([created_at]) @@map("courses") } model CourseInstructor { id Int @id @default(autoincrement()) course_id Int user_id Int is_primary Boolean @default(false) created_at DateTime @default(now()) course Course @relation(fields: [course_id], references: [id], onDelete: Cascade) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@unique([course_id, user_id]) @@index([course_id]) @@index([user_id]) @@map("course_instructors") } model Chapter { id Int @id @default(autoincrement()) course_id Int title Json // { th: "", en: "" } description Json? order Int created_at DateTime @default(now()) updated_at DateTime @updatedAt course Course @relation(fields: [course_id], references: [id], onDelete: Cascade) lessons Lesson[] @@index([course_id]) @@index([order]) @@map("chapters") } // ============================================ // Lessons // ============================================ enum LessonType { VIDEO TEXT PDF QUIZ } model Lesson { id Int @id @default(autoincrement()) chapter_id Int title Json // { th: "", en: "" } description Json? type LessonType content Json? // For TEXT type video_url String? @db.VarChar(500) video_duration Int? // in seconds order Int is_preview Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt chapter Chapter @relation(fields: [chapter_id], references: [id], onDelete: Cascade) attachments Attachment[] prerequisites LessonPrerequisite[] @relation("LessonPrerequisites") required_for LessonPrerequisite[] @relation("RequiredForLessons") quiz Quiz? progress LessonProgress[] @@index([chapter_id]) @@index([order]) @@map("lessons") } model LessonPrerequisite { id Int @id @default(autoincrement()) lesson_id Int prerequisite_lesson_id Int created_at DateTime @default(now()) lesson Lesson @relation("LessonPrerequisites", fields: [lesson_id], references: [id], onDelete: Cascade) prerequisite_lesson Lesson @relation("RequiredForLessons", fields: [prerequisite_lesson_id], references: [id], onDelete: Cascade) @@unique([lesson_id, prerequisite_lesson_id]) @@index([lesson_id]) @@index([prerequisite_lesson_id]) @@map("lesson_prerequisites") } model Attachment { id Int @id @default(autoincrement()) lesson_id Int filename String @db.VarChar(255) original_name String @db.VarChar(255) file_url String @db.VarChar(500) file_size BigInt mime_type String @db.VarChar(100) created_at DateTime @default(now()) lesson Lesson @relation(fields: [lesson_id], references: [id], onDelete: Cascade) @@index([lesson_id]) @@map("attachments") } // ============================================ // Quiz System // ============================================ enum ScorePolicy { HIGHEST LATEST AVERAGE } model Quiz { id Int @id @default(autoincrement()) lesson_id Int @unique title Json // { th: "", en: "" } description Json? passing_score Int @default(70) time_limit Int? // in minutes max_attempts Int @default(3) cooldown_hours Int @default(24) score_policy ScorePolicy @default(HIGHEST) shuffle_questions Boolean @default(true) shuffle_choices Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt lesson Lesson @relation(fields: [lesson_id], references: [id], onDelete: Cascade) questions QuizQuestion[] attempts QuizAttempt[] @@map("quizzes") } model QuizQuestion { id Int @id @default(autoincrement()) quiz_id Int question Json // { th: "", en: "" } choices Json // [{ th: "", en: "", is_correct: boolean }] points Int @default(1) order Int created_at DateTime @default(now()) updated_at DateTime @updatedAt quiz Quiz @relation(fields: [quiz_id], references: [id], onDelete: Cascade) @@index([quiz_id]) @@index([order]) @@map("quiz_questions") } model QuizAttempt { id Int @id @default(autoincrement()) quiz_id Int user_id Int answers Json // [{ question_id: int, choice_index: int }] score Int passed Boolean time_spent Int? // in seconds started_at DateTime completed_at DateTime @default(now()) quiz Quiz @relation(fields: [quiz_id], references: [id], onDelete: Cascade) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@index([quiz_id]) @@index([user_id]) @@index([completed_at]) @@map("quiz_attempts") } // ============================================ // Progress Tracking // ============================================ model Enrollment { id Int @id @default(autoincrement()) user_id Int course_id Int enrolled_at DateTime @default(now()) completed_at DateTime? progress_percent Int @default(0) last_accessed_at DateTime @default(now()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) course Course @relation(fields: [course_id], references: [id], onDelete: Cascade) @@unique([user_id, course_id]) @@index([user_id]) @@index([course_id]) @@map("enrollments") } model LessonProgress { id Int @id @default(autoincrement()) user_id Int lesson_id Int is_completed Boolean @default(false) video_progress Int @default(0) // seconds watched completed_at DateTime? last_watched_at DateTime @default(now()) created_at DateTime @default(now()) updated_at DateTime @updatedAt user User @relation(fields: [user_id], references: [id], onDelete: Cascade) lesson Lesson @relation(fields: [lesson_id], references: [id], onDelete: Cascade) @@unique([user_id, lesson_id]) @@index([user_id]) @@index([lesson_id]) @@map("lesson_progress") } model Certificate { id Int @id @default(autoincrement()) user_id Int course_id Int certificate_url String @db.VarChar(500) issued_at DateTime @default(now()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@unique([user_id, course_id]) @@index([user_id]) @@map("certificates") }