feat(access): Фаза 2a — режим «Матрица» класс × контент в админке

GET /api/access/matrix (классы + карта открытого контента одним запросом,
скоуп учителя). Клиент LS.accessMatrix. Третий режим вкладки «Доступ»:
таблица контент × классы с чекбоксами (правка в один клик) + поиск по
названию (обновляет только tbody — фокус ввода сохраняется), залипающие
заголовки. Тест /api/access смонтирован в харнесс; content-access.test 11/11
(+матрица: учитель видит свои классы и открытый контент, ученику 403).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-03 12:43:00 +03:00
parent 1bbddc00c8
commit 67a70c672d
5 changed files with 126 additions and 10 deletions
+15
View File
@@ -95,6 +95,21 @@ describe('contentAccess', () => {
assert.equal(db.prepare("SELECT COUNT(*) c FROM content_access WHERE scope='student' AND target_id=?").get(student.userId).c, 0);
});
it('GET /api/access/matrix — учитель видит свои классы и открытый контент', async () => {
clearHub();
setRule('class', classId, 1);
const r = await inject('GET', '/api/access/matrix', null, teacher.token);
assert.equal(r.status, 200, JSON.stringify(r.body));
const cls = (r.body.classes || []).find(c => c.id === classId);
assert.ok(cls, 'класс учителя в матрице');
assert.ok((r.body.open[classId].textbook || []).includes(HUB), 'открытый учебник в матрице');
});
it('GET /api/access/matrix — ученику 403', async () => {
const r = await inject('GET', '/api/access/matrix', null, student.token);
assert.equal(r.status, 403);
});
it('DELETE /api/classes/:id чистит правила класса (через purgeAccessFor)', async () => {
setRule('class', classId, 1);
const del = await inject('DELETE', `/api/classes/${classId}`, null, teacher.token);
+1
View File
@@ -44,6 +44,7 @@ app.use('/api/questions', require('../src/routes/questions'));
// Additional routes for integration tests
app.use('/api/permissions', require('../src/routes/permissions'));
app.use('/api/access', require('../src/routes/access'));
// Feature-gated routes (requireFeature checks app_settings in DB)
const { requireFeature } = require('../src/middleware/features');