From 866cfab03e188e8bb2833b193725b6b3589742ac Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Mon, 8 Dec 2025 10:29:40 +0100 Subject: [PATCH] feat: Implement `userService.getOrCreateUser` and integrate it across commands, remove old utility scripts, and fix daily bonus calculation. --- src/commands/economy/balance.ts | 7 +------ src/commands/economy/daily.ts | 1 + src/commands/economy/pay.ts | 8 +------- src/commands/inventory/inventory.ts | 8 +++++--- src/commands/user/profile.ts | 20 +------------------- src/index.ts | 13 +++++++++++++ src/modules/economy/economy.service.ts | 5 ++--- src/modules/user/user.service.ts | 18 ++++++++++++++++++ src/scripts/read-env.ts | 9 --------- src/scripts/test-db.ts | 23 ----------------------- src/scripts/test-env.ts | 4 ---- src/scripts/write-env.ts | 20 -------------------- 12 files changed, 42 insertions(+), 94 deletions(-) delete mode 100644 src/scripts/read-env.ts delete mode 100644 src/scripts/test-db.ts delete mode 100644 src/scripts/test-env.ts delete mode 100644 src/scripts/write-env.ts diff --git a/src/commands/economy/balance.ts b/src/commands/economy/balance.ts index 5b8199b..9abdb65 100644 --- a/src/commands/economy/balance.ts +++ b/src/commands/economy/balance.ts @@ -15,12 +15,7 @@ export const balance = createCommand({ await interaction.deferReply(); const targetUser = interaction.options.getUser("user") || interaction.user; - const user = await userService.getUserById(targetUser.id); - - if (!user) { - await interaction.editReply({ content: "❌ User not found in database." }); - return; - } + const user = await userService.getOrCreateUser(targetUser.id, targetUser.username); const embed = new EmbedBuilder() .setAuthor({ name: targetUser.username, iconURL: targetUser.displayAvatarURL() }) diff --git a/src/commands/economy/daily.ts b/src/commands/economy/daily.ts index 85fac35..b698042 100644 --- a/src/commands/economy/daily.ts +++ b/src/commands/economy/daily.ts @@ -1,6 +1,7 @@ import { createCommand } from "@/lib/utils"; import { SlashCommandBuilder, EmbedBuilder } from "discord.js"; import { economyService } from "@/modules/economy/economy.service"; +import { userService } from "@/modules/user/user.service"; export const daily = createCommand({ data: new SlashCommandBuilder() diff --git a/src/commands/economy/pay.ts b/src/commands/economy/pay.ts index d3692af..4bf5f61 100644 --- a/src/commands/economy/pay.ts +++ b/src/commands/economy/pay.ts @@ -21,7 +21,7 @@ export const pay = createCommand({ execute: async (interaction) => { await interaction.deferReply(); - const targetUser = interaction.options.getUser("user", true); + const targetUser = await userService.getOrCreateUser(interaction.options.getUser("user", true).id, interaction.options.getUser("user", true).username); const amount = BigInt(interaction.options.getInteger("amount", true)); const senderId = interaction.user.id; const receiverId = targetUser.id; @@ -31,12 +31,6 @@ export const pay = createCommand({ return; } - // Ensure receiver exists - let receiver = await userService.getUserById(receiverId); - if (!receiver) { - receiver = await userService.createUser(receiverId, targetUser.username, undefined); - } - try { await economyService.transfer(senderId, receiverId, amount); diff --git a/src/commands/inventory/inventory.ts b/src/commands/inventory/inventory.ts index bd417b1..93bd98b 100644 --- a/src/commands/inventory/inventory.ts +++ b/src/commands/inventory/inventory.ts @@ -1,6 +1,7 @@ import { createCommand } from "@/lib/utils"; import { SlashCommandBuilder, EmbedBuilder } from "discord.js"; import { inventoryService } from "@/modules/inventory/inventory.service"; +import { userService } from "@/modules/user/user.service"; export const inventory = createCommand({ data: new SlashCommandBuilder() @@ -15,11 +16,12 @@ export const inventory = createCommand({ await interaction.deferReply(); const targetUser = interaction.options.getUser("user") || interaction.user; - const items = await inventoryService.getInventory(targetUser.id); + const user = await userService.getOrCreateUser(targetUser.id, targetUser.username); + const items = await inventoryService.getInventory(user.id); if (!items || items.length === 0) { const embed = new EmbedBuilder() - .setTitle(`${targetUser.username}'s Inventory`) + .setTitle(`${user.username}'s Inventory`) .setDescription("Inventory is empty.") .setColor("Blue"); @@ -32,7 +34,7 @@ export const inventory = createCommand({ }).join("\n"); const embed = new EmbedBuilder() - .setTitle(`${targetUser.username}'s Inventory`) + .setTitle(`${user.username}'s Inventory`) .setDescription(description) .setColor("Blue") .setTimestamp(); diff --git a/src/commands/user/profile.ts b/src/commands/user/profile.ts index e0dc8fa..e9b980a 100644 --- a/src/commands/user/profile.ts +++ b/src/commands/user/profile.ts @@ -17,25 +17,7 @@ export const profile = createCommand({ const targetUser = interaction.options.getUser("user") || interaction.user; const targetMember = await interaction.guild?.members.fetch(targetUser.id).catch(() => null); - let user = await userService.getUserById(targetUser.id); - - if (!user) { - // Auto-create user if they don't exist - // Assuming no class assigned initially (null) - user = await userService.createUser(targetUser.id, targetUser.username, undefined); - } - - // Refetch to get class relation if needed (though createUser returns user, it might not have relations loaded if we add them later) - // For now, let's assume we might need to join class manually or update userService to return it. - // Actually, let's just use what we have. If we need class name, we might need a separate query or update userService to include relation. - // Let's check if 'class' is in the returned user object from userService.getUserById. - // Looking at userService.ts, it uses findFirst. If we want relations, we need to add `with`. - - // Let's quickly re-fetch with relations to be safe and get Class Name - // Or we can update userService given we are in "Implement Commands" but changing service might be out of scope? - // No, I should make sure the command works. - // Let's rely on standard Drizzle query here for the "view" part or update service. - // Updating service is cleaner. + const user = await userService.getOrCreateUser(targetUser.id, targetUser.username); const embed = new EmbedBuilder() .setTitle(`${targetUser.username}'s Profile`) diff --git a/src/index.ts b/src/index.ts index 722c847..cf78edb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { Events } from "discord.js"; import { KyokoClient } from "@lib/KyokoClient"; import { env } from "@lib/env"; +import { userService } from "@/modules/user/user.service"; // Load commands await KyokoClient.loadCommands(); @@ -20,6 +21,18 @@ KyokoClient.on(Events.InteractionCreate, async interaction => { return; } + + // Ensure user exists in database + try { + const user = await userService.getUserById(interaction.user.id); + if (!user) { + console.log(`🆕 Creating new user entry for ${interaction.user.tag}`); + await userService.createUser(interaction.user.id, interaction.user.username); + } + } catch (error) { + console.error("Failed to check/create user:", error); + } + try { await command.execute(interaction); } catch (error) { diff --git a/src/modules/economy/economy.service.ts b/src/modules/economy/economy.service.ts index a277ec5..03ef866 100644 --- a/src/modules/economy/economy.service.ts +++ b/src/modules/economy/economy.service.ts @@ -1,7 +1,6 @@ import { users, transactions, cooldowns } from "@/db/schema"; -import { eq, sql, and, gt } from "drizzle-orm"; +import { eq, sql, and } from "drizzle-orm"; import { DrizzleClient } from "@/lib/DrizzleClient"; -import { userService } from "@/modules/user/user.service"; const DAILY_REWARD_AMOUNT = 100n; const STREAK_BONUS = 10n; @@ -113,7 +112,7 @@ export const economyService = { streak = 1; } - const bonus = BigInt(streak) * STREAK_BONUS; + const bonus = (BigInt(streak) - 1n) * STREAK_BONUS; const totalReward = DAILY_REWARD_AMOUNT + bonus; diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index 2044116..d7ac09c 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -14,6 +14,24 @@ export const userService = { const user = await DrizzleClient.query.users.findFirst({ where: eq(users.username, username) }); return user; }, + getOrCreateUser: async (id: string, username: string, tx?: any) => { + const execute = async (txFn: any) => { + let user = await txFn.query.users.findFirst({ + where: eq(users.id, BigInt(id)), + with: { class: true } + }); + + if (!user) { + const [newUser] = await txFn.insert(users).values({ + id: BigInt(id), + username, + }).returning(); + user = { ...newUser, class: null }; + } + return user; + }; + return tx ? await execute(tx) : await DrizzleClient.transaction(execute); + }, createUser: async (id: string | bigint, username: string, classId?: bigint, tx?: any) => { const execute = async (txFn: any) => { const [user] = await txFn.insert(users).values({ diff --git a/src/scripts/read-env.ts b/src/scripts/read-env.ts deleted file mode 100644 index f6b715a..0000000 --- a/src/scripts/read-env.ts +++ /dev/null @@ -1,9 +0,0 @@ - -import { readFile } from "fs/promises"; - -try { - const content = await readFile(".env", "utf-8"); - console.log(content); -} catch (e) { - console.error(e); -} diff --git a/src/scripts/test-db.ts b/src/scripts/test-db.ts deleted file mode 100644 index 91b0333..0000000 --- a/src/scripts/test-db.ts +++ /dev/null @@ -1,23 +0,0 @@ - -import { userService } from "@/modules/user/user.service"; -import { DrizzleClient } from "@/lib/DrizzleClient"; -import { users } from "@/db/schema"; -import { eq } from "drizzle-orm"; - -console.log("Starting test..."); - -try { - const id = "109998942841765888"; - - console.log("Fetching user..."); - const user = await userService.getUserById(id); - console.log("User fetched:", user); - - if (user?.class) { - console.log("User class fetched:", user.class); - } -} catch (e) { - console.error("Error:", e); -} - -process.exit(0); diff --git a/src/scripts/test-env.ts b/src/scripts/test-env.ts deleted file mode 100644 index ccac329..0000000 --- a/src/scripts/test-env.ts +++ /dev/null @@ -1,4 +0,0 @@ - -import { env } from "@lib/env"; - -console.log("DATABASE_URL:", env.DATABASE_URL); diff --git a/src/scripts/write-env.ts b/src/scripts/write-env.ts deleted file mode 100644 index 89575b3..0000000 --- a/src/scripts/write-env.ts +++ /dev/null @@ -1,20 +0,0 @@ - -import { writeFile } from "fs/promises"; - -const content = `DB_USER=kyoko -DB_PASSWORD=kyoko -DB_NAME=kyoko -DB_PORT=5432 -DB_HOST=localhost -DISCORD_BOT_TOKEN=MTQ0Mzg4NTA3NDM0ODExODA5OA.GcX7aT.S6G2jWqLmPAOx04JBHhJn7TCPsx5pK5RMUxN3g -DISCORD_CLIENT_ID=1443885074348118098 -DISCORD_GUILD_ID=1443887793565728870 -DATABASE_URL=postgres://kyoko:kyoko@localhost:5432/kyoko -`; - -try { - await writeFile(".env", content); - console.log("Updated .env"); -} catch (e) { - console.error(e); -}