Files
Learn_System/backend/src/services/prepTracks.js
T
Maxim Dolgolyov 9509a67e25 feat(prep): мастер-флаг подготовки к направлению (ЦТ) + коллекции колод — бэкенд
Система «готовится к ЦТ»: флаг student_prep(user_id,track) открывает ученику
ВЕСЬ контент трека (карточки + курс + пробники) динамически, без материализации.
- мигр.078: таблица student_prep + flashcard_decks.collection + разметка ЦТ-колод 'ct-math'
- services/prepTracks.js: реестр треков (трек→коллекция/курсы/экзамены), устойчив до миграции
- contentAccess.resolve/allowedRefs: учитывают мастер-флаг (явный запрет ученика побеждает)
- flashcardController.deckAccess/listDecks: колоды коллекции открыты по флагу
- prepController + /api/prep: учитель (своим) и админ ставят/снимают флаг (ученику/классу)
- js/api.js: LS.prep* обёртки

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

79 lines
3.4 KiB
JavaScript

'use strict';
/* prepTracks — направления подготовки (ЦТ и др.) и их контент («мастер-флаг»).
*
* Флаг student_prep(user_id, track) открывает ученику ВЕСЬ контент трека:
* - коллекцию флешкарт (flashcard_decks.collection)
* - курсы (content_type='course', content_ref=id курса)
* - экзамен-модули (content_type='exam', content_ref=exam_key)
* Доступ резолвится ДИНАМИЧЕСКИ (без материализации правил content_access):
* - contentAccess.resolve/allowedRefs читают trackGrants/trackRefs;
* - flashcardController.deckAccess/listDecks читают studentCollections.
*
* Добавить направление = добавить запись в TRACKS (+ при необходимости миграцию,
* проставляющую collection нужным колодам). Никаких изменений в резолверах. */
const db = require('../db/db');
/* Реестр треков. collection — ключ коллекции колод (flashcard_decks.collection);
content — что открывает трек, по типам content_access. */
const TRACKS = {
'ct-math': {
title: 'ЦТ/ЦЭ — математика',
label: 'Подготовка к ЦТ',
collection: 'ct-math',
content: { course: ['13'], exam: ['ctmath'] },
},
};
function isTrack(track) { return Object.prototype.hasOwnProperty.call(TRACKS, track); }
/* Множество известных треков, включённых у пользователя.
Inline-prepare + try/catch: до миграции 078 (нет таблицы student_prep) фича
просто не активна (пустой набор), а не падает на загрузке модуля/в тестах. */
function studentTracks(userId) {
try {
const rows = db.prepare('SELECT track FROM student_prep WHERE user_id = ?').all(userId);
return new Set(rows.map(r => r.track).filter(isTrack));
} catch (_) {
return new Set();
}
}
/* Множество collection-ключей, открытых пользователю его треками. */
function studentCollections(userId) {
const out = new Set();
for (const t of studentTracks(userId)) {
if (TRACKS[t].collection) out.add(TRACKS[t].collection);
}
return out;
}
/* Открывает ли какой-либо трек пользователя данный (type, ref)? */
function trackGrants(userId, type, ref) {
const r = String(ref);
for (const t of studentTracks(userId)) {
const refs = TRACKS[t].content && TRACKS[t].content[type];
if (refs && refs.includes(r)) return true;
}
return false;
}
/* content_ref'ы данного типа, открытые треками пользователя (для allowedRefs). */
function trackRefs(userId, type) {
const out = new Set();
for (const t of studentTracks(userId)) {
const refs = TRACKS[t].content && TRACKS[t].content[type];
if (refs) refs.forEach(x => out.add(String(x)));
}
return out;
}
/* Список треков для UI: [{ key, title, label }]. */
function listTracks() {
return Object.entries(TRACKS).map(([key, t]) => ({ key, title: t.title, label: t.label }));
}
module.exports = {
TRACKS, isTrack, studentTracks, studentCollections,
trackGrants, trackRefs, listTracks,
};