import { Router } from 'express'; import { prisma } from '../db.js'; import { requireAuth, publicUser } from '../middleware.js'; import { normalizeRates, pickWeighted } from '../weighted.js'; import { pushDrop } from '../feed.js'; const router = Router(); function formatCase(c, includeRates = false) { const items = (c.items || []).map((ci) => ({ id: ci.item.id, name: ci.item.name, imageUrl: ci.item.imageUrl, rarity: ci.item.rarity, marketValue: ci.item.marketValue, dropRate: ci.dropRate, ...(includeRates ? {} : {}), })); const withChance = normalizeRates( items.map((i) => ({ ...i, dropRate: i.dropRate })) ); return { id: c.id, name: c.name, imageUrl: c.imageUrl, price: c.price, active: c.active, items: withChance.map(({ dropRate, chance, ...rest }) => ({ ...rest, dropRate, chance: Number(chance.toFixed(4)), })), }; } router.get('/', async (_req, res) => { try { const cases = await prisma.case.findMany({ where: { active: true }, include: { items: { include: { item: true } }, }, orderBy: { price: 'asc' }, }); res.json({ cases: cases.map((c) => formatCase(c)) }); } catch (err) { console.error(err); res.status(500).json({ error: 'Failed to load cases' }); } }); router.get('/:id', async (req, res) => { try { const id = Number(req.params.id); const c = await prisma.case.findUnique({ where: { id }, include: { items: { include: { item: true } }, }, }); if (!c || !c.active) { return res.status(404).json({ error: 'Case not found' }); } res.json({ case: formatCase(c, true) }); } catch (err) { console.error(err); res.status(500).json({ error: 'Failed to load case' }); } }); router.post('/:id/open', requireAuth, async (req, res) => { try { const caseId = Number(req.params.id); const userId = req.session.userId; const result = await prisma.$transaction(async (tx) => { const c = await tx.case.findUnique({ where: { id: caseId }, include: { items: { include: { item: true } }, }, }); if (!c || !c.active) { const err = new Error('Case not found'); err.status = 404; throw err; } if (!c.items.length) { const err = new Error('Case has no items'); err.status = 400; throw err; } const user = await tx.user.findUnique({ where: { id: userId } }); if (!user) { const err = new Error('User not found'); err.status = 401; throw err; } if (user.balance < c.price) { const err = new Error('Insufficient balance'); err.status = 400; throw err; } const winner = pickWeighted( c.items.map((ci) => ({ caseItem: ci, dropRate: ci.dropRate, })) ); const item = winner.caseItem.item; const updatedUser = await tx.user.update({ where: { id: userId }, data: { balance: { decrement: c.price } }, }); const inventoryItem = await tx.inventoryItem.create({ data: { userId, itemId: item.id, }, include: { item: true }, }); await tx.transaction.create({ data: { userId, type: 'open_case', amount: -c.price, meta: JSON.stringify({ caseId: c.id, caseName: c.name, itemId: item.id, itemName: item.name, inventoryItemId: inventoryItem.id, }), }, }); return { user: publicUser(updatedUser), caseName: c.name, item: { inventoryId: inventoryItem.id, id: item.id, name: item.name, imageUrl: item.imageUrl, rarity: item.rarity, marketValue: item.marketValue, }, }; }); pushDrop({ source: 'case', caseName: result.caseName, username: result.user.username, avatarUrl: result.user.avatarUrl || '', item: result.item, }); res.json({ user: result.user, item: result.item }); } catch (err) { console.error(err); const status = err.status || 500; res.status(status).json({ error: err.message || 'Failed to open case' }); } }); export default router;