feat: Implement userService.getOrCreateUser and integrate it across commands, remove old utility scripts, and fix daily bonus calculation.
This commit is contained in:
@@ -15,12 +15,7 @@ export const balance = createCommand({
|
|||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
const targetUser = interaction.options.getUser("user") || interaction.user;
|
const targetUser = interaction.options.getUser("user") || interaction.user;
|
||||||
const user = await userService.getUserById(targetUser.id);
|
const user = await userService.getOrCreateUser(targetUser.id, targetUser.username);
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
await interaction.editReply({ content: "❌ User not found in database." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setAuthor({ name: targetUser.username, iconURL: targetUser.displayAvatarURL() })
|
.setAuthor({ name: targetUser.username, iconURL: targetUser.displayAvatarURL() })
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
|
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
|
||||||
import { economyService } from "@/modules/economy/economy.service";
|
import { economyService } from "@/modules/economy/economy.service";
|
||||||
|
import { userService } from "@/modules/user/user.service";
|
||||||
|
|
||||||
export const daily = createCommand({
|
export const daily = createCommand({
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const pay = createCommand({
|
|||||||
execute: async (interaction) => {
|
execute: async (interaction) => {
|
||||||
await interaction.deferReply();
|
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 amount = BigInt(interaction.options.getInteger("amount", true));
|
||||||
const senderId = interaction.user.id;
|
const senderId = interaction.user.id;
|
||||||
const receiverId = targetUser.id;
|
const receiverId = targetUser.id;
|
||||||
@@ -31,12 +31,6 @@ export const pay = createCommand({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure receiver exists
|
|
||||||
let receiver = await userService.getUserById(receiverId);
|
|
||||||
if (!receiver) {
|
|
||||||
receiver = await userService.createUser(receiverId, targetUser.username, undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await economyService.transfer(senderId, receiverId, amount);
|
await economyService.transfer(senderId, receiverId, amount);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
|
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
|
||||||
import { inventoryService } from "@/modules/inventory/inventory.service";
|
import { inventoryService } from "@/modules/inventory/inventory.service";
|
||||||
|
import { userService } from "@/modules/user/user.service";
|
||||||
|
|
||||||
export const inventory = createCommand({
|
export const inventory = createCommand({
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
@@ -15,11 +16,12 @@ export const inventory = createCommand({
|
|||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
const targetUser = interaction.options.getUser("user") || interaction.user;
|
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) {
|
if (!items || items.length === 0) {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(`${targetUser.username}'s Inventory`)
|
.setTitle(`${user.username}'s Inventory`)
|
||||||
.setDescription("Inventory is empty.")
|
.setDescription("Inventory is empty.")
|
||||||
.setColor("Blue");
|
.setColor("Blue");
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ export const inventory = createCommand({
|
|||||||
}).join("\n");
|
}).join("\n");
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(`${targetUser.username}'s Inventory`)
|
.setTitle(`${user.username}'s Inventory`)
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.setColor("Blue")
|
.setColor("Blue")
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|||||||
@@ -17,25 +17,7 @@ export const profile = createCommand({
|
|||||||
const targetUser = interaction.options.getUser("user") || interaction.user;
|
const targetUser = interaction.options.getUser("user") || interaction.user;
|
||||||
const targetMember = await interaction.guild?.members.fetch(targetUser.id).catch(() => null);
|
const targetMember = await interaction.guild?.members.fetch(targetUser.id).catch(() => null);
|
||||||
|
|
||||||
let user = await userService.getUserById(targetUser.id);
|
const user = await userService.getOrCreateUser(targetUser.id, targetUser.username);
|
||||||
|
|
||||||
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 embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(`${targetUser.username}'s Profile`)
|
.setTitle(`${targetUser.username}'s Profile`)
|
||||||
|
|||||||
13
src/index.ts
13
src/index.ts
@@ -1,6 +1,7 @@
|
|||||||
import { Events } from "discord.js";
|
import { Events } from "discord.js";
|
||||||
import { KyokoClient } from "@lib/KyokoClient";
|
import { KyokoClient } from "@lib/KyokoClient";
|
||||||
import { env } from "@lib/env";
|
import { env } from "@lib/env";
|
||||||
|
import { userService } from "@/modules/user/user.service";
|
||||||
|
|
||||||
// Load commands
|
// Load commands
|
||||||
await KyokoClient.loadCommands();
|
await KyokoClient.loadCommands();
|
||||||
@@ -20,6 +21,18 @@ KyokoClient.on(Events.InteractionCreate, async interaction => {
|
|||||||
return;
|
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 {
|
try {
|
||||||
await command.execute(interaction);
|
await command.execute(interaction);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { users, transactions, cooldowns } from "@/db/schema";
|
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 { DrizzleClient } from "@/lib/DrizzleClient";
|
||||||
import { userService } from "@/modules/user/user.service";
|
|
||||||
|
|
||||||
const DAILY_REWARD_AMOUNT = 100n;
|
const DAILY_REWARD_AMOUNT = 100n;
|
||||||
const STREAK_BONUS = 10n;
|
const STREAK_BONUS = 10n;
|
||||||
@@ -113,7 +112,7 @@ export const economyService = {
|
|||||||
streak = 1;
|
streak = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bonus = BigInt(streak) * STREAK_BONUS;
|
const bonus = (BigInt(streak) - 1n) * STREAK_BONUS;
|
||||||
|
|
||||||
const totalReward = DAILY_REWARD_AMOUNT + bonus;
|
const totalReward = DAILY_REWARD_AMOUNT + bonus;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,24 @@ export const userService = {
|
|||||||
const user = await DrizzleClient.query.users.findFirst({ where: eq(users.username, username) });
|
const user = await DrizzleClient.query.users.findFirst({ where: eq(users.username, username) });
|
||||||
return user;
|
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) => {
|
createUser: async (id: string | bigint, username: string, classId?: bigint, tx?: any) => {
|
||||||
const execute = async (txFn: any) => {
|
const execute = async (txFn: any) => {
|
||||||
const [user] = await txFn.insert(users).values({
|
const [user] = await txFn.insert(users).values({
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
import { env } from "@lib/env";
|
|
||||||
|
|
||||||
console.log("DATABASE_URL:", env.DATABASE_URL);
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user