feat(permissions): B5 — группы прав (секции в UI + вкл/выкл всей группы)
registry: карта GROUP (Вопросы / Класс и ученики / Библиотека / Курсы и шаблоны / Геймификация / Контент / Тесты и активность / Профиль), проброшена в byRole.group. permissions.js: вкладка «Доступ · роли» рендерит права секциями по группам, у каждой — «включить все / выключить все» (с подтверждением, если в группе есть requireConfirmOff). Карточка вынесена в permCard(). Тест: definitions содержат group. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -162,6 +162,24 @@ const PERMISSIONS = {
|
||||
},
|
||||
};
|
||||
|
||||
/* Группы для секций в админ-UI (один источник; byRole проставляет group). */
|
||||
const GROUP = {
|
||||
// teacher
|
||||
'questions.manage': 'Вопросы', 'questions.delete': 'Вопросы',
|
||||
'students.invite': 'Класс и ученики', 'sessions.reset': 'Класс и ученики',
|
||||
'results.export': 'Класс и ученики', 'classes.manage': 'Класс и ученики',
|
||||
'schedule.manage': 'Класс и ученики', 'announcements.send': 'Класс и ученики',
|
||||
'library.upload': 'Библиотека', 'library.folders': 'Библиотека',
|
||||
'templates.manage': 'Курсы и шаблоны', 'templates.public': 'Курсы и шаблоны',
|
||||
'courses.manage': 'Курсы и шаблоны', 'courses.interactive': 'Курсы и шаблоны',
|
||||
'shop.manage': 'Геймификация', 'gamification.manage': 'Геймификация',
|
||||
// student
|
||||
'tests.free': 'Тесты и активность', 'board.post': 'Тесты и активность',
|
||||
'profile.edit': 'Профиль',
|
||||
'shop.purchase': 'Геймификация', 'gamification.challenges': 'Геймификация',
|
||||
'theory.access': 'Контент', 'simulations.access': 'Контент', 'simulations.quiz': 'Контент',
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether a given permission key exists in the registry.
|
||||
* Used by perm() helper in auth.js to fail early on typos.
|
||||
@@ -182,7 +200,7 @@ function listKeys() {
|
||||
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, requires: v.requires || [] }));
|
||||
.map(([key, v]) => ({ key, role: v.role, default: v.default, label: v.label, desc: v.desc, requireConfirmOff: !!v.requireConfirmOff, requires: v.requires || [], group: GROUP[key] || 'Прочее' }));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -197,6 +197,15 @@ describe('Permissions', () => {
|
||||
{ role: 'student', permission: 'simulations.access', enabled: true }, adminToken);
|
||||
});
|
||||
|
||||
// ── B5: группы прав в определениях ─────────────────────────────────────────
|
||||
it('B5: GET /api/permissions — у каждого определения есть group', async () => {
|
||||
const res = await inject('GET', '/api/permissions', null, adminToken);
|
||||
assert.equal(res.status, 200);
|
||||
assert.ok(Array.isArray(res.body.definitions) && res.body.definitions.length > 0);
|
||||
assert.ok(res.body.definitions.every(d => typeof d.group === 'string' && d.group.length > 0),
|
||||
'у каждого определения есть непустой group');
|
||||
});
|
||||
|
||||
// ── 11. A3: история изменений прав ─────────────────────────────────────────
|
||||
it('GET /api/permissions/log — история (admin видит записи; не-админу 403)', async () => {
|
||||
await inject('POST', '/api/permissions',
|
||||
|
||||
Reference in New Issue
Block a user