From e6e00c0d4a9740141dd368bb27049acfaa900160 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Fri, 19 Jul 2024 07:14:59 -0500 Subject: [PATCH] add freeze feature to rundown (#1106) * add frozen flag to runtime store * add middleware for when frozen * prevent rundown editing when frozen * add endpoint to router to set frozen state * add frozen property to rundown placeholder in client --- apps/client/src/common/stores/runtime.ts | 1 + .../src/api-data/rundown/rundown.controller.ts | 12 ++++++++++++ .../src/api-data/rundown/rundown.middleware.ts | 9 +++++++++ apps/server/src/api-data/rundown/rundown.router.ts | 12 ++++++++---- .../src/api-data/rundown/rundown.validation.ts | 9 +++++++++ apps/server/src/app.ts | 1 + .../src/services/rundown-service/RundownService.ts | 5 +++++ .../src/definitions/runtime/RuntimeStore.type.ts | 3 +++ 8 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 apps/server/src/api-data/rundown/rundown.middleware.ts diff --git a/apps/client/src/common/stores/runtime.ts b/apps/client/src/common/stores/runtime.ts index fff5df1fa..dd5a5667f 100644 --- a/apps/client/src/common/stores/runtime.ts +++ b/apps/client/src/common/stores/runtime.ts @@ -48,6 +48,7 @@ export const runtimeStorePlaceholder: RuntimeStore = { duration: 0, playback: SimplePlayback.Stop, }, + frozen: false, }; const deepCompare = (a: T, b: T) => isEqual(a, b); diff --git a/apps/server/src/api-data/rundown/rundown.controller.ts b/apps/server/src/api-data/rundown/rundown.controller.ts index eae0bd4b4..157d695c5 100644 --- a/apps/server/src/api-data/rundown/rundown.controller.ts +++ b/apps/server/src/api-data/rundown/rundown.controller.ts @@ -12,6 +12,7 @@ import { deleteEvent, editEvent, reorderEvent, + setFrozenState, swapEvents, } from '../../services/rundown-service/RundownService.js'; import { @@ -114,6 +115,17 @@ export async function rundownBatchPut(req: Request, res: Response) { + try { + const { frozen } = req.body; + setFrozenState(frozen); + res.status(200).send({ message: 'Rundown frozen state updated.' }); + } catch (error) { + const message = getErrorMessage(error); + res.status(400).send({ message }); + } +} + export async function rundownReorder(req: Request, res: Response) { if (failEmptyObjects(req.body, res)) { return; diff --git a/apps/server/src/api-data/rundown/rundown.middleware.ts b/apps/server/src/api-data/rundown/rundown.middleware.ts new file mode 100644 index 000000000..41f2d852e --- /dev/null +++ b/apps/server/src/api-data/rundown/rundown.middleware.ts @@ -0,0 +1,9 @@ +import { eventStore } from '../../stores/EventStore.js'; + +export const preventIfFrozen = function (req, res, next) { + if (eventStore.get('frozen')) { + res.status(403).send({ message: 'Rundown is frozen' }); + } else { + next(); + } +}; diff --git a/apps/server/src/api-data/rundown/rundown.router.ts b/apps/server/src/api-data/rundown/rundown.router.ts index e8f1f5970..5142b0c13 100644 --- a/apps/server/src/api-data/rundown/rundown.router.ts +++ b/apps/server/src/api-data/rundown/rundown.router.ts @@ -5,6 +5,7 @@ import { rundownApplyDelay, rundownBatchPut, rundownDelete, + rundownFrozenPost, rundownGetById, rundownGetNormalised, rundownGetPaginated, @@ -17,12 +18,14 @@ import { paramsMustHaveEventId, rundownArrayOfIds, rundownBatchPutValidator, + rundownFrozenPostValidator, rundownGetPaginatedQueryParams, rundownPostValidator, rundownPutValidator, rundownReorderValidator, rundownSwapValidator, } from './rundown.validation.js'; +import { preventIfFrozen } from './rundown.middleware.js'; export const router = express.Router(); @@ -31,13 +34,14 @@ router.get('/normalised', rundownGetNormalised); router.get('/:eventId', paramsMustHaveEventId, rundownGetById); // not used in Ontime frontend router.post('/', rundownPostValidator, rundownPost); +router.post('/frozen', rundownFrozenPostValidator, rundownFrozenPost); router.put('/', rundownPutValidator, rundownPut); router.put('/batch', rundownBatchPutValidator, rundownBatchPut); -router.patch('/reorder/', rundownReorderValidator, rundownReorder); -router.patch('/swap', rundownSwapValidator, rundownSwap); +router.patch('/reorder/', rundownReorderValidator, preventIfFrozen, rundownReorder); +router.patch('/swap', rundownSwapValidator, preventIfFrozen, rundownSwap); router.patch('/applydelay/:eventId', paramsMustHaveEventId, rundownApplyDelay); -router.delete('/', rundownArrayOfIds, deletesEventById); -router.delete('/all', rundownDelete); +router.delete('/', rundownArrayOfIds, preventIfFrozen, deletesEventById); +router.delete('/all', preventIfFrozen, rundownDelete); diff --git a/apps/server/src/api-data/rundown/rundown.validation.ts b/apps/server/src/api-data/rundown/rundown.validation.ts index 2de741ff9..117fb6fdc 100644 --- a/apps/server/src/api-data/rundown/rundown.validation.ts +++ b/apps/server/src/api-data/rundown/rundown.validation.ts @@ -21,6 +21,15 @@ export const rundownPutValidator = [ }, ]; +export const rundownFrozenPostValidator = [ + body('frozen').isBoolean().exists(), + (req: Request, res: Response, next: NextFunction) => { + const errors = validationResult(req); + if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() }); + next(); + }, +] + export const rundownBatchPutValidator = [ body('data').isObject().exists(), body('ids').isArray().exists(), diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 2ab452647..bf0f383dd 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -188,6 +188,7 @@ export const startServer = async ( playback: SimplePlayback.Stop, direction: SimpleDirection.CountDown, }, + frozen: false, }); // initialise logging service, escalateErrorFn is only exists in electron diff --git a/apps/server/src/services/rundown-service/RundownService.ts b/apps/server/src/services/rundown-service/RundownService.ts index e85018612..316720632 100644 --- a/apps/server/src/services/rundown-service/RundownService.ts +++ b/apps/server/src/services/rundown-service/RundownService.ts @@ -21,6 +21,7 @@ import { runtimeService } from '../runtime-service/RuntimeService.js'; import * as cache from './rundownCache.js'; import { getPlayableEvents } from './rundownUtils.js'; +import { eventStore } from '../../stores/EventStore.js'; type PatchWithId = (Partial | Partial | Partial) & { id: string }; @@ -262,3 +263,7 @@ export async function initRundown(rundown: Readonly, customFields // notify timer of change notifyChanges({ timer: true }); } + +export async function setFrozenState(state: boolean) { + eventStore.set('frozen', state); +} diff --git a/packages/types/src/definitions/runtime/RuntimeStore.type.ts b/packages/types/src/definitions/runtime/RuntimeStore.type.ts index 0829dbf38..123d180f1 100644 --- a/packages/types/src/definitions/runtime/RuntimeStore.type.ts +++ b/packages/types/src/definitions/runtime/RuntimeStore.type.ts @@ -22,4 +22,7 @@ export type RuntimeStore = { // extra timers auxtimer1: SimpleTimerState; + + // flags + frozen: boolean; };