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)
|
|||
|
|
}
|