feat: Implement custom error classes, a Drizzle transaction utility, and update Discord.js ephemeral message flags.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
|
import { SlashCommandBuilder, EmbedBuilder, MessageFlags } from "discord.js";
|
||||||
import { economyService } from "@/modules/economy/economy.service";
|
import { economyService } from "@/modules/economy/economy.service";
|
||||||
import { createErrorEmbed, createWarningEmbed } from "@lib/embeds";
|
import { createErrorEmbed, createWarningEmbed } from "@lib/embeds";
|
||||||
|
|
||||||
@@ -25,12 +25,12 @@ export const daily = createCommand({
|
|||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message.includes("Daily already claimed")) {
|
if (error.message.includes("Daily already claimed")) {
|
||||||
await interaction.reply({ embeds: [createWarningEmbed(error.message, "Cooldown")], ephemeral: true });
|
await interaction.reply({ embeds: [createWarningEmbed(error.message, "Cooldown")], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await interaction.reply({ embeds: [createErrorEmbed("An error occurred while claiming your daily reward.")], ephemeral: true });
|
await interaction.reply({ embeds: [createErrorEmbed("An error occurred while claiming your daily reward.")], flags: MessageFlags.Ephemeral });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
|
import { SlashCommandBuilder, EmbedBuilder, MessageFlags } from "discord.js";
|
||||||
import { economyService } from "@/modules/economy/economy.service";
|
import { economyService } from "@/modules/economy/economy.service";
|
||||||
import { userService } from "@/modules/user/user.service";
|
import { userService } from "@/modules/user/user.service";
|
||||||
import { config } from "@/lib/config";
|
import { config } from "@/lib/config";
|
||||||
import { createErrorEmbed, createWarningEmbed } from "@lib/embeds";
|
import { createErrorEmbed, createWarningEmbed } from "@/lib/embeds";
|
||||||
|
import { UserError } from "@/lib/errors";
|
||||||
|
|
||||||
export const pay = createCommand({
|
export const pay = createCommand({
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
@@ -27,12 +28,12 @@ export const pay = createCommand({
|
|||||||
const receiverId = targetUser.id;
|
const receiverId = targetUser.id;
|
||||||
|
|
||||||
if (amount < config.economy.transfers.minAmount) {
|
if (amount < config.economy.transfers.minAmount) {
|
||||||
await interaction.reply({ embeds: [createWarningEmbed(`Amount must be at least ${config.economy.transfers.minAmount}.`)], ephemeral: true });
|
await interaction.reply({ embeds: [createWarningEmbed(`Amount must be at least ${config.economy.transfers.minAmount}.`)], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (senderId === receiverId) {
|
if (senderId === receiverId) {
|
||||||
await interaction.reply({ embeds: [createWarningEmbed("You cannot pay yourself.")], ephemeral: true });
|
await interaction.reply({ embeds: [createWarningEmbed("You cannot pay yourself.")], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +49,12 @@ export const pay = createCommand({
|
|||||||
await interaction.reply({ embeds: [embed] });
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message.includes("Insufficient funds")) {
|
if (error instanceof UserError) {
|
||||||
await interaction.reply({ embeds: [createWarningEmbed("Insufficient funds.")], ephemeral: true });
|
await interaction.reply({ embeds: [createWarningEmbed(error.message)], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await interaction.reply({ embeds: [createErrorEmbed("Transfer failed.")], ephemeral: true });
|
await interaction.reply({ embeds: [createErrorEmbed("Transfer failed due to an unexpected error.")], flags: MessageFlags.Ephemeral });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, EmbedBuilder, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, ThreadAutoArchiveDuration } from "discord.js";
|
import { SlashCommandBuilder, EmbedBuilder, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, ThreadAutoArchiveDuration, MessageFlags } from "discord.js";
|
||||||
import { TradeService } from "@/modules/trade/trade.service";
|
import { TradeService } from "@/modules/trade/trade.service";
|
||||||
import { createErrorEmbed, createWarningEmbed } from "@lib/embeds";
|
import { createErrorEmbed, createWarningEmbed } from "@lib/embeds";
|
||||||
|
|
||||||
@@ -16,19 +16,19 @@ export const trade = createCommand({
|
|||||||
const targetUser = interaction.options.getUser("user", true);
|
const targetUser = interaction.options.getUser("user", true);
|
||||||
|
|
||||||
if (targetUser.id === interaction.user.id) {
|
if (targetUser.id === interaction.user.id) {
|
||||||
await interaction.reply({ embeds: [createWarningEmbed("You cannot trade with yourself.")], ephemeral: true });
|
await interaction.reply({ embeds: [createWarningEmbed("You cannot trade with yourself.")], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetUser.bot) {
|
if (targetUser.bot) {
|
||||||
await interaction.reply({ embeds: [createWarningEmbed("You cannot trade with bots.")], ephemeral: true });
|
await interaction.reply({ embeds: [createWarningEmbed("You cannot trade with bots.")], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Thread
|
// Create Thread
|
||||||
const channel = interaction.channel;
|
const channel = interaction.channel;
|
||||||
if (!channel || channel.type === ChannelType.DM) {
|
if (!channel || channel.type === ChannelType.DM) {
|
||||||
await interaction.reply({ embeds: [createErrorEmbed("Cannot start trade in DMs.")], ephemeral: true });
|
await interaction.reply({ embeds: [createErrorEmbed("Cannot start trade in DMs.")], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ export const trade = createCommand({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to delete setup message", err);
|
console.error("Failed to delete setup message", err);
|
||||||
}
|
}
|
||||||
await interaction.followUp({ embeds: [createErrorEmbed("Failed to create trade thread. Check permissions.")], ephemeral: true });
|
await interaction.followUp({ embeds: [createErrorEmbed("Failed to create trade thread. Check permissions.")], flags: MessageFlags.Ephemeral });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";
|
import { SlashCommandBuilder, EmbedBuilder, MessageFlags } from "discord.js";
|
||||||
import { questService } from "@/modules/quest/quest.service";
|
import { questService } from "@/modules/quest/quest.service";
|
||||||
import { createWarningEmbed } from "@lib/embeds";
|
import { createWarningEmbed } from "@lib/embeds";
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ export const quests = createCommand({
|
|||||||
.setName("quests")
|
.setName("quests")
|
||||||
.setDescription("View your active quests"),
|
.setDescription("View your active quests"),
|
||||||
execute: async (interaction) => {
|
execute: async (interaction) => {
|
||||||
await interaction.deferReply({ ephemeral: true });
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||||
|
|
||||||
const userQuests = await questService.getUserQuests(interaction.user.id);
|
const userQuests = await questService.getUserQuests(interaction.user.id);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createCommand } from "@lib/utils";
|
import { createCommand } from "@lib/utils";
|
||||||
import { KyokoClient } from "@/lib/BotClient";
|
import { KyokoClient } from "@/lib/BotClient";
|
||||||
import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } from "discord.js";
|
import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
|
||||||
import { createErrorEmbed } from "@lib/embeds";
|
import { createErrorEmbed } from "@lib/embeds";
|
||||||
|
|
||||||
export const reload = createCommand({
|
export const reload = createCommand({
|
||||||
@@ -9,7 +9,7 @@ export const reload = createCommand({
|
|||||||
.setDescription("Reloads all commands")
|
.setDescription("Reloads all commands")
|
||||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||||
execute: async (interaction) => {
|
execute: async (interaction) => {
|
||||||
await interaction.deferReply({ ephemeral: true });
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await KyokoClient.loadCommands(true);
|
await KyokoClient.loadCommands(true);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder, PermissionFlagsBits, TextChannel, NewsChannel, VoiceChannel } from "discord.js";
|
import { SlashCommandBuilder, PermissionFlagsBits, TextChannel, NewsChannel, VoiceChannel, MessageFlags } from "discord.js";
|
||||||
import { createErrorEmbed } from "@/lib/embeds";
|
import { createErrorEmbed } from "@/lib/embeds";
|
||||||
|
|
||||||
export const webhook = createCommand({
|
export const webhook = createCommand({
|
||||||
@@ -13,7 +13,7 @@ export const webhook = createCommand({
|
|||||||
.setRequired(true)
|
.setRequired(true)
|
||||||
),
|
),
|
||||||
execute: async (interaction) => {
|
execute: async (interaction) => {
|
||||||
await interaction.deferReply({ ephemeral: true });
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||||
|
|
||||||
const payloadString = interaction.options.getString("payload", true);
|
const payloadString = interaction.options.getString("payload", true);
|
||||||
let payload;
|
let payload;
|
||||||
|
|||||||
15
src/lib/db.ts
Normal file
15
src/lib/db.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { DrizzleClient } from "./DrizzleClient";
|
||||||
|
import type { Transaction } from "./types";
|
||||||
|
|
||||||
|
export const withTransaction = async <T>(
|
||||||
|
callback: (tx: Transaction) => Promise<T>,
|
||||||
|
tx?: Transaction
|
||||||
|
): Promise<T> => {
|
||||||
|
if (tx) {
|
||||||
|
return await callback(tx);
|
||||||
|
} else {
|
||||||
|
return await DrizzleClient.transaction(async (newTx) => {
|
||||||
|
return await callback(newTx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
18
src/lib/errors.ts
Normal file
18
src/lib/errors.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export class ApplicationError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserError extends ApplicationError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SystemError extends ApplicationError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,3 +11,8 @@ export interface Event<K extends keyof ClientEvents> {
|
|||||||
once?: boolean;
|
once?: boolean;
|
||||||
execute: (...args: ClientEvents[K]) => Promise<void> | void;
|
execute: (...args: ClientEvents[K]) => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { DrizzleClient } from "./DrizzleClient";
|
||||||
|
|
||||||
|
export type DbClient = typeof DrizzleClient;
|
||||||
|
export type Transaction = Parameters<Parameters<DbClient['transaction']>[0]>[0];
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
import { users, transactions, userTimers } from "@/db/schema";
|
import { users, transactions, userTimers } from "@/db/schema";
|
||||||
import { eq, sql, and } from "drizzle-orm";
|
import { eq, sql, and } from "drizzle-orm";
|
||||||
import { DrizzleClient } from "@/lib/DrizzleClient";
|
|
||||||
import { config } from "@/lib/config";
|
import { config } from "@/lib/config";
|
||||||
|
import { withTransaction } from "@/lib/db";
|
||||||
|
import type { Transaction } from "@/lib/types";
|
||||||
|
import { UserError } from "@/lib/errors";
|
||||||
|
|
||||||
export const economyService = {
|
export const economyService = {
|
||||||
transfer: async (fromUserId: string, toUserId: string, amount: bigint, tx?: any) => {
|
transfer: async (fromUserId: string, toUserId: string, amount: bigint, tx?: Transaction) => {
|
||||||
if (amount <= 0n) {
|
if (amount <= 0n) {
|
||||||
throw new Error("Amount must be positive");
|
throw new UserError("Amount must be positive");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromUserId === toUserId) {
|
if (fromUserId === toUserId) {
|
||||||
throw new Error("Cannot transfer to self");
|
throw new UserError("Cannot transfer to self");
|
||||||
}
|
}
|
||||||
|
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
// Check sender balance
|
// Check sender balance
|
||||||
const sender = await txFn.query.users.findFirst({
|
const sender = await txFn.query.users.findFirst({
|
||||||
where: eq(users.id, BigInt(fromUserId)),
|
where: eq(users.id, BigInt(fromUserId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!sender) {
|
if (!sender) {
|
||||||
throw new Error("Sender not found");
|
throw new UserError("Sender not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((sender.balance ?? 0n) < amount) {
|
if ((sender.balance ?? 0n) < amount) {
|
||||||
throw new Error("Insufficient funds");
|
throw new UserError("Insufficient funds");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduct from sender
|
// Deduct from sender
|
||||||
@@ -59,19 +61,11 @@ export const economyService = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, amount };
|
return { success: true, amount };
|
||||||
};
|
}, tx);
|
||||||
|
|
||||||
if (tx) {
|
|
||||||
return await execute(tx);
|
|
||||||
} else {
|
|
||||||
return await DrizzleClient.transaction(async (t) => {
|
|
||||||
return await execute(t);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
claimDaily: async (userId: string, tx?: any) => {
|
claimDaily: async (userId: string, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const startOfDay = new Date(now);
|
const startOfDay = new Date(now);
|
||||||
startOfDay.setHours(0, 0, 0, 0);
|
startOfDay.setHours(0, 0, 0, 0);
|
||||||
@@ -86,7 +80,7 @@ export const economyService = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (cooldown && cooldown.expiresAt > now) {
|
if (cooldown && cooldown.expiresAt > now) {
|
||||||
throw new Error(`Daily already claimed. Ready at ${cooldown.expiresAt}`);
|
throw new UserError(`Daily already claimed. Ready at ${cooldown.expiresAt}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user for streak logic
|
// Get user for streak logic
|
||||||
@@ -95,7 +89,7 @@ export const economyService = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error("User not found");
|
throw new Error("User not found"); // This might be system error because user should exist if authenticated, but keeping simple for now
|
||||||
}
|
}
|
||||||
|
|
||||||
let streak = (user.dailyStreak || 0) + 1;
|
let streak = (user.dailyStreak || 0) + 1;
|
||||||
@@ -145,26 +139,18 @@ export const economyService = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return { claimed: true, amount: totalReward, streak, nextReadyAt };
|
return { claimed: true, amount: totalReward, streak, nextReadyAt };
|
||||||
};
|
}, tx);
|
||||||
|
|
||||||
if (tx) {
|
|
||||||
return await execute(tx);
|
|
||||||
} else {
|
|
||||||
return await DrizzleClient.transaction(async (t) => {
|
|
||||||
return await execute(t);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
modifyUserBalance: async (id: string, amount: bigint, type: string, description: string, relatedUserId?: string | null, tx?: any) => {
|
modifyUserBalance: async (id: string, amount: bigint, type: string, description: string, relatedUserId?: string | null, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
if (amount < 0n) {
|
if (amount < 0n) {
|
||||||
// Check sufficient funds if removing
|
// Check sufficient funds if removing
|
||||||
const user = await txFn.query.users.findFirst({
|
const user = await txFn.query.users.findFirst({
|
||||||
where: eq(users.id, BigInt(id))
|
where: eq(users.id, BigInt(id))
|
||||||
});
|
});
|
||||||
if (!user || (user.balance ?? 0n) < -amount) {
|
if (!user || (user.balance ?? 0n) < -amount) {
|
||||||
throw new Error("Insufficient funds");
|
throw new UserError("Insufficient funds");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,14 +170,6 @@ export const economyService = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
};
|
}, tx);
|
||||||
|
|
||||||
if (tx) {
|
|
||||||
return await execute(tx);
|
|
||||||
} else {
|
|
||||||
return await DrizzleClient.transaction(async (t) => {
|
|
||||||
return await execute(t);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
|
||||||
import { inventory, items, users } from "@/db/schema";
|
import { inventory, items, users } from "@/db/schema";
|
||||||
import { eq, and, sql, count } from "drizzle-orm";
|
import { eq, and, sql, count } from "drizzle-orm";
|
||||||
import { DrizzleClient } from "@/lib/DrizzleClient";
|
import { DrizzleClient } from "@/lib/DrizzleClient";
|
||||||
import { economyService } from "@/modules/economy/economy.service";
|
import { economyService } from "@/modules/economy/economy.service";
|
||||||
import { config } from "@/lib/config";
|
import { config } from "@/lib/config";
|
||||||
|
import { withTransaction } from "@/lib/db";
|
||||||
|
import type { Transaction } from "@/lib/types";
|
||||||
|
|
||||||
export const inventoryService = {
|
export const inventoryService = {
|
||||||
addItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: any) => {
|
addItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
// Check if item exists in inventory
|
// Check if item exists in inventory
|
||||||
const existing = await txFn.query.inventory.findFirst({
|
const existing = await txFn.query.inventory.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
@@ -39,7 +40,7 @@ export const inventoryService = {
|
|||||||
.from(inventory)
|
.from(inventory)
|
||||||
.where(eq(inventory.userId, BigInt(userId)));
|
.where(eq(inventory.userId, BigInt(userId)));
|
||||||
|
|
||||||
if (inventoryCount.count >= config.inventory.maxSlots) {
|
if (inventoryCount && inventoryCount.count >= config.inventory.maxSlots) {
|
||||||
throw new Error(`Inventory full (Max ${config.inventory.maxSlots} slots)`);
|
throw new Error(`Inventory full (Max ${config.inventory.maxSlots} slots)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +57,11 @@ export const inventoryService = {
|
|||||||
.returning();
|
.returning();
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
};
|
}, tx);
|
||||||
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
removeItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: any) => {
|
removeItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
const existing = await txFn.query.inventory.findFirst({
|
const existing = await txFn.query.inventory.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
eq(inventory.userId, BigInt(userId)),
|
eq(inventory.userId, BigInt(userId)),
|
||||||
@@ -93,8 +93,7 @@ export const inventoryService = {
|
|||||||
.returning();
|
.returning();
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
};
|
}, tx);
|
||||||
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getInventory: async (userId: string) => {
|
getInventory: async (userId: string) => {
|
||||||
@@ -106,8 +105,8 @@ export const inventoryService = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
buyItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: any) => {
|
buyItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
const item = await txFn.query.items.findFirst({
|
const item = await txFn.query.items.findFirst({
|
||||||
where: eq(items.id, itemId),
|
where: eq(items.id, itemId),
|
||||||
});
|
});
|
||||||
@@ -123,9 +122,7 @@ export const inventoryService = {
|
|||||||
await inventoryService.addItem(userId, itemId, quantity, txFn);
|
await inventoryService.addItem(userId, itemId, quantity, txFn);
|
||||||
|
|
||||||
return { success: true, item, totalPrice };
|
return { success: true, item, totalPrice };
|
||||||
};
|
}, tx);
|
||||||
|
|
||||||
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getItem: async (itemId: number) => {
|
getItem: async (itemId: number) => {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { users, userTimers } from "@/db/schema";
|
import { users, userTimers } from "@/db/schema";
|
||||||
import { eq, sql, and } from "drizzle-orm";
|
import { eq, sql, and } from "drizzle-orm";
|
||||||
import { DrizzleClient } from "@/lib/DrizzleClient";
|
import { withTransaction } from "@/lib/db";
|
||||||
import { config } from "@/lib/config";
|
import { config } from "@/lib/config";
|
||||||
|
import type { Transaction } from "@/lib/types";
|
||||||
|
|
||||||
export const levelingService = {
|
export const levelingService = {
|
||||||
// Calculate XP required for a specific level
|
// Calculate XP required for a specific level
|
||||||
@@ -10,8 +11,8 @@ export const levelingService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Pure XP addition - No cooldown checks
|
// Pure XP addition - No cooldown checks
|
||||||
addXp: async (id: string, amount: bigint, tx?: any) => {
|
addXp: async (id: string, amount: bigint, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
// Get current state
|
// Get current state
|
||||||
const user = await txFn.query.users.findFirst({
|
const user = await txFn.query.users.findFirst({
|
||||||
where: eq(users.id, BigInt(id)),
|
where: eq(users.id, BigInt(id)),
|
||||||
@@ -43,20 +44,12 @@ export const levelingService = {
|
|||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
return { user: updatedUser, levelUp, currentLevel };
|
return { user: updatedUser, levelUp, currentLevel };
|
||||||
};
|
}, tx);
|
||||||
|
|
||||||
if (tx) {
|
|
||||||
return await execute(tx);
|
|
||||||
} else {
|
|
||||||
return await DrizzleClient.transaction(async (t) => {
|
|
||||||
return await execute(t);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handle chat XP with cooldowns
|
// Handle chat XP with cooldowns
|
||||||
processChatXp: async (id: string, tx?: any) => {
|
processChatXp: async (id: string, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
// check if an xp cooldown is in place
|
// check if an xp cooldown is in place
|
||||||
const cooldown = await txFn.query.userTimers.findFirst({
|
const cooldown = await txFn.query.userTimers.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
@@ -93,14 +86,6 @@ export const levelingService = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return { awarded: true, amount, ...result };
|
return { awarded: true, amount, ...result };
|
||||||
};
|
}, tx);
|
||||||
|
|
||||||
if (tx) {
|
|
||||||
return await execute(tx);
|
|
||||||
} else {
|
|
||||||
return await DrizzleClient.transaction(async (t) => {
|
|
||||||
return await execute(t);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import { eq, and, sql } from "drizzle-orm";
|
|||||||
import { DrizzleClient } from "@/lib/DrizzleClient";
|
import { DrizzleClient } from "@/lib/DrizzleClient";
|
||||||
import { economyService } from "@/modules/economy/economy.service";
|
import { economyService } from "@/modules/economy/economy.service";
|
||||||
import { levelingService } from "@/modules/leveling/leveling.service";
|
import { levelingService } from "@/modules/leveling/leveling.service";
|
||||||
|
import { withTransaction } from "@/lib/db";
|
||||||
|
import type { Transaction } from "@/lib/types";
|
||||||
|
|
||||||
export const questService = {
|
export const questService = {
|
||||||
assignQuest: async (userId: string, questId: number, tx?: any) => {
|
assignQuest: async (userId: string, questId: number, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
return await txFn.insert(userQuests)
|
return await txFn.insert(userQuests)
|
||||||
.values({
|
.values({
|
||||||
userId: BigInt(userId),
|
userId: BigInt(userId),
|
||||||
@@ -16,12 +18,11 @@ export const questService = {
|
|||||||
})
|
})
|
||||||
.onConflictDoNothing() // Ignore if already assigned
|
.onConflictDoNothing() // Ignore if already assigned
|
||||||
.returning();
|
.returning();
|
||||||
};
|
}, tx);
|
||||||
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProgress: async (userId: string, questId: number, progress: number, tx?: any) => {
|
updateProgress: async (userId: string, questId: number, progress: number, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
return await txFn.update(userQuests)
|
return await txFn.update(userQuests)
|
||||||
.set({ progress: progress })
|
.set({ progress: progress })
|
||||||
.where(and(
|
.where(and(
|
||||||
@@ -29,12 +30,11 @@ export const questService = {
|
|||||||
eq(userQuests.questId, questId)
|
eq(userQuests.questId, questId)
|
||||||
))
|
))
|
||||||
.returning();
|
.returning();
|
||||||
};
|
}, tx);
|
||||||
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
completeQuest: async (userId: string, questId: number, tx?: any) => {
|
completeQuest: async (userId: string, questId: number, tx?: Transaction) => {
|
||||||
const execute = async (txFn: any) => {
|
return await withTransaction(async (txFn) => {
|
||||||
const userQuest = await txFn.query.userQuests.findFirst({
|
const userQuest = await txFn.query.userQuests.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
eq(userQuests.userId, BigInt(userId)),
|
eq(userQuests.userId, BigInt(userId)),
|
||||||
@@ -73,9 +73,7 @@ export const questService = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, rewards: results };
|
return { success: true, rewards: results };
|
||||||
};
|
}, tx);
|
||||||
|
|
||||||
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getUserQuests: async (userId: string) => {
|
getUserQuests: async (userId: string) => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { DrizzleClient } from "@/lib/DrizzleClient";
|
|||||||
import { economyService } from "@/modules/economy/economy.service";
|
import { economyService } from "@/modules/economy/economy.service";
|
||||||
import { inventoryService } from "@/modules/inventory/inventory.service";
|
import { inventoryService } from "@/modules/inventory/inventory.service";
|
||||||
import { itemTransactions } from "@/db/schema";
|
import { itemTransactions } from "@/db/schema";
|
||||||
|
import type { Transaction } from "@/lib/types";
|
||||||
|
|
||||||
export class TradeService {
|
export class TradeService {
|
||||||
private static sessions = new Map<string, TradeSession>();
|
private static sessions = new Map<string, TradeSession>();
|
||||||
@@ -136,7 +137,7 @@ export class TradeService {
|
|||||||
this.endSession(threadId);
|
this.endSession(threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async processTransfer(tx: any, from: TradeParticipant, to: TradeParticipant, threadId: string) {
|
private static async processTransfer(tx: Transaction, from: TradeParticipant, to: TradeParticipant, threadId: string) {
|
||||||
// 1. Money
|
// 1. Money
|
||||||
if (from.offer.money > 0n) {
|
if (from.offer.money > 0n) {
|
||||||
await economyService.modifyUserBalance(
|
await economyService.modifyUserBalance(
|
||||||
|
|||||||
Reference in New Issue
Block a user