refactor(rundown): new entries are appended to rundown

This commit is contained in:
Carlos Valente
2026-07-18 23:44:49 +02:00
committed by Carlos Valente
parent 8363f06a5c
commit a8c611911d
16 changed files with 501 additions and 195 deletions
@@ -1,4 +1,5 @@
import { body, param } from 'express-validator';
import type { EntryId, InsertOptions, OntimeGroup, Rundown } from 'ontime-types';
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
@@ -37,15 +38,27 @@ export const rundownImportValidator = [
export const entryPostValidator = [
body('type').isString().isIn(['event', 'delay', 'group', 'milestone']),
body('after').optional().isString(),
body('before').optional().isString(),
body('after')
.optional()
.custom((value) => value === true || typeof value === 'string')
.withMessage('Allowed values for after are an ID or true.'),
body('before')
.optional()
.custom((value) => value === true || typeof value === 'string')
.withMessage('Allowed values for before are an ID or true.'),
requestValidationFunction,
];
export const clonePostValidator = [
body('after').optional().isString(),
body('before').optional().isString(),
body('after')
.optional()
.custom((value) => value === true || typeof value === 'string')
.withMessage('Allowed values for after are an ID or true.'),
body('before')
.optional()
.custom((value) => value === true || typeof value === 'string')
.withMessage('Allowed values for before are an ID or true.'),
requestValidationFunction,
];
@@ -93,4 +106,44 @@ export const entryRenumberValidator = [
requestValidationFunction,
];
/**
* Ensures callers use a single insertion anchor so placement is unambiguous.
*/
export function assertSingleInsertAnchor(options: InsertOptions) {
if (options.after !== undefined && options.before !== undefined) {
throw new Error('Use only one insertion anchor: after or before');
}
}
/**
* Ensures an ID anchor exists and belongs to the order it is intended to position within.
*/
export function assertInsertAnchorInOrder(rundown: Rundown, parent: OntimeGroup | null, options: InsertOptions) {
const anchor = getStringInsertAnchor(options);
if (anchor === undefined) return;
if (!Object.hasOwn(rundown.entries, anchor)) {
throw new Error(`Insertion anchor with ID ${anchor} does not exist`);
}
const insertionList = parent ? parent.entries : rundown.order;
if (!insertionList.includes(anchor)) {
throw new Error(`Insertion anchor with ID ${anchor} is not in the target order`);
}
}
/** Ensures an ID anchor refers to an entry in the rundown. */
export function assertInsertAnchorExists(rundown: Rundown, options: InsertOptions) {
const anchor = getStringInsertAnchor(options);
if (anchor !== undefined && !Object.hasOwn(rundown.entries, anchor)) {
throw new Error(`Insertion anchor with ID ${anchor} does not exist`);
}
}
function getStringInsertAnchor(options: InsertOptions): EntryId | undefined {
if (typeof options.after === 'string') return options.after;
if (typeof options.before === 'string') return options.before;
return undefined;
}
// #endregion operations on rundown entries =======================