librechat-balances/services/history.js
DJP a9c514d5b1 Add history tracking and user history view
- 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>
2026-03-23 13:33:08 -04:00

70 lines
1.9 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const DATA_FILE = path.join(__dirname, '..', 'data', 'history.json');
function ensureDataDir() {
const dir = path.dirname(DATA_FILE);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, JSON.stringify([], null, 2));
}
}
function readAll() {
ensureDataDir();
const raw = fs.readFileSync(DATA_FILE, 'utf8');
return JSON.parse(raw);
}
function writeAll(data) {
ensureDataDir();
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}
function log(entry) {
const history = readAll();
history.push({
id: Date.now().toString(36) + Math.random().toString(36).slice(2, 7),
email: entry.email,
action: entry.action,
amount: entry.amount,
source: entry.source,
omgJobNumber: entry.omgJobNumber || null,
balanceAfter: entry.balanceAfter || null,
timestamp: new Date().toISOString(),
});
writeAll(history);
}
function getByEmail(email) {
return readAll()
.filter(h => h.email.toLowerCase() === email.toLowerCase())
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
}
function searchByEmail(query) {
const regex = new RegExp(query, 'i');
const all = readAll();
const emails = [...new Set(all.filter(h => regex.test(h.email)).map(h => h.email))];
return emails.map(email => {
const entries = all.filter(h => h.email === email);
const totalAdded = entries
.filter(h => h.action === 'add' || h.action === 'approve')
.reduce((sum, h) => sum + (h.amount || 0), 0);
return {
email,
totalRequests: entries.length,
totalAdded,
lastActivity: entries.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))[0]?.timestamp,
};
});
}
function getAll() {
return readAll().sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
}
module.exports = { log, getByEmail, searchByEmail, getAll };