Session 18: Admin dashboard + Tank01 prefetch endpoint (1443 tests)

This commit is contained in:
Kev
2026-06-11 22:29:38 -04:00
parent beaf8b2a61
commit 0e3839a90a
9 changed files with 813 additions and 2 deletions
+6
View File
@@ -22,6 +22,7 @@ const shareCardRoutes = require('./routes/shareCard');
const pushRoutes = require('./routes/push');
const gradingRoutes = require('./routes/grading');
const correctionRoutes = require('./routes/corrections');
const internalRoutes = require('./routes/internal');
const { missionHeader } = require('./middleware/mission');
const app = express();
@@ -137,6 +138,11 @@ app.use('/api/grading', express.json({ limit: '10mb' }), gradingRoutes);
app.use('/api/grading', express.json({ limit: '256kb' }), correctionRoutes);
const widgetRoutes = require('./routes/widget');
app.use('/api/widget', widgetRoutes);
// Session 18 — internal ops endpoints (admin dashboard triggers,
// shared-key auth via `VYNDR_INTERNAL_KEY`). Never reachable from
// the public surface; the Next.js admin route proxies through with
// the key kept server-side.
app.use('/api/internal', internalRoutes);
// Session 10 — Sentry's Express error handler catches uncaught
// errors from every route mounted above. Must come AFTER routes but
+65
View File
@@ -0,0 +1,65 @@
'use strict';
/**
* Internal ops endpoints (Session 18).
*
* Reachable only with the shared `VYNDR_INTERNAL_KEY` — never
* exposed to end users. The admin dashboard wires the Tank01
* prefetch button to POST here through the Next.js server (the
* key never touches a browser).
*
* Deviation from spec: the spec suggested `execSync('node scripts/tank01-prefetch.js')`.
* We import the module instead — same behavior, but in-process and
* testable. The module already exposes `main(argv)` which returns
* the same summary object the spec expected to parse out of stdout.
*/
const express = require('express');
const { requireInternalAuth } = require('../middleware/internalAuth');
const tank01Prefetch = require('../../scripts/tank01-prefetch');
const router = express.Router();
router.use(requireInternalAuth({ loopbackOnly: false }));
/**
* POST /api/internal/prefetch/tank01
*
* Body (all optional):
* { max?: number, sports?: string[]|string, dryRun?: boolean }
*
* Builds an argv array equivalent to the CLI form and hands it to
* the prefetch module. Returns the module's summary on success.
*/
router.post('/prefetch/tank01', async (req, res) => {
const body = (req.body && typeof req.body === 'object') ? req.body : {};
// Build argv. `main()` parses its own args, so all the validation
// (numeric bounds, allowed sports) stays in one place — we just
// translate JSON shapes into CLI flags.
const argv = ['node', 'scripts/tank01-prefetch.js'];
if (Number.isFinite(body.max) && body.max > 0) {
argv.push(`--max=${Math.floor(body.max)}`);
}
if (body.dryRun === true) {
argv.push('--dry-run');
}
if (body.sports) {
const sportsList = Array.isArray(body.sports)
? body.sports.join(',')
: String(body.sports);
argv.push(`--sports=${sportsList}`);
}
try {
const summary = await tank01Prefetch.main(argv);
return res.json({ ok: true, summary });
} catch (err) {
const message = err && err.message ? err.message : String(err);
console.error('[internal/prefetch/tank01] failed:', message);
return res.status(500).json({ ok: false, error: message });
}
});
module.exports = router;