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>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
'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();
|
||||
Reference in New Issue
Block a user