Initialize Stage 0: Next.js 16 scaffold with auth and role-based routing
- Next.js 16.2.2 + React 19 + TypeScript + Tailwind v4 - Better Auth with email/password and role system (student/curator/admin) - Prisma 7 schema: User, Session, Account, Verification + full LMS model - Role-based dashboards: student /dashboard, curator /curator/dashboard, admin /admin/dashboard - Auth pages: login, register, verify-email - Better Auth API route handler - Middleware for route protection - Docker Compose with PostgreSQL 16 - Seed script with test users (admin/curator/student) - CLAUDE.md and ROADMAP.md project documentation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
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[]
|
||||
}
|
||||
|
||||
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 Course {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
title String
|
||||
description String?
|
||||
coverImage String?
|
||||
published Boolean @default(false)
|
||||
order Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
modules Module[]
|
||||
enrollments CourseEnrollment[]
|
||||
}
|
||||
|
||||
model Module {
|
||||
id String @id @default(cuid())
|
||||
courseId String
|
||||
title 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())
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([userId, courseId])
|
||||
}
|
||||
|
||||
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?
|
||||
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
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user