// 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" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============================================ // Core Models // ============================================ model User { id String @id @default(uuid()) email String @unique password String firstName String? lastName String? isActive Boolean @default(true) // Core Relations roles UserRole[] refreshTokens RefreshToken[] // Video SaaS Relations projects Project[] subscriptions Subscription[] creditTransactions CreditTransaction[] templateUsages TemplateUsage[] notifications Notification[] preferences UserPreference? // Multi-tenancy (optional) tenantId String? tenant Tenant? @relation(fields: [tenantId], references: [id]) // Timestamps & Soft Delete createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? @@index([email]) @@index([tenantId]) } model Role { id String @id @default(uuid()) name String @unique description String? isSystem Boolean @default(false) // Relations users UserRole[] permissions RolePermission[] // Timestamps & Soft Delete createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? @@index([name]) } model Permission { id String @id @default(uuid()) name String @unique description String? resource String // e.g., "users", "posts" action String // e.g., "create", "read", "update", "delete" // Relations roles RolePermission[] // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([resource, action]) @@index([resource]) } // Many-to-many: User <-> Role model UserRole { id String @id @default(uuid()) userId String roleId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([userId, roleId]) @@index([userId]) @@index([roleId]) } // Many-to-many: Role <-> Permission model RolePermission { id String @id @default(uuid()) roleId String permissionId String role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([roleId, permissionId]) @@index([roleId]) @@index([permissionId]) } // ============================================ // Authentication // ============================================ model RefreshToken { id String @id @default(uuid()) token String @unique userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) expiresAt DateTime createdAt DateTime @default(now()) @@index([token]) @@index([userId]) } // ============================================ // Multi-tenancy (Optional) // ============================================ model Tenant { id String @id @default(uuid()) name String slug String @unique isActive Boolean @default(true) // Relations users User[] // Timestamps & Soft Delete createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? @@index([slug]) } // ============================================ // i18n / Translations (Optional - DB driven) // ============================================ model Translation { id String @id @default(uuid()) key String locale String // e.g., "en", "tr", "de" value String namespace String @default("common") // e.g., "common", "errors", "validation" // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([key, locale, namespace]) @@index([key]) @@index([locale]) @@index([namespace]) } // ============================================ // Video SaaS — Enums // ============================================ enum ProjectStatus { DRAFT GENERATING_SCRIPT PENDING GENERATING_MEDIA RENDERING COMPLETED FAILED } enum AspectRatio { PORTRAIT_9_16 SQUARE_1_1 LANDSCAPE_16_9 } enum VideoStyle { CINEMATIC DOCUMENTARY EDUCATIONAL STORYTELLING NEWS PROMOTIONAL ARTISTIC MINIMALIST } enum MediaType { VIDEO_CLIP AUDIO_NARRATION AUDIO_MUSIC SUBTITLE THUMBNAIL FINAL_VIDEO } enum TransitionType { CUT FADE DISSOLVE SLIDE_LEFT SLIDE_RIGHT SLIDE_UP ZOOM_IN ZOOM_OUT WIPE } enum RenderJobStatus { QUEUED PROCESSING COMPLETED FAILED CANCELLED } enum RenderStage { VIDEO_GENERATION TTS_GENERATION MUSIC_GENERATION AMBIENT_GENERATION MEDIA_MERGE SUBTITLE_OVERLAY FINALIZATION UPLOAD } enum SourceType { MANUAL X_TWEET YOUTUBE } // ============================================ // Video SaaS — Project & Scenes // ============================================ model Project { id String @id @default(uuid()) title String @db.VarChar(200) description String? @db.VarChar(1000) prompt String @db.VarChar(2000) // AI Generated Script scriptJson Json? // Gemini API raw JSON output scriptVersion Int @default(0) // Configuration language String @default("tr") @db.VarChar(5) // ISO 639-1 aspectRatio AspectRatio @default(PORTRAIT_9_16) videoStyle VideoStyle @default(CINEMATIC) targetDuration Int @default(60) // saniye // SEO & Social Content (skill-enhanced) seoKeywords String[] // Hedeflenen SEO anahtar kelimeler seoTitle String? @db.VarChar(200) seoDescription String? @db.VarChar(500) seoSchemaJson Json? // VideoObject structured data socialContent Json? // { youtubeTitle, tiktokCaption, instagramCaption, twitterText } referenceUrl String? @db.VarChar(500) // İçerik Kaynağı sourceType SourceType @default(MANUAL) // MANUAL, X_TWEET, YOUTUBE sourceTweetData Json? // X/Twitter tweet verisi (id, author, metrics, media) // Processing status ProjectStatus @default(DRAFT) progress Int @default(0) // 0-100 errorMessage String? // Output finalVideoUrl String? thumbnailUrl String? // Stats creditsUsed Int @default(0) viewCount Int @default(0) // Template Support isTemplate Boolean @default(false) templateId String? // Hangi şablondan klonlandı? template Template? @relation("ClonedFrom", fields: [templateId], references: [id]) // Relations userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) scenes Scene[] mediaAssets MediaAsset[] renderJobs RenderJob[] templateEntry Template? @relation("SourceProject") // Timestamps & Soft Delete createdAt DateTime @default(now()) updatedAt DateTime @updatedAt completedAt DateTime? deletedAt DateTime? @@index([userId]) @@index([status]) @@index([sourceType]) @@index([isTemplate]) @@index([createdAt]) } model Scene { id String @id @default(uuid()) order Int // Sahne sırası (1, 2, 3...) title String? @db.VarChar(200) // Content narrationText String @db.Text // Hedef dildeki anlatım metni visualPrompt String @db.Text // İngilizce — Higgsfield AI prompt subtitleText String? @db.Text // Ekranda görünecek altyazı // Timing duration Float @default(5.0) // saniye transitionType TransitionType @default(CUT) // Relations projectId String project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) mediaAssets MediaAsset[] // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([projectId]) @@index([order]) } // ============================================ // Video SaaS — Media & Render // ============================================ model MediaAsset { id String @id @default(uuid()) type MediaType // Storage s3Key String? // Cloudflare R2 / S3 object key s3Bucket String? @db.VarChar(100) url String? // Public CDN URL // Metadata fileName String? @db.VarChar(255) mimeType String? @db.VarChar(100) sizeBytes BigInt? durationMs Int? // Medya süresi (video/audio için) // AI Provider Info aiProvider String? @db.VarChar(50) // higgsfield, elevenlabs, suno aiJobId String? // Dış API job ID'si // Relations projectId String project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) sceneId String? // null = proje genelinde (müzik, final vb.) scene Scene? @relation(fields: [sceneId], references: [id], onDelete: SetNull) // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([projectId]) @@index([sceneId]) @@index([type]) } model RenderJob { id String @id @default(uuid()) status RenderJobStatus @default(QUEUED) currentStage RenderStage? // Queue Info queueName String @default("video-generation") @db.VarChar(100) bullJobId String? @db.VarChar(100) // BullMQ job ID // Retry attemptNumber Int @default(1) maxAttempts Int @default(3) // Processing workerHostname String? @db.VarChar(100) processingTimeMs Int? // Toplam render süresi errorMessage String? // Output finalVideoUrl String? finalVideoS3Key String? // Relations projectId String project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) logs RenderLog[] // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt startedAt DateTime? completedAt DateTime? @@index([projectId]) @@index([status]) @@index([bullJobId]) } model RenderLog { id String @id @default(uuid()) stage RenderStage level String @default("info") @db.VarChar(10) // info, warn, error message String @db.Text durationMs Int? // Bu aşamanın süresi metadata Json? // Ek JSON veri // Relations renderJobId String renderJob RenderJob @relation(fields: [renderJobId], references: [id], onDelete: Cascade) // Timestamps createdAt DateTime @default(now()) @@index([renderJobId]) @@index([stage]) } // ============================================ // Video SaaS — Template Marketplace // ============================================ model Template { id String @id @default(uuid()) // Display title String @db.VarChar(200) description String? @db.VarChar(500) thumbnailUrl String? previewVideoUrl String? // Categorization category String @default("general") @db.VarChar(50) tags String[] // PostgreSQL array language String @default("tr") @db.VarChar(5) // Source originalProjectId String @unique originalProject Project @relation("SourceProject", fields: [originalProjectId], references: [id]) // Stats usageCount Int @default(0) rating Float @default(0) ratingCount Int @default(0) isFeatured Boolean @default(false) isPublished Boolean @default(true) // Relations clonedProjects Project[] @relation("ClonedFrom") usages TemplateUsage[] // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([category]) @@index([language]) @@index([isFeatured]) @@index([usageCount]) } model TemplateUsage { id String @id @default(uuid()) templateId String template Template @relation(fields: [templateId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) clonedProjectId String? // Oluşturulan projenin ID'si createdAt DateTime @default(now()) @@index([templateId]) @@index([userId]) } // ============================================ // Video SaaS — Billing & Credits // ============================================ model Plan { id String @id @default(uuid()) name String @unique @db.VarChar(50) // free, pro, business displayName String @db.VarChar(100) description String? @db.VarChar(500) // Pricing monthlyPrice Int @default(0) // cent cinsinden (1900 = $19) yearlyPrice Int? // Yıllık indirimli fiyat currency String @default("usd") @db.VarChar(3) // Limits monthlyCredits Int @default(3) maxDuration Int @default(30) // saniye maxResolution String @default("720p") @db.VarChar(10) maxProjects Int @default(5) // Stripe stripePriceId String? @db.VarChar(100) stripeYearlyPriceId String? @db.VarChar(100) // Features features Json? // { "templates": true, "priorityQueue": false, ... } isActive Boolean @default(true) sortOrder Int @default(0) // Relations subscriptions Subscription[] // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([name]) @@index([isActive]) } model Subscription { id String @id @default(uuid()) status String @default("active") @db.VarChar(20) // active, canceled, past_due, trialing // Stripe stripeSubscriptionId String? @unique @db.VarChar(100) stripeCustomerId String? @db.VarChar(100) // Billing Cycle currentPeriodStart DateTime? currentPeriodEnd DateTime? cancelAtPeriodEnd Boolean @default(false) // Relations userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) planId String plan Plan @relation(fields: [planId], references: [id]) // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt canceledAt DateTime? @@index([userId]) @@index([planId]) @@index([stripeSubscriptionId]) @@index([status]) } model CreditTransaction { id String @id @default(uuid()) amount Int // Pozitif: ekleme, Negatif: harcama type String @db.VarChar(30) // grant, usage, refund, bonus description String? @db.VarChar(200) // Relations userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) projectId String? // Hangi projede harcandı // Balance Snapshot balanceAfter Int @default(0) // Timestamps createdAt DateTime @default(now()) @@index([userId]) @@index([type]) @@index([createdAt]) } // ============================================ // Video SaaS — User Preferences & Notifications // ============================================ model UserPreference { id String @id @default(uuid()) // Defaults defaultLanguage String @default("tr") @db.VarChar(5) defaultVideoStyle VideoStyle @default(CINEMATIC) defaultDuration Int @default(60) // UI theme String @default("dark") @db.VarChar(10) emailNotifications Boolean @default(true) pushNotifications Boolean @default(true) // Relations userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) // Timestamps createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) } model Notification { id String @id @default(uuid()) type String @db.VarChar(30) // render_complete, render_failed, credit_low, system title String @db.VarChar(200) message String? @db.Text isRead Boolean @default(false) metadata Json? // { projectId, renderJobId, ... } // Relations userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) // Timestamps createdAt DateTime @default(now()) readAt DateTime? @@index([userId]) @@index([isRead]) @@index([createdAt]) }