Files
Learn_System/.claude/memory/project_content_access.md
Maxim Dolgolyov 8a7091ddec chore(memory): снимок файлов памяти Claude в репозиторий для переноса
Копия пользовательской автопамяти (29 фактов + индекс MEMORY.md) в
.claude/memory/, чтобы переносить между машинами через git.
README.md — как восстановить в пользовательскую папку на другой машине.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:32:16 +03:00

77 lines
11 KiB
Markdown
Raw Permalink 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.
---
name: project_content_access
description: "Система доступа к учебникам/экзаменам по классам и ученикам из админ-панели (allowlist, ученик > класс)"
metadata:
node_type: memory
type: project
originSessionId: d08c4099-7d49-4f89-b842-d9d7af56af47
---
Доступ к учебникам и экзамен-модулям («экзамен 9 класс» = exam_key `math9`) управляется из админ-панели (вкладка «Доступ к учебникам», группа **«Пользователи»**, рядом с «Права доступа»). Реализовано 2026-05-30.
**Модель:** ALLOWLIST — по умолчанию закрыто, нужно явно открыть. Правило ученика важнее правила класса (точечные исключения). Управляют админ (все классы/ученики) и учителя (только свои классы и ученики своих классов / привязанные через teacher_students).
**Why:** так выбрал пользователь (безопаснее). Миграция 040 при внедрении выдала всем существующим классам доступ к текущему контенту, чтобы переход не отнял доступ задним числом; новый контент по умолчанию закрыт.
**How to apply:**
- Таблица `content_access` (миграция 040): content_type ('textbook'|'exam'), content_ref (top-level slug учебника / exam_key), scope ('class'|'student'), target_id, allow (1 открыть / 0 закрыть-исключение). Главы (parent_slug != NULL) наследуют доступ хаба.
- Резолвинг — `backend/src/services/contentAccess.js` (canAccessTextbook/canAccessExam/filterTextbooks/allowedRefs). Админ/учитель проходят всегда.
- Гейты: `textbooks.js` фильтр каталога + `router.param('slug')`; `exam-prep.js` фильтр /tracks + `router.param('examKey')`. HTML-страницы не гейтятся на сервере (JWT в localStorage) — клиентский редирект на /403 в `textbook-tracker.js` (loadServerProgress) и `exam-prep/common.js` (boot).
- API `/api/access` (`routes/access.js`, admin+teacher): GET catalog, GET targets, GET summary, GET class/:id, GET rules, POST rules.
- Фронт: `LS.accessCatalog/accessTargets/accessSummary/accessClassOpen/accessRules/accessSetRule`; секция `frontend/js/admin/sections/access.js` — два режима «По контенту» / «По классу», массовые «Открыть всем/Закрыть у всех», бейджи N/M открытых классов.
- При удалении класса/ученика правила чистятся вручную (нет FK): `classController.deleteClass` и `adminController._deleteUserTx`.
При добавлении нового учебника/экзамена он закрыт по умолчанию — открыть классам через админку.
**РЕВЬЮ + ПЕРЕРАБОТКА (2026-06-03):** проведено ревью всей системы прав (есть 2,5 системы: content_access
для учебников/экзаменов по классам; role/user_permissions через [registry.js] глобально по ролям — туда
входят `simulations.access`, испытания, магазин, manage-права; курсы — отдельно по is_published+класс).
План: `plans/access-redesign/PLAN.md` (4 фазы). Пользователь сказал «включай всё» + «делаем как лучше».
- **Фаза 0 ГОТОВА (commit 1bbddc0):** `contentAccess.purgeAccessFor(scope,id)` — единая чистка правил (нет FK);
deleteClass и adminController._deleteUserTx переведены на неё; confirm() на массовое «Закрыть» в админ-UI;
тест `backend/tests/content-access.test.js` (резолвер allowlist, ученик>класс, наследование главой,
admin/teacher bypass, purge). Решение по kickMember: персональные правила привязаны к УЧЕНИКУ, не к
членству → при исключении НЕ чистим (намеренный override).
- **Фаза 2a ГОТОВА (commit 67a70c6):** режим **«Матрица»** в админ-секции access.js (3-й таб) — таблица
контент×классы с чекбоксами + поиск (обновляет только tbody, фокус сохраняется). Backend
`GET /api/access/matrix` (классы+карта открытого, скоуп учителя); клиент `LS.accessMatrix`. `/api/access`
смонтирован в тест-харнесс setup.js. Тест 11/11.
- **Фаза 2b ГОТОВА (commit 596e8d8):** поиск + подзаголовки по предмету в левой колонке (режим «По контенту»,
обновляет только список — фокус ввода сохраняется) + бейдж **«эффективный доступ»** у ученика в раскрытом
классе («видит/не видит · лично|по классу|по умолч.», считается клиентски из `_rules`).
- **Фаза 1 (модель ДОБАВОЧНАЯ) — СИМУЛЯЦИИ ГОТОВЫ (commits 9a145e5 + 4549b4e):** content_ref для sim =
`lab_sims.id` (TEXT, напр. 'graph'). Миграция **051** пересобрала `content_access` с CHECK
`('textbook','exam','course','sim')` + мост «открыть все включённые симуляции всем существующим классам».
`GET /api/lab/sims` (lab.js) фильтрует список для НЕпривилегированных по `allowedRefs(uid,'sim')`; admin/
teacher — все. Ролевой `simulations.access` остался «модуль вкл.» (добавочно, AND). Админ-секция «Доступ»
обобщена на тип 'sim' (catalog/summary/matrix/class в access.js route + UI helpers BUCKET/KEYNAME/
CONTENT_TYPES). Тесты: lab-access 4/4, content-access 12, lab-sims переведён на admin. **ВАЖНО:** новый
класс получает симуляции только после явного открытия в админке (allowlist) — мост покрыл лишь классы,
существовавшие на момент миграции 051.
- **Фаза 1c — КУРСЫ ГОТОВЫ (commit 9b7585a):** content_ref = `courses.id` (как TEXT). Миграция **052**
мост «открыть все опубликованные курсы всем существующим классам». `courseController.list`+`search`
фильтруют для НЕпривилегированных через `courseVisible(user)`; admin/teacher — все. catalog отдаёт курсы;
`CONTENT_TYPES` в admin access.js = textbook,exam,sim,**course** (все 4 типа в UI). Тест course-access 4/4.
`class_courses` оставлен для назначений с дедлайном (сверх видимости).
- **ФАЗА 1 ЗАВЕРШЕНА (симуляции + курсы).** Backend 213 pass (3 baseline-Auth; «intro» chemistry8-page
флакует под нагрузкой — НЕ про доступ, в изоляции зелёный). Харнесс setup.js монтирует /api/access,
/api/lab, /api/courses. **ВАЖНО (allowlist):** новый класс/новый опубликованный курс/новая симуляция по
умолчанию закрыты — открыть в админке; loose-ученики (без класса) не видят sim/курсы без личного правила.
- **Фаза 2c ГОТОВА (commits d1f2473, 6a874a3, b702b04, 3a59f56):** массовые операции матрицы (клик по
контенту/классу), «Открыть весь предмет классу» (режим «По классу»), **история правил** (GET
/api/access/log, admin-only, из admin_audit_log; кнопка «История изменений» в режиме «По контенту»;
клиент LS.accessLog), **пресет «Скопировать доступ из класса»** (режим «По классу»), **объединение
вкладок по смыслу** («Доступ · контент» + «Доступ · роли» рядом в admin.html). content-access тест 13/13.
Полное слияние двух вкладок в одну с под-вкладками НЕ делалось (структурно крупнее, оставлено на потом).
- **Фаза 3 — ОТЛОЖЕНА ОСОЗНАННО (низкий ROI, решение пользователя 2026-06-03).** Серверный гейт HTML
`/textbook/:slug`, `/exam-prep/:examKey` (сейчас отдаются всем; блок только клиентским редиректом на /403,
ДАННЫЕ через API уже гейтятся). Чтобы гейтить сам HTML на сервере, нужен переход с JWT-в-localStorage на
**httpOnly-cookie сессию** — переделка ВСЕЙ аутентификации (логин/каждый запрос/logout/token_version/CSRF/
мобилка), большой риск ради крошечной выгоды (видно лишь пустой каркас страницы, не контент). Это школьная
платформа, не ПДн/финансы. ДЕЛАТЬ ТОЛЬКО при конкретном требовании приватности контента или комплаенсе.
План: `plans/access-redesign/PLAN.md` Фаза 3. Отдельная ветка `feature/html-access-gate`.
**Возможные улучшения (старое, до ревью — теперь решено ДЕЛАТЬ, см. план):**
1. *Единая per-class модель для всего контента.* Сейчас неоднородность: учебники/экзамены гейтятся по классам (`content_access`), а теория/курсы (`theory.access`) и симуляции (`simulations.access`) — глобально через role-permissions (см. registry.js). Можно расширить `content_access` типами `course`/`sim`, чтобы их тоже можно было открывать/закрывать по классам. Решили пока НЕ делать (меняет поведение двух работающих типов контента).
2. *Серверный гейт HTML-страниц.* `/textbook/:slug` и `/exam-prep/*` отдают статический HTML без проверки токена (JWT в localStorage, не cookie) — защита только на API + клиентский редирект на /403. Неподделываемая блокировка самих страниц требует cookie-аутентификации (крупная отдельная задача).