Files
Learn_System/plans/sim-builder/phase-6-sharing.md
T

8.5 KiB
Raw Blame History

Phase 6: Раздача / шаблоны / клон / программа

Status: Implemented (pending commit — за оркестратором) Parent plan: PLAN.md Domain: fullstack

Objective

Экосистема вокруг custom-sims: публикация, раздача классу, клонирование чужих, старт из шаблонов, привязка к программе (учебник/тема).

Tasks

  • Публикация: тумблер draft↔published. В билдере — кнопка «Опубликовать»/«Снять» (PUT status, setStatus); в каталоге — кнопки на owner-карточке (publish/unpublish). Только владелец/admin.
  • Раздача классу: 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).
  • Клон: POST /api/custom-sims/:id/clone — копия spec вызвавшему как draft, title += « (копия)», version=1. Источник: своя (любая) ИЛИ чужая published. Кнопка «Клонировать к себе» на чужой published-карточке (только для teacher/admin) → ведёт на /sim-builder?id=<newId>.
  • Шаблоны: 4 встроенных спеки в TEMPLATES (sim-builder.js): пустая, маятник, график y=f(x), бросок. Кнопка «Шаблон» в тулбаре → модалка выбора → loadFromSim как новая симуляция. «Создать из существующей» = clone (с чужой карточки).
  • Привязка к программе: переиспользован 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: остаток).
  • Тесты: 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)
  • backend/src/routes/customSims.js — роуты share/clone (modify)
  • backend/src/controllers/.../lab links — связи для custom (reuse lab.js links, modify при необходимости)
  • frontend/sim-builder.html / frontend/js/labs/lab-glue.js — шаблоны, кнопки публикации/клона/раздачи (modify)
  • js/api.js — методы share/clone (modify)
  • backend/tests/custom-sims-share.test.js (new)

Acceptance Criteria

  • Учитель публикует; другой учитель видит и клонирует к себе (draft).
  • Выданная классу симуляция доступна ученикам класса (с уведомлением).
  • Старт из шаблона создаёт рабочую заготовку. Привязка к учебнику показывает чип/кнопку.

Notes

  • Раздача — переиспользовать существующий механизм доступа/уведомлений, не строить новый.
  • Решение копия-vs-ссылка зафиксировать в CONTEXT.md.

Review Checklist

  • Все задачи выполнены (привязка к программе — backend+чтение; UI-редактор связей в Handoff)
  • Ownership на publish/share/clone/links покрыт тестами (чужой draft → 403, чужой published clone → ОК)
  • Ученик класса получает уведомление; не-владелец/не-свой-класс → 403
  • 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 переиспользуем.