174 lines
4.8 KiB
JavaScript
174 lines
4.8 KiB
JavaScript
import { Router } from 'express';
|
|
import { prisma } from '../db.js';
|
|
import { requireAuth, publicUser } from '../middleware.js';
|
|
import {
|
|
maxVaultSlots,
|
|
settleVaultYield,
|
|
summarizeVaultState,
|
|
} from '../vault.js';
|
|
import { isConnected } from '../presence.js';
|
|
|
|
const router = Router();
|
|
|
|
router.post('/stake', requireAuth, async (req, res) => {
|
|
try {
|
|
const ids = [...new Set((req.body.inventoryItemIds || []).map(Number).filter(Boolean))];
|
|
if (!ids.length) {
|
|
return res.status(400).json({ error: 'Select at least one item to stake' });
|
|
}
|
|
|
|
const mode = isConnected(req.session.userId) ? 'online' : 'offline';
|
|
|
|
const result = await prisma.$transaction(async (tx) => {
|
|
const user = await tx.user.findUnique({ where: { id: req.session.userId } });
|
|
if (!user) {
|
|
const err = new Error('User not found');
|
|
err.status = 401;
|
|
throw err;
|
|
}
|
|
|
|
const maxSlots = maxVaultSlots(user);
|
|
const used = await tx.inventoryItem.count({
|
|
where: { userId: user.id, staked: true },
|
|
});
|
|
const free = maxSlots - used;
|
|
if (ids.length > free) {
|
|
const err = new Error(`Vault full (${used}/${maxSlots} slots)`);
|
|
err.status = 400;
|
|
throw err;
|
|
}
|
|
|
|
const invItems = await tx.inventoryItem.findMany({
|
|
where: {
|
|
id: { in: ids },
|
|
userId: user.id,
|
|
locked: false,
|
|
staked: false,
|
|
},
|
|
});
|
|
if (invItems.length !== ids.length) {
|
|
const err = new Error('Invalid, locked, or already staked items');
|
|
err.status = 400;
|
|
throw err;
|
|
}
|
|
|
|
const now = new Date();
|
|
await tx.inventoryItem.updateMany({
|
|
where: { id: { in: ids }, userId: user.id },
|
|
data: {
|
|
staked: true,
|
|
stakedAt: now,
|
|
lastYieldAt: now,
|
|
},
|
|
});
|
|
|
|
// Shared timer: start only when vault was empty
|
|
let updatedUser = user;
|
|
if (used === 0 || !user.vaultYieldAt) {
|
|
updatedUser = await tx.user.update({
|
|
where: { id: user.id },
|
|
data: { vaultYieldAt: now },
|
|
});
|
|
}
|
|
|
|
const staked = await tx.inventoryItem.findMany({
|
|
where: { userId: user.id, staked: true },
|
|
include: { item: true },
|
|
});
|
|
|
|
return {
|
|
user: updatedUser,
|
|
stakedIds: ids,
|
|
vault: summarizeVaultState(updatedUser, staked, mode),
|
|
};
|
|
});
|
|
|
|
res.json({
|
|
user: publicUser(result.user),
|
|
stakedIds: result.stakedIds,
|
|
vault: result.vault,
|
|
});
|
|
} catch (err) {
|
|
const status = err.status || 500;
|
|
if (status >= 500) console.error(err);
|
|
res.status(status).json({ error: err.message || 'Failed to stake items' });
|
|
}
|
|
});
|
|
|
|
router.post('/unstake', requireAuth, async (req, res) => {
|
|
try {
|
|
const ids = [...new Set((req.body.inventoryItemIds || []).map(Number).filter(Boolean))];
|
|
if (!ids.length) {
|
|
return res.status(400).json({ error: 'Select at least one item to unstake' });
|
|
}
|
|
|
|
const mode = isConnected(req.session.userId) ? 'online' : 'offline';
|
|
const settled = await settleVaultYield(req.session.userId, mode);
|
|
|
|
const result = await prisma.$transaction(async (tx) => {
|
|
const user = await tx.user.findUnique({ where: { id: req.session.userId } });
|
|
if (!user) {
|
|
const err = new Error('User not found');
|
|
err.status = 401;
|
|
throw err;
|
|
}
|
|
|
|
const invItems = await tx.inventoryItem.findMany({
|
|
where: {
|
|
id: { in: ids },
|
|
userId: user.id,
|
|
staked: true,
|
|
},
|
|
});
|
|
if (invItems.length !== ids.length) {
|
|
const err = new Error('Invalid or not-staked items');
|
|
err.status = 400;
|
|
throw err;
|
|
}
|
|
|
|
await tx.inventoryItem.updateMany({
|
|
where: { id: { in: ids }, userId: user.id },
|
|
data: {
|
|
staked: false,
|
|
stakedAt: null,
|
|
lastYieldAt: null,
|
|
},
|
|
});
|
|
|
|
const remaining = await tx.inventoryItem.findMany({
|
|
where: { userId: user.id, staked: true },
|
|
include: { item: true },
|
|
});
|
|
|
|
let refreshed = user;
|
|
if (remaining.length === 0 && user.vaultYieldAt) {
|
|
refreshed = await tx.user.update({
|
|
where: { id: user.id },
|
|
data: { vaultYieldAt: null },
|
|
});
|
|
} else {
|
|
refreshed = await tx.user.findUnique({ where: { id: user.id } });
|
|
}
|
|
|
|
return {
|
|
user: refreshed,
|
|
unstakedIds: ids,
|
|
vault: summarizeVaultState(refreshed, remaining, mode),
|
|
};
|
|
});
|
|
|
|
res.json({
|
|
user: publicUser(result.user),
|
|
unstakedIds: result.unstakedIds,
|
|
yieldCredited: settled.credited,
|
|
vault: result.vault,
|
|
});
|
|
} catch (err) {
|
|
const status = err.status || 500;
|
|
if (status >= 500) console.error(err);
|
|
res.status(status).json({ error: err.message || 'Failed to unstake items' });
|
|
}
|
|
});
|
|
|
|
export default router;
|