refactor: standardize transaction pattern in class.service.ts

Replace manual transaction handling with withTransaction helper pattern
for consistency with other services (economy, inventory, quest, leveling).

Also fix validation bug in modifyClassBalance:
- Before: if (balance < amount)
- After: if (balance + amount < 0n)

This correctly validates negative amounts (debits) to prevent balances
going below zero.
This commit is contained in:
syntaxbullet
2025-12-24 21:57:14 +01:00
parent 10a760edf4
commit 77d3fafdce

View File

@@ -2,14 +2,16 @@ import { classes, users } from "@/db/schema";
import { eq, sql } from "drizzle-orm"; import { eq, sql } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient"; import { DrizzleClient } from "@/lib/DrizzleClient";
import { UserError } from "@/lib/errors"; import { UserError } from "@/lib/errors";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@/lib/types";
export const classService = { export const classService = {
getAllClasses: async () => { getAllClasses: async () => {
return await DrizzleClient.query.classes.findMany(); return await DrizzleClient.query.classes.findMany();
}, },
assignClass: async (userId: string, classId: bigint, tx?: any) => { assignClass: async (userId: string, classId: bigint, tx?: Transaction) => {
const execute = async (txFn: any) => { return await withTransaction(async (txFn) => {
const cls = await txFn.query.classes.findFirst({ const cls = await txFn.query.classes.findFirst({
where: eq(classes.id, classId), where: eq(classes.id, classId),
}); });
@@ -22,8 +24,7 @@ export const classService = {
.returning(); .returning();
return user; return user;
}; }, tx);
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
}, },
getClassBalance: async (classId: bigint) => { getClassBalance: async (classId: bigint) => {
const cls = await DrizzleClient.query.classes.findFirst({ const cls = await DrizzleClient.query.classes.findFirst({
@@ -31,15 +32,15 @@ export const classService = {
}); });
return cls?.balance || 0n; return cls?.balance || 0n;
}, },
modifyClassBalance: async (classId: bigint, amount: bigint, tx?: any) => { modifyClassBalance: async (classId: bigint, amount: bigint, tx?: Transaction) => {
const execute = async (txFn: any) => { return await withTransaction(async (txFn) => {
const cls = await txFn.query.classes.findFirst({ const cls = await txFn.query.classes.findFirst({
where: eq(classes.id, classId), where: eq(classes.id, classId),
}); });
if (!cls) throw new UserError("Class not found"); if (!cls) throw new UserError("Class not found");
if ((cls.balance ?? 0n) < amount) { if ((cls.balance ?? 0n) + amount < 0n) {
throw new UserError("Insufficient class funds"); throw new UserError("Insufficient class funds");
} }
@@ -51,35 +52,31 @@ export const classService = {
.returning(); .returning();
return updatedClass; return updatedClass;
}; }, tx);
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
}, },
updateClass: async (id: bigint, data: Partial<typeof classes.$inferInsert>, tx?: any) => { updateClass: async (id: bigint, data: Partial<typeof classes.$inferInsert>, tx?: Transaction) => {
const execute = async (txFn: any) => { return await withTransaction(async (txFn) => {
const [updatedClass] = await txFn.update(classes) const [updatedClass] = await txFn.update(classes)
.set(data) .set(data)
.where(eq(classes.id, id)) .where(eq(classes.id, id))
.returning(); .returning();
return updatedClass; return updatedClass;
}; }, tx);
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
}, },
createClass: async (data: typeof classes.$inferInsert, tx?: any) => { createClass: async (data: typeof classes.$inferInsert, tx?: Transaction) => {
const execute = async (txFn: any) => { return await withTransaction(async (txFn) => {
const [newClass] = await txFn.insert(classes) const [newClass] = await txFn.insert(classes)
.values(data) .values(data)
.returning(); .returning();
return newClass; return newClass;
}; }, tx);
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
}, },
deleteClass: async (id: bigint, tx?: any) => { deleteClass: async (id: bigint, tx?: Transaction) => {
const execute = async (txFn: any) => { return await withTransaction(async (txFn) => {
await txFn.delete(classes).where(eq(classes.id, id)); await txFn.delete(classes).where(eq(classes.id, id));
}; }, tx);
return tx ? await execute(tx) : await DrizzleClient.transaction(execute);
} }
}; };