'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, };