generated from fahricansecer/boilerplate-be
640 lines
17 KiB
Plaintext
640 lines
17 KiB
Plaintext
// 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])
|
||
}
|