177 lines
4.3 KiB
JavaScript
177 lines
4.3 KiB
JavaScript
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;
|