Files
Learn_System/backend/src/permissions/registry.js
T
Maxim Dolgolyov 7eea33a135 feat(perm-ui): P0 usability improvements (search, default-dot, confirm-critical, wording)
- registry.js: добавлен флаг requireConfirmOff для 7 критичных прав (questions.manage, classes.manage, library.upload, courses.manage, sessions.reset, theory.access, simulations.access); byRole() теперь возвращает это поле
- admin.html: subtitle в модале прав — «учителя» → «пользователя»; tooltip на кнопке «Сбросить всё по умолчанию»; поле поиска над сеткой прав; CSS .perm-modified-dot (amber, 8px)
- admin.js: badge «Инд.» → «Индивидуально» (font-size 11px); renderPermissions() рисует .perm-modified-dot когда значение отличается от registry default; togglePermission() показывает LS.confirm перед выключением критичных прав; window.filterPermissions() скрывает карточки и role-блоки по поисковому запросу

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 14:43:49 +03:00

200 lines
8.4 KiB
JavaScript

'use strict';
/**
* Single source of truth for all granular permissions.
* Keys here MUST match what controllers/routes use in requirePermission(...).
*
* Shape:
* <key>: {
* role: 'teacher' | 'student', // primary role for admin UI grouping
* roles: string[], // all roles this key applies to
* default: 0 | 1, // PERM_DEFAULTS value
* label: 'Short label for admin UI',
* desc: 'Russian description for admin UI'
* }
*
* DISCREPANCY NOTE (2026-05-17):
* auth.js PERM_DEFAULTS includes a 'free_student' role with the same 8
* permission keys as 'student' (identical key strings, identical defaults).
* permissionsController.js ALL_PERMISSIONS does NOT list free_student entries
* (seedDefaults/admin UI only handles teacher and student).
* Resolution: each student-role key carries roles:['student','free_student']
* so that requirePermission() fallback works correctly for free_student users.
* The admin UI behaviour (filtering to teacher/student only) is preserved —
* permissionsController reads from registry.byRole('teacher') and
* registry.byRole('student').
*/
const PERMISSIONS = {
/* ── Teacher ─────────────────────────────────────────────────────────── */
'questions.manage': {
role: 'teacher', roles: ['teacher'], default: 0,
label: 'Управление вопросами',
desc: 'Создавать, редактировать и копировать вопросы в банке',
requireConfirmOff: true,
},
'questions.delete': {
role: 'teacher', roles: ['teacher'], default: 0,
label: 'Удалять вопросы',
desc: 'Удалять вопросы из банка (требует "Управление вопросами")',
},
'students.invite': {
role: 'teacher', roles: ['teacher'], default: 0,
label: 'Регистрировать учеников',
desc: 'Создавать новые аккаунты учеников напрямую из панели',
},
'sessions.reset': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Сброс попыток',
desc: 'Сбрасывать прохождение теста ученика в своём классе',
requireConfirmOff: true,
},
'results.export': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Экспорт результатов',
desc: 'Выгружать результаты и оценки класса в CSV',
},
'classes.manage': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Управление классами',
desc: 'Создавать, редактировать и удалять свои классы',
requireConfirmOff: true,
},
'library.upload': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Загрузка файлов',
desc: 'Загружать файлы в библиотеку',
requireConfirmOff: true,
},
'library.folders': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Управление папками',
desc: 'Создавать папки и настраивать доступ к ним',
},
'schedule.manage': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Дедлайны заданий',
desc: 'Устанавливать дедлайны и временные окна для заданий',
},
'announcements.send': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Объявления',
desc: 'Публиковать объявления в своих классах',
},
'templates.manage': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Управление шаблонами',
desc: 'Создавать и использовать шаблоны курсов и уроков',
},
'templates.public': {
role: 'teacher', roles: ['teacher'], default: 0,
label: 'Публикация шаблонов',
desc: 'Делать свои шаблоны публичными для всех учителей',
},
'courses.manage': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Управление курсами',
desc: 'Создавать и редактировать теоретические курсы и уроки',
requireConfirmOff: true,
},
'courses.interactive': {
role: 'teacher', roles: ['teacher'], default: 1,
label: 'Интерактивные блоки',
desc: 'Добавлять интерактивные задания в уроки (сопоставление, пропуски, порядок)',
},
'shop.manage': {
role: 'teacher', roles: ['teacher'], default: 0,
label: 'Управление магазином',
desc: 'Создавать и редактировать товары в магазине наград',
},
'gamification.manage': {
role: 'teacher', roles: ['teacher'], default: 0,
label: 'Управление геймификацией',
desc: 'Начислять XP/монеты ученикам, управлять достижениями',
},
/* ── Student (also applies to free_student — same keys, same defaults) ── */
'tests.free': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Свободные тесты',
desc: 'Проходить тесты без задания (по предмету / случайно)',
},
'board.post': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Реакции на доске',
desc: 'Ставить реакции на задания на доске',
},
'profile.edit': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Редактирование профиля',
desc: 'Изменять своё имя и пароль',
},
'shop.purchase': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Покупки в магазине',
desc: 'Покупать предметы в магазине наград за монеты',
},
'gamification.challenges': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Испытания недели',
desc: 'Участвовать в еженедельных испытаниях и получать награды',
},
'theory.access': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Доступ к теории',
desc: 'Просматривать теоретические курсы и уроки',
requireConfirmOff: true,
},
'simulations.access': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Доступ к симуляциям',
desc: 'Открывать лабораторию с физическими, химическими и биологическими симуляциями',
requireConfirmOff: true,
},
'simulations.quiz': {
role: 'student', roles: ['student', 'free_student'], default: 1,
label: 'Задания в симуляциях',
desc: 'Использовать режим "Задания" в симуляциях (квиз-режим)',
},
};
/**
* Check whether a given permission key exists in the registry.
* Used by perm() helper in auth.js to fail early on typos.
*/
function isKnown(key) {
return Object.prototype.hasOwnProperty.call(PERMISSIONS, key);
}
/** Return all registered permission keys. */
function listKeys() {
return Object.keys(PERMISSIONS);
}
/**
* Return all entries whose primary role matches.
* Returns objects shaped like ALL_PERMISSIONS entries (key, role, default, label, desc).
*/
function byRole(role) {
return Object.entries(PERMISSIONS)
.filter(([, v]) => v.role === role)
.map(([key, v]) => ({ key, role: v.role, default: v.default, label: v.label, desc: v.desc, requireConfirmOff: !!v.requireConfirmOff }));
}
/**
* Build a PERM_DEFAULTS-style lookup: { role: { key: bool } }
* Used by auth.js requirePermission fallback.
*/
function buildDefaultsMap() {
const map = {};
for (const [key, v] of Object.entries(PERMISSIONS)) {
for (const r of v.roles) {
if (!map[r]) map[r] = {};
map[r][key] = v.default === 1;
}
}
return map;
}
module.exports = { PERMISSIONS, isKnown, listKeys, byRole, buildDefaultsMap };