fix(lab-content-engine): phase 5 — read-роуты auth-only, мутации inline admin
GET /related и /links возвращали 200 без токена: они были ПОСЛЕ blanket
router.use(requireRole('admin')) (хрупкий порядок при повторном mount роутера
в тестах). Убрал blanket; каждая мутация (patch/reorder/links POST+DELETE)
имеет INLINE requireRole('admin'); read-роуты — auth-only.
Также lab-links seed переведён на seedRow() (NOT NULL дрейф схемы).
lab-links 18/18, lab-sims 11/11, route-auth: 0 роутов lab.js во флаге.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,8 +69,11 @@ router.get('/sims', (_req, res) => {
|
|||||||
res.json({ module_disabled: readModuleDisabled(), sims });
|
res.json({ module_disabled: readModuleDisabled(), sims });
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ── admin mutations ───────────────────────────────────────────────────── */
|
/* ── admin mutations ───────────────────────────────────────────────────────
|
||||||
router.use(requireRole('admin'));
|
ВАЖНО: НЕ используем blanket `router.use(requireRole('admin'))` — он применялся
|
||||||
|
бы и к ниже определённым READ-роутам Фазы 5 (/related, /links), которые должны
|
||||||
|
быть доступны любому авторизованному пользователю. Каждая мутация защищена
|
||||||
|
INLINE requireRole('admin') (так же видит route-auth линтер). */
|
||||||
|
|
||||||
/* PATCH /api/lab/sims/:id body: { enabled?, featured?, tags?, subject?, grade?, title?, cat? } */
|
/* PATCH /api/lab/sims/:id body: { enabled?, featured?, tags?, subject?, grade?, title?, cat? } */
|
||||||
router.patch('/sims/:id', requireRole('admin'), (req, res) => {
|
router.patch('/sims/:id', requireRole('admin'), (req, res) => {
|
||||||
@@ -125,7 +128,7 @@ router.patch('/sims/:id', requireRole('admin'), (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* POST /api/lab/sims/reorder body: { order: [id, id, ...] } */
|
/* POST /api/lab/sims/reorder body: { order: [id, id, ...] } */
|
||||||
router.post('/sims/reorder', (req, res) => {
|
router.post('/sims/reorder', requireRole('admin'), (req, res) => {
|
||||||
const order = (req.body && req.body.order) || [];
|
const order = (req.body && req.body.order) || [];
|
||||||
if (!Array.isArray(order) || !order.length) {
|
if (!Array.isArray(order) || !order.length) {
|
||||||
return res.status(400).json({ error: 'order должен быть непустым массивом id' });
|
return res.status(400).json({ error: 'order должен быть непустым массивом id' });
|
||||||
|
|||||||
@@ -42,15 +42,11 @@ describe('/api/lab curriculum links', () => {
|
|||||||
before(async () => {
|
before(async () => {
|
||||||
adminToken = (await getToken('admin')).token;
|
adminToken = (await getToken('admin')).token;
|
||||||
studentToken = (await getToken('student')).token;
|
studentToken = (await getToken('student')).token;
|
||||||
// Seed a textbook + topic to link against.
|
// Seed a textbook + topic to link against (schema-robust — fills NOT NULL cols).
|
||||||
db.prepare(`INSERT INTO textbooks (slug, title, subject, grade, is_active)
|
|
||||||
VALUES ('phys-test', 'Физика тест', 'physics', 9, 1)
|
|
||||||
ON CONFLICT(slug) DO NOTHING`).run();
|
|
||||||
tbSlug = 'phys-test';
|
tbSlug = 'phys-test';
|
||||||
const subj = db.prepare(`INSERT INTO subjects (name) VALUES ('LinkTest Subj')`).run();
|
seedRow('textbooks', { slug: tbSlug, title: 'Физика тест', subject: 'physics', grade: 9, is_active: 1 });
|
||||||
const tp = db.prepare(`INSERT INTO topics (subject_id, name) VALUES (?, 'Колебания тест')`)
|
const subjId = seedRow('subjects', { name: 'LinkTest Subj', slug: 'linktest-subj' });
|
||||||
.run(subj.lastInsertRowid);
|
topicId = seedRow('topics', { subject_id: subjId, name: 'Колебания тест', slug: 'kolebaniya-test' });
|
||||||
topicId = tp.lastInsertRowid;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GET /related requires auth (401)', async () => {
|
it('GET /related requires auth (401)', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user