Files
kycnotme/web/prisma/schema.prisma
2025-06-14 18:56:58 +00:00

698 lines
23 KiB
Plaintext

// This is your Prisma schema file
datasource db {
provider = "postgres"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
generator json {
provider = "prisma-json-types-generator"
}
enum CommentStatus {
PENDING
HUMAN_PENDING
APPROVED
VERIFIED
REJECTED
}
enum OrderIdStatus {
PENDING
APPROVED
REJECTED
WITHDRAWN
}
model Comment {
id Int @id @default(autoincrement())
/// Computed via trigger. Do not update through prisma.
upvotes Int @default(0)
status CommentStatus @default(PENDING)
suspicious Boolean @default(false)
requiresAdminReview Boolean @default(false)
communityNote String?
verificationNote String?
internalNote String?
privateContext String?
orderId String? @db.VarChar(100)
orderIdStatus OrderIdStatus? @default(PENDING)
kycRequested Boolean @default(false)
fundsBlocked Boolean @default(false)
content String
rating Int? @db.SmallInt
ratingActive Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
serviceId Int
parentId Int?
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade)
replies Comment[] @relation("CommentReplies")
karmaTransactions KarmaTransaction[]
votes CommentVote[]
notificationPreferenceswatchedComments NotificationPreferences[] @relation("watchedComments")
Notification Notification[]
@@unique([serviceId, orderId], name: "unique_orderId_per_service")
@@index([status])
@@index([createdAt])
@@index([serviceId])
@@index([authorId])
@@index([upvotes])
@@index([rating])
@@index([ratingActive])
}
enum VerificationStatus {
COMMUNITY_CONTRIBUTED
// COMMUNITY_VERIFIED
APPROVED
VERIFICATION_SUCCESS
VERIFICATION_FAILED
}
enum ServiceInfoBanner {
NONE
NO_LONGER_OPERATIONAL
}
enum ServiceVisibility {
PUBLIC
UNLISTED
HIDDEN
ARCHIVED
}
enum Currency {
MONERO
BITCOIN
LIGHTNING
FIAT
CASH
}
enum EventType {
WARNING
WARNING_SOLVED
ALERT
ALERT_SOLVED
INFO
NORMAL
UPDATE
}
enum ServiceUserRole {
OWNER
ADMIN
MODERATOR
SUPPORT
TEAM_MEMBER
}
enum AccountStatusChange {
ADMIN_TRUE
ADMIN_FALSE
VERIFIED_TRUE
VERIFIED_FALSE
MODERATOR_TRUE
MODERATOR_FALSE
SPAMMER_TRUE
SPAMMER_FALSE
}
enum NotificationType {
TEST
COMMENT_STATUS_CHANGE
REPLY_COMMENT_CREATED
COMMUNITY_NOTE_ADDED
/// Comment that is not a reply. May include a rating.
ROOT_COMMENT_CREATED
SUGGESTION_CREATED
SUGGESTION_MESSAGE
SUGGESTION_STATUS_CHANGE
// KARMA_UNLOCK // TODO: [KARMA_UNLOCK] Will be added later, when karma unloks are in the database, not in the code.
KARMA_CHANGE
/// Marked as spammer, promoted to admin, etc.
ACCOUNT_STATUS_CHANGE
EVENT_CREATED
SERVICE_VERIFICATION_STATUS_CHANGE
}
enum CommentStatusChange {
MARKED_AS_SPAM
UNMARKED_AS_SPAM
MARKED_FOR_ADMIN_REVIEW
UNMARKED_FOR_ADMIN_REVIEW
STATUS_CHANGED_TO_APPROVED
STATUS_CHANGED_TO_VERIFIED
STATUS_CHANGED_TO_REJECTED
STATUS_CHANGED_TO_PENDING
}
enum ServiceVerificationStatusChange {
STATUS_CHANGED_TO_COMMUNITY_CONTRIBUTED
STATUS_CHANGED_TO_APPROVED
STATUS_CHANGED_TO_VERIFICATION_SUCCESS
STATUS_CHANGED_TO_VERIFICATION_FAILED
}
enum ServiceSuggestionStatusChange {
STATUS_CHANGED_TO_PENDING
STATUS_CHANGED_TO_APPROVED
STATUS_CHANGED_TO_REJECTED
STATUS_CHANGED_TO_WITHDRAWN
}
enum KarmaTransactionAction {
COMMENT_APPROVED
COMMENT_VERIFIED
COMMENT_SPAM
COMMENT_SPAM_REVERTED
COMMENT_UPVOTE
COMMENT_DOWNVOTE
COMMENT_VOTE_REMOVED
SUGGESTION_APPROVED
MANUAL_ADJUSTMENT
}
enum AnnouncementType {
INFO
WARNING
ALERT
}
model Notification {
id Int @id @default(autoincrement())
userId Int
user User @relation("NotificationOwner", fields: [userId], references: [id], onDelete: Cascade)
type NotificationType
read Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
aboutComment Comment? @relation(fields: [aboutCommentId], references: [id])
aboutCommentId Int?
aboutEvent Event? @relation(fields: [aboutEventId], references: [id])
aboutEventId Int?
aboutService Service? @relation(fields: [aboutServiceId], references: [id])
aboutServiceId Int?
aboutServiceSuggestion ServiceSuggestion? @relation(fields: [aboutServiceSuggestionId], references: [id])
aboutServiceSuggestionId Int?
aboutServiceSuggestionMessage ServiceSuggestionMessage? @relation(fields: [aboutServiceSuggestionMessageId], references: [id])
aboutServiceSuggestionMessageId Int?
aboutAccountStatusChange AccountStatusChange?
aboutCommentStatusChange CommentStatusChange?
aboutServiceVerificationStatusChange ServiceVerificationStatusChange?
aboutSuggestionStatusChange ServiceSuggestionStatusChange?
aboutKarmaTransaction KarmaTransaction? @relation(fields: [aboutKarmaTransactionId], references: [id])
aboutKarmaTransactionId Int?
@@index([userId])
@@index([read])
@@index([createdAt])
@@index([userId, read, createdAt])
@@index([userId, type, aboutCommentId])
@@index([userId, type, aboutServiceSuggestionMessageId], map: "idx_notification_suggestion_message")
@@index([userId, type, aboutServiceSuggestionId], map: "idx_notification_suggestion_status")
@@index([userId, type, aboutAccountStatusChange], map: "idx_notification_account_status")
}
model NotificationPreferences {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
userId Int @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
enableOnMyCommentStatusChange Boolean @default(true)
enableAutowatchMyComments Boolean @default(true)
enableNotifyPendingRepliesOnWatch Boolean @default(false)
karmaNotificationThreshold Int @default(10)
onEventCreatedForServices Service[] @relation("onEventCreatedForServices")
onRootCommentCreatedForServices Service[] @relation("onRootCommentCreatedForServices")
onVerificationChangeForServices Service[] @relation("onVerificationChangeForServices")
watchedComments Comment[] @relation("watchedComments")
onServiceVerificationChangeFilter NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
}
model NotificationPreferenceOnServiceVerificationChangeFilterFilter {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
verificationStatus VerificationStepStatus
notificationPreferences NotificationPreferences @relation(fields: [notificationPreferencesId], references: [id], onDelete: Cascade)
notificationPreferencesId Int
categories Category[]
attributes Attribute[]
currencies Currency[]
/// 0-10
scores Int[]
@@unique([verificationStatus, notificationPreferencesId])
}
model Event {
id Int @id @default(autoincrement())
title String
content String
source String?
type EventType
visible Boolean @default(true)
startedAt DateTime
/// If null, the event is ongoing. If same as startedAt, the event is a one-time event. If startedAt is in the future, the event is upcoming.
endedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
serviceId Int
Notification Notification[]
@@index([visible])
@@index([startedAt])
@@index([createdAt])
@@index([endedAt])
@@index([type])
@@index([serviceId])
}
enum ServiceSuggestionStatus {
PENDING
APPROVED
REJECTED
WITHDRAWN
}
enum ServiceSuggestionType {
CREATE_SERVICE
EDIT_SERVICE
}
enum KycLevelClarification {
NONE
DEPENDS_ON_PARTNERS
}
model ServiceSuggestion {
id Int @id @default(autoincrement())
type ServiceSuggestionType
status ServiceSuggestionStatus @default(PENDING)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
userId Int
serviceId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
messages ServiceSuggestionMessage[]
Notification Notification[]
KarmaTransaction KarmaTransaction[]
@@index([userId])
@@index([serviceId])
}
model ServiceSuggestionMessage {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
userId Int
suggestionId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
suggestion ServiceSuggestion @relation(fields: [suggestionId], references: [id], onDelete: Cascade)
notifications Notification[]
@@index([userId])
@@index([suggestionId])
@@index([createdAt])
}
model Service {
id Int @id @default(autoincrement())
name String
slug String @unique
previousSlugs String[] @default([])
description String
categories Category[] @relation("ServiceToCategory")
kycLevel Int @default(4)
kycLevelClarification KycLevelClarification @default(NONE)
overallScore Int @default(0)
privacyScore Int @default(0)
trustScore Int @default(0)
/// Computed via trigger. Do not update through prisma.
averageUserRating Float?
serviceVisibility ServiceVisibility @default(PUBLIC)
serviceInfoBanner ServiceInfoBanner @default(NONE)
serviceInfoBannerNotes String?
verificationStatus VerificationStatus @default(COMMUNITY_CONTRIBUTED)
verificationSummary String?
verificationRequests ServiceVerificationRequest[]
verificationProofMd String?
/// [UserSentiment]
userSentiment Json?
userSentimentAt DateTime?
referral String?
acceptedCurrencies Currency[] @default([])
serviceUrls String[]
tosUrls String[] @default([])
onionUrls String[] @default([])
i2pUrls String[] @default([])
imageUrl String?
/// [TosReview]
tosReview Json?
tosReviewAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
/// Computed via trigger when the visibility is PUBLIC or (ARCHIVED and listedAt was null). Do not update through prisma.
listedAt DateTime?
/// Computed via trigger when the verification status is APPROVED. Do not update through prisma.
approvedAt DateTime?
/// Computed via trigger when the verification status is VERIFICATION_SUCCESS. Do not update through prisma.
verifiedAt DateTime?
/// Computed via trigger when the verification status is VERIFICATION_FAILED. Do not update through prisma.
spamAt DateTime?
/// Computed via trigger. Do not update through prisma.
isRecentlyApproved Boolean @default(false)
comments Comment[]
events Event[]
contactMethods ServiceContactMethod[] @relation("ServiceToContactMethod")
attributes ServiceAttribute[]
verificationSteps VerificationStep[]
suggestions ServiceSuggestion[]
internalNotes InternalServiceNote[] @relation("ServiceRecievedNotes")
onEventCreatedForServices NotificationPreferences[] @relation("onEventCreatedForServices")
onRootCommentCreatedForServices NotificationPreferences[] @relation("onRootCommentCreatedForServices")
onVerificationChangeForServices NotificationPreferences[] @relation("onVerificationChangeForServices")
Notification Notification[]
affiliatedUsers ServiceUser[] @relation("ServiceUsers")
@@index([listedAt])
@@index([approvedAt])
@@index([verifiedAt])
@@index([spamAt])
@@index([overallScore])
@@index([privacyScore])
@@index([trustScore])
@@index([averageUserRating])
@@index([name])
@@index([verificationStatus])
@@index([kycLevel])
@@index([createdAt])
@@index([updatedAt])
@@index([slug])
@@index([previousSlugs])
@@index([serviceVisibility])
}
model ServiceContactMethod {
id Int @id @default(autoincrement())
/// Only include it if you want to override the formatted value.
label String?
/// Including the protocol (e.g. "mailto:", "tel:", "https://")
value String
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
services Service @relation("ServiceToContactMethod", fields: [serviceId], references: [id], onDelete: Cascade)
serviceId Int
}
enum AttributeCategory {
PRIVACY
TRUST
}
enum AttributeType {
GOOD
BAD
WARNING
INFO
}
model Attribute {
id Int @id @default(autoincrement())
slug String @unique
title String
/// Markdown
description String
privacyPoints Int @default(0)
trustPoints Int @default(0)
category AttributeCategory
type AttributeType
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
services ServiceAttribute[]
notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
}
model InternalUserNote {
id Int @id @default(autoincrement())
/// Markdown
content String
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
user User @relation("UserRecievedNotes", fields: [userId], references: [id], onDelete: Cascade)
userId Int
addedByUser User? @relation("UserAddedNotes", fields: [addedByUserId], references: [id], onDelete: SetNull)
addedByUserId Int?
@@index([userId])
@@index([addedByUserId])
@@index([createdAt])
}
model InternalServiceNote {
id Int @id @default(autoincrement())
/// Markdown
content String
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
service Service @relation("ServiceRecievedNotes", fields: [serviceId], references: [id], onDelete: Cascade)
serviceId Int
addedByUser User? @relation("UserAddedServiceNotes", fields: [addedByUserId], references: [id], onDelete: SetNull)
addedByUserId Int?
@@index([serviceId])
@@index([addedByUserId])
@@index([createdAt])
}
model User {
id Int @id @default(autoincrement())
name String @unique
displayName String?
link String?
picture String?
spammer Boolean @default(false)
verified Boolean @default(false)
admin Boolean @default(false)
moderator Boolean @default(false)
verifiedLink String?
secretTokenHash String @unique
feedId String @unique @default(cuid(2))
/// Computed via trigger. Do not update through prisma.
totalKarma Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
lastLoginAt DateTime @default(now())
comments Comment[]
karmaTransactions KarmaTransaction[]
grantedKarmaTransactions KarmaTransaction[] @relation("KarmaGrantedBy")
commentVotes CommentVote[]
suggestions ServiceSuggestion[]
suggestionMessages ServiceSuggestionMessage[]
internalNotes InternalUserNote[] @relation("UserRecievedNotes")
addedInternalNotes InternalUserNote[] @relation("UserAddedNotes")
addedServiceNotes InternalServiceNote[] @relation("UserAddedServiceNotes")
verificationRequests ServiceVerificationRequest[]
notifications Notification[] @relation("NotificationOwner")
notificationPreferences NotificationPreferences?
serviceAffiliations ServiceUser[] @relation("UserServices")
pushSubscriptions PushSubscription[]
@@index([createdAt])
@@index([totalKarma])
}
model CommentVote {
id Int @id @default(autoincrement())
downvote Boolean @default(false) // false = upvote, true = downvote
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
commentId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([commentId, userId]) // Ensure one vote per user per comment
@@index([commentId])
@@index([userId])
}
model ServiceAttribute {
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
serviceId Int
attribute Attribute @relation(fields: [attributeId], references: [id], onDelete: Cascade)
attributeId Int
createdAt DateTime @default(now())
@@id([serviceId, attributeId])
}
model KarmaTransaction {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
action KarmaTransactionAction
points Int @default(0)
comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
commentId Int?
suggestion ServiceSuggestion? @relation(fields: [suggestionId], references: [id], onDelete: Cascade)
suggestionId Int?
description String
processed Boolean @default(false)
createdAt DateTime @default(now())
grantedBy User? @relation("KarmaGrantedBy", fields: [grantedByUserId], references: [id], onDelete: SetNull)
grantedByUserId Int?
Notification Notification[]
@@index([createdAt])
@@index([userId])
@@index([processed])
@@index([suggestionId])
@@index([commentId])
@@index([grantedByUserId])
}
enum VerificationStepStatus {
PENDING
IN_PROGRESS
PASSED
FAILED
WARNING
}
model VerificationStep {
id Int @id @default(autoincrement())
title String
description String
status VerificationStepStatus @default(PENDING)
evidenceMd String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
serviceId Int
@@index([serviceId])
@@index([status])
@@index([createdAt])
}
model Category {
id Int @id @default(autoincrement())
name String @unique
icon String
slug String @unique
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
services Service[] @relation("ServiceToCategory")
notificationPreferencesOnServiceVerificationChange NotificationPreferenceOnServiceVerificationChangeFilterFilter[]
@@index([name])
@@index([slug])
}
model ServiceVerificationRequest {
id Int @id @default(autoincrement())
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
serviceId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@unique([serviceId, userId])
@@index([serviceId])
@@index([userId])
@@index([createdAt])
}
model ServiceScoreRecalculationJob {
id Int @id @default(autoincrement())
serviceId Int @unique
createdAt DateTime @default(now())
processedAt DateTime? @updatedAt
@@index([processedAt])
@@index([createdAt])
}
model ServiceUser {
id Int @id @default(autoincrement())
userId Int
user User @relation("UserServices", fields: [userId], references: [id], onDelete: Cascade)
serviceId Int
service Service @relation("ServiceUsers", fields: [serviceId], references: [id], onDelete: Cascade)
role ServiceUserRole
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, serviceId])
@@index([userId])
@@index([serviceId])
@@index([role])
}
model Announcement {
id Int @id @default(autoincrement())
content String
type AnnouncementType
link String?
linkText String?
startDate DateTime
endDate DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@index([isActive, startDate, endDate])
}
model PushSubscription {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
endpoint String @unique
/// Public key for encryption
p256dh String
/// Authentication secret
auth String
/// To identify different devices
userAgent String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([endpoint])
}