'use strict'; /* ─────────────────────────────────────────────────────────────────────────── open_ctmath_for_class.js — открыть ЦТ-математику профильному классу. Делает (идемпотентно): 1. courses.is_published = 1 для курса id=13 «ЦЭ/ЦТ — Математика». 2. content_access: открыть классу доступ к • курсу (content_type='course', content_ref='13') • экзамену (content_type='exam', content_ref='ctmath') (scope='class', allow=1; upsert по UNIQUE(content_type,content_ref,scope,target_id)). Модель доступа — ALLOWLIST (services/contentAccess.js): по умолчанию закрыто, правило ученика > класса, админ/учитель видят всё. Поэтому без этих правил ученики класса курс/экзамен НЕ видят, даже если курс опубликован. Цель — класс #4 «10Б · Математика» (выбор пользователя). Сменить — флагом --class=. Скрипт сверяет имя класса и печатает его перед записью. Запуск: node backend/scripts/open_ctmath_for_class.js # DRY-RUN node backend/scripts/open_ctmath_for_class.js --apply # запись node backend/scripts/open_ctmath_for_class.js --class=4 --apply ⚠️ Outward-facing: после --apply и рестарта сервера ученики класса увидят курс и пробники. Массовую запись запускает ПОЛЬЗОВАТЕЛЬ вручную. ─────────────────────────────────────────────────────────────────────────── */ const { DatabaseSync } = require('node:sqlite'); const path = require('path'); const APPLY = process.argv.includes('--apply'); const COURSE_ID = 13; const EXAM_KEY = 'ctmath'; const classArg = (process.argv.find(a => a.startsWith('--class=')) || '').split('=')[1]; const CLASS_ID = Number.isInteger(+classArg) && +classArg > 0 ? +classArg : 4; const db = new DatabaseSync(path.join(__dirname, '..', 'data', 'learnspace.db')); const get = (sql, ...a) => db.prepare(sql).get(...a); console.log(`\n=== open_ctmath_for_class (${APPLY ? 'APPLY' : 'DRY-RUN'}) ===\n`); /* ── Защитные проверки ─────────────────────────────────────────────────────── */ const course = get('SELECT id, title, is_published, created_by FROM courses WHERE id=?', COURSE_ID); if (!course) { console.error(`✗ Курс id=${COURSE_ID} не найден. Прерывание.`); db.close(); process.exit(1); } const klass = get('SELECT id, name FROM classes WHERE id=?', CLASS_ID); if (!klass) { console.error(`✗ Класс id=${CLASS_ID} не найден. Прерывание.`); db.close(); process.exit(1); } const track = get('SELECT exam_key, enabled FROM exam_tracks WHERE exam_key=?', EXAM_KEY); if (!track) { console.error(`✗ Трек '${EXAM_KEY}' не найден в exam_tracks. Прерывание.`); db.close(); process.exit(1); } const members = get('SELECT COUNT(*) n FROM class_members WHERE class_id=?', CLASS_ID).n; console.log(`Курс: id=${course.id} «${course.title}» (is_published=${course.is_published})`); console.log(`Класс: id=${klass.id} «${klass.name}» (учеников: ${members})`); console.log(`Экзамен: ${track.exam_key} (enabled=${track.enabled})\n`); /* ── План действий ─────────────────────────────────────────────────────────── */ const actions = []; if (course.is_published !== 1) { actions.push({ desc: `опубликовать курс id=${COURSE_ID} (is_published 0 → 1)`, run: () => db.prepare('UPDATE courses SET is_published=1 WHERE id=?').run(COURSE_ID) }); } else { console.log('• курс уже опубликован — пропуск'); } const accessRow = db.prepare(`SELECT allow FROM content_access WHERE content_type=? AND content_ref=? AND scope='class' AND target_id=?`); const upsertAccess = db.prepare(` INSERT INTO content_access (content_type, content_ref, scope, target_id, allow, created_by) VALUES (?, ?, 'class', ?, 1, ?) ON CONFLICT (content_type, content_ref, scope, target_id) DO UPDATE SET allow=1, created_by=excluded.created_by, created_at=datetime('now')`); for (const [type, ref] of [['course', String(COURSE_ID)], ['exam', EXAM_KEY]]) { const cur = accessRow.get(type, ref, CLASS_ID); if (cur && cur.allow === 1) { console.log(`• доступ ${type}:${ref} классу #${CLASS_ID} уже открыт — пропуск`); continue; } actions.push({ desc: `открыть доступ ${type}:${ref} классу #${CLASS_ID} (allow=1)`, run: () => upsertAccess.run(type, ref, CLASS_ID, course.created_by || null) }); } console.log(`\nК применению (${actions.length}):`); actions.forEach(a => console.log(' - ' + a.desc)); if (!actions.length) { console.log('\nВсё уже в нужном состоянии — менять нечего.\n'); db.close(); process.exit(0); } if (!APPLY) { console.log('\nDRY-RUN: ничего не записано. Для записи: node backend/scripts/open_ctmath_for_class.js --apply\n'); db.close(); process.exit(0); } db.exec('BEGIN'); try { for (const a of actions) a.run(); db.exec('COMMIT'); console.log(`\n✓ Применено: ${actions.length}. Курс и пробники ЦТ открыты классу «${klass.name}».`); console.log(' (после рестарта сервера ученики класса увидят их в каталоге / на дашборде)\n'); } catch (e) { db.exec('ROLLBACK'); console.error('\n✗ Ошибка, откат:', e.message); process.exitCode = 1; } db.close();