Fastify + Prisma backend, React + Vite frontend, Docker deployment. Multi-board feedback platform with anonymous cookie auth, passkey upgrade path, ALTCHA spam protection, plugin system, and full privacy-first architecture.
214 lines
5.5 KiB
Plaintext
214 lines
5.5 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
|
|
}
|
|
|
|
enum PostStatus {
|
|
OPEN
|
|
UNDER_REVIEW
|
|
PLANNED
|
|
IN_PROGRESS
|
|
DONE
|
|
DECLINED
|
|
}
|
|
|
|
model Board {
|
|
id String @id @default(cuid())
|
|
slug String @unique
|
|
name String
|
|
description String?
|
|
externalUrl String?
|
|
isArchived Boolean @default(false)
|
|
voteBudget Int @default(10)
|
|
voteBudgetReset String @default("monthly")
|
|
lastBudgetReset DateTime?
|
|
allowMultiVote Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
posts Post[]
|
|
activityEvents ActivityEvent[]
|
|
pushSubscriptions PushSubscription[]
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
authMethod AuthMethod @default(COOKIE)
|
|
tokenHash String? @unique
|
|
username String?
|
|
usernameIdx String? @unique
|
|
displayName String?
|
|
darkMode String @default("system")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
passkeys Passkey[]
|
|
posts Post[]
|
|
comments Comment[]
|
|
reactions Reaction[]
|
|
votes Vote[]
|
|
pushSubscriptions PushSubscription[]
|
|
}
|
|
|
|
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 PostStatus @default(OPEN)
|
|
category String?
|
|
voteCount Int @default(0)
|
|
isPinned Boolean @default(false)
|
|
boardId String
|
|
authorId String
|
|
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[]
|
|
activityEvents ActivityEvent[]
|
|
pushSubscriptions PushSubscription[]
|
|
}
|
|
|
|
model StatusChange {
|
|
id String @id @default(cuid())
|
|
postId String
|
|
fromStatus PostStatus
|
|
toStatus PostStatus
|
|
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
|
|
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)
|
|
reactions Reaction[]
|
|
}
|
|
|
|
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)
|
|
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])
|
|
}
|
|
|
|
model AdminUser {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
passwordHash String
|
|
createdAt DateTime @default(now())
|
|
|
|
responses AdminResponse[]
|
|
}
|
|
|
|
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?
|
|
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())
|
|
}
|