feat: add certificates, groups, support system, and moderation
Backend changes: - Add Certificate generation service with beautiful HTML templates - Add CourseGroup, GroupMember, GroupMessage models for group collaboration - Add Homework and HomeworkSubmission models with AI + teacher grading - Add SupportTicket and TicketMessage models for help desk - Add Moderation API for admin/moderator course approval workflow - All new modules: CertificatesModule, GroupsModule, SupportModule, ModerationModule Frontend changes: - Add certificate download button when course completed - Update course page to load enrollment progress from backend - Integrate lesson completion with backend API Database schema now supports: - Course groups with chat functionality - Homework assignments with dual AI/human grading - Support ticket system with admin responses - Full moderation workflow (PENDING_REVIEW -> PUBLISHED/REJECTED) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -42,6 +42,11 @@ model User {
|
||||
purchases Purchase[]
|
||||
reviews Review[]
|
||||
generations CourseGeneration[]
|
||||
groupMembers GroupMember[]
|
||||
groupMessages GroupMessage[]
|
||||
homeworkSubmissions HomeworkSubmission[]
|
||||
supportTickets SupportTicket[]
|
||||
ticketMessages TicketMessage[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@ -183,6 +188,7 @@ model Course {
|
||||
purchases Purchase[]
|
||||
reviews Review[]
|
||||
generation CourseGeneration?
|
||||
groups CourseGroup[]
|
||||
|
||||
// Vector embedding for semantic search
|
||||
embedding Unsupported("vector(1536)")?
|
||||
@ -234,6 +240,7 @@ model Lesson {
|
||||
|
||||
// Relations
|
||||
chapter Chapter @relation(fields: [chapterId], references: [id], onDelete: Cascade)
|
||||
homework Homework[]
|
||||
|
||||
// Vector embedding for semantic search
|
||||
embedding Unsupported("vector(1536)")?
|
||||
@ -431,3 +438,137 @@ model LessonProgress {
|
||||
@@index([enrollmentId])
|
||||
@@map("lesson_progress")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Course Groups & Collaboration
|
||||
// ============================================
|
||||
|
||||
model CourseGroup {
|
||||
id String @id @default(uuid())
|
||||
courseId String @map("course_id")
|
||||
name String
|
||||
description String? @db.Text
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||||
members GroupMember[]
|
||||
messages GroupMessage[]
|
||||
|
||||
@@index([courseId])
|
||||
@@map("course_groups")
|
||||
}
|
||||
|
||||
model GroupMember {
|
||||
id String @id @default(uuid())
|
||||
groupId String @map("group_id")
|
||||
userId String @map("user_id")
|
||||
role String @default("student") // "teacher", "student"
|
||||
|
||||
joinedAt DateTime @default(now()) @map("joined_at")
|
||||
|
||||
group CourseGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([groupId, userId])
|
||||
@@map("group_members")
|
||||
}
|
||||
|
||||
model GroupMessage {
|
||||
id String @id @default(uuid())
|
||||
groupId String @map("group_id")
|
||||
userId String @map("user_id")
|
||||
content String @db.Text
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
group CourseGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([groupId])
|
||||
@@map("group_messages")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Homework & Assignments
|
||||
// ============================================
|
||||
|
||||
model Homework {
|
||||
id String @id @default(uuid())
|
||||
lessonId String @map("lesson_id")
|
||||
title String
|
||||
description String @db.Text
|
||||
dueDate DateTime? @map("due_date")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
|
||||
submissions HomeworkSubmission[]
|
||||
|
||||
@@index([lessonId])
|
||||
@@map("homework")
|
||||
}
|
||||
|
||||
model HomeworkSubmission {
|
||||
id String @id @default(uuid())
|
||||
homeworkId String @map("homework_id")
|
||||
userId String @map("user_id")
|
||||
content String @db.Text
|
||||
|
||||
// AI grading
|
||||
aiScore Int? @map("ai_score") // 0-100
|
||||
aiFeedback String? @db.Text @map("ai_feedback")
|
||||
|
||||
// Teacher grading
|
||||
teacherScore Int? @map("teacher_score") // 0-100
|
||||
teacherFeedback String? @db.Text @map("teacher_feedback")
|
||||
|
||||
submittedAt DateTime @default(now()) @map("submitted_at")
|
||||
gradedAt DateTime? @map("graded_at")
|
||||
|
||||
homework Homework @relation(fields: [homeworkId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([homeworkId, userId])
|
||||
@@map("homework_submissions")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Support Tickets
|
||||
// ============================================
|
||||
|
||||
model SupportTicket {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
title String
|
||||
status String @default("open") // "open", "in_progress", "resolved", "closed"
|
||||
priority String @default("normal") // "low", "normal", "high"
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
messages TicketMessage[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@map("support_tickets")
|
||||
}
|
||||
|
||||
model TicketMessage {
|
||||
id String @id @default(uuid())
|
||||
ticketId String @map("ticket_id")
|
||||
userId String @map("user_id")
|
||||
content String @db.Text
|
||||
isStaff Boolean @default(false) @map("is_staff")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
ticket SupportTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([ticketId])
|
||||
@@map("ticket_messages")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user