Files
Content-Hunter_BE/prisma/schema.prisma
Harun CAN fc88faddb9
All checks were successful
Backend Deploy 🚀 / build-and-deploy (push) Successful in 2m1s
main
2026-02-10 12:27:14 +03:00

2217 lines
55 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Content Hunter - Complete Database Schema v2.0
// Includes: Neuro Marketing, SEO, Source Accounts, Video/Thumbnail, Building Blocks
// Version: 2.0.0
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ============================================
// ENUMS
// ============================================
enum UserPlan {
FREE
STARTER
PRO
ULTIMATE
ENTERPRISE
}
enum ContentType {
BLOG
TWITTER
INSTAGRAM
LINKEDIN
FACEBOOK
TIKTOK
YOUTUBE
THREADS
PINTEREST
}
enum MasterContentType {
BLOG
NEWSLETTER
PODCAST_SCRIPT
VIDEO_SCRIPT
THREAD
}
enum ContentStatus {
DRAFT
REVIEW
APPROVED
SCHEDULED
PUBLISHED
FAILED
}
enum TrendSource {
GOOGLE_TRENDS
TWITTER
REDDIT
NEWSAPI
RSS
YOUTUBE
CUSTOM
}
enum TrendStatus {
NEW
REVIEWED
SELECTED
DISMISSED
EXPIRED
}
enum MediaType {
IMAGE
VIDEO
GIF
AUDIO
}
enum SocialPlatform {
TWITTER
INSTAGRAM
LINKEDIN
FACEBOOK
TIKTOK
YOUTUBE
THREADS
PINTEREST
}
enum AuthMethod {
OAUTH
CREDENTIALS
}
enum ScheduleStatus {
SCHEDULED
PUBLISHING
PUBLISHED
FAILED
CANCELLED
}
enum WorkspaceRole {
OWNER
ADMIN
EDITOR
VIEWER
}
enum ApprovalStatus {
PENDING
APPROVED
REJECTED
}
enum CreditTransactionType {
PURCHASE
SPEND
REFUND
BONUS
RESET
ADMIN_ADJUST
}
enum CreditCategory {
TREND_SCAN
DEEP_RESEARCH
MASTER_CONTENT
BUILDING_BLOCKS
PLATFORM_CONTENT
IMAGE_GENERATION
VIDEO_SCRIPT
THUMBNAIL
SEO_OPTIMIZATION
NEURO_ANALYSIS
SOURCE_ANALYSIS
AUTO_PUBLISH
}
enum BuildingBlockType {
HOOK
PAIN_POINT
PARADOX
QUOTE
STATISTIC
TRANSFORMATION_ARC
OBJECTION_HANDLER
CTA
METAPHOR
STORY
INSIGHT
}
enum WritingStyleType {
PATIENT_OBSERVER
HUSTLER_ACHIEVER
CONTRARIAN_THINKER
CUSTOM
}
enum PsychologyTriggerCategory {
CURIOSITY
SOCIAL_PROOF
SCARCITY
URGENCY
AUTHORITY
RECIPROCITY
LOSS_AVERSION
PATTERN_INTERRUPT
EMOTIONAL_RESONANCE
CONTROVERSY
}
enum ContentCategoryType {
PROVEN
EXPERIMENT
}
enum SourceType {
ARTICLE
STUDY
REPORT
SOCIAL_POST
VIDEO
PODCAST
BOOK
OFFICIAL_STATEMENT
RESEARCH_PAPER
NEWS
CUSTOM
}
enum VerificationLevel {
VERIFIED // 100% kaynak doğrulamalı
OPINION // Yazarın görüşü
INSPIRED // Kaynak içerikten ilham alınmış
}
enum InstagramFormat {
STATIC_IMAGE
CAROUSEL
REEL
STORY
}
// İçerik üretim dilleri (10 dil desteği)
enum ContentLanguage {
EN // English
TR // Turkish
ES // Spanish
FR // French
DE // German
ZH // Chinese (Mandarin)
PT // Portuguese
AR // Arabic
RU // Russian
JA // Japanese
}
// ============================================
// USER & AUTHENTICATION
// ============================================
model User {
id String @id @default(uuid())
email String @unique
password String
firstName String?
lastName String?
avatar String?
isActive Boolean @default(true)
emailVerified Boolean @default(false)
// Subscription & Credits
plan UserPlan @default(FREE)
credits Int @default(50)
creditsResetAt DateTime?
subscription Subscription?
stripeCustomerId String? @unique
// Settings
timezone String @default("UTC")
language String @default("en")
// Relations
roles UserRole[]
refreshTokens RefreshToken[]
brandVoice BrandVoice?
writingStyles WritingStyle[]
workspaces WorkspaceMember[]
ownedWorkspaces Workspace[] @relation("WorkspaceOwner")
niches Niche[]
masterContents MasterContent[]
contents Content[]
socialAccounts SocialAccount[]
sourceAccounts SourceAccount[]
templates Template[]
assets Asset[]
media Media[]
scheduledPosts ScheduledPost[]
apiKeys ApiKey[]
creditTransactions CreditTransaction[]
deepResearches DeepResearch[]
contentCalendars ContentCalendar[]
videoContents VideoContent[]
workflowTemplates WorkflowTemplate[]
contentSessions ContentSession[]
// Approval workflow
approvalRequests ContentApproval[] @relation("ApprovalRequests")
approvalReviews ContentApproval[] @relation("ApprovalReviews")
// Multi-tenancy
tenantId String?
tenant Tenant? @relation(fields: [tenantId], references: [id])
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
lastLoginAt DateTime?
@@index([email])
@@index([tenantId])
@@index([plan])
}
model Role {
id String @id @default(uuid())
name String @unique
description String?
isSystem Boolean @default(false)
users UserRole[]
permissions RolePermission[]
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
action String
roles RolePermission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([resource, action])
@@index([resource])
}
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])
}
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])
}
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
// ============================================
model Tenant {
id String @id @default(uuid())
name String
slug String @unique
domain String? @unique
logo String?
isActive Boolean @default(true)
brandColor String?
customCss String? @db.Text
users User[]
workspaces Workspace[]
translations Translation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([slug])
@@index([domain])
}
model Translation {
id String @id @default(uuid())
key String
locale String
value String
namespace String @default("common")
tenantId String?
tenant Tenant? @relation(fields: [tenantId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([key, locale, namespace, tenantId])
@@index([key])
@@index([locale])
}
// ============================================
// CREDITS & BILLING
// ============================================
model SubscriptionPlan {
id String @id @default(uuid())
name UserPlan @unique
displayName String
description String?
monthlyPrice Decimal @default(0) @db.Decimal(10, 2)
yearlyPrice Decimal @default(0) @db.Decimal(10, 2)
currency String @default("USD")
stripeMonthlyPriceId String?
stripeYearlyPriceId String?
monthlyCredits Int @default(50)
maxWorkspaces Int @default(1)
maxTeamMembers Int @default(1)
maxNiches Int @default(1)
maxTemplates Int @default(5)
maxScheduledPosts Int @default(10)
maxSocialAccounts Int @default(2)
maxSourceAccounts Int @default(5)
maxStorageMb Int @default(100)
features Json @default("{}")
isActive Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscriptions Subscription[]
}
model Subscription {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id])
planId String
plan SubscriptionPlan @relation(fields: [planId], references: [id])
stripeSubscriptionId String? @unique
status String
currentPeriodStart DateTime
currentPeriodEnd DateTime
cancelAtPeriodEnd Boolean @default(false)
canceledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([status])
@@index([stripeSubscriptionId])
}
model CreditTransaction {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
amount Int
balanceAfter Int
type CreditTransactionType
category CreditCategory?
description String?
referenceId String?
referenceType String?
adminUserId String?
adminNote String?
metadata Json?
createdAt DateTime @default(now())
@@index([userId])
@@index([type])
@@index([category])
@@index([createdAt])
}
// ============================================
// BRAND VOICE & WRITING STYLES
// ============================================
model BrandVoice {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String?
description String? @db.Text
tone String[] @default([])
vocabulary String[] @default([])
avoidWords String[] @default([])
sampleContent String? @db.Text
aiProfile Json?
aiProfileVersion String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model WritingStyle {
id String @id @default(uuid())
userId String?
user User? @relation(fields: [userId], references: [id])
type WritingStyleType
name String
traits String[] @default([])
tone String?
vocabulary String[] @default([])
avoidWords String[] @default([])
examples String? @db.Text
bestFor String[] @default([])
// Advanced style attributes
sentenceLength String?
emojiUsage String?
hashtagStyle String?
structurePreference String?
engagementStyle String?
signatureElements String[] @default([])
preferredPhrases String[] @default([])
isDefault Boolean @default(false)
isSystem Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
masterContents MasterContent[]
@@index([userId])
@@index([type])
}
// ============================================
// WORKSPACE & COLLABORATION
// ============================================
model Workspace {
id String @id @default(uuid())
name String
slug String @unique
description String?
logo String?
isActive Boolean @default(true)
tenantId String?
tenant Tenant? @relation(fields: [tenantId], references: [id])
ownerId String
owner User @relation("WorkspaceOwner", fields: [ownerId], references: [id])
settings Json @default("{}")
requireApproval Boolean @default(false)
members WorkspaceMember[]
niches Niche[]
masterContents MasterContent[]
contents Content[]
templates Template[]
socialAccounts SocialAccount[]
sourceAccounts SourceAccount[]
approvalWorkflows ApprovalWorkflow[]
calendars ContentCalendar[]
webhooks Webhook[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([slug])
@@index([ownerId])
}
model WorkspaceMember {
id String @id @default(uuid())
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role WorkspaceRole @default(VIEWER)
permissions Json?
joinedAt DateTime @default(now())
@@unique([workspaceId, userId])
@@index([workspaceId])
@@index([userId])
}
model ApprovalWorkflow {
id String @id @default(uuid())
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String
description String?
isActive Boolean @default(true)
isDefault Boolean @default(false)
steps ApprovalStep[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([workspaceId])
}
model ApprovalStep {
id String @id @default(uuid())
workflowId String
workflow ApprovalWorkflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
order Int
name String?
approverRole WorkspaceRole?
approverUserId String?
createdAt DateTime @default(now())
@@index([workflowId])
}
model ContentApproval {
id String @id @default(uuid())
contentId String
content Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
// Workflow step
stepOrder Int @default(0)
status ApprovalStatus @default(PENDING)
// Requester
requestedById String
requestedBy User @relation("ApprovalRequests", fields: [requestedById], references: [id])
notes String? @db.Text
// Reviewer
reviewedById String?
reviewedBy User? @relation("ApprovalReviews", fields: [reviewedById], references: [id])
reviewedAt DateTime?
feedback String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contentId])
@@index([status])
@@index([requestedById])
}
// ============================================
// NICHE & TRENDS
// ============================================
model Niche {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
name String
description String? @db.Text
keywords String[] @default([])
sources NicheSource[]
scanFrequency String @default("daily")
autoScan Boolean @default(false)
autoScanCron String?
lastScannedAt DateTime?
trends Trend[]
masterContents MasterContent[]
contents Content[]
seoKeywords SeoKeyword[]
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([userId])
@@index([workspaceId])
}
model NicheSource {
id String @id @default(uuid())
nicheId String
niche Niche @relation(fields: [nicheId], references: [id], onDelete: Cascade)
type String
url String
name String?
isActive Boolean @default(true)
lastFetchedAt DateTime?
lastError String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([nicheId])
}
model Trend {
id String @id @default(uuid())
nicheId String
niche Niche @relation(fields: [nicheId], references: [id], onDelete: Cascade)
title String
description String? @db.Text
source TrendSource
sourceUrl String?
sourceData Json?
score Float @default(0)
velocity Float?
volume Int?
keywords String[] @default([])
relatedTopics String[] @default([])
sentiment String?
status TrendStatus @default(NEW)
masterContents MasterContent[]
contents Content[]
scanId String?
scan TrendScan? @relation(fields: [scanId], references: [id])
discoveredAt DateTime @default(now())
updatedAt DateTime @updatedAt
expiresAt DateTime?
@@index([nicheId])
@@index([source])
@@index([score])
@@index([status])
@@index([discoveredAt])
@@unique([nicheId, title])
}
model TrendScan {
id String @id @default(uuid())
nicheId String
status String @default("pending")
trendsFound Int @default(0)
newTrends Int @default(0)
sources String[] @default([])
errors Json?
creditsUsed Int @default(0)
trends Trend[]
startedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
@@index([nicheId])
@@index([status])
@@index([createdAt])
}
// ============================================
// NEURO MARKETING & PSYCHOLOGY
// ============================================
model PsychologyTrigger {
id String @id @default(uuid())
name String @unique
category PsychologyTriggerCategory
description String @db.Text
examples String[] @default([])
bestFor String[] @default([])
// Templates/phrases for this trigger
templates String[] @default([])
usageCount Int @default(0)
avgEngagement Float?
isSystem Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([category])
}
model EmotionalHook {
id String @id @default(uuid())
emotion String // curiosity, fear, excitement, anger, joy
hookType String // question, statement, story, statistic
template String @db.Text
examples String[] @default([])
usageCount Int @default(0)
avgEngagement Float?
isSystem Boolean @default(true)
createdAt DateTime @default(now())
@@index([emotion])
@@index([hookType])
}
model ContentPsychology {
id String @id @default(uuid())
contentId String @unique
content Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
triggersUsed String[] @default([])
hookType String?
emotionalTone String?
// Scoring
engagementScore Float?
viralPotential Float?
controversyLevel Int? // 1-10
// AI analysis
aiAnalysis Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ============================================
// SEO INTELLIGENCE
// ============================================
model SeoKeyword {
id String @id @default(uuid())
keyword String
nicheId String?
niche Niche? @relation(fields: [nicheId], references: [id])
searchVolume Int?
difficulty Float?
cpc Float?
trend String?
relatedKeywords String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([keyword, nicheId])
@@index([nicheId])
@@index([keyword])
}
model ContentSeo {
id String @id @default(uuid())
contentId String @unique
content Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
primaryKeyword String?
secondaryKeywords String[] @default([])
keywordDensity Float?
metaTitle String?
metaDescription String?
slugSuggestion String?
seoScore Int?
readabilityScore Float?
improvements Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ============================================
// SOURCE ACCOUNTS & INSPIRATION
// ============================================
model SourceAccount {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
platform SocialPlatform
username String
displayName String?
profileUrl String
bio String? @db.Text
followersCount Int?
avgEngagement Float?
posts SourcePost[]
isActive Boolean @default(true)
lastFetchedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, platform, username])
@@index([userId])
@@index([workspaceId])
@@index([platform])
}
model SourcePost {
id String @id @default(uuid())
accountId String
account SourceAccount @relation(fields: [accountId], references: [id], onDelete: Cascade)
platformPostId String
content String @db.Text
mediaUrls String[] @default([])
likes Int?
comments Int?
shares Int?
engagementRate Float?
analysis SourcePostAnalysis?
postedAt DateTime?
fetchedAt DateTime @default(now())
@@unique([accountId, platformPostId])
@@index([accountId])
@@index([engagementRate])
}
model SourcePostAnalysis {
id String @id @default(uuid())
postId String @unique
post SourcePost @relation(fields: [postId], references: [id], onDelete: Cascade)
// Extracted elements (Viral Post Breakdown)
hook String? @db.Text
pain String? @db.Text
payoff String? @db.Text
cta String? @db.Text
mainIdea String? @db.Text
psychologyTriggers String[] @default([])
structure Json?
// Reusable skeleton
structureSkeleton Json?
// Generated original content (plagiarism-free)
inspiredContent String? @db.Text
isProcessed Boolean @default(false)
processedAt DateTime?
createdAt DateTime @default(now())
}
model ViralPostAnalysis {
id String @id @default(uuid())
userId String
originalPost String @db.Text
sourceUrl String?
platform SocialPlatform
engagementCount Int?
// Analysis results
hook String? @db.Text
pain String? @db.Text
payoff String? @db.Text
cta String? @db.Text
psychologyTriggers String[] @default([])
// Reusable template
structureSkeleton Json?
createdAt DateTime @default(now())
@@index([userId])
@@index([platform])
}
// ============================================
// MASTER CONTENT & BUILDING BLOCKS
// ============================================
model MasterContent {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
nicheId String?
niche Niche? @relation(fields: [nicheId], references: [id])
trendId String?
trend Trend? @relation(fields: [trendId], references: [id])
type MasterContentType
title String
body String @db.Text
summary String? @db.Text
// Writing style used
writingStyleId String?
writingStyle WritingStyle? @relation(fields: [writingStyleId], references: [id])
// Research reference
researchNotes String? @db.Text
targetAudience String? @db.Text
outline String[] @default([])
status ContentStatus @default(DRAFT)
// Building blocks (denormalized for easy access)
hooks String[] @default([])
painPoints String[] @default([])
paradoxes String[] @default([])
quotes String[] @default([])
// Building blocks extracted
buildingBlocks BuildingBlock[]
// Derivatives generated
contents Content[]
// Video content if applicable
videoContent VideoContent?
// Research reference
researchId String?
research DeepResearch? @relation(fields: [researchId], references: [id])
// Credits
creditsUsed Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([workspaceId])
@@index([type])
}
model BuildingBlock {
id String @id @default(uuid())
masterContentId String
masterContent MasterContent @relation(fields: [masterContentId], references: [id], onDelete: Cascade)
type BuildingBlockType
content String @db.Text
// Scoring
engagementPotential Float?
// Usage tracking
isSelected Boolean @default(false)
usedInContentIds String[] @default([])
usageCount Int @default(0)
createdAt DateTime @default(now())
@@index([masterContentId])
@@index([type])
}
// ============================================
// TWO-PHASE CONTENT CREATION
// ============================================
model ContentSession {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
phase String @default("context_gathering") // context_gathering, content_generation
// Phase 1: Context Gathering
targetAudience String? @db.Text
keyTakeaway String? @db.Text
personalStories String? @db.Text
emotionToEvoke String?
beliefToChallenge String? @db.Text
actionToInspire String? @db.Text
// Additional context
additionalContext Json?
// Phase 2: Generated variations
variations ContentVariation[]
// Selected content
selectedVariationId String?
// Final content
contentId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([phase])
}
model ContentVariation {
id String @id @default(uuid())
sessionId String
session ContentSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
order Int @default(0)
content String @db.Text
// Scoring
aiScore Float?
isSelected Boolean @default(false)
createdAt DateTime @default(now())
@@index([sessionId])
}
// ============================================
// CONTENT
// ============================================
model Content {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
nicheId String?
niche Niche? @relation(fields: [nicheId], references: [id])
trendId String?
trend Trend? @relation(fields: [trendId], references: [id])
masterContentId String?
masterContent MasterContent? @relation(fields: [masterContentId], references: [id])
type ContentType
title String?
body String @db.Text
summary String? @db.Text
htmlBody String? @db.Text
markdownBody String? @db.Text
hashtags String[] @default([])
keywords String[] @default([])
// Content category for growth formula
category ContentCategoryType @default(EXPERIMENT)
goldPostId String? // If spin-off, reference to gold post
// AI metadata
aiModel String?
aiPrompt String? @db.Text
creditsUsed Int @default(0)
// Çoklu dil desteği
sourceLanguage ContentLanguage? // Kaynak içeriğin dili (prompt veya referans)
targetLanguage ContentLanguage @default(EN) // Üretilen içeriğin dili
status ContentStatus @default(DRAFT)
// Relations
citations Citation[]
variants ContentVariant[]
media Media[]
scheduledPosts ScheduledPost[]
approvals ContentApproval[]
analytics ContentAnalytics[]
// SEO & Psychology
seoData ContentSeo?
psychology ContentPsychology?
// A/B Testing
abTestId String?
abTest ABTest? @relation(fields: [abTestId], references: [id])
// Research reference
researchId String?
research DeepResearch? @relation(fields: [researchId], references: [id])
// CORE RULE: Source Verification
isSourceVerified Boolean @default(false)
verificationLevel VerificationLevel @default(VERIFIED)
sources ContentSource[] // Mandatory sources for verified content
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
publishedAt DateTime?
scheduledAt DateTime?
publishedUrl String?
@@index([userId])
@@index([workspaceId])
@@index([nicheId])
@@index([trendId])
@@index([masterContentId])
@@index([type])
@@index([status])
@@index([category])
@@index([isSourceVerified])
}
model ContentVariant {
id String @id @default(uuid())
contentId String
content Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
platform SocialPlatform
text String @db.Text
hashtags String[] @default([])
mentions String[] @default([])
characterCount Int?
// Metrics
impressions Int @default(0)
clicks Int @default(0)
engagements Int @default(0)
shares Int @default(0)
conversions Int @default(0)
// Status
name String? // Variant name for A/B testing
isActive Boolean @default(true)
isWinner Boolean @default(false)
media Media[]
scheduledPosts ScheduledPost[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contentId])
@@index([platform])
}
model Citation {
id String @id @default(uuid())
contentId String
content Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
title String
url String
author String?
source String?
publishedDate DateTime?
excerpt String? @db.Text
order Int @default(0)
createdAt DateTime @default(now())
@@index([contentId])
}
model DeepResearch {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
topic String
query String @db.Text
nicheId String?
trendId String?
sources Json?
summary String? @db.Text
keyFindings Json?
outline Json?
status String @default("pending")
error String?
creditsUsed Int @default(0)
masterContents MasterContent[]
contents Content[]
startedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
@@index([userId])
@@index([status])
}
// ============================================
// VIDEO & THUMBNAIL
// ============================================
model VideoContent {
id String @id @default(uuid())
masterContentId String? @unique
masterContent MasterContent? @relation(fields: [masterContentId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
title String
description String? @db.Text
script String @db.Text
duration Int?
// SEO optimized
seoTitle String?
seoDescription String?
tags String[] @default([])
// Timestamps/chapters
timestamps Json?
// Thumbnails
thumbnails VideoThumbnail[]
creditsUsed Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([masterContentId])
}
model VideoThumbnail {
id String @id @default(uuid())
videoId String
video VideoContent @relation(fields: [videoId], references: [id], onDelete: Cascade)
headline String?
subheadline String?
style String?
// Neuro optimization
emotionalTrigger String?
colorScheme String?
faceExpression String?
// Generated image
imageUrl String?
prompt String? @db.Text
isSelected Boolean @default(false)
createdAt DateTime @default(now())
@@index([videoId])
}
// ============================================
// MEDIA & TEMPLATES
// ============================================
model Media {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
type MediaType
filename String
originalFilename String?
mimeType String
size Int
storagePath String
publicUrl String?
thumbnailUrl String?
width Int?
height Int?
duration Int?
isAiGenerated Boolean @default(false)
aiModel String?
aiPrompt String? @db.Text
creditsUsed Int @default(0)
contentId String?
content Content? @relation(fields: [contentId], references: [id])
variantId String?
variant ContentVariant? @relation(fields: [variantId], references: [id])
createdAt DateTime @default(now())
@@index([userId])
@@index([contentId])
@@index([type])
}
model Template {
id String @id @default(uuid())
userId String?
user User? @relation(fields: [userId], references: [id])
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
name String
description String?
platform SocialPlatform?
type String?
width Int
height Int
layers TemplateLayer[]
presetData Json?
thumbnailUrl String?
isPublic Boolean @default(false)
isSystem Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([userId])
@@index([workspaceId])
@@index([platform])
@@index([isSystem])
}
model TemplateLayer {
id String @id @default(uuid())
templateId String
template Template @relation(fields: [templateId], references: [id], onDelete: Cascade)
name String
type String
order Int
x Float @default(0)
y Float @default(0)
width Float
height Float
rotation Float @default(0)
content Json?
style Json?
assetId String?
asset Asset? @relation(fields: [assetId], references: [id])
isLocked Boolean @default(false)
isVisible Boolean @default(true)
@@index([templateId])
}
model Asset {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
name String
type String
filename String
mimeType String
size Int
storagePath String
publicUrl String?
width Int?
height Int?
layers TemplateLayer[]
createdAt DateTime @default(now())
@@index([userId])
@@index([type])
}
// ============================================
// SOCIAL MEDIA & PUBLISHING
// ============================================
model SocialAccount {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
platform SocialPlatform
platformUserId String?
username String?
displayName String?
profileImageUrl String?
authMethod AuthMethod @default(OAUTH)
accessToken String? @db.Text
refreshToken String? @db.Text
tokenExpiresAt DateTime?
tokenScope String?
encryptedCredentials String? @db.Text
isActive Boolean @default(true)
lastUsedAt DateTime?
lastError String?
errorCount Int @default(0)
optimalTimes OptimalPostTime[]
posts PublishedPost[]
scheduledPosts ScheduledPost[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, platform, platformUserId])
@@index([userId])
@@index([workspaceId])
@@index([platform])
}
model ScheduledPost {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
contentId String?
content Content? @relation(fields: [contentId], references: [id])
variantId String?
variant ContentVariant? @relation(fields: [variantId], references: [id])
socialAccountId String
socialAccount SocialAccount @relation(fields: [socialAccountId], references: [id])
textSnapshot String @db.Text
mediaUrls String[] @default([])
scheduledFor DateTime
timezone String @default("UTC")
isRecurring Boolean @default(false)
recurrenceRule String?
parentPostId String?
aiSuggestedTime DateTime?
aiConfidence Float?
requiresApproval Boolean @default(true)
isApproved Boolean @default(false)
approvedBy String?
approvedAt DateTime?
status ScheduleStatus @default(SCHEDULED)
publishedPostId String?
publishedPost PublishedPost? @relation(fields: [publishedPostId], references: [id])
error String?
retryCount Int @default(0)
creditsUsed Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([socialAccountId])
@@index([scheduledFor])
@@index([status])
}
model PublishedPost {
id String @id @default(uuid())
socialAccountId String
socialAccount SocialAccount @relation(fields: [socialAccountId], references: [id])
platformPostId String
platformUrl String?
content String @db.Text
mediaUrls String[] @default([])
scheduledPosts ScheduledPost[]
analytics PostAnalytics[]
// Gold post detection
isGoldPost Boolean @default(false)
engagementMultiplier Float?
publishedAt DateTime @default(now())
@@index([socialAccountId])
@@index([platformPostId])
@@index([publishedAt])
@@index([isGoldPost])
}
// ============================================
// ANALYTICS & A/B TESTING
// ============================================
model ContentAnalytics {
id String @id @default(uuid())
contentId String
content Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
platform SocialPlatform
views Int @default(0)
likes Int @default(0)
comments Int @default(0)
shares Int @default(0)
saves Int @default(0)
clicks Int @default(0)
impressions Int @default(0)
reach Int @default(0)
engagementRate Float?
predictedEngagement Float?
predictionAccuracy Float?
recordedAt DateTime @default(now())
@@index([contentId])
@@index([platform])
@@index([recordedAt])
}
model PostAnalytics {
id String @id @default(uuid())
publishedPostId String
publishedPost PublishedPost @relation(fields: [publishedPostId], references: [id], onDelete: Cascade)
views Int @default(0)
likes Int @default(0)
comments Int @default(0)
shares Int @default(0)
saves Int @default(0)
clicks Int @default(0)
impressions Int @default(0)
reach Int @default(0)
engagementRate Float?
snapshotAt DateTime @default(now())
@@index([publishedPostId])
@@index([snapshotAt])
}
model ABTest {
id String @id @default(uuid())
userId String
workspaceId String?
name String
description String?
metric String
contents Content[]
winnerId String?
status String @default("running")
startedAt DateTime @default(now())
endedAt DateTime?
@@index([userId])
@@index([status])
}
model EngagementPrediction {
id String @id @default(uuid())
contentId String
platform SocialPlatform
predictedViews Int?
predictedLikes Int?
predictedComments Int?
predictedShares Int?
predictedEngRate Float?
confidence Float?
modelVersion String?
createdAt DateTime @default(now())
@@index([contentId])
}
// ============================================
// CONTENT CALENDAR & WORKFLOW
// ============================================
model ContentCalendar {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
name String
description String?
isAiGenerated Boolean @default(false)
aiPrompt String? @db.Text
startDate DateTime
endDate DateTime
entries CalendarEntry[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([workspaceId])
}
model CalendarEntry {
id String @id @default(uuid())
calendarId String
calendar ContentCalendar @relation(fields: [calendarId], references: [id], onDelete: Cascade)
date DateTime
time String?
contentId String?
suggestedTopic String?
suggestedPlatform SocialPlatform?
suggestedType String?
// Growth formula tracking
contentCategory ContentCategoryType?
notes String? @db.Text
status String @default("planned")
@@index([calendarId])
@@index([date])
}
model OptimalPostTime {
id String @id @default(uuid())
socialAccountId String
account SocialAccount @relation(fields: [socialAccountId], references: [id], onDelete: Cascade)
platform SocialPlatform
dayOfWeek Int
hour Int
score Float
dataPoints Int @default(0)
updatedAt DateTime @updatedAt
@@unique([socialAccountId, platform, dayOfWeek, hour])
@@index([socialAccountId])
}
model WorkflowTemplate {
id String @id @default(uuid())
userId String?
user User? @relation(fields: [userId], references: [id])
name String
description String?
postsPerDay Int @default(3)
provenPercent Int @default(33)
experimentPercent Int @default(67)
days WorkflowDay[]
isSystem Boolean @default(false)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([isSystem])
}
model WorkflowDay {
id String @id @default(uuid())
templateId String
template WorkflowTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
dayOfWeek Int
tasks WorkflowTask[]
@@index([templateId])
}
model WorkflowTask {
id String @id @default(uuid())
dayId String
day WorkflowDay @relation(fields: [dayId], references: [id], onDelete: Cascade)
order Int
type String
title String
description String?
duration Int?
@@index([dayId])
}
// ============================================
// YOUTUBE & EXTERNAL SOURCES
// ============================================
model YouTubeVideo {
id String @id @default(uuid())
videoId String @unique
url String
title String
description String? @db.Text
channelId String?
channelTitle String?
publishedAt DateTime?
duration Int?
thumbnailUrl String?
viewCount Int?
likeCount Int?
commentCount Int?
transcript String? @db.Text
transcriptLanguage String?
transcriptStatus String @default("pending")
lastFetchedAt DateTime @default(now())
@@index([videoId])
@@index([channelId])
}
model RssFeed {
id String @id @default(uuid())
url String @unique
title String?
description String?
siteUrl String?
fetchInterval Int @default(3600)
lastFetchedAt DateTime?
lastError String?
isActive Boolean @default(true)
items RssFeedItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([url])
}
model RssFeedItem {
id String @id @default(uuid())
feedId String
feed RssFeed @relation(fields: [feedId], references: [id], onDelete: Cascade)
guid String
title String
link String
description String? @db.Text
content String? @db.Text
author String?
categories String[] @default([])
publishedAt DateTime?
extractedContent String? @db.Text
isProcessed Boolean @default(false)
fetchedAt DateTime @default(now())
@@unique([feedId, guid])
@@index([feedId])
@@index([publishedAt])
}
// ============================================
// API & WEBHOOKS
// ============================================
model ApiKey {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
key String @unique
keyPrefix String
keyHash String
permissions String[] @default([])
rateLimit Int @default(1000)
isActive Boolean @default(true)
lastUsedAt DateTime?
usageCount Int @default(0)
expiresAt DateTime?
createdAt DateTime @default(now())
@@index([keyHash])
@@index([userId])
}
model Webhook {
id String @id @default(uuid())
userId String
workspaceId String?
workspace Workspace? @relation(fields: [workspaceId], references: [id])
name String
url String
secret String?
events String[] @default([])
isActive Boolean @default(true)
lastTriggeredAt DateTime?
consecutiveFailures Int @default(0)
logs WebhookLog[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([workspaceId])
}
model WebhookLog {
id String @id @default(uuid())
webhookId String
webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade)
event String
payload Json
statusCode Int?
responseBody String? @db.Text
responseTime Int?
success Boolean
error String?
sentAt DateTime @default(now())
@@index([webhookId])
@@index([sentAt])
}
// ============================================
// SYSTEM & JOBS
// ============================================
model SystemSetting {
id String @id @default(uuid())
key String @unique
value Json
description String?
isPublic Boolean @default(false)
updatedAt DateTime @updatedAt
@@index([key])
}
model JobLog {
id String @id @default(uuid())
jobName String
jobId String?
status String
input Json?
output Json?
error String? @db.Text
duration Int?
startedAt DateTime @default(now())
completedAt DateTime?
@@index([jobName])
@@index([status])
@@index([startedAt])
}
// ============================================
// COPYWRITING FORMULAS
// ============================================
model CopywritingFormula {
id String @id @default(uuid())
name String @unique
acronym String? // AIDA, PAS, BAB, etc.
description String @db.Text
steps Json // Array of steps
examples String[] @default([])
bestFor String[] @default([])
isSystem Boolean @default(true)
createdAt DateTime @default(now())
}
model CtaTemplate {
id String @id @default(uuid())
category String // action, urgency, benefit, social_proof
template String
examples String[] @default([])
usageCount Int @default(0)
avgEngagement Float?
isSystem Boolean @default(true)
createdAt DateTime @default(now())
@@index([category])
}
// ============================================
// CORE RULE: SOURCE VERIFICATION
// ============================================
model ContentSource {
id String @id @default(uuid())
contentId String
content Content @relation(fields: [contentId], references: [id], onDelete: Cascade)
// Source info
sourceType SourceType
title String
url String?
author String?
publisher String?
publishDate DateTime?
// Verification
isVerified Boolean @default(false)
verifiedAt DateTime?
verifiedBy String? // AI or UserId
// Usage in content
excerpt String? @db.Text // Kullanılan kısım
claimMade String? @db.Text // Bu kaynaktan yapılan iddia
// Reliability
reliabilityScore Float? // 0-1 arası güvenilirlik
order Int @default(0)
createdAt DateTime @default(now())
@@index([contentId])
@@index([sourceType])
@@index([isVerified])
}
// ============================================
// VIRAL LEARNING DATABASE
// ============================================
model ViralLearning {
id String @id @default(uuid())
// Source post info
platform SocialPlatform
sourceUrl String?
originalContent String @db.Text
engagementCount Int?
// Extracted patterns
hookPattern String? @db.Text
painPattern String? @db.Text
payoffPattern String? @db.Text
ctaPattern String? @db.Text
// Psychology analysis
psychologyTriggers String[] @default([])
emotionalHooks String[] @default([])
// Structure template
structureTemplate Json?
// Learning metadata
successFactors String[] @default([])
targetAudience String?
contentType String?
// Usage tracking
timesUsed Int @default(0)
derivedContents String[] @default([]) // Content IDs
avgDerivedEngagement Float?
// Tags for retrieval
tags String[] @default([])
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([platform])
@@index([tags])
@@index([timesUsed])
}
// ============================================
// INSTAGRAM SPECIFIC CONTENT
// ============================================
model InstagramContent {
id String @id @default(uuid())
contentId String
format InstagramFormat
// Caption
caption String @db.Text
hashtags String[] @default([])
mentions String[] @default([])
// Carousel specific
carouselSlides CarouselSlide[]
// Reel specific
reelDuration Int?
reelScript String? @db.Text
audioTrack String?
// Static image specific
imageAltText String?
// Cover image for all formats
coverImageUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contentId])
@@index([format])
}
model CarouselSlide {
id String @id @default(uuid())
instagramContentId String
instagramContent InstagramContent @relation(fields: [instagramContentId], references: [id], onDelete: Cascade)
order Int
// Slide content
imageUrl String?
text String? @db.Text
headline String?
// Template info
templateId String?
createdAt DateTime @default(now())
@@index([instagramContentId])
@@index([order])
}