// 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" previewFeatures = ["postgresqlExtensions"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") extensions = [pgvector(map: "vector"), uuidOssp(map: "uuid-ossp")] } // ============================================ // User & Authentication // ============================================ model User { id String @id @default(uuid()) supabaseId String @unique @map("supabase_id") email String @unique name String? avatarUrl String? @map("avatar_url") // Subscription subscriptionTier SubscriptionTier @default(FREE) @map("subscription_tier") // Timestamps createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Role role UserRole @default(USER) // Relations settings UserSettings? subscription Subscription? courses Course[] @relation("AuthoredCourses") enrollments Enrollment[] lessonProgress LessonProgress[] purchases Purchase[] reviews Review[] generations CourseGeneration[] groupMembers GroupMember[] groupMessages GroupMessage[] homeworkSubmissions HomeworkSubmission[] supportTickets SupportTicket[] ticketMessages TicketMessage[] @@map("users") } model UserSettings { id String @id @default(uuid()) userId String @unique @map("user_id") // AI Settings - user can override default model customAiModel String? @map("custom_ai_model") // e.g., "qwen/qwen3-coder-next" // Notification settings emailNotifications Boolean @default(true) @map("email_notifications") marketingEmails Boolean @default(false) @map("marketing_emails") // UI Preferences theme String @default("system") // "light", "dark", "system" language String @default("ru") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("user_settings") } enum UserRole { USER MODERATOR ADMIN } // ============================================ // Subscription & Payments // ============================================ enum SubscriptionTier { FREE PREMIUM PRO } model Subscription { id String @id @default(uuid()) userId String @unique @map("user_id") tier SubscriptionTier @default(FREE) // Stripe stripeCustomerId String? @unique @map("stripe_customer_id") stripeSubscriptionId String? @unique @map("stripe_subscription_id") stripePriceId String? @map("stripe_price_id") // Billing cycle currentPeriodStart DateTime? @map("current_period_start") currentPeriodEnd DateTime? @map("current_period_end") cancelAtPeriodEnd Boolean @default(false) @map("cancel_at_period_end") // Usage tracking coursesCreatedThisMonth Int @default(0) @map("courses_created_this_month") usageResetDate DateTime? @map("usage_reset_date") // Status status String @default("active") // active, canceled, past_due, incomplete createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("subscriptions") } // ============================================ // Courses // ============================================ enum CourseStatus { DRAFT GENERATING PENDING_REVIEW PUBLISHED REJECTED ARCHIVED } model Course { id String @id @default(uuid()) authorId String @map("author_id") // Basic info title String description String? @db.Text slug String @unique coverImage String? @map("cover_image") // Status status CourseStatus @default(DRAFT) // Marketplace isPublished Boolean @default(false) @map("is_published") price Decimal? @db.Decimal(10, 2) // null = free course currency String @default("USD") // Author verification — author checked the content and vouches for quality isVerified Boolean @default(false) @map("is_verified") // Categorization categoryId String? @map("category_id") tags String[] @default([]) difficulty String? // "beginner", "intermediate", "advanced" estimatedHours Int? @map("estimated_hours") // SEO & metadata metaTitle String? @map("meta_title") metaDescription String? @map("meta_description") // Stats viewCount Int @default(0) @map("view_count") enrollmentCount Int @default(0) @map("enrollment_count") averageRating Float? @map("average_rating") // Timestamps publishedAt DateTime? @map("published_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Moderation moderationNote String? @db.Text @map("moderation_note") moderatedAt DateTime? @map("moderated_at") // Relations author User @relation("AuthoredCourses", fields: [authorId], references: [id], onDelete: Cascade) category Category? @relation(fields: [categoryId], references: [id]) chapters Chapter[] enrollments Enrollment[] purchases Purchase[] reviews Review[] generation CourseGeneration? groups CourseGroup[] // Vector embedding for semantic search embedding Unsupported("vector(1536)")? @@index([authorId]) @@index([status]) @@index([isPublished]) @@index([categoryId]) @@map("courses") } model Chapter { id String @id @default(uuid()) courseId String @map("course_id") title String description String? @db.Text order Int @default(0) // Timestamps createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) lessons Lesson[] @@index([courseId]) @@map("chapters") } model Lesson { id String @id @default(uuid()) chapterId String @map("chapter_id") title String content Json? // TipTap JSON content order Int @default(0) // Duration estimate durationMinutes Int? @map("duration_minutes") // Media videoUrl String? @map("video_url") // Timestamps createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations chapter Chapter @relation(fields: [chapterId], references: [id], onDelete: Cascade) homework Homework[] // Vector embedding for semantic search embedding Unsupported("vector(1536)")? @@index([chapterId]) @@map("lessons") } // ============================================ // AI Course Generation // ============================================ enum GenerationStatus { PENDING ANALYZING ASKING_QUESTIONS WAITING_FOR_ANSWERS RESEARCHING GENERATING_OUTLINE GENERATING_CONTENT COMPLETED FAILED CANCELLED } model CourseGeneration { id String @id @default(uuid()) userId String @map("user_id") courseId String? @unique @map("course_id") // Input initialPrompt String @db.Text @map("initial_prompt") // AI Configuration aiModel String @map("ai_model") // Status & Progress status GenerationStatus @default(PENDING) progress Int @default(0) // 0-100 currentStep String? @map("current_step") // Clarifying questions flow questions Json? // Array of questions to ask answers Json? // User's answers // Generated outline (intermediate state) generatedOutline Json? @map("generated_outline") // Error handling errorMessage String? @db.Text @map("error_message") retryCount Int @default(0) @map("retry_count") // Job tracking jobId String? @map("job_id") // BullMQ job ID // Timestamps startedAt DateTime? @map("started_at") completedAt DateTime? @map("completed_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) course Course? @relation(fields: [courseId], references: [id], onDelete: SetNull) @@index([userId]) @@index([status]) @@map("course_generations") } // ============================================ // Marketplace (Future) // ============================================ model Category { id String @id @default(uuid()) name String @unique slug String @unique description String? @db.Text icon String? parentId String? @map("parent_id") order Int @default(0) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id]) children Category[] @relation("CategoryHierarchy") courses Course[] @@map("categories") } model Purchase { id String @id @default(uuid()) userId String @map("user_id") courseId String @map("course_id") // Payment details amount Decimal @db.Decimal(10, 2) currency String @default("USD") stripePaymentId String? @map("stripe_payment_id") // Status status String @default("completed") // pending, completed, refunded // Access accessGrantedAt DateTime @default(now()) @map("access_granted_at") accessExpiresAt DateTime? @map("access_expires_at") // null = lifetime access createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) @@unique([userId, courseId]) @@index([userId]) @@index([courseId]) @@map("purchases") } model Review { id String @id @default(uuid()) userId String @map("user_id") courseId String @map("course_id") rating Int // 1-5 title String? content String? @db.Text // Moderation isApproved Boolean @default(true) @map("is_approved") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) @@unique([userId, courseId]) @@index([courseId]) @@map("reviews") } // ============================================ // Enrollment & Progress // ============================================ model Enrollment { id String @id @default(uuid()) userId String @map("user_id") courseId String @map("course_id") // Progress progress Int @default(0) // 0-100 completedAt DateTime? @map("completed_at") // Certificate certificateUrl String? @map("certificate_url") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) lessons LessonProgress[] @@unique([userId, courseId]) @@index([userId]) @@index([courseId]) @@map("enrollments") } model LessonProgress { id String @id @default(uuid()) userId String @map("user_id") enrollmentId String @map("enrollment_id") lessonId String @map("lesson_id") completedAt DateTime? @map("completed_at") quizScore Int? @map("quiz_score") // 0-100 createdAt DateTime @default(now()) @map("created_at") // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment Enrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade) @@unique([userId, lessonId]) @@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") }