feat: Implement custom error classes, a Drizzle transaction utility, and update Discord.js ephemeral message flags.

This commit is contained in:
syntaxbullet
2025-12-15 22:14:17 +01:00
parent 3c81fd8396
commit 7e986fae5a
14 changed files with 112 additions and 114 deletions

View File

@@ -1,30 +1,32 @@
import { users, transactions, userTimers } from "@/db/schema";
import { eq, sql, and } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { config } from "@/lib/config";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@/lib/types";
import { UserError } from "@/lib/errors";
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) {
throw new Error("Amount must be positive");
throw new UserError("Amount must be positive");
}
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
const sender = await txFn.query.users.findFirst({
where: eq(users.id, BigInt(fromUserId)),
});
if (!sender) {
throw new Error("Sender not found");
throw new UserError("Sender not found");
}
if ((sender.balance ?? 0n) < amount) {
throw new Error("Insufficient funds");
throw new UserError("Insufficient funds");
}
// Deduct from sender
@@ -59,19 +61,11 @@ export const economyService = {
});
return { success: true, amount };
};
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await execute(t);
});
}
}, tx);
},
claimDaily: async (userId: string, tx?: any) => {
const execute = async (txFn: any) => {
claimDaily: async (userId: string, tx?: Transaction) => {
return await withTransaction(async (txFn) => {
const now = new Date();
const startOfDay = new Date(now);
startOfDay.setHours(0, 0, 0, 0);
@@ -86,7 +80,7 @@ export const economyService = {
});
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
@@ -95,7 +89,7 @@ export const economyService = {
});
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;
@@ -145,26 +139,18 @@ export const economyService = {
});
return { claimed: true, amount: totalReward, streak, nextReadyAt };
};
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await execute(t);
});
}
}, tx);
},
modifyUserBalance: async (id: string, amount: bigint, type: string, description: string, relatedUserId?: string | null, tx?: any) => {
const execute = async (txFn: any) => {
modifyUserBalance: async (id: string, amount: bigint, type: string, description: string, relatedUserId?: string | null, tx?: Transaction) => {
return await withTransaction(async (txFn) => {
if (amount < 0n) {
// Check sufficient funds if removing
const user = await txFn.query.users.findFirst({
where: eq(users.id, BigInt(id))
});
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;
};
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await execute(t);
});
}
}, tx);
},
};