354 lines
9.9 KiB
Text
354 lines
9.9 KiB
Text
// 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")
|
|
}
|