diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index 97c0faf2..13557a3f 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -25,6 +25,7 @@ const { normaliseEventTimeTriple } = require('../services/eventService'); const { hasColumnCached } = require('../utils/schemaCache'); const { validateFileType } = require('../utils/fileSecurityUtils'); const { requireEventOwnership } = require('../middleware/ownership'); +const { requireFeatureFlag } = require('../middleware/requireFeatureFlag'); const { getFrontendBaseUrl } = require('../utils/frontendUrl'); const downloadZipService = require('../services/downloadZipService'); @@ -1929,7 +1930,7 @@ async function loadOwnedEvent(req) { // Generate (or rotate) the slideshow share token. Idempotent in intent: each // call mints a fresh token, which both "Generate" (first time) and "Regenerate" // (rotate, kills the old link) use. -router.post('/:id/slideshow/generate', adminAuth, requirePermission('events.edit'), requireEventOwnership, async (req, res) => { +router.post('/:id/slideshow/generate', adminAuth, requirePermission('events.edit'), requireFeatureFlag('slideshow'), requireEventOwnership, async (req, res) => { try { const event = await loadOwnedEvent(req); if (!event) { @@ -1988,7 +1989,7 @@ router.post('/:id/slideshow/disable', adminAuth, requirePermission('events.edit' // Update the LIVE slideshow settings (display time / transition style / speed). // A running projector picks these up via the show-page settings poll within a // few seconds — no need to regenerate the link. -router.patch('/:id/slideshow', adminAuth, requirePermission('events.edit'), requireEventOwnership, [ +router.patch('/:id/slideshow', adminAuth, requirePermission('events.edit'), requireFeatureFlag('slideshow'), requireEventOwnership, [ body('show_interval_ms').optional().isInt({ min: 1000, max: 120000 }), body('show_transition').optional().isIn(SLIDESHOW_TRANSITIONS), body('show_transition_ms').optional().isInt({ min: 100, max: 5000 }), diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index 2d198270..f2b7226f 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -29,6 +29,7 @@ const { setGalleryAuthCookies } = require('../utils/tokenUtils'); // Read globals from app_settings (the real table) — settingsService.getSetting // queries a non-existent `settings` table and throws. const { getAppSetting } = require('../utils/appSettings'); +const { isFeatureEnabled } = require('../middleware/requireFeatureFlag'); const fs = require('fs'); // Get storage path from environment or default @@ -245,6 +246,10 @@ function slideshowPhotosQuery(eventId) { // dead link reveals nothing and stops any projector on its next poll. async function resolveSlideshow(slug, token) { if (!token) return null; + // The `slideshow` feature flag is a master kill-switch: when an admin turns + // Live Slideshow off, every existing /show/ link dies on its next request + // (the running projector stops within one /state poll), not just the admin UI. + if (!(await isFeatureEnabled('slideshow'))) return null; const event = await db('events') .where({ slug,