571 lines
16 KiB
Plaintext
571 lines
16 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum AuthMethod {
|
|
COOKIE
|
|
PASSKEY
|
|
}
|
|
|
|
enum PostType {
|
|
FEATURE_REQUEST
|
|
BUG_REPORT
|
|
}
|
|
|
|
model Board {
|
|
id String @id @default(cuid())
|
|
slug String @unique
|
|
name String
|
|
description String?
|
|
externalUrl String?
|
|
iconName String?
|
|
iconColor String?
|
|
isArchived Boolean @default(false)
|
|
voteBudget Int @default(10)
|
|
voteBudgetReset String @default("monthly")
|
|
lastBudgetReset DateTime?
|
|
allowMultiVote Boolean @default(false)
|
|
rssEnabled Boolean @default(true)
|
|
rssFeedCount Int @default(50)
|
|
staleDays Int @default(0)
|
|
position Int @default(0)
|
|
sensitivityLevel String @default("normal")
|
|
velocityThreshold Int?
|
|
quarantined Boolean @default(false)
|
|
requireVoteVerification Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
posts Post[]
|
|
activityEvents ActivityEvent[]
|
|
pushSubscriptions PushSubscription[]
|
|
statusConfig BoardStatus[]
|
|
templates BoardTemplate[]
|
|
changelogEntries ChangelogEntry[]
|
|
baseline BoardBaseline?
|
|
brigadePatterns BrigadePattern[]
|
|
}
|
|
|
|
model BoardStatus {
|
|
id String @id @default(cuid())
|
|
boardId String
|
|
status String
|
|
label String
|
|
color String
|
|
position Int @default(0)
|
|
enabled Boolean @default(true)
|
|
|
|
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([boardId, status])
|
|
@@index([boardId, position])
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
authMethod AuthMethod @default(COOKIE)
|
|
tokenHash String? @unique
|
|
username String?
|
|
usernameIdx String? @unique
|
|
displayName String?
|
|
avatarPath String?
|
|
darkMode String @default("system")
|
|
firstActionType String?
|
|
firstActionAt DateTime?
|
|
actionDiversityScore Float @default(0)
|
|
voteTimingStdDev Float?
|
|
boardInteractionCount Int @default(0)
|
|
flagCount Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
passkeys Passkey[]
|
|
posts Post[]
|
|
comments Comment[]
|
|
reactions Reaction[]
|
|
votes Vote[]
|
|
notifications Notification[]
|
|
pushSubscriptions PushSubscription[]
|
|
attachments Attachment[]
|
|
adminLink AdminUser?
|
|
edits EditHistory[]
|
|
recoveryCode RecoveryCode?
|
|
}
|
|
|
|
model RecoveryCode {
|
|
id String @id @default(cuid())
|
|
codeHash String
|
|
phraseIdx String @unique
|
|
userId String @unique
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model Passkey {
|
|
id String @id @default(cuid())
|
|
credentialId String
|
|
credentialIdIdx String @unique
|
|
credentialPublicKey Bytes
|
|
counter BigInt
|
|
credentialDeviceType String
|
|
credentialBackedUp Boolean
|
|
transports String?
|
|
userId String
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model Post {
|
|
id String @id @default(cuid())
|
|
type PostType
|
|
title String
|
|
description Json
|
|
status String @default("OPEN")
|
|
statusReason String?
|
|
category String?
|
|
templateId String?
|
|
voteCount Int @default(0)
|
|
viewCount Int @default(0)
|
|
isPinned Boolean @default(false)
|
|
isEditLocked Boolean @default(false)
|
|
isThreadLocked Boolean @default(false)
|
|
isVotingLocked Boolean @default(false)
|
|
frozenAt DateTime?
|
|
votesVisibleAfter DateTime?
|
|
onBehalfOf String?
|
|
boardId String
|
|
authorId String
|
|
lastActivityAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
|
statusChanges StatusChange[]
|
|
comments Comment[]
|
|
votes Vote[]
|
|
adminResponses AdminResponse[]
|
|
adminNotes AdminNote[]
|
|
notifications Notification[]
|
|
activityEvents ActivityEvent[]
|
|
pushSubscriptions PushSubscription[]
|
|
tags PostTag[]
|
|
attachments Attachment[]
|
|
editHistory EditHistory[]
|
|
voteSnapshots PostVoteSnapshot[]
|
|
|
|
@@index([boardId, status])
|
|
}
|
|
|
|
model StatusChange {
|
|
id String @id @default(cuid())
|
|
postId String
|
|
fromStatus String
|
|
toStatus String
|
|
reason String?
|
|
changedBy String
|
|
createdAt DateTime @default(now())
|
|
|
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model Comment {
|
|
id String @id @default(cuid())
|
|
body String
|
|
postId String
|
|
authorId String
|
|
replyToId String?
|
|
isAdmin Boolean @default(false)
|
|
isEditLocked Boolean @default(false)
|
|
adminUserId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
|
replyTo Comment? @relation("CommentReplies", fields: [replyToId], references: [id], onDelete: SetNull)
|
|
replies Comment[] @relation("CommentReplies")
|
|
adminUser AdminUser? @relation(fields: [adminUserId], references: [id], onDelete: SetNull)
|
|
reactions Reaction[]
|
|
attachments Attachment[]
|
|
editHistory EditHistory[]
|
|
|
|
@@index([replyToId])
|
|
@@index([postId, createdAt])
|
|
}
|
|
|
|
model Reaction {
|
|
id String @id @default(cuid())
|
|
emoji String
|
|
commentId String
|
|
userId String
|
|
createdAt DateTime @default(now())
|
|
|
|
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([commentId, userId, emoji])
|
|
}
|
|
|
|
model Vote {
|
|
id String @id @default(cuid())
|
|
weight Int @default(1)
|
|
importance String?
|
|
phantom Boolean @default(false)
|
|
voterIp String?
|
|
referrer String?
|
|
postId String
|
|
voterId String
|
|
budgetPeriod String
|
|
createdAt DateTime @default(now())
|
|
|
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
voter User @relation(fields: [voterId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([postId, voterId])
|
|
}
|
|
|
|
enum TeamRole {
|
|
SUPER_ADMIN
|
|
ADMIN
|
|
MODERATOR
|
|
}
|
|
|
|
model AdminUser {
|
|
id String @id @default(cuid())
|
|
email String? @unique
|
|
passwordHash String?
|
|
role TeamRole @default(ADMIN)
|
|
displayName String?
|
|
teamTitle String?
|
|
linkedUserId String? @unique
|
|
invitedById String?
|
|
createdAt DateTime @default(now())
|
|
|
|
linkedUser User? @relation(fields: [linkedUserId], references: [id], onDelete: SetNull)
|
|
invitedBy AdminUser? @relation("TeamInviter", fields: [invitedById], references: [id], onDelete: SetNull)
|
|
invitees AdminUser[] @relation("TeamInviter")
|
|
responses AdminResponse[]
|
|
notes AdminNote[]
|
|
comments Comment[]
|
|
createdInvites TeamInvite[] @relation("InviteCreator")
|
|
claimedInvite TeamInvite? @relation("InviteClaimer")
|
|
}
|
|
|
|
model TeamInvite {
|
|
id String @id @default(cuid())
|
|
tokenHash String @unique
|
|
role TeamRole
|
|
label String?
|
|
expiresAt DateTime
|
|
createdById String
|
|
claimedById String? @unique
|
|
claimedAt DateTime?
|
|
recoveryHash String?
|
|
recoveryIdx String?
|
|
createdAt DateTime @default(now())
|
|
|
|
createdBy AdminUser @relation("InviteCreator", fields: [createdById], references: [id], onDelete: Cascade)
|
|
claimedBy AdminUser? @relation("InviteClaimer", fields: [claimedById], references: [id], onDelete: SetNull)
|
|
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model AdminResponse {
|
|
id String @id @default(cuid())
|
|
body String
|
|
postId String
|
|
adminId String
|
|
createdAt DateTime @default(now())
|
|
|
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
admin AdminUser @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model ActivityEvent {
|
|
id String @id @default(cuid())
|
|
type String
|
|
boardId String
|
|
postId String?
|
|
metadata Json
|
|
createdAt DateTime @default(now())
|
|
|
|
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
post Post? @relation(fields: [postId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([boardId, createdAt])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model PushSubscription {
|
|
id String @id @default(cuid())
|
|
endpoint String
|
|
endpointIdx String @unique
|
|
keysP256dh String
|
|
keysAuth String
|
|
userId String
|
|
boardId String?
|
|
postId String?
|
|
failureCount Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
board Board? @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
post Post? @relation(fields: [postId], references: [id], onDelete: SetNull)
|
|
}
|
|
|
|
model Category {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
slug String @unique
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model Notification {
|
|
id String @id @default(cuid())
|
|
type String
|
|
title String
|
|
body String
|
|
postId String?
|
|
userId String
|
|
read Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
post Post? @relation(fields: [postId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([userId, read, createdAt])
|
|
}
|
|
|
|
model Webhook {
|
|
id String @id @default(cuid())
|
|
url String
|
|
secret String
|
|
events String[] @default(["status_changed", "post_created", "comment_added"])
|
|
active Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model PostMerge {
|
|
id String @id @default(cuid())
|
|
sourcePostId String
|
|
targetPostId String
|
|
mergedBy String
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([sourcePostId])
|
|
}
|
|
|
|
model ChangelogEntry {
|
|
id String @id @default(cuid())
|
|
title String
|
|
body String
|
|
boardId String?
|
|
publishedAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
board Board? @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([boardId, publishedAt])
|
|
}
|
|
|
|
model AdminNote {
|
|
id String @id @default(cuid())
|
|
body String
|
|
postId String
|
|
adminId String
|
|
createdAt DateTime @default(now())
|
|
|
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
admin AdminUser @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model Tag {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
color String @default("#6366F1")
|
|
createdAt DateTime @default(now())
|
|
|
|
posts PostTag[]
|
|
}
|
|
|
|
model PostTag {
|
|
postId String
|
|
tagId String
|
|
|
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([postId, tagId])
|
|
}
|
|
|
|
model Attachment {
|
|
id String @id @default(cuid())
|
|
filename String
|
|
path String
|
|
size Int
|
|
mimeType String
|
|
postId String?
|
|
commentId String?
|
|
uploaderId String
|
|
createdAt DateTime @default(now())
|
|
|
|
post Post? @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
|
uploader User @relation(fields: [uploaderId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model BoardTemplate {
|
|
id String @id @default(cuid())
|
|
boardId String
|
|
name String
|
|
fields Json
|
|
isDefault Boolean @default(false)
|
|
position Int @default(0)
|
|
|
|
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([boardId, position])
|
|
}
|
|
|
|
model SiteSettings {
|
|
id String @id @default("default")
|
|
appName String @default("Echoboard")
|
|
logoUrl String?
|
|
faviconUrl String?
|
|
accentColor String @default("#F59E0B")
|
|
headerFont String?
|
|
bodyFont String?
|
|
poweredByVisible Boolean @default(true)
|
|
customCss String?
|
|
ogImageUrl String?
|
|
ogDescription String?
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model BlockedToken {
|
|
id String @id @default(cuid())
|
|
tokenHash String @unique
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model EditHistory {
|
|
id String @id @default(cuid())
|
|
postId String?
|
|
commentId String?
|
|
editedBy String?
|
|
previousTitle String?
|
|
previousDescription Json?
|
|
previousBody String?
|
|
createdAt DateTime @default(now())
|
|
|
|
post Post? @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
|
editor User? @relation(fields: [editedBy], references: [id], onDelete: SetNull)
|
|
|
|
@@index([postId, createdAt])
|
|
@@index([commentId, createdAt])
|
|
}
|
|
|
|
model Plugin {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
version String
|
|
description String?
|
|
author String?
|
|
enabled Boolean @default(false)
|
|
dirPath String
|
|
entryPoint String @default("index.js")
|
|
config Json @default("{}")
|
|
configSchema Json @default("[]")
|
|
installedAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
data PluginData[]
|
|
}
|
|
|
|
model PluginData {
|
|
id String @id @default(cuid())
|
|
pluginId String
|
|
key String
|
|
value Json
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
plugin Plugin @relation(fields: [pluginId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([pluginId, key])
|
|
@@index([pluginId])
|
|
}
|
|
|
|
model AnomalyEvent {
|
|
id String @id @default(cuid())
|
|
type String
|
|
severity String
|
|
targetType String
|
|
targetId String
|
|
boardId String?
|
|
metadata Json @default("{}")
|
|
status String @default("pending")
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([status, createdAt])
|
|
@@index([targetType, targetId])
|
|
@@index([boardId, createdAt])
|
|
}
|
|
|
|
model BoardBaseline {
|
|
id String @id @default(cuid())
|
|
boardId String @unique
|
|
avgVotesPerHour Float @default(0)
|
|
avgPostsPerDay Float @default(0)
|
|
avgReactionsPerHour Float @default(0)
|
|
peakHourOfDay Int @default(12)
|
|
peakDayOfWeek Int @default(2)
|
|
updatedAt DateTime @updatedAt
|
|
|
|
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model PostVoteSnapshot {
|
|
id String @id @default(cuid())
|
|
postId String
|
|
voteCount Int
|
|
snapshotAt DateTime @default(now())
|
|
|
|
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([postId, snapshotAt])
|
|
}
|
|
|
|
model BrigadePattern {
|
|
id String @id @default(cuid())
|
|
boardId String?
|
|
features Json
|
|
matchCount Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
|
|
board Board? @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model AdminWebhookConfig {
|
|
id String @id @default(cuid())
|
|
url String
|
|
events String[]
|
|
active Boolean @default(true)
|
|
}
|