feat(sim-builder): тумблер «Конструктор симуляций» в админке (feature_sim_builder_enabled) — гейт авторинга + скрытие/редирект
This commit is contained in:
@@ -525,7 +525,7 @@ function getFeatures(_req, res) {
|
|||||||
function updateFeatures(req, res) {
|
function updateFeatures(req, res) {
|
||||||
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
|
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
|
||||||
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
|
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
|
||||||
'gamification', 'assistant'];
|
'gamification', 'assistant', 'sim_builder'];
|
||||||
const updates = req.body;
|
const updates = req.body;
|
||||||
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
|
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
|
||||||
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
|
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
|
||||||
|
|||||||
@@ -6,29 +6,35 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { authMiddleware, requireRole } = require('../middleware/auth');
|
const { authMiddleware, requireRole } = require('../middleware/auth');
|
||||||
|
const { requireFeature } = require('../middleware/features');
|
||||||
const c = require('../controllers/customSimController');
|
const c = require('../controllers/customSimController');
|
||||||
|
|
||||||
router.use(authMiddleware);
|
router.use(authMiddleware);
|
||||||
|
|
||||||
|
// «Конструктор симуляций» можно отключить в админке (feature_sim_builder_enabled).
|
||||||
|
// Чтение/проигрывание уже сохранённых симуляций остаётся доступным; гейтим только
|
||||||
|
// авторинг — создание/правку/удаление/раздачу/клон/связи.
|
||||||
|
const gate = requireFeature('sim_builder');
|
||||||
|
|
||||||
router.get('/', c.list);
|
router.get('/', c.list);
|
||||||
// @public-by-design: router-level authMiddleware (above) + ownership/published check in handler
|
// @public-by-design: router-level authMiddleware (above) + ownership/published check in handler
|
||||||
router.get('/:id', c.get);
|
router.get('/:id', c.get);
|
||||||
// @public-by-design: router-level authMiddleware (above) + ownership/published check in handler
|
// @public-by-design: router-level authMiddleware (above) + ownership/published check in handler
|
||||||
router.get('/:id/related', c.related);
|
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
|
// @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
|
// @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
|
// Фаза 6 — раздача классу / клон / курикулумные связи. Мутации — inline
|
||||||
// requireRole(teacher,admin) + per-row ownership в хендлере.
|
// requireRole(teacher,admin) + per-row ownership в хендлере.
|
||||||
router.post('/:id/share', requireRole('teacher', 'admin'), c.share);
|
router.post('/:id/share', gate, requireRole('teacher', 'admin'), c.share);
|
||||||
router.post('/:id/clone', requireRole('teacher', 'admin'), c.clone);
|
router.post('/:id/clone', gate, requireRole('teacher', 'admin'), c.clone);
|
||||||
// @public-by-design: router-level authMiddleware (above) + per-row ownership check in handler
|
// @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
|
// @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;
|
module.exports = router;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
||||||
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
||||||
{ key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' },
|
{ key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' },
|
||||||
|
{ key: 'sim_builder', label: 'Конструктор симуляций', desc: 'Создание учителем своих интерактивных симуляций (рабочее поле, формулы, физика, графики)', icon: 'pencil-ruler' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const FS_FEATURES = [
|
const FS_FEATURES = [
|
||||||
|
|||||||
@@ -189,6 +189,13 @@
|
|||||||
var ip = LS.initPage() || {};
|
var ip = LS.initPage() || {};
|
||||||
if (!(ip.isTeacher || ip.isAdmin)) { location.href = '/dashboard'; return; }
|
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) {
|
if (!window.SimEngine || !window.SimExpr || !window.SimBuilder) {
|
||||||
document.getElementById('sbu-preview').innerHTML =
|
document.getElementById('sbu-preview').innerHTML =
|
||||||
'<div style="padding:40px;color:#fff">Движок симуляций не загрузился. Обновите страницу.</div>';
|
'<div style="padding:40px;color:#fff">Движок симуляций не загрузился. Обновите страницу.</div>';
|
||||||
|
|||||||
@@ -860,6 +860,7 @@ async function hideDisabledFeatures() {
|
|||||||
biochem: ['/biochem', '/biochem-library', '/biochem-reactions'],
|
biochem: ['/biochem', '/biochem-library', '/biochem-reactions'],
|
||||||
live_quiz: ['/live-quiz'],
|
live_quiz: ['/live-quiz'],
|
||||||
classroom: ['/classroom'],
|
classroom: ['/classroom'],
|
||||||
|
sim_builder: ['/sim-builder', '/sim-builder.html'],
|
||||||
exam9: ['/exam9', '/exam9.html'],
|
exam9: ['/exam9', '/exam9.html'],
|
||||||
textbooks: ['/textbooks', '/textbooks.html', '/textbook'],
|
textbooks: ['/textbooks', '/textbooks.html', '/textbook'],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user