'use strict'; /** * Single source of truth for all granular permissions. * Keys here MUST match what controllers/routes use in requirePermission(...). * * Shape: * : { * 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 };