Files

1198 lines
42 KiB
Plaintext
Raw Permalink 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.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
}
model Category {
id Int @id @default(autoincrement())
name String @db.VarChar(50)
slug String @unique @db.VarChar(50)
icon String? @db.VarChar(100)
parentId Int? @map("parent_id")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
tools Tool[]
pendingTools PendingTool[]
parent Category? @relation("CategoryToCategory", fields: [parentId], references: [id])
children Category[] @relation("CategoryToCategory")
@@map("categories")
}
model SkillCategory {
id Int @id @default(autoincrement())
name String @db.VarChar(50)
slug String @unique @db.VarChar(50)
icon String? @db.VarChar(100)
parentId Int? @map("parent_id")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
skills Skill[]
pendingSkills PendingSkill[]
parent SkillCategory? @relation("SkillCategoryToSkillCategory", fields: [parentId], references: [id])
children SkillCategory[] @relation("SkillCategoryToSkillCategory")
@@map("skill_categories")
}
model Tool {
id Int @id @default(autoincrement())
name String @db.VarChar(100)
slug String @unique @db.VarChar(100)
categoryId Int @map("category_id")
description String @db.Text
logoUrl String? @map("logo_url") @db.VarChar(255)
websiteUrl String @map("website_url") @db.VarChar(255)
pricingModel PricingModel @default(freemium) @map("pricing_model")
pricingDetail String? @map("pricing_detail") @db.VarChar(200)
features Json?
screenshots Json? // Array of { url: string, alt?: string } - max 5 screenshots
tags Json?
viewCount Int @default(0) @map("view_count")
commentCount Int @default(0) @map("comment_count")
avgRating Float @default(0) @map("avg_rating")
status Status @default(draft)
lastCheckedAt DateTime? @map("last_checked_at")
websiteOk Boolean @default(true) @map("website_ok")
logoOk Boolean @default(true) @map("logo_ok")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
category Category @relation(fields: [categoryId], references: [id])
favorites Favorite[]
browsingHistory BrowsingHistory[]
reviews Review[]
collectionItems CollectionItem[]
comments Comment[]
@@index([status, viewCount]) // for published tools sorted by views
@@index([status, createdAt]) // for recent published tools
@@index([categoryId, status])
@@fulltext([name, description])
@@map("tools")
}
model PendingTool {
id Int @id @default(autoincrement())
name String @db.VarChar(100)
slug String? @db.VarChar(100)
categoryId Int? @map("category_id")
description String? @db.Text
logoUrl String? @map("logo_url") @db.VarChar(255)
websiteUrl String? @map("website_url") @db.VarChar(255)
pricingModel PricingModel? @default(freemium) @map("pricing_model")
pricingDetail String? @map("pricing_detail") @db.VarChar(200)
features Json?
tags Json?
sourceUrl String? @map("source_url") @db.VarChar(255)
status PendingStatus @default(pending)
checkWebsiteOk Boolean? @map("check_website_ok")
checkLogoOk Boolean? @map("check_logo_ok")
checkDescOk Boolean? @map("check_desc_ok")
checkCategoryOk Boolean? @map("check_category_ok")
checkDetail Json? @map("check_detail")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
category Category? @relation(fields: [categoryId], references: [id])
@@map("pending_tools")
}
model Skill {
id Int @id @default(autoincrement())
name String @db.VarChar(100)
slug String @unique @db.VarChar(100)
categoryId Int @map("category_id")
description String @db.Text
logoUrl String? @map("logo_url") @db.VarChar(255)
sourceUrl String @map("source_url") @db.VarChar(255)
sourceType String @default("gitee") @map("source_type") @db.VarChar(20)
rating Float @default(0) @db.Float
features Json?
tags Json?
viewCount Int @default(0) @map("view_count")
status Status @default(draft)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
category SkillCategory @relation(fields: [categoryId], references: [id])
favorites Favorite[]
browsingHistory BrowsingHistory[]
reviews Review[]
collectionItems CollectionItem[]
skillReview SkillReview?
comments Comment[]
lastReviewedAt DateTime? @map("last_reviewed_at")
githubStarsHistory Json? @map("github_stars_history")
githubStars Int @default(0) @map("github_stars")
starsChangeDay Int @default(0) @map("stars_change_day")
starsChangeWeek Int @default(0) @map("stars_change_week")
starsChangeMonth Int @default(0) @map("stars_change_month")
commentCount Int @default(0) @map("comment_count")
avgRating Float @default(0) @map("avg_rating")
@@index([status, viewCount]) // for published skills sorted by views
@@index([status, createdAt]) // for recent published skills
@@index([categoryId, status])
@@map("skills")
}
model PendingSkill {
id Int @id @default(autoincrement())
name String @db.VarChar(100)
slug String? @db.VarChar(100)
categoryId Int? @map("category_id")
description String? @db.Text
logoUrl String? @map("logo_url") @db.VarChar(255)
sourceUrl String? @map("source_url") @db.VarChar(255)
sourceType String? @map("source_type") @db.VarChar(20)
rating Float @default(0) @db.Float
features Json?
tags Json?
status PendingStatus @default(pending)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
category SkillCategory? @relation(fields: [categoryId], references: [id])
@@map("pending_skills")
}
enum PricingModel {
free
freemium
paid
subscription
}
enum Status {
draft
published
}
enum PendingStatus {
pending
approved
rejected
}
model SystemConfig {
id Int @id @default(autoincrement())
key String @unique @db.VarChar(50)
value Json
@@map("system_configs")
}
model User {
id Int @id @default(autoincrement())
email String @unique @db.VarChar(255)
passwordHash String? @db.VarChar(255)
name String? @db.VarChar(100)
avatarUrl String?
bio String? @db.Text
oauthProvider String? @db.VarChar(50)
oauthId String? @unique @db.VarChar(255)
role String @default("user") @db.VarChar(20)
roleId Int? @map("role_id")
points Int @default(0)
level UserLevel @default(LV01)
isBot Boolean @default(false) @map("is_bot")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
botConfig BotConfig?
assignedRole Role? @relation(fields: [roleId], references: [id])
favorites Favorite[]
browsingHistory BrowsingHistory[]
reviews Review[]
collections Collection[]
comments Comment[]
commentLikes CommentLike[]
notifications Notification[]
forumTopics ForumTopic[]
forumPosts ForumPost[]
pointsHistory PointsHistory[]
topicLikes ForumTopicLike[]
postLikes ForumPostLike[]
categoryFollows ForumCategoryFollow[]
topicBookmarks ForumTopicBookmark[]
topicDrafts ForumDraft[]
reports ForumReport[]
resolvedReports ForumReport[] @relation("ForumReportResolver")
checkIns ForumCheckIn[]
moderationLogs ForumModerationLog[] @relation("ForumModerationActor")
achievements UserAchievement[]
topicSubscriptions ForumTopicSubscription[]
apiKeys ApiKey[]
@@unique([oauthProvider, oauthId])
@@index([isBot]) // for filtering bot users
@@index([role]) // for role-based queries
@@map("users")
}
model PointsHistory {
id Int @id @default(autoincrement())
userId Int @map("user_id")
points Int
reason String @db.VarChar(50)
detail String? @db.VarChar(200)
refType String? @map("ref_type") @db.VarChar(50)
refId Int? @map("ref_id")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, createdAt])
@@map("points_history")
}
enum UserLevel {
LV01
LV02
LV03
LV04
LV05
LV06
LV07
LV08
LV09
LV10
LV11
LV12
LV13
LV14
LV15
LV16
LV17
LV18
LV19
LV20
}
model Role {
id Int @id @default(autoincrement())
name String @db.VarChar(50)
slug String @unique @db.VarChar(50)
isSystem Boolean @default(false) @map("is_system")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
permissions RolePermission[]
users User[]
@@map("roles")
}
model Permission {
id Int @id @default(autoincrement())
groupName String @map("group_name") @db.VarChar(50)
name String @db.VarChar(50)
key String @unique @db.VarChar(80)
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
rolePermissions RolePermission[]
@@index([groupName, sortOrder])
@@map("permissions")
}
model RolePermission {
roleId Int @map("role_id")
permissionId Int @map("permission_id")
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
@@id([roleId, permissionId])
@@map("role_permissions")
}
model Favorite {
id Int @id @default(autoincrement())
userId Int @map("user_id")
toolId Int? @map("tool_id")
skillId Int? @map("skill_id")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tool Tool? @relation(fields: [toolId], references: [id], onDelete: Cascade)
skill Skill? @relation(fields: [skillId], references: [id], onDelete: Cascade)
@@unique([userId, toolId])
@@unique([userId, skillId])
@@index([userId])
@@map("favorites")
}
model BrowsingHistory {
id Int @id @default(autoincrement())
userId Int @map("user_id")
toolId Int? @map("tool_id")
skillId Int? @map("skill_id")
viewedAt DateTime @default(now()) @updatedAt @map("viewed_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tool Tool? @relation(fields: [toolId], references: [id], onDelete: Cascade)
skill Skill? @relation(fields: [skillId], references: [id], onDelete: Cascade)
@@unique([userId, toolId])
@@unique([userId, skillId])
@@index([userId, viewedAt])
@@map("browsing_histories")
}
model Review {
id Int @id @default(autoincrement())
userId Int @map("user_id")
toolId Int? @map("tool_id")
skillId Int? @map("skill_id")
rating Int?
comment String @db.Text
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tool Tool? @relation(fields: [toolId], references: [id], onDelete: Cascade)
skill Skill? @relation(fields: [skillId], references: [id], onDelete: Cascade)
@@unique([userId, toolId])
@@unique([userId, skillId])
@@index([toolId, createdAt])
@@index([skillId, createdAt])
@@map("reviews")
}
model Collection {
id Int @id @default(autoincrement())
userId Int @map("user_id")
name String @db.VarChar(50)
description String? @db.Text
sortOrder Int @default(0) @map("sort_order")
isPublic Boolean @default(false) @map("is_public")
publicSlug String? @unique @map("public_slug") @db.VarChar(100)
viewCount Int @default(0) @map("view_count")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
items CollectionItem[]
@@index([userId])
@@index([isPublic, viewCount])
@@map("collections")
}
model CollectionItem {
id Int @id @default(autoincrement())
collectionId Int @map("collection_id")
toolId Int? @map("tool_id")
skillId Int? @map("skill_id")
addedAt DateTime @default(now()) @map("added_at")
collection Collection @relation(fields: [collectionId], references: [id], onDelete: Cascade)
tool Tool? @relation(fields: [toolId], references: [id], onDelete: Cascade)
skill Skill? @relation(fields: [skillId], references: [id], onDelete: Cascade)
@@unique([collectionId, toolId])
@@unique([collectionId, skillId])
@@index([collectionId])
@@map("collection_items")
}
model DailyReport {
id Int @id @default(autoincrement())
date DateTime @unique @db.Date
title String @db.VarChar(200)
summary String? @db.Text
hotCount Int @default(0) @map("hot_count")
coverUrl String? @map("cover_url") @db.VarChar(255)
status Status @default(draft)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
items DailyReportItem[]
@@index([date])
@@index([status])
@@map("daily_reports")
}
model DailyReportItem {
id Int @id @default(autoincrement())
reportId Int @map("report_id")
sortOrder Int @default(0) @map("sort_order")
category String @default("tech") @db.VarChar(20)
title String @db.VarChar(200)
summary String? @db.Text
detail String? @db.Text
sourceUrl String? @map("source_url") @db.VarChar(500)
imageUrl String? @map("image_url") @db.VarChar(255)
hotScore Int @default(0) @map("hot_score")
createdAt DateTime @default(now()) @map("created_at")
report DailyReport @relation(fields: [reportId], references: [id], onDelete: Cascade)
@@index([reportId, sortOrder])
@@map("daily_report_items")
}
model SkillReview {
id Int @id @default(autoincrement())
skillId Int @unique @map("skill_id")
skill Skill @relation(fields: [skillId], references: [id])
capability Float
devExp Float @map("dev_exp")
costLicense Float @map("cost_license")
community Float
performance Float
overall Float
reviews Json
longArticle String? @map("long_article") @db.Text
videoScript String? @map("video_script") @db.Text
socialCards Json? @map("social_cards")
status ReviewStatus @default(DRAFT)
reviewedAt DateTime @updatedAt @map("reviewed_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([skillId])
@@index([overall])
@@map("skill_reviews")
}
model ReviewGenerationLog {
id Int @id @default(autoincrement())
skillId Int @map("skill_id")
status String @db.VarChar(20)
prompt String @db.Text
response String? @db.Text
error String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
@@index([skillId])
@@map("review_generation_logs")
}
enum ReviewStatus {
DRAFT
PUBLISHED
REJECTED
OUTDATED
}
model TaskLog {
id Int @id @default(autoincrement())
taskKey String @map("task_key") @db.VarChar(50)
taskName String @map("task_name") @db.VarChar(100)
status String @db.VarChar(20)
startedAt DateTime @map("started_at")
finishedAt DateTime? @map("finished_at")
duration Int?
result Json?
error String? @db.Text
triggerBy String @default("cron") @map("trigger_by") @db.VarChar(20)
createdAt DateTime @default(now()) @map("created_at")
@@index([taskKey, startedAt])
@@map("task_logs")
}
model TaskConfig {
id Int @id @default(autoincrement())
taskKey String @unique @map("task_key") @db.VarChar(50)
taskName String @map("task_name") @db.VarChar(100)
description String? @db.VarChar(255)
enabled Boolean @default(true)
cronExpr String? @map("cron_expr") @db.VarChar(50)
config Json?
updatedAt DateTime @updatedAt @map("updated_at")
@@map("task_configs")
}
model Comment {
id Int @id @default(autoincrement())
userId Int @map("user_id")
toolId Int? @map("tool_id")
skillId Int? @map("skill_id")
parentId Int? @map("parent_id")
content String @db.Text
rating Int?
likeCount Int @default(0) @map("like_count")
isPinned Boolean @default(false) @map("is_pinned")
status CommentStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tool Tool? @relation(fields: [toolId], references: [id], onDelete: Cascade)
skill Skill? @relation(fields: [skillId], references: [id], onDelete: Cascade)
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
replies Comment[] @relation("CommentReplies")
likes CommentLike[]
@@index([toolId, createdAt])
@@index([skillId, createdAt])
@@index([userId])
@@map("comments")
}
enum CommentStatus {
ACTIVE
HIDDEN
DELETED
}
model CommentLike {
id Int @id @default(autoincrement())
commentId Int @map("comment_id")
userId Int @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([commentId, userId])
@@map("comment_likes")
}
model Notification {
id Int @id @default(autoincrement())
userId Int @map("user_id")
type NotifType
title String @db.VarChar(100)
content String? @db.VarChar(255)
link String? @db.VarChar(255)
isRead Boolean @default(false) @map("is_read")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, isRead, createdAt])
@@map("notifications")
}
enum NotifType {
COMMENT_REPLY
COMMENT_LIKE
TOPIC_REPLY
MENTION
BEST_ANSWER
POST_LIKE
TOPIC_LIKE
SYSTEM
DIGEST
}
model ForumCategory {
id Int @id @default(autoincrement())
name String @db.VarChar(30)
slug String @unique @db.VarChar(30)
description String? @db.VarChar(100)
icon String? @db.VarChar(30)
parentId Int? @map("parent_id")
sortOrder Int @default(0) @map("sort_order")
topicCount Int @default(0) @map("topic_count")
followerCount Int @default(0) @map("follower_count")
createdAt DateTime @default(now()) @map("created_at")
parent ForumCategory? @relation("CategoryHierarchy", fields: [parentId], references: [id])
children ForumCategory[] @relation("CategoryHierarchy")
topics ForumTopic[]
followers ForumCategoryFollow[]
@@map("forum_categories")
}
model ForumTopic {
id Int @id @default(autoincrement())
categoryId Int @map("category_id")
userId Int @map("user_id")
title String @db.VarChar(100)
slug String @unique @db.VarChar(120)
content String @db.Text
isPinned Boolean @default(false) @map("is_pinned")
isLocked Boolean @default(false) @map("is_locked")
isFeatured Boolean @default(false) @map("is_featured")
isHidden Boolean @default(false) @map("is_hidden")
viewCount Int @default(0) @map("view_count")
replyCount Int @default(0) @map("reply_count")
likeCount Int @default(0) @map("like_count")
lastReplyAt DateTime? @map("last_reply_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 行业智库元数据:{ kind:"thinktank", industry:<板块slug>, version: n, lastTopic, lastRoundAt }
// 主题帖 topic.content = 最新"打法"(1 楼),楼层 post = 每周研讨对话原文/真人问答
thinktank Json? @map("thinktank")
category ForumCategory @relation(fields: [categoryId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
posts ForumPost[]
likes ForumTopicLike[]
tags ForumTopicTag[]
bookmarks ForumTopicBookmark[]
reports ForumReport[]
subscribers ForumTopicSubscription[]
@@index([categoryId, lastReplyAt])
@@index([categoryId, isPinned, createdAt])
@@index([categoryId, updatedAt]) // for recent topics by category
@@index([createdAt]) // for chronological queries
@@index([userId])
@@index([isFeatured, lastReplyAt])
@@index([isHidden])
@@fulltext([title])
@@fulltext([title, content])
@@map("forum_topics")
}
model ForumPost {
id Int @id @default(autoincrement())
topicId Int @map("topic_id")
userId Int @map("user_id")
content String @db.Text
isAnswer Boolean @default(false) @map("is_answer")
isHidden Boolean @default(false) @map("is_hidden")
likeCount Int @default(0) @map("like_count")
replyToId Int? @map("reply_to_id")
replyToSnapshot String? @map("reply_to_snapshot") @db.VarChar(300)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
topic ForumTopic @relation(fields: [topicId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
likes ForumPostLike[]
replyTo ForumPost? @relation("PostReplies", fields: [replyToId], references: [id], onDelete: SetNull)
replies ForumPost[] @relation("PostReplies")
@@index([topicId, createdAt])
@@index([topicId, isHidden, createdAt]) // for filtered post queries
@@index([userId])
@@index([replyToId])
@@index([isHidden])
@@map("forum_posts")
}
model ForumTopicLike {
id Int @id @default(autoincrement())
topicId Int @map("topic_id")
userId Int @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
topic ForumTopic @relation(fields: [topicId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([topicId, userId])
@@index([userId])
@@map("forum_topic_likes")
}
model ForumPostLike {
id Int @id @default(autoincrement())
postId Int @map("post_id")
userId Int @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
post ForumPost @relation(fields: [postId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([postId, userId])
@@index([userId])
@@map("forum_post_likes")
}
model ForumCategoryFollow {
id Int @id @default(autoincrement())
categoryId Int @map("category_id")
userId Int @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
category ForumCategory @relation(fields: [categoryId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([categoryId, userId])
@@index([userId])
@@map("forum_category_follows")
}
model ForumTag {
id Int @id @default(autoincrement())
name String @unique @db.VarChar(20)
slug String @unique @db.VarChar(30)
topicCount Int @default(0) @map("topic_count")
createdAt DateTime @default(now()) @map("created_at")
topics ForumTopicTag[]
@@map("forum_tags")
}
model ForumTopicTag {
id Int @id @default(autoincrement())
topicId Int @map("topic_id")
tagId Int @map("tag_id")
topic ForumTopic @relation(fields: [topicId], references: [id], onDelete: Cascade)
tag ForumTag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@unique([topicId, tagId])
@@index([tagId])
@@map("forum_topic_tags")
}
model ForumDraft {
id Int @id @default(autoincrement())
userId Int @map("user_id")
categoryId Int? @map("category_id")
title String? @db.VarChar(100)
content String @db.Text
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, updatedAt])
@@map("forum_drafts")
}
model ForumTopicBookmark {
id Int @id @default(autoincrement())
topicId Int @map("topic_id")
userId Int @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
topic ForumTopic @relation(fields: [topicId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([topicId, userId])
@@index([userId, createdAt])
@@map("forum_topic_bookmarks")
}
model ForumReport {
id Int @id @default(autoincrement())
reporterId Int @map("reporter_id")
topicId Int? @map("topic_id")
postId Int? @map("post_id")
reason String @db.VarChar(50)
detail String? @db.VarChar(500)
status String @default("pending") @db.VarChar(20)
resolution String? @db.VarChar(20)
resolvedBy Int? @map("resolved_by")
resolvedAt DateTime? @map("resolved_at")
createdAt DateTime @default(now()) @map("created_at")
reporter User @relation(fields: [reporterId], references: [id], onDelete: Cascade)
topic ForumTopic? @relation(fields: [topicId], references: [id], onDelete: Cascade)
resolver User? @relation("ForumReportResolver", fields: [resolvedBy], references: [id], onDelete: SetNull)
@@index([status, createdAt])
@@index([reporterId])
@@index([topicId])
@@index([postId])
@@index([resolvedBy])
@@map("forum_reports")
}
model ForumCheckIn {
id Int @id @default(autoincrement())
userId Int @map("user_id")
checkInDate DateTime @map("check_in_date") @db.Date
streak Int @default(1)
pointsEarned Int @default(0) @map("points_earned")
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, checkInDate])
@@index([userId, checkInDate])
@@map("forum_check_ins")
}
model ForumModerationLog {
id Int @id @default(autoincrement())
actorId Int @map("actor_id")
targetType String @map("target_type") @db.VarChar(20)
targetId Int @map("target_id")
action String @db.VarChar(30)
reason String? @db.VarChar(200)
createdAt DateTime @default(now()) @map("created_at")
actor User @relation("ForumModerationActor", fields: [actorId], references: [id], onDelete: Cascade)
@@index([actorId, createdAt])
@@index([targetType, targetId])
@@index([createdAt])
@@map("forum_moderation_logs")
}
model BotConfig {
id Int @id @default(autoincrement())
userId Int @unique @map("user_id")
displayName String @map("display_name") @db.VarChar(50)
personality Json
primaryForums Json @map("primary_forums")
activeHours Json @map("active_hours")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
memories BotMemory[]
botPersonaVariants BotPersonaVariant[] @relation("BotVariants")
adversarialLearnings BotAdversarialLearning[] @relation("BotAdversarialLearnings")
crossForumAffinities BotCrossForumAffinity[]
@@map("bot_configs")
}
model BotMemory {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
memoryType String @map("memory_type") @db.VarChar(20)
layer String @default("LONGTERM") @map("layer") @db.VarChar(20)
content Json
importance Float @default(0.5)
contextTags Json? @map("context_tags")
ttl DateTime? @map("ttl")
createdAt DateTime @default(now()) @map("created_at")
bot BotConfig @relation(fields: [botId], references: [id])
@@index([botId, layer, importance])
@@index([botId, memoryType, createdAt])
@@index([botId, createdAt])
@@map("bot_memories")
}
model BotDailyStat {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
date DateTime @db.Date
topicCreated Int @default(0) @map("topic_created")
replySent Int @default(0) @map("reply_sent")
followUpSent Int @default(0) @map("follow_up_sent")
repliesReceived Int @default(0) @map("replies_received")
likesReceived Int @default(0) @map("likes_received")
humanInteractions Int @default(0) @map("human_interactions")
feedbackProcessed Int @default(0) @map("feedback_processed")
topTopicId Int? @map("top_topic_id")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([botId, date])
@@index([botId, date]) // for bot daily stats queries
@@index([date])
@@map("bot_daily_stats")
}
model BotSkill {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
name String @db.VarChar(50)
category String @db.VarChar(30)
hookPattern String @map("hook_pattern") @db.Text
bodyTemplate String @map("body_template") @db.Text
applicability String? @db.Text
sourceTopicIds Json @map("source_topic_ids")
successRate Float @default(0) @map("success_rate")
usageCount Int @default(0) @map("usage_count")
avgReplies Float @default(0) @map("avg_replies")
avgLikes Float @default(0) @map("avg_likes")
version Int @default(1)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([botId, category, isActive])
@@index([botId, successRate])
@@map("bot_skills")
}
model BotWeeklyReview {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
weekStart DateTime @map("week_start") @db.Date
weekEnd DateTime @map("week_end") @db.Date
topicsCreated Int @default(0) @map("topics_created")
repliesSent Int @default(0) @map("replies_sent")
repliesReceived Int @default(0) @map("replies_received")
humanRatio Float @default(0) @map("human_ratio")
likesReceived Int @default(0) @map("likes_received")
engagementScore Float @default(0) @map("engagement_score")
nextWeekQuota String @map("next_week_quota") @db.VarChar(20)
reflection String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
@@unique([botId, weekStart])
@@index([botId, weekStart])
@@map("bot_weekly_reviews")
}
model BotPersona {
botId Int @id @map("bot_id")
avgReplyLength Int @default(0) @map("avg_reply_length")
questionRatio Float @default(0) @map("question_ratio")
exclamationCount Int @default(0) @map("exclamation_count")
avgTopicLength Int @default(0) @map("avg_topic_length")
stanceKeywords Json? @map("stance_keywords")
topHumanUsers Json? @map("top_human_users")
topTopicTypes Json? @map("top_topic_types")
lastTopicTitles Json? @map("last_topic_titles")
postSampleCount Int @default(0) @map("post_sample_count")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("bot_personas")
}
model BotPersonaVariant {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
experimentId Int? @map("experiment_id")
variantKey String @map("variant_key") @db.VarChar(20)
label String @db.VarChar(50)
description String? @db.VarChar(200)
stanceKeywords Json? @map("stance_keywords")
topTopicTypes Json? @map("top_topic_types")
topHumanUsers Json? @map("top_human_users")
styleHints Json? @map("style_hints")
weight Float @default(0.5)
isControl Boolean @default(false) @map("is_control")
isActive Boolean @default(true) @map("is_active")
sampleCount Int @default(0) @map("sample_count")
replyCount Int @default(0) @map("reply_count")
likeCount Int @default(0) @map("like_count")
humanReplyCount Int @default(0) @map("human_reply_count")
engagementScore Float @default(0) @map("engagement_score")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
botConfig BotConfig @relation("BotVariants", fields: [botId], references: [id], onDelete: Cascade)
experiment BotPersonaExperiment? @relation("ExperimentVariants", fields: [experimentId], references: [id], onDelete: SetNull)
assignments BotPersonaAssignment[]
metrics BotPersonaMetric[]
@@unique([botId, variantKey])
@@index([botId, isActive])
@@index([experimentId])
@@map("bot_persona_variants")
}
model BotPersonaExperiment {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
name String @db.VarChar(80)
description String? @db.VarChar(200)
status String @default("active") @db.VarChar(20)
winnerVariantKey String? @map("winner_variant_key") @db.VarChar(20)
minSamplesPerArm Int @default(20) @map("min_samples_per_arm")
significanceLevel Float @default(0.1) @map("significance_level")
primaryMetric String @default("engagement_score") @map("primary_metric") @db.VarChar(30)
config Json?
startedAt DateTime @default(now()) @map("started_at")
endedAt DateTime? @map("ended_at")
lastAnalyzedAt DateTime? @map("last_analyzed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
variants BotPersonaVariant[] @relation("ExperimentVariants")
@@index([botId, status])
@@index([status])
@@map("bot_persona_experiments")
}
model BotPersonaAssignment {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
variantId Int @map("variant_id")
refType String @map("ref_type") @db.VarChar(20)
refId Int @map("ref_id")
forumSlug String? @map("forum_slug") @db.VarChar(30)
replies Int @default(0)
likes Int @default(0)
humanReplies Int @default(0) @map("human_replies")
impressions Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
variant BotPersonaVariant @relation(fields: [variantId], references: [id], onDelete: Cascade)
@@unique([refType, refId])
@@index([botId, variantId])
@@index([variantId, createdAt])
@@map("bot_persona_assignments")
}
model BotPersonaMetric {
id Int @id @default(autoincrement())
variantId Int @map("variant_id")
date DateTime @db.Date
impressions Int @default(0)
replies Int @default(0)
likes Int @default(0)
humanReplies Int @default(0) @map("human_replies")
contentCount Int @default(0) @map("content_count")
avgReplies Float @default(0) @map("avg_replies")
avgLikes Float @default(0) @map("avg_likes")
computedScore Float @default(0) @map("computed_score")
updatedAt DateTime @updatedAt @map("updated_at")
variant BotPersonaVariant @relation(fields: [variantId], references: [id], onDelete: Cascade)
@@unique([variantId, date])
@@index([date])
@@map("bot_persona_metrics")
}
model BotAdversarialLearning {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
weekKey String @map("week_key") @db.VarChar(20)
sourceRefType String @map("source_ref_type") @db.VarChar(20)
sourceRefId Int @map("source_ref_id")
sourceUserId Int @map("source_user_id")
sourceUserName String? @map("source_user_name") @db.VarChar(50)
forumSlug String? @map("forum_slug") @db.VarChar(30)
sourceTitle String? @map("source_title") @db.VarChar(200)
sourceContent String @map("source_content") @db.Text
sourceMetrics Json @map("source_metrics")
relevanceScore Float @default(0) @map("relevance_score")
learnedInsight String @map("learned_insight") @db.Text
learnCategories Json? @map("learn_categories")
status String @default("active") @db.VarChar(20)
usedCount Int @default(0) @map("used_count")
learnedAt DateTime @default(now()) @map("learned_at")
expiresAt DateTime? @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
botConfig BotConfig @relation("BotAdversarialLearnings", fields: [botId], references: [id], onDelete: Cascade)
@@unique([botId, weekKey])
@@index([botId, status])
@@index([weekKey])
@@index([sourceRefType, sourceRefId])
@@map("bot_adversarial_learnings")
}
model BotCrossForumAffinity {
id Int @id @default(autoincrement())
botId Int @map("bot_id")
forumSlug String @map("forum_slug") @db.VarChar(30)
affinity Float @default(0)
sampleCount Int @default(0) @map("sample_count")
reason String? @db.VarChar(100)
updatedAt DateTime @updatedAt @map("updated_at")
bot BotConfig @relation(fields: [botId], references: [id], onDelete: Cascade)
@@unique([botId, forumSlug])
@@index([botId, affinity])
@@map("bot_cross_forum_affinities")
}
model Achievement {
key String @id @db.VarChar(50)
name String @db.VarChar(50)
description String @db.VarChar(200)
icon String @db.VarChar(20)
tier String @default("bronze") @db.VarChar(20)
points Int @default(0)
category String @default("general") @db.VarChar(30)
sortOrder Int @default(0) @map("sort_order")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
users UserAchievement[]
@@index([category, isActive, sortOrder])
@@map("achievements")
}
model UserAchievement {
id Int @id @default(autoincrement())
userId Int @map("user_id")
achievementKey String @map("achievement_key") @db.VarChar(50)
progress Int @default(0)
unlockedAt DateTime? @map("unlocked_at")
notifiedAt DateTime? @map("notified_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
achievement Achievement @relation(fields: [achievementKey], references: [key])
@@unique([userId, achievementKey])
@@index([userId, unlockedAt])
@@index([achievementKey])
@@map("user_achievements")
}
model ForumTopicSubscription {
id Int @id @default(autoincrement())
topicId Int @map("topic_id")
userId Int @map("user_id")
notifyReply Boolean @default(true) @map("notify_reply")
createdAt DateTime @default(now()) @map("created_at")
topic ForumTopic @relation(fields: [topicId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([topicId, userId])
@@index([userId, createdAt])
@@map("forum_topic_subscriptions")
}
model Feedback {
id Int @id @default(autoincrement())
type String @default("suggestion") @db.VarChar(20)
content String @db.Text
contact String? @db.VarChar(255)
pageUrl String? @map("page_url") @db.VarChar(500)
userId String? @map("user_id") @db.VarChar(50)
status String @default("pending") @db.VarChar(20)
adminNote String? @map("admin_note") @db.Text
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([status, createdAt])
@@map("feedbacks")
}
model ApiKey {
id Int @id @default(autoincrement())
userId Int @map("user_id")
name String @db.VarChar(100)
keyHash String @unique @map("key_hash") @db.VarChar(64)
keyPrefix String @map("key_prefix") @db.VarChar(8)
tier String @default("free") @db.VarChar(20)
rateLimit Int @default(100) @map("rate_limit")
lastUsedAt DateTime? @map("last_used_at")
requestCount Int @default(0) @map("request_count")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
expiresAt DateTime? @map("expires_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([keyPrefix])
@@map("api_keys")
}