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:
2026-04-07 10:32:37 +05:00
commit 80ca4b2d9d
41 changed files with 10138 additions and 0 deletions
+266
View File
@@ -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)
}