#5 Gateway Trust Model: - Removed inventory /test endpoint - Updated docs/trust-model.md with accurate description: - Per-user services (trips, fitness) vs gateway-key services clearly separated - Known limitations documented (no per-user isolation on shared services) - No false claims about per-user auth where it doesn't exist #8 Dependency Security: - Workflow reviewed and confirmed sane - Added .gitea/README.md documenting runner requirement - Status: repo-side complete, operationally blocked on runner setup #9 Performance Hardening: - Budget /transactions/recent: 30s cache (1.1s→41ms on repeat) - Budget /uncategorized-count: 2min cache (1.3s→42ms on repeat) - Both endpoints document Actual Budget per-account API constraint - Budget buildLookups: 2min cache (already in place) - All inventory full scans already eliminated (prior commit)
This commit is contained in:
@@ -201,11 +201,21 @@ app.get('/transactions', requireReady, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Recent transactions across all accounts ------------------------------
|
||||
// ---- Recent transactions across all accounts (cached 30s) -----------------
|
||||
// NOTE: Actual Budget API requires per-account queries. There is no cross-account
|
||||
// transaction endpoint. Fan-out across accounts is unavoidable. Cache mitigates
|
||||
// repeated calls from dashboard + page load.
|
||||
|
||||
let recentTxnCache = { data: null, limit: 0, expiresAt: 0 };
|
||||
|
||||
app.get('/transactions/recent', requireReady, async (_req, res) => {
|
||||
try {
|
||||
const limit = parseInt(_req.query.limit, 10) || 20;
|
||||
|
||||
if (recentTxnCache.data && recentTxnCache.limit >= limit && Date.now() < recentTxnCache.expiresAt) {
|
||||
return res.json(recentTxnCache.data.slice(0, limit));
|
||||
}
|
||||
|
||||
const accounts = await api.getAccounts();
|
||||
const { payeeMap, accountMap, categoryMap } = await buildLookups();
|
||||
|
||||
@@ -221,9 +231,10 @@ app.get('/transactions/recent', requireReady, async (_req, res) => {
|
||||
}
|
||||
|
||||
all.sort((a, b) => (b.date > a.date ? 1 : b.date < a.date ? -1 : 0));
|
||||
all = all.slice(0, limit);
|
||||
const enriched = all.slice(0, Math.max(limit, 100)).map((t) => enrichTransaction(t, payeeMap, accountMap, categoryMap));
|
||||
|
||||
res.json(all.map((t) => enrichTransaction(t, payeeMap, accountMap, categoryMap)));
|
||||
recentTxnCache = { data: enriched, limit: Math.max(limit, 100), expiresAt: Date.now() + 30000 };
|
||||
res.json(enriched.slice(0, limit));
|
||||
} catch (err) {
|
||||
console.error('[budget] GET /transactions/recent error:', err);
|
||||
res.status(500).json({ error: err.message });
|
||||
@@ -359,10 +370,17 @@ app.post('/make-transfer', requireReady, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Uncategorized count (total across all accounts) ----------------------
|
||||
// ---- Uncategorized count (cached 2 min) ------------------------------------
|
||||
// NOTE: Fans out across all accounts — Actual API constraint.
|
||||
|
||||
let uncatCache = { count: 0, expiresAt: 0 };
|
||||
|
||||
app.get('/uncategorized-count', requireReady, async (_req, res) => {
|
||||
try {
|
||||
if (Date.now() < uncatCache.expiresAt) {
|
||||
return res.json({ count: uncatCache.count });
|
||||
}
|
||||
|
||||
const accounts = await api.getAccounts();
|
||||
const startDate = '2000-01-01';
|
||||
const endDate = new Date().toISOString().slice(0, 10);
|
||||
@@ -373,6 +391,7 @@ app.get('/uncategorized-count', requireReady, async (_req, res) => {
|
||||
const txns = await api.getTransactions(acct.id, startDate, endDate);
|
||||
total += txns.filter((t) => !t.category && !t.transfer_id && t.amount !== 0).length;
|
||||
}
|
||||
uncatCache = { count: total, expiresAt: Date.now() + 120000 };
|
||||
res.json({ count: total });
|
||||
} catch (err) {
|
||||
console.error('[budget] GET /uncategorized-count error:', err);
|
||||
|
||||
Reference in New Issue
Block a user