- All balance changes (top-ups, sets, bulk ops, request approvals) are now logged to data/history.json - New "History" tab in admin sidebar - Search by email to see all changes for a user with totals - Partial name search shows matching users with summary stats - Each entry shows action type, amount, source, OMG job #, resulting balance, and timestamp Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
4.4 KiB
JavaScript
159 lines
4.4 KiB
JavaScript
const { Router } = require('express');
|
|
const balanceService = require('../services/balance');
|
|
const history = require('../services/history');
|
|
|
|
const router = Router();
|
|
|
|
router.get('/stats', async (req, res) => {
|
|
try {
|
|
const stats = await balanceService.getStats(req.db);
|
|
res.json(stats);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.get('/balances', async (req, res) => {
|
|
try {
|
|
const { page = 1, limit = 50 } = req.query;
|
|
const result = await balanceService.getAllBalances(req.db, parseInt(page), parseInt(limit));
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.get('/balances/low', async (req, res) => {
|
|
try {
|
|
const { threshold = 1000000 } = req.query;
|
|
const users = await balanceService.getLowBalances(req.db, parseInt(threshold));
|
|
res.json({ users });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.get('/balances/search', async (req, res) => {
|
|
try {
|
|
const { q } = req.query;
|
|
if (!q || q.length < 2) {
|
|
return res.json({ users: [] });
|
|
}
|
|
const users = await balanceService.searchUsers(req.db, q);
|
|
res.json({ users });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// History endpoints
|
|
router.get('/history', (req, res) => {
|
|
try {
|
|
const { email } = req.query;
|
|
if (email) {
|
|
const entries = history.getByEmail(email);
|
|
const totalAdded = entries
|
|
.filter(h => h.action === 'add' || h.action === 'approve')
|
|
.reduce((sum, h) => sum + (h.amount || 0), 0);
|
|
return res.json({ entries, totalAdded });
|
|
}
|
|
return res.json({ entries: history.getAll() });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.get('/history/search', (req, res) => {
|
|
try {
|
|
const { q } = req.query;
|
|
if (!q || q.length < 2) {
|
|
return res.json({ users: [] });
|
|
}
|
|
const users = history.searchByEmail(q);
|
|
return res.json({ users });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.post('/balances/bulk/add', async (req, res) => {
|
|
try {
|
|
const { amount } = req.body;
|
|
if (typeof amount !== 'number' || amount === 0) {
|
|
return res.status(400).json({ error: 'Invalid amount' });
|
|
}
|
|
const result = await balanceService.addToAll(req.db, amount);
|
|
history.log({ email: 'ALL_USERS', action: 'bulk_add', amount, source: 'admin' });
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.post('/balances/bulk/set', async (req, res) => {
|
|
try {
|
|
const { amount } = req.body;
|
|
if (typeof amount !== 'number' || amount < 0) {
|
|
return res.status(400).json({ error: 'Invalid amount' });
|
|
}
|
|
const result = await balanceService.setAll(req.db, amount);
|
|
history.log({ email: 'ALL_USERS', action: 'bulk_set', amount, source: 'admin' });
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.get('/balances/:userId', async (req, res) => {
|
|
try {
|
|
const balance = await balanceService.getUserBalance(req.db, req.params.userId);
|
|
if (!balance) {
|
|
return res.status(404).json({ error: 'User not found' });
|
|
}
|
|
res.json(balance);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.post('/balances/:userId/set', async (req, res) => {
|
|
try {
|
|
const { amount } = req.body;
|
|
if (typeof amount !== 'number' || amount < 0) {
|
|
return res.status(400).json({ error: 'Invalid amount' });
|
|
}
|
|
const result = await balanceService.setBalance(req.db, req.params.userId, amount);
|
|
history.log({
|
|
email: result.email,
|
|
action: 'set',
|
|
amount,
|
|
source: 'admin',
|
|
balanceAfter: result.tokenCredits,
|
|
});
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.post('/balances/:userId/add', async (req, res) => {
|
|
try {
|
|
const { amount } = req.body;
|
|
if (typeof amount !== 'number' || amount === 0) {
|
|
return res.status(400).json({ error: 'Invalid amount' });
|
|
}
|
|
const result = await balanceService.addBalance(req.db, req.params.userId, amount);
|
|
history.log({
|
|
email: result.email,
|
|
action: 'add',
|
|
amount,
|
|
source: 'admin',
|
|
balanceAfter: result.tokenCredits,
|
|
});
|
|
res.json(result);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|