feat(sim-builder): тумблер «Конструктор симуляций» в админке (feature_sim_builder_enabled) — гейт авторинга + скрытие/редирект

This commit is contained in:
Maxim Dolgolyov
2026-06-13 15:22:59 +03:00
parent 6743dfcbce
commit 225e252e3c
5 changed files with 23 additions and 8 deletions
+1 -1
View File
@@ -525,7 +525,7 @@ function getFeatures(_req, res) {
function updateFeatures(req, res) {
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
'gamification', 'assistant'];
'gamification', 'assistant', 'sim_builder'];
const updates = req.body;
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
+13 -7
View File
@@ -6,29 +6,35 @@
const express = require('express');
const router = express.Router();
const { authMiddleware, requireRole } = require('../middleware/auth');
const { requireFeature } = require('../middleware/features');
const c = require('../controllers/customSimController');
router.use(authMiddleware);
// «Конструктор симуляций» можно отключить в админке (feature_sim_builder_enabled).
// Чтение/проигрывание уже сохранённых симуляций остаётся доступным; гейтим только
// авторинг — создание/правку/удаление/раздачу/клон/связи.
const gate = requireFeature('sim_builder');
router.get('/', c.list);
// @public-by-design: router-level authMiddleware (above) + ownership/published check in handler
router.get('/:id', c.get);
// @public-by-design: router-level authMiddleware (above) + ownership/published check in handler
router.get('/:id/related', c.related);
router.post('/', requireRole('teacher', 'admin'), c.create);
router.post('/', gate, requireRole('teacher', 'admin'), c.create);
// @public-by-design: router-level authMiddleware (above) + per-row ownership check in handler
router.put('/:id', requireRole('teacher', 'admin'), c.update);
router.put('/:id', gate, requireRole('teacher', 'admin'), c.update);
// @public-by-design: router-level authMiddleware (above) + per-row ownership check in handler
router.delete('/:id', requireRole('teacher', 'admin'), c.remove);
router.delete('/:id', gate, requireRole('teacher', 'admin'), c.remove);
// Фаза 6 — раздача классу / клон / курикулумные связи. Мутации — inline
// requireRole(teacher,admin) + per-row ownership в хендлере.
router.post('/:id/share', requireRole('teacher', 'admin'), c.share);
router.post('/:id/clone', requireRole('teacher', 'admin'), c.clone);
router.post('/:id/share', gate, requireRole('teacher', 'admin'), c.share);
router.post('/:id/clone', gate, requireRole('teacher', 'admin'), c.clone);
// @public-by-design: router-level authMiddleware (above) + per-row ownership check in handler
router.post('/:id/links', requireRole('teacher', 'admin'), c.addLink);
router.post('/:id/links', gate, requireRole('teacher', 'admin'), c.addLink);
// @public-by-design: router-level authMiddleware (above) + per-row ownership check in handler
router.delete('/:id/links/:linkId', requireRole('teacher', 'admin'), c.removeLink);
router.delete('/:id/links/:linkId', gate, requireRole('teacher', 'admin'), c.removeLink);
module.exports = router;
+1
View File
@@ -17,6 +17,7 @@
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
{ key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' },
{ key: 'sim_builder', label: 'Конструктор симуляций', desc: 'Создание учителем своих интерактивных симуляций (рабочее поле, формулы, физика, графики)', icon: 'pencil-ruler' },
];
const FS_FEATURES = [
+7
View File
@@ -189,6 +189,13 @@
var ip = LS.initPage() || {};
if (!(ip.isTeacher || ip.isAdmin)) { location.href = '/dashboard'; return; }
// Фича-гейт: «Конструктор симуляций» можно отключить в админке (feature_sim_builder_enabled).
if (LS.loadFeatures) {
LS.loadFeatures().then(function (feats) {
if (feats && feats.sim_builder === false) { LS.toast && LS.toast('Конструктор симуляций отключён', 'warn'); location.href = '/dashboard'; }
}).catch(function () {});
}
if (!window.SimEngine || !window.SimExpr || !window.SimBuilder) {
document.getElementById('sbu-preview').innerHTML =
'<div style="padding:40px;color:#fff">Движок симуляций не загрузился. Обновите страницу.</div>';
+1
View File
@@ -860,6 +860,7 @@ async function hideDisabledFeatures() {
biochem: ['/biochem', '/biochem-library', '/biochem-reactions'],
live_quiz: ['/live-quiz'],
classroom: ['/classroom'],
sim_builder: ['/sim-builder', '/sim-builder.html'],
exam9: ['/exam9', '/exam9.html'],
textbooks: ['/textbooks', '/textbooks.html', '/textbook'],
};