Session 52: Coming Soon teaser + infrastructure verification (2239 tests)
Phase 1 — Push-to-Book teaser (feature not live; teaser only): - StatStrip: "BOOK IT ⟶" per graded prop (hover: "Push-to-Book coming soon"). - GradeResultCard: "PUSH-TO-BOOK · COMING SOON" footer. Phase 2 — infrastructure verification: - snapshotScheduler logs armed AND disarmed state (incl SNAPSHOT_CRON) so container logs disambiguate off-vs-crashed. - NEW GET /api/internal/snapshot/status (internal-key gated): cron_armed, cron_hours_utc, last_snapshot per sport (gradeCount/deltaCount), redis_keys existence map, ticker_count. The post-deploy pipeline health probe. - Finding: Redis AOF/RDB persistence is a server-side (Coolify) config the app can't set/verify — documented. Phase 3 — delta pipeline (verified sound, no fix needed): - runSnapshot already rotates :latest->:previous and diffs locked lines; added opt-in SNAPSHOT_DEBUG=1 [deltas] log + a trace test asserting :previous is preserved verbatim and the delta math is correct. Backend 2234 -> 2239 tests (+5), 192 suites. Web build clean (exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,49 @@ router.post('/snapshot/all', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/internal/snapshot/status (Session 52) — verification probe. Reports
|
||||
* whether the in-process cron is armed, the freshest snapshot per sport, which
|
||||
* pipeline Redis keys exist, and the ticker item count. Read-only; safe to poll.
|
||||
* GET vs the POST /snapshot/:sport below — no route collision.
|
||||
*/
|
||||
router.get('/snapshot/status', async (req, res) => {
|
||||
const { cacheGet } = require('../utils/redis');
|
||||
const { HOURS_UTC } = require('../snapshotScheduler');
|
||||
const SPORTS = ['mlb', 'nba', 'wnba'];
|
||||
try {
|
||||
const redis_keys = {};
|
||||
const last_snapshot = {};
|
||||
for (const sp of SPORTS) {
|
||||
const latestKey = `snapshot:${sp}:latest`;
|
||||
const prevKey = `snapshot:${sp}:previous`;
|
||||
const gradesKey = `grades:${sp}`;
|
||||
const [latest, prev, grades] = await Promise.all([cacheGet(latestKey), cacheGet(prevKey), cacheGet(gradesKey)]);
|
||||
redis_keys[latestKey] = !!latest;
|
||||
redis_keys[prevKey] = !!prev;
|
||||
redis_keys[gradesKey] = !!grades;
|
||||
if (latest) {
|
||||
last_snapshot[sp] = {
|
||||
updated_at: latest.updated_at || null,
|
||||
gradeCount: Array.isArray(latest.grades) ? latest.grades.length : 0,
|
||||
deltaCount: Array.isArray(latest.deltas) ? latest.deltas.length : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
const ticker = await cacheGet('ticker:items');
|
||||
redis_keys['ticker:items'] = !!ticker;
|
||||
return res.json({
|
||||
cron_armed: process.env.SNAPSHOT_CRON === '1',
|
||||
cron_hours_utc: HOURS_UTC,
|
||||
last_snapshot,
|
||||
redis_keys,
|
||||
ticker_count: Array.isArray(ticker) ? ticker.length : 0,
|
||||
});
|
||||
} catch (err) {
|
||||
return res.status(500).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/snapshot/:sport', async (req, res) => {
|
||||
const snapshot = require('../services/snapshotService');
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user