Files
lms-sb/prisma/schema.prisma
T
admins 3855bbd4be Add homework review workflow: statuses, audio, file attachments, tabs
- HomeworkSubmission: add status (PENDING/REVIEWING/APPROVED/REJECTED) + statusAt
- HomeworkFeedback: add files (Json) + audioUrl fields
- Curator detail page: meta table, content tabs, feedback history with audio/files
- FeedbackForm: file upload, audio recorder (Web Audio API + S3), action buttons
- AudioRecorder component: record → preview → upload to S3
- ContentTabs: toggle between homework description and lesson content (TipTap read-only)
- Homework list: 4-color status badges with proper filtering
- API routes: /api/curator/upload and /api/curator/audio-upload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 14:01:55 +05:00

313 lines
9.6 KiB
Plaintext

generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
// ─────────────────────────────────────────────
// Better Auth core tables
// ─────────────────────────────────────────────
model User {
id String @id @default(cuid())
name String
email String @unique
emailVerified Boolean @default(false)
image String?
role String @default("student") // student | curator | admin
banned Boolean? @default(false)
banReason String?
banExpires DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions Session[]
accounts Account[]
enrollments CourseEnrollment[]
progress LessonProgress[]
submissions HomeworkSubmission[]
comments LessonComment[]
feedbacks HomeworkFeedback[]
accessLogs AccessLog[] @relation("AccessLogUser")
adminAccessLogs AccessLog[] @relation("AccessLogAdmin")
}
model Session {
id String @id @default(cuid())
userId String
token String @unique
expiresAt DateTime
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Account {
id String @id @default(cuid())
userId String
accountId String
providerId String
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Verification {
id String @id @default(cuid())
identifier String
value String
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ─────────────────────────────────────────────
// LMS core tables
// ─────────────────────────────────────────────
model Category {
id String @id @default(cuid())
title String
slug String @unique
order Int @default(0)
createdAt DateTime @default(now())
courses Course[]
}
model Course {
id String @id @default(cuid())
slug String @unique
title String
description String?
coverImage String?
published Boolean @default(false)
order Int @default(0)
categoryId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
modules Module[]
enrollments CourseEnrollment[]
accessLogs AccessLog[]
}
model Module {
id String @id @default(cuid())
courseId String
title String
description String?
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
lessons Lesson[]
}
model Lesson {
id String @id @default(cuid())
moduleId String
title String
content Json?
kinescopeId String?
order Int @default(0)
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
module Module @relation(fields: [moduleId], references: [id], onDelete: Cascade)
progress LessonProgress[]
quiz Quiz?
homework Homework?
comments LessonComment[]
files LessonFile[]
}
model LessonFile {
id String @id @default(cuid())
lessonId String
name String
url String
size Int
createdAt DateTime @default(now())
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
}
model CourseEnrollment {
userId String
courseId String
enrolledAt DateTime @default(now())
expiresAt DateTime?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@id([userId, courseId])
}
model AccessLog {
id String @id @default(cuid())
courseId String
userId String
action String // "granted" | "revoked"
method String @default("manual")
grantedById String?
note String?
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
user User @relation("AccessLogUser", fields: [userId], references: [id], onDelete: Cascade)
grantedBy User? @relation("AccessLogAdmin", fields: [grantedById], references: [id], onDelete: SetNull)
}
model LessonProgress {
userId String
lessonId String
completedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
@@id([userId, lessonId])
}
// ─────────────────────────────────────────────
// Quizzes
// ─────────────────────────────────────────────
model Quiz {
id String @id @default(cuid())
lessonId String @unique
showAnswers Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
questions QuizQuestion[]
attempts QuizAttempt[]
}
model QuizQuestion {
id String @id @default(cuid())
quizId String
text String
type QuizQuestionType
order Int @default(0)
quiz Quiz @relation(fields: [quizId], references: [id], onDelete: Cascade)
options QuizOption[]
}
model QuizOption {
id String @id @default(cuid())
questionId String
text String
isCorrect Boolean @default(false)
order Int @default(0)
question QuizQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade)
}
model QuizAttempt {
id String @id @default(cuid())
userId String
quizId String
score Int
answers Json
completedAt DateTime @default(now())
quiz Quiz @relation(fields: [quizId], references: [id], onDelete: Cascade)
}
enum QuizQuestionType {
SINGLE
MULTIPLE
TEXT
}
// ─────────────────────────────────────────────
// Homework
// ─────────────────────────────────────────────
model Homework {
id String @id @default(cuid())
lessonId String @unique
description String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
submissions HomeworkSubmission[]
}
model HomeworkSubmission {
id String @id @default(cuid())
homeworkId String
userId String
text String?
files Json?
status String @default("PENDING") // PENDING | REVIEWING | APPROVED | REJECTED
statusAt DateTime?
submittedAt DateTime @default(now())
homework Homework @relation(fields: [homeworkId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
feedbacks HomeworkFeedback[]
}
model HomeworkFeedback {
id String @id @default(cuid())
submissionId String
curatorId String
text String
files Json? // [{name, url, size}]
audioUrl String?
createdAt DateTime @default(now())
submission HomeworkSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
curator User @relation(fields: [curatorId], references: [id], onDelete: Cascade)
}
// ─────────────────────────────────────────────
// Comments
// ─────────────────────────────────────────────
model LessonComment {
id String @id @default(cuid())
lessonId String
userId String
text String
deleted Boolean @default(false)
createdAt DateTime @default(now())
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
// ─────────────────────────────────────────────
// Platform Settings (key-value store)
// ─────────────────────────────────────────────
model Settings {
key String @id
value String @db.Text
updatedAt DateTime @updatedAt
}