generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique passwordHash String name String? activeAccountId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt accounts Account[] sessions Session[] settings Setting? } model Session { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) token String @unique expiresAt DateTime createdAt DateTime @default(now()) } model Account { id String @id @default(cuid()) userId String? user User? @relation(fields: [userId], references: [id], onDelete: Cascade) username String? displayName String? persona String? styleProfile String? styleBenchmark String? brief String? productBrief String? targetAudience String? goals String? storageState String @default("") valid Boolean @default(true) threadsAccessToken String? threadsUserId String? threadsTokenExpiresAt DateTime? automationEnabled Boolean @default(false) searchViaApi Boolean @default(false) /// mixed | threads | brave | crawler | threads_brave | threads_crawler | brave_crawler searchSourceMode String @default("mixed") publishViaApi Boolean @default(false) devMode Boolean @default(true) scrapeReplies Boolean @default(true) repliesPerPost Int @default(10) publishHeaded Boolean @default(false) playwrightDebug Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt automationRules AutomationRule[] } /// 每帳號 × 每任務的自動化規則:mode=manual 先審核,mode=auto 自動執行(受 dailyCap 與排程限制) model AutomationRule { id String @id @default(cuid()) accountId String account Account @relation(fields: [accountId], references: [id], onDelete: Cascade) taskType String // scan | generate | outreach | engagement | publish mode String @default("manual") // manual | auto dailyCap Int @default(20) schedule String @default("0 9 * * *") enabled Boolean @default(false) lastRunAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([accountId, taskType]) } /// 自動化動作稽核紀錄,供成效回溯與「殺停」後檢視 model ActionLog { id String @id @default(cuid()) accountId String? taskType String // scan | generate | outreach | engagement | publish action String // publish-post | reply-outreach | reply-inbound | scan | generate mode String @default("auto") // auto | manual status String @default("success") // success | failed | skipped targetId String? externalId String? permalink String? detail String? error String? createdAt DateTime @default(now()) } model BrandProfile { id String @id @default(cuid()) accountId String? name String notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt products ProductProfile[] topics Topic[] } model ProductProfile { id String @id @default(cuid()) accountId String? brandId String? brand BrandProfile? @relation(fields: [brandId], references: [id], onDelete: Cascade) label String context String @default("") matchTags String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt topics Topic[] } model Topic { id String @id @default(cuid()) accountId String? label String query String brief String? productContext String? brandProfileId String? brandProfile BrandProfile? @relation(fields: [brandProfileId], references: [id], onDelete: SetNull) productProfileId String? productProfile ProductProfile? @relation(fields: [productProfileId], references: [id], onDelete: SetNull) topicGoal String @default("viral") researchMap String? selectedTags String? active Boolean @default(true) createdAt DateTime @default(now()) scans Scan[] } model Scan { id String @id @default(cuid()) accountId String? topicId String topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade) scanMode String @default("single") scanGoal String @default("viral") scanTags String? searchSource String? // api | browser | web | hybrid:這次海巡實際用哪種方式找到貼文 repliesFetched Boolean @default(false) // 是否有抓到別人貼文的留言(需瀏覽器) repliesCount Int @default(0) // 抓到的留言則數 createdAt DateTime @default(now()) items ScanItem[] } model ScanItem { id String @id @default(cuid()) scanId String scan Scan @relation(fields: [scanId], references: [id], onDelete: Cascade) externalId String? text String permalink String? authorName String? postedAt DateTime? likeCount Int? replyCount Int? score Float searchTag String? relevanceScore Float? placementScore Float? placementReason String? qualityTier String? qualityReason String? combinedScore Float? mediaUrls String? mediaType String? viralAnalysis String? replies Reply[] outreachTargets OutreachTarget[] } model Reply { id String @id @default(cuid()) scanItemId String scanItem ScanItem @relation(fields: [scanItemId], references: [id], onDelete: Cascade) text String authorName String? likeCount Int? postedAt DateTime? } model Draft { id String @id @default(cuid()) accountId String? topicId String? text String angle String? rationale String? hook String? referenceNotes String? searchTag String? sortOrder Int? sources String? draftType String? imageBrief String? imagePath String? imagePaths String? scanItemId String? status String @default("PENDING") factCheckResult String? createdAt DateTime @default(now()) } model Published { id String @id @default(cuid()) accountId String? topicId String? externalId String? text String angle String? hook String? rationale String? draftType String? permalink String? publishedAt DateTime @default(now()) views Int? likes Int? replies Int? reposts Int? quotes Int? snapshots PerformanceSnapshot[] inboundReplies InboundReply[] } model PerformanceSnapshot { id String @id @default(cuid()) publishedId String? published Published? @relation(fields: [publishedId], references: [id], onDelete: Cascade) profileId String? views Int? likes Int? replies Int? reposts Int? quotes Int? followers Int? source String @default("threads-api") capturedAt DateTime @default(now()) } model InboundReply { id String @id @default(cuid()) publishedId String? published Published? @relation(fields: [publishedId], references: [id], onDelete: Cascade) externalId String? @unique parentId String? text String authorName String? permalink String? postedAt DateTime? likeCount Int? sentiment String? intent String? status String @default("NEW") createdAt DateTime @default(now()) replyDrafts ReplyDraft[] } model ReplyDraft { id String @id @default(cuid()) inboundReplyId String inboundReply InboundReply @relation(fields: [inboundReplyId], references: [id], onDelete: Cascade) text String rationale String? status String @default("PENDING") createdAt DateTime @default(now()) publishedAt DateTime? } model OutreachTarget { id String @id @default(cuid()) scanItemId String scanItem ScanItem @relation(fields: [scanItemId], references: [id], onDelete: Cascade) status String @default("NEW") relevance Float? reason String? createdAt DateTime @default(now()) drafts OutreachDraft[] } model OutreachDraft { id String @id @default(cuid()) outreachTargetId String outreachTarget OutreachTarget @relation(fields: [outreachTargetId], references: [id], onDelete: Cascade) text String angle String? rationale String? status String @default("PENDING") createdAt DateTime @default(now()) publishedAt DateTime? } model BackgroundJob { id String @id @default(cuid()) accountId String? type String status String @default("pending") topicId String? label String? payload String? result String? error String? progress String? progressDetail String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt completedAt DateTime? } model Setting { id String @id @default(cuid()) userId String? @unique user User? @relation(fields: [userId], references: [id], onDelete: Cascade) persona String? aiProvider String @default("xai") aiModel String @default("grok-3") researchAiProvider String? researchAiModel String? draftsPerScan Int @default(4) matrixRows Int @default(7) scanCron String @default("0 9 * * *") scrapeReplies Boolean @default(true) repliesPerPost Int @default(10) playwrightDebug Boolean @default(false) providerApiKeys String? threadsAppId String? threadsAppSecret String? appUrl String? publishViaApi Boolean @default(true) searchViaApi Boolean @default(true) publishHeaded Boolean @default(true) devMode Boolean @default(false) connectionMigrated Boolean @default(false) }