Files
Nexumi/apps/bot/prisma/schema.prisma
smueller c7fb3d7041 Add global /about command and configuration
- Introduced `BotAboutConfig` model for managing the global `/about` message in the owner panel.
- Implemented the `/about` command with localization support and integrated it into the help catalog.
- Enhanced the dashboard with a new owner section for configuring the `/about` message type and content.
- Updated components to handle the new command and its interactions, including support for different message formats (TEXT, EMBED, COMPONENTS_V2).
- Added localization entries for the `/about` command in English and German, improving accessibility for users.
- Documented the implementation in the phase tracking documentation for future reference.
2026-07-24 12:26:00 +02:00

999 lines
30 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Guild {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
settings GuildSettings?
cases Case[]
escalationRules EscalationRule[]
autoModConfig AutoModConfig?
autoModRules AutoModRule[]
loggingConfig LoggingConfig?
logChannels LogChannel[]
welcomeConfig WelcomeConfig?
verificationConfig VerificationConfig?
verificationRecords VerificationRecord[]
levelingConfig LevelingConfig?
memberLevels MemberLevel[]
levelRewards LevelReward[]
economyConfig EconomyConfig?
memberEconomies MemberEconomy[]
shopItems ShopItem[]
inventoryItems InventoryItem[]
polls Poll[]
reminders Reminder[]
afkStatuses AfkStatus[]
giveaways Giveaway[]
ticketConfig TicketConfig?
ticketCategories TicketCategory[]
tickets Ticket[]
selfRolePanels SelfRolePanel[]
tags Tag[]
starboardConfig StarboardConfig?
starboardEntries StarboardEntry[]
suggestionConfig SuggestionConfig?
suggestions Suggestion[]
birthdays Birthday[]
birthdayConfig BirthdayConfig?
tempVoiceConfig TempVoiceConfig?
tempVoiceChannels TempVoiceChannel[]
statsConfig StatsConfig?
inviteStats InviteStat[]
inviteJoins InviteJoin[]
activityStats ActivityStat[]
socialFeeds SocialFeed[]
scheduledMessages ScheduledMessage[]
guildBackups GuildBackup[]
componentMessageBindings ComponentMessageBinding[]
dashboardAccessRules DashboardAccessRule[]
dashboardAuditLogs DashboardAuditLog[]
commandOverrides CommandOverride[]
}
model GuildSettings {
id String @id @default(cuid())
guildId String @unique
locale String @default("de")
timezone String @default("Europe/Berlin")
moderationEnabled Boolean @default(true)
/// Snipe/editsnipe storage and commands (SPEC: default off for privacy).
snipeEnabled Boolean @default(false)
/// Global command channel whitelist (empty = all channels unless denied).
commandAllowedChannelIds String[] @default([])
/// Global command channel blacklist.
commandDeniedChannelIds String[] @default([])
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model DashboardAccessRule {
id String @id @default(cuid())
guildId String
roleId String
modules Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, roleId])
@@index([guildId])
}
model DashboardAuditLog {
id String @id @default(cuid())
guildId String?
actorUserId String
action String
path String?
before Json?
after Json?
createdAt DateTime @default(now())
guild Guild? @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, createdAt])
@@index([actorUserId, createdAt])
}
model User {
id String @id
createdAt DateTime @default(now())
warnings Warning[]
notes ModNote[]
}
model Case {
id String @id @default(cuid())
caseNumber Int
guildId String
targetUserId String
moderatorId String
action String
reason String?
metadata Json?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
warnings Warning[]
moderationNote ModNote[]
@@unique([guildId, caseNumber])
@@index([guildId, targetUserId])
}
model Warning {
id String @id @default(cuid())
guildId String
userId String
moderatorId String
reason String?
caseId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
case Case? @relation(fields: [caseId], references: [id], onDelete: SetNull)
@@index([guildId, userId])
}
model ModNote {
id String @id @default(cuid())
guildId String
userId String
moderatorId String
note String
caseId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
case Case? @relation(fields: [caseId], references: [id], onDelete: SetNull)
@@index([guildId, userId])
}
model EscalationRule {
id String @id @default(cuid())
guildId String
warnCount Int
action String
durationMs Int?
reason String?
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, warnCount])
@@index([guildId, enabled])
}
model AutoModConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
antiRaidEnabled Boolean @default(false)
antiRaidJoinThreshold Int @default(10)
antiRaidWindowSeconds Int @default(10)
antiRaidAction String @default("LOCKDOWN")
antiNukeEnabled Boolean @default(false)
antiNukeBanThreshold Int @default(5)
antiNukeChannelThreshold Int @default(3)
antiNukeWindowSeconds Int @default(60)
lockdownActive Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model AutoModRule {
id String @id @default(cuid())
guildId String
ruleType String
enabled Boolean @default(true)
action String @default("DELETE")
threshold Json?
exceptions Json?
wordList Json?
durationMs Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, ruleType])
@@index([guildId, enabled])
}
model LoggingConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
ignoreBots Boolean @default(true)
ignoredChannelIds String[] @default([])
ignoredRoleIds String[] @default([])
/// Days to keep Case / dashboard audit rows; 0 = keep forever.
retentionDays Int @default(90)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model LogChannel {
id String @id @default(cuid())
guildId String
eventType String
channelId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, eventType])
@@index([guildId])
}
model WelcomeConfig {
id String @id @default(cuid())
guildId String @unique
welcomeEnabled Boolean @default(false)
leaveEnabled Boolean @default(false)
welcomeChannelId String?
leaveChannelId String?
welcomeType String @default("TEXT")
welcomeContent String?
welcomeEmbed Json?
welcomeComponents Json?
leaveType String @default("TEXT")
leaveContent String?
leaveEmbed Json?
leaveComponents Json?
welcomeDmEnabled Boolean @default(false)
welcomeDmContent String?
userAutoroleIds String[] @default([])
botAutoroleIds String[] @default([])
imageTitle String?
imageSubtitle String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model VerificationConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(false)
channelId String?
verifiedRoleId String?
unverifiedRoleId String?
mode String @default("BUTTON")
/// MATH | RECAPTCHA_V2 | RECAPTCHA_V3 | HCAPTCHA | TURNSTILE
captchaProvider String @default("MATH")
minAccountAgeDays Int @default(0)
failAction String @default("KICK")
altDetectionEnabled Boolean @default(false)
altBlockOnMatch Boolean @default(true)
altIpWindowDays Int @default(30)
altInviteWindowHours Int @default(48)
altMaxAccountsPerInvite Int @default(3)
panelChannelId String?
panelMessageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
/// One row per successful (or blocked-as-alt) verification attempt fingerprint.
model VerificationRecord {
id String @id @default(cuid())
guildId String
userId String
inviteCode String?
inviterId String?
ipHash String?
userAgentHash String?
captchaProvider String?
flaggedAsAlt Boolean @default(false)
matchedUserId String?
createdAt DateTime @default(now())
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
@@index([guildId, ipHash, createdAt])
@@index([guildId, inviteCode, createdAt])
}
model LevelingConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
textXpMin Int @default(15)
textXpMax Int @default(25)
textCooldownSeconds Int @default(60)
voiceXpPerMinute Int @default(10)
noXpChannelIds String[] @default([])
noXpRoleIds String[] @default([])
roleMultipliers Json?
channelMultipliers Json?
levelUpMode String @default("CHANNEL")
levelUpChannelId String?
levelUpMessage String?
stackRewards Boolean @default(true)
cardAccentColor String @default("#6366F1")
cardBackgroundColor String @default("#111827")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model MemberLevel {
id String @id @default(cuid())
guildId String
userId String
xp Int @default(0)
level Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
@@index([guildId, xp])
}
model LevelReward {
id String @id @default(cuid())
guildId String
level Int
roleId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, level])
@@index([guildId])
}
model EconomyConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
currencyName String @default("Coins")
currencySymbol String @default("🪙")
dailyAmount Int @default(200)
weeklyAmount Int @default(1000)
workMin Int @default(50)
workMax Int @default(150)
workCooldownSeconds Int @default(3600)
startingBalance Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model MemberEconomy {
id String @id @default(cuid())
guildId String
userId String
balance Int @default(0)
lastDailyAt DateTime?
lastWeeklyAt DateTime?
lastWorkAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
@@index([guildId, balance])
}
model ShopItem {
id String @id @default(cuid())
guildId String
name String
description String?
price Int
roleId String?
stock Int?
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
inventory InventoryItem[]
@@index([guildId, enabled])
}
model InventoryItem {
id String @id @default(cuid())
guildId String
userId String
itemId String
quantity Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
item ShopItem @relation(fields: [itemId], references: [id], onDelete: Cascade)
@@unique([guildId, userId, itemId])
@@index([guildId, userId])
}
model Poll {
id String @id @default(cuid())
guildId String
channelId String
messageId String?
creatorId String
question String
options Json
multiSelect Boolean @default(false)
anonymous Boolean @default(false)
endsAt DateTime?
closedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
votes PollVote[]
@@index([guildId])
}
model PollVote {
id String @id @default(cuid())
pollId String
userId String
optionIndex Int
createdAt DateTime @default(now())
poll Poll @relation(fields: [pollId], references: [id], onDelete: Cascade)
@@unique([pollId, userId, optionIndex])
@@index([pollId])
}
model Reminder {
id String @id @default(cuid())
guildId String?
userId String
channelId String
content String
remindAt DateTime
recurringCron String?
jobId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild? @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([remindAt])
}
model AfkStatus {
id String @id @default(cuid())
guildId String
userId String
reason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
}
model Giveaway {
id String @id @default(cuid())
guildId String
channelId String
messageId String?
prize String
winnerCount Int @default(1)
endsAt DateTime
requiredRoleId String?
requiredLevel Int?
requiredMemberDays Int?
paused Boolean @default(false)
endedAt DateTime?
winners String[] @default([])
entrants String[] @default([])
hostId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, endsAt])
}
model TicketConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
mode String @default("CHANNEL")
categoryId String?
logChannelId String?
transcriptDm Boolean @default(false)
inactivityHours Int @default(72)
ratingEnabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model TicketCategory {
id String @id @default(cuid())
guildId String
name String
description String?
emoji String?
supportRoleIds String[] @default([])
formFields Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
tickets Ticket[]
@@unique([guildId, name])
@@index([guildId])
}
model Ticket {
id String @id @default(cuid())
guildId String
categoryId String?
openerId String
claimedById String?
channelId String?
threadId String?
status String @default("OPEN")
priority String @default("NORMAL")
subject String?
formAnswers Json?
transcriptHtml String?
rating Int?
closedAt DateTime?
lastActivityAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
category TicketCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
@@index([guildId, status])
@@index([channelId])
}
model SelfRolePanel {
id String @id @default(cuid())
guildId String
channelId String
messageId String?
title String
description String?
mode String @default("BUTTONS")
behavior String @default("NORMAL")
roles Json
expiresAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId])
}
model Tag {
id String @id @default(cuid())
guildId String
name String
content String?
embed Json?
components Json?
responseType String @default("TEXT")
triggerWord String?
allowedRoleIds String[] @default([])
allowedChannelIds String[] @default([])
createdById String
useCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, name])
@@index([guildId])
}
model StarboardConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(false)
channelId String?
emoji String @default("⭐")
threshold Int @default(3)
allowSelfStar Boolean @default(false)
ignoredChannelIds String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model StarboardEntry {
id String @id @default(cuid())
guildId String
sourceChannelId String
sourceMessageId String
starboardMessageId String
starCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, sourceMessageId])
@@index([guildId])
}
model SuggestionConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
openChannelId String?
approvedChannelId String?
deniedChannelId String?
createThread Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model Suggestion {
id String @id @default(cuid())
guildId String
authorId String
content String
status String @default("OPEN")
messageId String?
channelId String?
threadId String?
upvotes Int @default(0)
downvotes Int @default(0)
voterUpIds String[] @default([])
voterDownIds String[] @default([])
staffReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, status])
}
model BirthdayConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
channelId String?
roleId String?
message String?
timezone String @default("Europe/Berlin")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model Birthday {
id String @id @default(cuid())
guildId String
userId String
month Int
day Int
year Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
@@index([guildId, month, day])
}
model TempVoiceConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(false)
hubChannelId String?
categoryId String?
defaultName String @default("{user}'s Channel")
defaultLimit Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model TempVoiceChannel {
id String @id @default(cuid())
guildId String
channelId String @unique
ownerId String
controlMessageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, ownerId])
}
model StatsConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(false)
membersChannelId String?
onlineChannelId String?
boostsChannelId String?
membersTemplate String @default("Members: {count}")
onlineTemplate String @default("Online: {count}")
boostsTemplate String @default("Boosts: {count}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model InviteStat {
id String @id @default(cuid())
guildId String
inviterId String
code String?
joins Int @default(0)
leaves Int @default(0)
fakes Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, inviterId])
@@index([guildId, joins])
}
model InviteJoin {
id String @id @default(cuid())
guildId String
userId String
inviterId String?
code String?
isFake Boolean @default(false)
leftAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
@@index([guildId, inviterId])
}
model ActivityStat {
id String @id @default(cuid())
guildId String
userId String
channelId String?
messageCount Int @default(0)
voiceMinutes Int @default(0)
day DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId, channelId, day])
@@index([guildId, day])
}
model SocialFeed {
id String @id @default(cuid())
guildId String
type String
sourceId String
channelId String
rolePingId String?
template String?
lastItemId String?
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, type])
@@index([enabled])
}
model ScheduledMessage {
id String @id @default(cuid())
guildId String
channelId String
creatorId String
content String?
embed Json?
components Json?
rolePingId String?
cron String?
runAt DateTime?
jobId String?
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, enabled])
}
model ComponentMessageBinding {
id String @id @default(cuid())
guildId String
channelId String
messageId String?
payload Json
createdById String
createdAt DateTime @default(now())
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, createdAt])
@@index([messageId])
}
model GuildBackup {
id String @id @default(cuid())
guildId String
name String
createdById String
payload Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, createdAt])
}
model CommandOverride {
id String @id @default(cuid())
guildId String
commandName String
enabled Boolean @default(true)
allowedRoleIds String[] @default([])
deniedRoleIds String[] @default([])
allowedChannelIds String[] @default([])
deniedChannelIds String[] @default([])
cooldownSeconds Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, commandName])
@@index([guildId])
}
model OwnerTeamMember {
id String @id @default(cuid())
userId String @unique
role String
note String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model GlobalUserBlacklist {
id String @id @default(cuid())
userId String @unique
reason String?
note String?
createdById String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model GuildBlacklist {
id String @id @default(cuid())
guildId String @unique
reason String?
createdById String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model FeatureFlag {
id String @id @default(cuid())
key String @unique
enabled Boolean @default(false)
rolloutPercent Int @default(0)
whitelistGuildIds String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model BotPresenceConfig {
id String @id @default("singleton")
status String @default("online")
activityType String @default("Watching")
activityText String @default("nexumi.de")
rotatingMessages Json?
maintenanceMode Boolean @default(false)
maintenanceMessage String?
updatedAt DateTime @updatedAt
}
/// Global `/about` message (Owner panel). Singleton row id = "singleton".
model BotAboutConfig {
id String @id @default("singleton")
enabled Boolean @default(true)
/// TEXT | EMBED | COMPONENTS_V2
messageType String @default("EMBED")
content String?
embed Json?
components Json?
updatedAt DateTime @updatedAt
}
model ChangelogEntry {
id String @id @default(cuid())
version String
title String
body String
publishedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model OwnerAuditLog {
id String @id @default(cuid())
actorUserId String
action String
targetType String?
targetId String?
before Json?
after Json?
createdAt DateTime @default(now())
@@index([createdAt])
}
/// Per-tier limits and feature gates (FREE / PREMIUM). Seeded on first use.
model PremiumTierConfig {
tier String @id
maxTags Int
maxFeeds Int
maxBackups Int
/// JSON object of feature key → boolean (tags, feeds, guildbackup, …).
features Json
updatedAt DateTime @updatedAt
}
model GuildPremiumAssignment {
id String @id @default(cuid())
guildId String @unique
tier String
note String?
assignedById String
expiresAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([tier])
}
model UserPremiumAssignment {
id String @id @default(cuid())
userId String @unique
tier String
note String?
assignedById String
expiresAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([tier])
}
model GdprDeletionLog {
id String @id @default(cuid())
userId String
guildId String?
summary Json?
createdAt DateTime @default(now())
@@index([userId, createdAt])
}