fix(slideshow): feature flag is a master kill-switch, not just admin UI
Disabling the `slideshow` feature previously only hid the admin UI — the public
/show/:token route ignored the flag, so already-minted links kept working. Gate
resolveSlideshow on isFeatureEnabled('slideshow') so every /session and /state
404s when the feature is off: clicking Start shows "link not active" and a
running projector stops within one /state poll. Belt-and-braces: also gate the
admin generate + settings PATCH endpoints with requireFeatureFlag so links can't
be minted/changed while off (disable stays open so stale tokens can be cleared).
This commit is contained in:
@@ -25,6 +25,7 @@ const { normaliseEventTimeTriple } = require('../services/eventService');
|
|||||||
const { hasColumnCached } = require('../utils/schemaCache');
|
const { hasColumnCached } = require('../utils/schemaCache');
|
||||||
const { validateFileType } = require('../utils/fileSecurityUtils');
|
const { validateFileType } = require('../utils/fileSecurityUtils');
|
||||||
const { requireEventOwnership } = require('../middleware/ownership');
|
const { requireEventOwnership } = require('../middleware/ownership');
|
||||||
|
const { requireFeatureFlag } = require('../middleware/requireFeatureFlag');
|
||||||
const { getFrontendBaseUrl } = require('../utils/frontendUrl');
|
const { getFrontendBaseUrl } = require('../utils/frontendUrl');
|
||||||
const downloadZipService = require('../services/downloadZipService');
|
const downloadZipService = require('../services/downloadZipService');
|
||||||
|
|
||||||
@@ -1929,7 +1930,7 @@ async function loadOwnedEvent(req) {
|
|||||||
// Generate (or rotate) the slideshow share token. Idempotent in intent: each
|
// Generate (or rotate) the slideshow share token. Idempotent in intent: each
|
||||||
// call mints a fresh token, which both "Generate" (first time) and "Regenerate"
|
// call mints a fresh token, which both "Generate" (first time) and "Regenerate"
|
||||||
// (rotate, kills the old link) use.
|
// (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 {
|
try {
|
||||||
const event = await loadOwnedEvent(req);
|
const event = await loadOwnedEvent(req);
|
||||||
if (!event) {
|
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).
|
// Update the LIVE slideshow settings (display time / transition style / speed).
|
||||||
// A running projector picks these up via the show-page settings poll within a
|
// A running projector picks these up via the show-page settings poll within a
|
||||||
// few seconds — no need to regenerate the link.
|
// 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_interval_ms').optional().isInt({ min: 1000, max: 120000 }),
|
||||||
body('show_transition').optional().isIn(SLIDESHOW_TRANSITIONS),
|
body('show_transition').optional().isIn(SLIDESHOW_TRANSITIONS),
|
||||||
body('show_transition_ms').optional().isInt({ min: 100, max: 5000 }),
|
body('show_transition_ms').optional().isInt({ min: 100, max: 5000 }),
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const { setGalleryAuthCookies } = require('../utils/tokenUtils');
|
|||||||
// Read globals from app_settings (the real table) — settingsService.getSetting
|
// Read globals from app_settings (the real table) — settingsService.getSetting
|
||||||
// queries a non-existent `settings` table and throws.
|
// queries a non-existent `settings` table and throws.
|
||||||
const { getAppSetting } = require('../utils/appSettings');
|
const { getAppSetting } = require('../utils/appSettings');
|
||||||
|
const { isFeatureEnabled } = require('../middleware/requireFeatureFlag');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
// Get storage path from environment or default
|
// 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.
|
// dead link reveals nothing and stops any projector on its next poll.
|
||||||
async function resolveSlideshow(slug, token) {
|
async function resolveSlideshow(slug, token) {
|
||||||
if (!token) return null;
|
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')
|
const event = await db('events')
|
||||||
.where({
|
.where({
|
||||||
slug,
|
slug,
|
||||||
|
|||||||
Reference in New Issue
Block a user