feat(sim-builder): фаза 6 — раздача классу, клон, шаблоны, привязка к программе (custom_sims)
This commit is contained in:
@@ -1,6 +1,30 @@
|
||||
# Feature Context: Конструктор симуляций (SimForge)
|
||||
|
||||
## Current State
|
||||
- **Фаза 6 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Файлы:
|
||||
`backend/src/controllers/customSimController.js` (+share/clone/related/addLink/removeLink, импорт
|
||||
`pushNotif`), `backend/src/routes/customSims.js` (+POST `/:id/share`, POST `/:id/clone`, GET
|
||||
`/:id/related`, POST `/:id/links`, DELETE `/:id/links/:linkId`), `js/api.js` (+`customSimShare/
|
||||
Clone/Related/AddLink/DelLink`), `frontend/js/labs/lab-glue.js` (аддитивно в IIFE LabCustom:
|
||||
кнопки share/clone/publish-toggle на карточках + делегат + `shareToClass/clone/setStatus`, ICON-блок),
|
||||
`frontend/js/sim-builder.js` (тулбар: «Шаблон»/«Раздать»/publish-toggle; методы `setStatus/
|
||||
openShareModal/openTemplateModal`; данные `TEMPLATES`×4; ICON.template/unpublish),
|
||||
`backend/tests/custom-sims-share.test.js` (new, 15 it, все зелёные).
|
||||
- **РЕШЕНИЕ копия-vs-доступ (зафиксировано):** published custom-sim видна ВСЕМ в каталоге /lab
|
||||
(`list`/`get` отдают published любому; custom-sim НЕ гейтится allowlist'ом content_access 'sim' —
|
||||
тот гейтит только legacy `lab_sims`). Поэтому «раздать классу» = (1) авто-публикация
|
||||
(status→published), (2) ДОЛГОВЕЧНОЕ адресное уведомление ученикам класса через `pushNotif`
|
||||
(notifications-таблица + SSE) со ссылкой `/lab?sim=custom:<id>`. БЕЗ копии (в отличие от «Моих
|
||||
материалов», где оригинал приватный и копия обязательна) и БЕЗ записи content_access.
|
||||
- **Привязка к программе:** переиспользован `lab_sim_links` с `sim_id='custom:<id>'` (sim_id TEXT —
|
||||
отдельная таблица не нужна). Связями СВОЕЙ симуляции управляет владелец/admin (не только admin как
|
||||
у lab_sims). Backend + GET `/related` готовы; UI-редактор связей + чипы в каталоге — остаток (handoff).
|
||||
- **Клон:** копия spec вызвавшему как draft (title += ' (копия)', version=1). Источник: своя любая
|
||||
ИЛИ чужая published (чужой draft → 403).
|
||||
- Верификация: `node --check` всех 6 изм. файлов OK; эмодзи нет (скан — только `→`/`∑` в комментариях,
|
||||
как в существующем коде); eval/Function нет; `npm run lint:routes` 0 unprotected (baseline 0);
|
||||
`npm test` 216/224 pass (8 fail = тот же baseline: 3 auth.test + 5 page-тестов без jsdom — не моя
|
||||
фаза; обе custom-sims-сьюты зелёные). git status: только мои файлы; classroom.html/lab.html не тронуты.
|
||||
- **Фаза 5 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Только
|
||||
**аддитивные** правки двух файлов параллельной сессии (без рефактора их кода): рабочее дерево
|
||||
по ним было ЧИСТЫМ до начала. classroom.html / backend / `_sim_deps.js` НЕ тронуты.
|
||||
@@ -111,8 +135,12 @@
|
||||
- Reuse > переписывание: сначала смотреть `_fx_motion`, `_graph_panel`, `graph.js`.
|
||||
|
||||
## RESUME STATE
|
||||
- Последний коммит фичи: — (Ф0 + Ф1 + Ф2 + Ф3 + Ф4 + Ф5 реализованы, ещё не закоммичены — ждут оркестратора)
|
||||
- Текущая фаза: Phase 5 — Каталог (✅ Implemented, pending commit) → дальше Phase 6 — Раздача / шаблоны / клон / программа
|
||||
- Последний коммит фичи: — (Ф0..Ф6 реализованы, ещё не закоммичены — ждут оркестратора)
|
||||
- Текущая фаза: Phase 6 — Раздача / шаблоны / клон / программа (✅ Implemented, pending commit) →
|
||||
дальше Phase 7 — Доска онлайн-урока (последняя)
|
||||
- Эндпоинты Ф6: share/clone/related/links на `/api/custom-sims/:id/*`; клиент `LS.customSimShare/
|
||||
Clone/Related/AddLink/DelLink`. Раздача = авто-publish + pushNotif (НЕ копия). Связи — lab_sim_links
|
||||
`sim_id='custom:<id>'`. Остаток Ф6: UI-редактор связей в билдере + чипы в каталоге (backend готов).
|
||||
- Файлы Ф5 (аддитивные правки зоны параллельной сессии — БЕЗ рефактора): `frontend/js/labs/lab-init.js`
|
||||
(+7 строк: хук `LabCustom.resolveId` в `openSim`), `frontend/js/labs/lab-glue.js` (renderSims +`!m._custom`
|
||||
и вызов renderSection; init зовёт `LabCustom.init()`; новый IIFE `window.LabCustom`). `_sim_deps.js`,
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
- [x] Phase 3: БД + API (custom_sims) [domain: backend] → [subplan](./phase-3-persistence-api.md)
|
||||
- [x] Phase 4: Билдер (редактор) [domain: frontend] → [subplan](./phase-4-builder-ui.md)
|
||||
- [x] Phase 5: Каталог (custom-sims в /lab) [domain: fullstack] → [subplan](./phase-5-catalog.md)
|
||||
- [ ] Phase 6: Раздача / шаблоны / клон / программа [domain: fullstack] → [subplan](./phase-6-sharing.md)
|
||||
- [x] Phase 6: Раздача / шаблоны / клон / программа [domain: fullstack] → [subplan](./phase-6-sharing.md)
|
||||
- [ ] Phase 7: Доска онлайн-урока [domain: fullstack] → [subplan](./phase-7-classroom.md)
|
||||
|
||||
## Phase Progress Log
|
||||
@@ -58,7 +58,7 @@
|
||||
| Phase 3: Persistence + API | backend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 4: Builder UI | frontend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 5: Catalog | fullstack | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 6: Sharing | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 6: Sharing | fullstack | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 7: Classroom | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
|
||||
## Final Review
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Phase 6: Раздача / шаблоны / клон / программа
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** ✅ Implemented (pending commit — за оркестратором)
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
@@ -9,16 +9,27 @@
|
||||
старт из шаблонов, привязка к программе (учебник/тема).
|
||||
|
||||
## Tasks
|
||||
- [ ] Публикация: тумблер draft↔published в билдере/каталоге (PUT status). Только владелец/админ.
|
||||
- [ ] Раздача классу: `POST /api/custom-sims/:id/share { classId }` — по паттерну «Мои материалы»
|
||||
(`shareMaterial`): ученики класса получают доступ/уведомление. Решить — ссылка-доступ или копия
|
||||
(рекоменд.: доступ-ссылка на published + запись в lab_sim_links/доступ; копия избыточна).
|
||||
- [ ] Клон: `POST /api/custom-sims/:id/clone` — копия спеки новому владельцу (draft). Кнопка «Клонировать» на чужой published-карточке.
|
||||
- [ ] Шаблоны: набор стартовых спек (встроенные JSON-фикстуры: пустая, маятник, график, бросок) →
|
||||
«Создать из шаблона» в билдере; «Создать из существующей» = clone.
|
||||
- [ ] Привязка к программе: переиспользовать `lab_sim_links` (kind=textbook|topic) для `custom:<id>`;
|
||||
чип «Связано с программой» (как у встроенных, `_loadRelated`) и кнопка «В лабораторию» с карточки учебника.
|
||||
- [ ] Тесты: share (доступ ученику), clone (новый владелец, draft), ownership на публикации.
|
||||
- [x] Публикация: тумблер draft↔published. В билдере — кнопка «Опубликовать»/«Снять» (PUT status,
|
||||
`setStatus`); в каталоге — кнопки на owner-карточке (publish/unpublish). Только владелец/admin.
|
||||
- [x] Раздача классу: `POST /api/custom-sims/:id/share { classId }` (requireRole teacher,admin +
|
||||
per-row ownership). РЕШЕНИЕ: published custom-sim И ТАК видна всем в каталоге, поэтому раздача =
|
||||
авто-публикация (status→published) + ДОЛГОВЕЧНОЕ уведомление ученикам класса (`pushNotif`,
|
||||
notifications-таблица + SSE) со ссылкой `/lab?sim=custom:<id>`. БЕЗ копии и БЕЗ content_access
|
||||
(custom-sim не гейтится allowlist'ом 'sim' — тот гейтит только legacy lab_sims).
|
||||
- [x] Клон: `POST /api/custom-sims/:id/clone` — копия spec вызвавшему как draft, title += « (копия)»,
|
||||
version=1. Источник: своя (любая) ИЛИ чужая published. Кнопка «Клонировать к себе» на чужой
|
||||
published-карточке (только для teacher/admin) → ведёт на `/sim-builder?id=<newId>`.
|
||||
- [x] Шаблоны: 4 встроенных спеки в `TEMPLATES` (sim-builder.js): пустая, маятник, график y=f(x),
|
||||
бросок. Кнопка «Шаблон» в тулбаре → модалка выбора → `loadFromSim` как новая симуляция.
|
||||
«Создать из существующей» = clone (с чужой карточки).
|
||||
- [x] Привязка к программе: переиспользован `lab_sim_links` с `sim_id='custom:<id>'` (sim_id —
|
||||
TEXT, отдельная таблица не нужна). Эндпоинты на роутере custom-sims: GET `/:id/related`,
|
||||
POST `/:id/links`, DELETE `/:id/links/:linkId` (владелец/admin управляет связями СВОЕЙ симуляции).
|
||||
Клиент: `customSimRelated/AddLink/DelLink`. Backend + чтение готовы; UI-редактор связей в билдере
|
||||
и чипы в каталоге — НЕ сделаны (см. Handoff: остаток).
|
||||
- [x] Тесты: `backend/tests/custom-sims-share.test.js` (15 it): share (авто-publish +
|
||||
durable-уведомление, 400/403/404), clone (новый владелец/draft/spec, чужой published OK, чужой
|
||||
draft 403), links (привязка/удаление/related, ownership). seedRow-паттерн (class/members/textbook).
|
||||
|
||||
## Files to Modify/Create
|
||||
- `backend/src/controllers/customSimController.js` — share/clone/publish (modify)
|
||||
@@ -38,10 +49,42 @@
|
||||
- Решение копия-vs-ссылка зафиксировать в CONTEXT.md.
|
||||
|
||||
## Review Checklist
|
||||
- [ ] Все задачи выполнены
|
||||
- [ ] Ownership на publish/share/clone покрыт тестами
|
||||
- [ ] Ученик класса получает доступ; чужой — нет
|
||||
- [ ] Reuse материалов/доступа/links, без дублей
|
||||
- [x] Все задачи выполнены (привязка к программе — backend+чтение; UI-редактор связей в Handoff)
|
||||
- [x] Ownership на publish/share/clone/links покрыт тестами (чужой draft → 403, чужой published clone → ОК)
|
||||
- [x] Ученик класса получает уведомление; не-владелец/не-свой-класс → 403
|
||||
- [x] Reuse материалов/доступа/links, без дублей (pushNotif, lab_sim_links, паттерн share из materials)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Заполняет реализатор -->
|
||||
|
||||
### Что реализовано (Ф6)
|
||||
- **Backend** (`customSimController.js` + `customSims.js`):
|
||||
- `POST /:id/share { classId }` — авто-publish + `pushNotif(uid,'sim_shared',msg,'/lab?sim=custom:<id>')`
|
||||
каждому ученику класса. Возвращает `{ ok, sent, status:'published' }`. requireRole(teacher,admin) +
|
||||
ownership симуляции + проверка `classes.teacher_id`.
|
||||
- `POST /:id/clone` → 201 `{ id }`. Источник: своя (любая) ИЛИ чужая published. status=draft, version=1,
|
||||
title += ' (копия)', spec_json копируется как есть.
|
||||
- `GET /:id/related` (auth, own/published) / `POST /:id/links` / `DELETE /:id/links/:linkId`
|
||||
(owner/admin) — поверх `lab_sim_links`, `sim_id='custom:<id>'`. DELETE симуляции чистит её связи.
|
||||
- **Решение копия-vs-доступ:** доступ (published виден всем) + уведомление; НЕ копия, НЕ content_access.
|
||||
- **Клиент** (`js/api.js`): `customSimShare/Clone/Related/AddLink/DelLink` в `window.LS`.
|
||||
- **Каталог** (`lab-glue.js`, IIFE `LabCustom`, аддитивно): owner-карточка — кнопки Раздать классу /
|
||||
Опубликовать↔Снять; чужая published (для teacher/admin) — «Клонировать к себе». Делегат `data-act`
|
||||
расширен (share/clone/publish/unpublish). Модалка выбора класса (`LS.getClasses`+`LS.modal`).
|
||||
Публичное API: добавлены `LabCustom.share/clone/setStatus`.
|
||||
- **Билдер** (`sim-builder.js`): тулбар — «Шаблон» (TEMPLATES×4: пустая/маятник/график/бросок →
|
||||
`loadFromSim` как новая), «Раздать» (для сохранённой), publish-toggle (Опубликовать↔Снять).
|
||||
Новые методы: `setStatus`, `openShareModal`, `openTemplateModal`. ICON.template/unpublish.
|
||||
|
||||
### Остаток (минимум по плану — не сделано, низкий приоритет)
|
||||
- UI-редактор курикулумных связей в билдере (select учебника из `/api/access/catalog` + добавить/
|
||||
удалить) и чипы «Связано с программой» в каталоге/на странице sim. Backend и `/related` готовы —
|
||||
это чисто фронтовая надстройка. Обратный поиск «какие custom-sim привязаны к учебнику» (как
|
||||
`/api/lab/links?kind=textbook&ref_id=`) для custom НЕ добавлен: `/api/lab/links` джойнит `lab_sims`,
|
||||
а у custom строк там нет — при желании добавить отдельный bulk-эндпоинт или LEFT JOIN на custom_sims.
|
||||
|
||||
### Для Ф7 (доска онлайн-урока)
|
||||
- Источник sim для доски: `LS.customSimGet(id)` → `sim.spec`; рендер — `window.SimEngine.mount(host, spec)`
|
||||
(как в билдере/каталоге). Deep-link/идентификатор: `custom:<dbid>`; в LabRegistry — `customsim_<dbid>`
|
||||
(resolveId). published-симуляция доступна всем (для учеников на доске — без доп. прав).
|
||||
- Раздача классу уже шлёт уведомление со ссылкой `/lab?...` — на доске ссылку при необходимости
|
||||
заменить на классную сессию; механизм `pushNotif` переиспользуем.
|
||||
|
||||
Reference in New Issue
Block a user