Session 7d: Audit fixes - rate limiting, error leak, parallel parlays, analyze cache, bundle analyzer

This commit is contained in:
Kev
2026-06-10 03:12:20 -04:00
parent d954e4d952
commit 6f4a353de9
18 changed files with 913 additions and 72 deletions
+33 -2
View File
@@ -1,8 +1,39 @@
const express = require('express');
const { analyzeProp } = require('../services/propAnalyzer');
const { cacheGet, cacheSet } = require('../utils/redis');
const { createRateLimit } = require('../middleware/rateLimit');
const router = express.Router();
// SEC-1 (Session 7d): /prop and /batch are public — both proxy to the
// prop analyzer which makes upstream API calls. Cap to 10 requests per
// IP per minute so a single bad actor can't burn analyzer credits.
const analyzeLimit = createRateLimit({ windowMs: 60_000, max: 10 });
router.use(analyzeLimit);
// PERF-1 (Session 7d): cache analyzeProp results in Redis. Same prop hit
// twice within 60s reuses the previous analysis instead of re-doing the
// upstream chain. 60s is short enough that line moves still surface.
const ANALYZE_TTL_SECONDS = 60;
function analyzeCacheKey(prop) {
const sport = (prop.sport || 'nba').toLowerCase();
const player = String(prop.player || '').trim().toLowerCase();
const stat = String(prop.stat_type || '').trim().toLowerCase();
const line = Number(prop.line);
const direction = String(prop.direction || '').toLowerCase();
return `analyze:${sport}:${player}:${stat}:${line}:${direction}`;
}
async function cachedAnalyze(prop) {
const key = analyzeCacheKey(prop);
const cached = await cacheGet(key);
if (cached) return { ...cached, _cache: 'HIT' };
const result = await analyzeProp(prop);
// cacheSet swallows failures (degraded Redis) — analysis still flows
// even when the cache is down.
await cacheSet(key, result, ANALYZE_TTL_SECONDS);
return { ...result, _cache: 'MISS' };
}
const VALID_STAT_TYPES = new Set([
'points', 'rebounds', 'assists', 'threes', 'blocks',
'steals', 'pra', 'turnovers',
@@ -32,7 +63,7 @@ router.post('/prop', async (req, res) => {
}
try {
const result = await analyzeProp(req.body);
const result = await cachedAnalyze(req.body);
return res.json(result);
} catch (err) {
if (err.response && err.response.status === 404) {
@@ -61,7 +92,7 @@ router.post('/batch', async (req, res) => {
}
try {
const result = await analyzeProp(prop);
const result = await cachedAnalyze(prop);
results.push(result);
} catch (err) {
results.push({