project init

This commit is contained in:
2026-02-06 02:17:59 +03:00
commit b9d9b9ed17
129 changed files with 22835 additions and 0 deletions

View File

@ -0,0 +1,27 @@
{
"name": "@coursecraft/database",
"version": "0.1.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "rm -rf dist .turbo",
"dev": "tsc --watch",
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate dev",
"db:migrate:deploy": "prisma migrate deploy",
"db:seed": "ts-node prisma/seed.ts",
"studio": "prisma studio"
},
"dependencies": {
"@prisma/client": "^5.10.0"
},
"devDependencies": {
"@types/node": "^20.11.0",
"prisma": "^5.10.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.0"
}
}

View File

@ -0,0 +1,362 @@
// 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")
// Relations
settings UserSettings?
subscription Subscription?
courses Course[] @relation("AuthoredCourses")
purchases Purchase[]
reviews Review[]
generations CourseGeneration[]
@@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")
}
// ============================================
// 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
PUBLISHED
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 (future)
isPublished Boolean @default(false) @map("is_published")
price Decimal? @db.Decimal(10, 2) // null = private course
currency String @default("USD")
// 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")
// Relations
author User @relation("AuthoredCourses", fields: [authorId], references: [id], onDelete: Cascade)
category Category? @relation(fields: [categoryId], references: [id])
chapters Chapter[]
purchases Purchase[]
reviews Review[]
generation CourseGeneration?
// 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)
// 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")
}

View File

@ -0,0 +1,36 @@
import { PrismaClient } from '@prisma/client';
// Export Prisma Client
export * from '@prisma/client';
// Singleton pattern for Prisma Client
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
// Type exports for convenience
export type {
User,
UserSettings,
Subscription,
Course,
Chapter,
Lesson,
CourseGeneration,
Category,
Purchase,
Review,
} from '@prisma/client';
// Enum re-exports
export { SubscriptionTier, CourseStatus, GenerationStatus } from '@prisma/client';

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}