fix(categories): address PR #790 review — event ownership, migration renumber, nits

- 🔴 Event ownership: GET /event/:eventId and DELETE /reorder/:eventId now use
  requireEventOwnership; POST /reorder (event_id in body) gets the equivalent
  inline check (super_admin bypasses; others limited to owned/ownerless events).
  New test covers a settings.edit-holding non-super_admin blocked (403) on all
  three per-event routes.
- 🔴 Migration renumber: 158→159, 159→160 (upstream #788 already took 158);
  headers + the test's require path updated.
- 🟢 Nits: stale inline "Drag the arrows" fallback → "Use the arrows" (matches
  en.json; control is click-only); invalid bg-accent-dark/150 → bg-accent-dark.
This commit is contained in:
Luca
2026-07-10 20:10:40 +02:00
parent 8d0a946478
commit a4b4485d32
6 changed files with 68 additions and 12 deletions
+17 -2
View File
@@ -4,6 +4,7 @@ const { db, logActivity } = require('../database/db');
const { formatBoolean } = require('../utils/dbCompat');
const { adminAuth } = require('../middleware/auth');
const { requirePermission } = require('../middleware/permissions');
const { requireEventOwnership } = require('../middleware/ownership');
const { getEventCategoriesOrdered } = require('../utils/categoryOrder');
const logger = require('../utils/logger');
const router = express.Router();
@@ -26,7 +27,7 @@ router.get('/global', adminAuth, requirePermission('settings.view'), async (req,
// Get categories for a specific event (global + event-specific), resolved to
// the event's effective order: per-event override, else global default, else
// name (#782). Each row carries `override_position` (null when not customised).
router.get('/event/:eventId', adminAuth, requirePermission('settings.view'), async (req, res) => {
router.get('/event/:eventId', adminAuth, requirePermission('settings.view'), requireEventOwnership, async (req, res) => {
try {
const categories = await getEventCategoriesOrdered(req.params.eventId);
res.json(categories);
@@ -282,6 +283,20 @@ router.post('/reorder', adminAuth, requirePermission('settings.edit'), [
const eventId = parseInt(req.body.event_id, 10);
const orderedIds = req.body.orderedIds.map((id) => parseInt(id, 10));
// Event ownership (event_id comes from the body, so requireEventOwnership —
// which reads req.params — can't be used here). Mirror it: super_admins
// bypass; other admins may only reorder events they own (ownerless
// legacy/system events allowed).
if (req.admin.roleName !== 'super_admin') {
const event = await db('events').where('id', eventId).first();
if (!event) {
return res.status(404).json({ error: 'Event not found' });
}
if (event.created_by && event.created_by !== req.admin.id) {
return res.status(403).json({ error: 'Access denied' });
}
}
// Every id must be a category available to this event: a shared global OR
// one of the event's own categories. Anything else is out of scope.
const available = await db('photo_categories')
@@ -317,7 +332,7 @@ router.post('/reorder', adminAuth, requirePermission('settings.edit'), [
});
// Clear an event's override — revert this gallery to the global default order.
router.delete('/reorder/:eventId', adminAuth, requirePermission('settings.edit'), async (req, res) => {
router.delete('/reorder/:eventId', adminAuth, requirePermission('settings.edit'), requireEventOwnership, async (req, res) => {
try {
const eventId = parseInt(req.params.eventId, 10);
await db('event_category_order').where('event_id', eventId).del();
+1 -1
View File
@@ -4,7 +4,7 @@
* Resolves an event's categories into their effective display order, layering:
* 1. per-event override — event_category_order.position, when the event has
* been customised;
* 2. the global default — photo_categories.display_order (migration 158);
* 2. the global default — photo_categories.display_order (migration 159);
* 3. name.
*
* Globals and event-specific categories are ordered together so a custom order