fix: complete remaining partial issues (#5, #8, #9)
Some checks failed
Security Checks / dependency-audit (push) Has been cancelled
Security Checks / secret-scanning (push) Has been cancelled
Security Checks / dockerfile-lint (push) Has been cancelled

#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:
Yusuf Suleman
2026-03-29 15:17:28 -05:00
parent 6087be599b
commit ac5c758056
4 changed files with 84 additions and 24 deletions

View File

@@ -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);