Implement leveling and economy features with associated models and commands

- Added new models for leveling and economy functionalities in the Prisma schema, including LevelingConfig, MemberLevel, LevelReward, EconomyConfig, MemberEconomy, ShopItem, and InventoryItem.
- Introduced commands for managing XP, economy transactions, and shop interactions, enhancing user engagement.
- Updated job handling to include reminders and poll closing functionalities.
- Enhanced localization support for new commands and features in both German and English.
- Improved the bot's job processing capabilities with a dedicated reminder worker for scheduled tasks.
This commit is contained in:
smueller
2026-07-22 12:49:17 +02:00
parent 2518119257
commit fa5910ec43
38 changed files with 6759 additions and 144 deletions

View File

@@ -0,0 +1,180 @@
-- CreateTable
CREATE TABLE "LevelingConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"textXpMin" INTEGER NOT NULL DEFAULT 15,
"textXpMax" INTEGER NOT NULL DEFAULT 25,
"textCooldownSeconds" INTEGER NOT NULL DEFAULT 60,
"voiceXpPerMinute" INTEGER NOT NULL DEFAULT 10,
"noXpChannelIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"noXpRoleIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"roleMultipliers" JSONB,
"channelMultipliers" JSONB,
"levelUpMode" TEXT NOT NULL DEFAULT 'CHANNEL',
"levelUpChannelId" TEXT,
"levelUpMessage" TEXT,
"stackRewards" BOOLEAN NOT NULL DEFAULT true,
"cardAccentColor" TEXT NOT NULL DEFAULT '#6366F1',
"cardBackgroundColor" TEXT NOT NULL DEFAULT '#111827',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LevelingConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "MemberLevel" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"xp" INTEGER NOT NULL DEFAULT 0,
"level" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "MemberLevel_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "LevelReward" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"level" INTEGER NOT NULL,
"roleId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LevelReward_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "EconomyConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"currencyName" TEXT NOT NULL DEFAULT 'Coins',
"currencySymbol" TEXT NOT NULL DEFAULT '🪙',
"dailyAmount" INTEGER NOT NULL DEFAULT 200,
"weeklyAmount" INTEGER NOT NULL DEFAULT 1000,
"workMin" INTEGER NOT NULL DEFAULT 50,
"workMax" INTEGER NOT NULL DEFAULT 150,
"workCooldownSeconds" INTEGER NOT NULL DEFAULT 3600,
"startingBalance" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "EconomyConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "MemberEconomy" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"balance" INTEGER NOT NULL DEFAULT 0,
"lastDailyAt" TIMESTAMP(3),
"lastWeeklyAt" TIMESTAMP(3),
"lastWorkAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "MemberEconomy_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "ShopItem" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"price" INTEGER NOT NULL,
"roleId" TEXT,
"stock" INTEGER,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ShopItem_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "InventoryItem" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"itemId" TEXT NOT NULL,
"quantity" INTEGER NOT NULL DEFAULT 1,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "InventoryItem_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Poll" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"messageId" TEXT,
"creatorId" TEXT NOT NULL,
"question" TEXT NOT NULL,
"options" JSONB NOT NULL,
"multiSelect" BOOLEAN NOT NULL DEFAULT false,
"anonymous" BOOLEAN NOT NULL DEFAULT false,
"endsAt" TIMESTAMP(3),
"closedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Poll_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "PollVote" (
"id" TEXT NOT NULL,
"pollId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"optionIndex" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PollVote_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Reminder" (
"id" TEXT NOT NULL,
"guildId" TEXT,
"userId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"remindAt" TIMESTAMP(3) NOT NULL,
"recurringCron" TEXT,
"jobId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Reminder_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "AfkStatus" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"reason" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AfkStatus_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "LevelingConfig_guildId_key" ON "LevelingConfig"("guildId");
CREATE UNIQUE INDEX "MemberLevel_guildId_userId_key" ON "MemberLevel"("guildId", "userId");
CREATE INDEX "MemberLevel_guildId_xp_idx" ON "MemberLevel"("guildId", "xp");
CREATE UNIQUE INDEX "LevelReward_guildId_level_key" ON "LevelReward"("guildId", "level");
CREATE INDEX "LevelReward_guildId_idx" ON "LevelReward"("guildId");
CREATE UNIQUE INDEX "EconomyConfig_guildId_key" ON "EconomyConfig"("guildId");
CREATE UNIQUE INDEX "MemberEconomy_guildId_userId_key" ON "MemberEconomy"("guildId", "userId");
CREATE INDEX "MemberEconomy_guildId_balance_idx" ON "MemberEconomy"("guildId", "balance");
CREATE INDEX "ShopItem_guildId_enabled_idx" ON "ShopItem"("guildId", "enabled");
CREATE UNIQUE INDEX "InventoryItem_guildId_userId_itemId_key" ON "InventoryItem"("guildId", "userId", "itemId");
CREATE INDEX "InventoryItem_guildId_userId_idx" ON "InventoryItem"("guildId", "userId");
CREATE INDEX "Poll_guildId_idx" ON "Poll"("guildId");
CREATE UNIQUE INDEX "PollVote_pollId_userId_optionIndex_key" ON "PollVote"("pollId", "userId", "optionIndex");
CREATE INDEX "PollVote_pollId_idx" ON "PollVote"("pollId");
CREATE INDEX "Reminder_userId_idx" ON "Reminder"("userId");
CREATE INDEX "Reminder_remindAt_idx" ON "Reminder"("remindAt");
CREATE UNIQUE INDEX "AfkStatus_guildId_userId_key" ON "AfkStatus"("guildId", "userId");
ALTER TABLE "LevelingConfig" ADD CONSTRAINT "LevelingConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "MemberLevel" ADD CONSTRAINT "MemberLevel_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "LevelReward" ADD CONSTRAINT "LevelReward_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "EconomyConfig" ADD CONSTRAINT "EconomyConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "MemberEconomy" ADD CONSTRAINT "MemberEconomy_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ShopItem" ADD CONSTRAINT "ShopItem_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "InventoryItem" ADD CONSTRAINT "InventoryItem_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "InventoryItem" ADD CONSTRAINT "InventoryItem_itemId_fkey" FOREIGN KEY ("itemId") REFERENCES "ShopItem"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Poll" ADD CONSTRAINT "Poll_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "PollVote" ADD CONSTRAINT "PollVote_pollId_fkey" FOREIGN KEY ("pollId") REFERENCES "Poll"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Reminder" ADD CONSTRAINT "Reminder_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "AfkStatus" ADD CONSTRAINT "AfkStatus_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -20,6 +20,16 @@ model Guild {
logChannels LogChannel[]
welcomeConfig WelcomeConfig?
verificationConfig VerificationConfig?
levelingConfig LevelingConfig?
memberLevels MemberLevel[]
levelRewards LevelReward[]
economyConfig EconomyConfig?
memberEconomies MemberEconomy[]
shopItems ShopItem[]
inventoryItems InventoryItem[]
polls Poll[]
reminders Reminder[]
afkStatuses AfkStatus[]
}
model GuildSettings {
@@ -203,3 +213,179 @@ model VerificationConfig {
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
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])
}