import { inventory, items, users } from "@/db/schema"; import { eq, and, sql, count } from "drizzle-orm"; import { DrizzleClient } from "@/lib/DrizzleClient"; import { economyService } from "@/modules/economy/economy.service"; import { config } from "@/lib/config"; export const inventoryService = { addItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: any) => { const execute = async (txFn: any) => { // Check if item exists in inventory const existing = await txFn.query.inventory.findFirst({ where: and( eq(inventory.userId, BigInt(userId)), eq(inventory.itemId, itemId) ), }); if (existing) { const newQuantity = (existing.quantity ?? 0n) + quantity; if (newQuantity > config.inventory.maxStackSize) { throw new Error(`Cannot exceed max stack size of ${config.inventory.maxStackSize}`); } const [entry] = await txFn.update(inventory) .set({ quantity: newQuantity, }) .where(and( eq(inventory.userId, BigInt(userId)), eq(inventory.itemId, itemId) )) .returning(); return entry; } else { // Check Slot Limit const [inventoryCount] = await txFn .select({ count: count() }) .from(inventory) .where(eq(inventory.userId, BigInt(userId))); if (inventoryCount.count >= config.inventory.maxSlots) { throw new Error(`Inventory full (Max ${config.inventory.maxSlots} slots)`); } if (quantity > config.inventory.maxStackSize) { throw new Error(`Cannot exceed max stack size of ${config.inventory.maxStackSize}`); } const [entry] = await txFn.insert(inventory) .values({ userId: BigInt(userId), itemId: itemId, quantity: quantity, }) .returning(); return entry; } }; return tx ? await execute(tx) : await DrizzleClient.transaction(execute); }, removeItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: any) => { const execute = async (txFn: any) => { const existing = await txFn.query.inventory.findFirst({ where: and( eq(inventory.userId, BigInt(userId)), eq(inventory.itemId, itemId) ), }); if (!existing || (existing.quantity ?? 0n) < quantity) { throw new Error("Insufficient item quantity"); } if ((existing.quantity ?? 0n) === quantity) { // Delete if quantity becomes 0 await txFn.delete(inventory) .where(and( eq(inventory.userId, BigInt(userId)), eq(inventory.itemId, itemId) )); return { itemId, quantity: 0n, userId: BigInt(userId) }; } else { const [entry] = await txFn.update(inventory) .set({ quantity: sql`${inventory.quantity} - ${quantity}`, }) .where(and( eq(inventory.userId, BigInt(userId)), eq(inventory.itemId, itemId) )) .returning(); return entry; } }; return tx ? await execute(tx) : await DrizzleClient.transaction(execute); }, getInventory: async (userId: string) => { return await DrizzleClient.query.inventory.findMany({ where: eq(inventory.userId, BigInt(userId)), with: { item: true, }, }); }, buyItem: async (userId: string, itemId: number, quantity: bigint = 1n, tx?: any) => { const execute = async (txFn: any) => { const item = await txFn.query.items.findFirst({ where: eq(items.id, itemId), }); if (!item) throw new Error("Item not found"); if (!item.price) throw new Error("Item is not for sale"); const totalPrice = item.price * quantity; // Deduct Balance using economy service (passing tx ensures atomicity) await economyService.modifyUserBalance(userId, -totalPrice, 'PURCHASE', `Bought ${quantity}x ${item.name}`, null, txFn); await inventoryService.addItem(userId, itemId, quantity, txFn); return { success: true, item, totalPrice }; }; return tx ? await execute(tx) : await DrizzleClient.transaction(execute); }, getItem: async (itemId: number) => { return await DrizzleClient.query.items.findFirst({ where: eq(items.id, itemId), }); }, };