344 lines
11 KiB
Plaintext
344 lines
11 KiB
Plaintext
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)
|
||
}
|