feat: allow user to duplicate a rundown

This commit is contained in:
Carlos Valente
2025-10-26 13:35:10 +01:00
committed by Carlos Valente
parent 17f80baf48
commit 32bf0f53f6
6 changed files with 97 additions and 5 deletions
@@ -3,11 +3,14 @@ import { MILLIS_PER_HOUR } from 'ontime-utils';
import { assertType } from 'vitest';
import { demoDb } from '../../../models/demoProject.js';
import {
calculateDayOffset,
createEvent,
deleteById,
doesInvalidateMetadata,
duplicateRundown,
getInsertAfterId,
hasChanges,
} from '../rundown.utils.js';
@@ -260,3 +263,22 @@ describe('getInsertAfterId()', () => {
expect(getInsertAfterId(rundown, rundown.entries.group as OntimeGroup, undefined, '32')).toBe('31');
});
});
describe('duplicateRundown', () => {
it("duplicates a given rundown", () => {
const demoRundown = demoDb.rundowns['default'];
const title = 'Duplicated Rundown';
const duplicatedRundown = duplicateRundown(demoRundown, title);
expect(duplicatedRundown).toMatchObject({
title: title,
entries: expect.any(Object),
order: expect.any(Array),
flatOrder: expect.any(Array),
})
expect(demoRundown.id).not.toEqual(duplicatedRundown.id);
expect(duplicatedRundown.order.length).toEqual(demoRundown.order.length);
expect(duplicatedRundown.flatOrder.length).toEqual(demoRundown.flatOrder.length);
expect(Object.keys(duplicatedRundown.entries).length).toEqual(Object.keys(demoRundown.entries).length);
})
})
@@ -32,7 +32,7 @@ import {
import { paramsWithId } from '../validation-utils/validationFunction.js';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import { defaultRundown } from '../../models/dataModel.js';
import { normalisedToRundownArray } from './rundown.utils.js';
import { duplicateRundown, normalisedToRundownArray } from './rundown.utils.js';
export const router = express.Router();
@@ -95,6 +95,24 @@ router.post('/', rundownPostValidator, async (req: Request, res: Response<Projec
}
});
/**
* Duplicates an existing rundown
*/
router.post('/:id/duplicate', paramsWithId, async (req: Request, res: Response<ProjectRundownsList | ErrorResponse>) => {
try {
const dataProvider = getDataProvider();
const rundown = dataProvider.getRundown(req.params.id);
const duplicatedRundown: Rundown = duplicateRundown(rundown, `Copy of ${rundown.title}`);
await dataProvider.setRundown(duplicatedRundown.id, duplicatedRundown);
const projectRundowns = getDataProvider().getProjectRundowns();
res.status(201).json({ loaded: getCurrentRundown().id, rundowns: normalisedToRundownArray(projectRundowns) });
} catch (error) {
const message = getErrorMessage(error);
res.status(400).send({ message });
}
});
/**
* Deletes a rundown if not loaded
*/
@@ -34,6 +34,7 @@ import {
milestone as milestoneDef,
} from '../../models/eventsDefinition.js';
import { makeString } from '../../utils/parserUtils.js';
import { RundownMetadata } from './rundown.types.js';
type CompleteEntry<T> =
@@ -517,3 +518,18 @@ export function normalisedToRundownArray(rundowns: ProjectRundowns): ProjectRund
return { id, numEntries: flatOrder.length, title, revision };
});
}
/**
* Duplicates an existing rundown ensuring all IDs are unique
*/
export function duplicateRundown(rundown: Rundown, newTitle: string): Rundown {
const newRundownId = generateId();
const newRundown = structuredClone(rundown);
newRundown.id = newRundownId;
newRundown.title = newTitle;
newRundown.revision = 0;
return newRundown;
}