29301ff87d
- study: хоткеи Space/стрелки=флип, 1-4/←→=оценка - превью интервалов = точная копия серверного SM-2 (было враньё «<1 мин») - поиск/фильтр карточек внутри колоды - drag-reorder карточек + endpoint PUT /decks/:id/reorder (requireOwnership) - flashcard_decks добавлен в ALLOWED_TABLES requireOwnership - эмодзи в empty-state → inline SVG .ic - deleteCard: нативный confirm() → LS.confirm Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2.0 KiB
JavaScript
55 lines
2.0 KiB
JavaScript
'use strict';
|
|
const db = require('../db/db');
|
|
|
|
/**
|
|
* Factory: returns middleware that loads a resource by req.params[paramKey],
|
|
* verifies ownership against req.user.id, and attaches the row as req.resource.
|
|
*
|
|
* Simple usage (table lookup):
|
|
* requireOwnership({ table: 'tests', ownerField: 'created_by' })
|
|
*
|
|
* Complex usage (JOIN / aliased field):
|
|
* requireOwnership({
|
|
* fetchFn: id => db.prepare(`
|
|
* SELECT a.id, COALESCE(c.teacher_id, a.created_by) AS teacher_id
|
|
* FROM assignments a LEFT JOIN classes c ON c.id = a.class_id WHERE a.id = ?
|
|
* `).get(id),
|
|
* ownerField: 'teacher_id',
|
|
* })
|
|
*
|
|
* Options:
|
|
* table — DB table name for `SELECT * FROM {table} WHERE id = ?`
|
|
* fetchFn — (id) => row|undefined (alternative to `table`)
|
|
* ownerField — row field compared to req.user.id (required)
|
|
* paramKey — req.params key for the record ID (default: 'id')
|
|
* adminBypass — admin role always passes (default: true)
|
|
*/
|
|
const ALLOWED_TABLES = new Set(['tests','classes','assignments','questions','courses','lessons','files','folders','shop_items','live_sessions','flashcard_decks']);
|
|
|
|
function requireOwnership({ table, fetchFn, ownerField, paramKey = 'id', adminBypass = true }) {
|
|
if (table && !ALLOWED_TABLES.has(table)) throw new Error(`requireOwnership: unknown table "${table}"`);
|
|
// Pre-compile statement once at middleware creation (server startup)
|
|
const stmt = table ? db.prepare(`SELECT * FROM ${table} WHERE id = ?`) : null;
|
|
const fetch = fetchFn || (id => stmt.get(id));
|
|
|
|
return (req, res, next) => {
|
|
const row = fetch(req.params[paramKey]);
|
|
|
|
if (!row) return res.status(404).json({ error: 'Not found' });
|
|
|
|
if (adminBypass && req.user?.role === 'admin') {
|
|
req.resource = row;
|
|
return next();
|
|
}
|
|
|
|
if (row[ownerField] !== req.user?.id) {
|
|
return res.status(403).json({ error: 'Forbidden' });
|
|
}
|
|
|
|
req.resource = row;
|
|
next();
|
|
};
|
|
}
|
|
|
|
module.exports = { requireOwnership };
|