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>
This commit is contained in:
Maxim Dolgolyov
2026-06-19 15:29:00 +03:00
parent 5193fd8252
commit 9509a67e25
8 changed files with 267 additions and 4 deletions
+9
View File
@@ -1065,6 +1065,7 @@ window.LS = {
adminGetAssistant, adminSaveAssistant, adminTestAssistant, adminReindexTextbooks,
adminSaveProvider, adminDeleteProvider, adminSetActiveProvider, adminAssistantModels,
fcListDecks, fcCreateDeck, fcAddCard, fcStudySession, fcReview,
prepListTracks, prepMyTracks, prepStudentTracks, prepSetStudent, prepUnsetStudent, prepClassStatus, prepSetClass,
escapeHtml, esc,
parseDate, fmtRelTime, safeHref,
initPage,
@@ -1317,6 +1318,14 @@ async function fcCreateDeck(d) { return req('POST', '/flashcards/decks', d
async function fcAddCard(deckId, d) { return req('POST', `/flashcards/decks/${deckId}/cards`, d); }
async function fcStudySession(deckId){ return req('GET', `/flashcards/decks/${deckId}/study`); }
async function fcReview(cardId, quality) { return req('POST', `/flashcards/cards/${cardId}/review`, { quality }); }
/* ── prep tracks (мастер-флаг «подготовка к ЦТ» и т.п.) ──────────────────── */
async function prepListTracks() { return req('GET', '/prep/tracks'); }
async function prepMyTracks() { return req('GET', '/prep/me'); }
async function prepStudentTracks(uid) { return req('GET', `/prep/student/${uid}`); }
async function prepSetStudent(uid, track) { return req('POST', `/prep/student/${uid}`, { track }); }
async function prepUnsetStudent(uid, track){ return req('DELETE', `/prep/student/${uid}?track=${encodeURIComponent(track)}`); }
async function prepClassStatus(classId, track) { return req('GET', `/prep/class/${classId}?track=${encodeURIComponent(track)}`); }
async function prepSetClass(classId, track, on) { return req('POST', `/prep/class/${classId}`, { track, on: !!on }); }
async function deleteFile(id) { return req('DELETE', `/files/${id}`); }
async function getFileAccess(id) { return req('GET', `/files/${id}/access`); }
async function assignFile(id, data) { return req('POST', `/files/${id}/assign`, data); }