Files
Learn_System/backend/scripts/open_ctmath_for_class.js
T
Maxim Dolgolyov fd656ed63f feat(ctmath): скрипт открытия ЦТ-математики классу (publish курса 13 + доступ)
Идемпотентно: courses.is_published=1 (курс 13) + content_access classу #4
«10Б · Математика» на курс (course:13) и экзамен-модуль (exam:ctmath).
Модель — allowlist (без правил ученики не видят даже опубликованный курс).
Цель класса флагом --class=<id> (деф. 4), сверка имени. DRY-RUN по умолчанию,
запись с --apply (outward-facing, запускает пользователь).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:06:51 +03:00

104 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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=<id>. Скрипт сверяет имя класса и печатает его перед записью.
Запуск:
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();