expose mcp server in ontime

This commit is contained in:
Carlos Valente
2026-07-01 12:48:40 +02:00
committed by GitHub
parent ce8a4d230b
commit 48f9fb2609
19 changed files with 2291 additions and 77 deletions
@@ -16,7 +16,7 @@ import {
deleteAllEntries,
deleteEntries,
deleteRundown,
duplicateRundown,
duplicateExistingRundown,
editEntry,
groupEntries,
loadRundown,
@@ -107,7 +107,7 @@ router.post(
paramsWithId,
async (req: Request, res: Response<ProjectRundownsList | ErrorResponse>) => {
try {
const projectRundowns = await duplicateRundown(req.params.id);
const projectRundowns = await duplicateExistingRundown(req.params.id);
res.status(201).json({ loaded: getCurrentRundown().id, rundowns: normalisedToRundownArray(projectRundowns) });
} catch (error) {
const message = getErrorMessage(error);
@@ -643,7 +643,7 @@ export function isCurrentRundown(id: string) {
/**
* @throws if the provided id does not exist
*/
export function loadRundown(id: string) {
export async function loadRundown(id: string) {
const dataProvider = getDataProvider();
if (isCurrentRundown(id)) {
return dataProvider.getProjectRundowns();
@@ -651,7 +651,7 @@ export function loadRundown(id: string) {
const rundown = dataProvider.getRundown(id);
const customField = dataProvider.getCustomFields();
initRundown(rundown, customField);
await initRundown(rundown, customField);
return dataProvider.getProjectRundowns();
}
@@ -659,7 +659,11 @@ export function loadRundown(id: string) {
* Sets a new rundown in the cache
* and marks it as the currently loaded one
*/
export function initRundown(rundown: Readonly<Rundown>, customFields: Readonly<CustomFields>, reload: boolean = false) {
export async function initRundown(
rundown: Readonly<Rundown>,
customFields: Readonly<CustomFields>,
reload: boolean = false,
) {
runtimeService.stop();
const { rundownMetadata, revision } = rundownCache.init(rundown, customFields);
logger.info(LogOrigin.Server, `Switch to rundown: ${rundown.id}`);
@@ -690,36 +694,14 @@ export async function createNewRundown(title: string) {
}
/**
* duplicate a rundown
* @throws
*/
export async function duplicateRundown(id: string) {
const dataProvider = getDataProvider();
const rundown = dataProvider.getRundown(id);
const newRundownId = generateId();
const newRundown: Rundown = structuredClone(rundown);
newRundown.id = newRundownId;
newRundown.title = `Copy of ${rundown.title}`;
newRundown.revision = 0;
const newProjectRundowns = await dataProvider.setRundown(newRundownId, newRundown);
setImmediate(() => {
sendRefetch(RefetchKey.ProjectRundowns);
});
return newProjectRundowns;
}
/**
* rename a rundown
* @throws
* Renames an existing rundown
* @throws if the provided id does not exist
*/
export async function renameRundown(id: string, title: string) {
const dataProvider = getDataProvider();
const rundown = dataProvider.getRundown(id);
const newProjectRundowns = await dataProvider.setRundown(rundown.id, { ...rundown, title });
await dataProvider.setRundown(id, { ...rundown, title });
/**
* If we are modifying the loaded rundown we re-init it
@@ -728,33 +710,58 @@ export async function renameRundown(id: string, title: string) {
if (isCurrentRundown(id)) {
const rundown = dataProvider.getRundown(id);
const customField = dataProvider.getCustomFields();
initRundown(rundown, customField);
// init rundown does its own refetch
await initRundown(rundown, customField);
} else {
setImmediate(() => {
sendRefetch(RefetchKey.ProjectRundowns);
});
}
return newProjectRundowns;
return dataProvider.getProjectRundowns();
}
/**
* delete a rundown
* @throws
* Duplicates an existing rundown without making it the loaded one
* @throws if the provided id does not exist
*/
export async function deleteRundown(id: string) {
if (isCurrentRundown(id)) throw new Error('Cannot delete loaded rundown');
export async function duplicateExistingRundown(id: string) {
const dataProvider = getDataProvider();
const projectRundowns = dataProvider.getProjectRundowns();
const rundown = dataProvider.getRundown(id);
// might never hit this as it is likely covered by the case of trying to delete the loaded rundown
if (Object.keys(projectRundowns).length <= 1) throw new Error('Cannot delete the last rundown');
const newProjectRundowns = await dataProvider.deleteRundown(id);
const duplicatedRundown: Rundown = structuredClone(rundown);
duplicatedRundown.id = generateId();
duplicatedRundown.title = `Copy of ${rundown.title}`;
duplicatedRundown.revision = 0;
await dataProvider.setRundown(duplicatedRundown.id, duplicatedRundown);
setImmediate(() => {
sendRefetch(RefetchKey.ProjectRundowns);
});
return newProjectRundowns;
return dataProvider.getProjectRundowns();
}
/**
* Deletes a rundown
* @throws if attempting to delete the loaded rundown or the last rundown in the project
*/
export async function deleteRundown(id: string) {
if (isCurrentRundown(id)) {
throw new Error('Cannot delete loaded rundown');
}
const dataProvider = getDataProvider();
if (Object.keys(dataProvider.getProjectRundowns()).length <= 1) {
throw new Error('Cannot delete the last rundown');
}
const projectRundowns = await dataProvider.deleteRundown(id);
setImmediate(() => {
sendRefetch(RefetchKey.ProjectRundowns);
});
return projectRundowns;
}
@@ -34,43 +34,34 @@ import {
import { RundownMetadata } from './rundown.types.js';
type CompleteEntry<T> =
T extends Partial<OntimeEvent>
? OntimeEvent
: T extends Partial<OntimeDelay>
? OntimeDelay
: T extends Partial<OntimeGroup>
? OntimeGroup
: T extends Partial<OntimeMilestone>
? OntimeMilestone
: never;
/**
* Generates a fully formed RundownEntry of the patch type
*/
export function generateEvent<
T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeGroup> | Partial<OntimeMilestone>,
>(rundown: Rundown, eventData: T, afterId: EntryId | null, parent?: EntryId): CompleteEntry<T> {
export function generateEvent(
rundown: Rundown,
eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeGroup> | Partial<OntimeMilestone>,
afterId: EntryId | null,
parent?: EntryId,
): OntimeEntry {
if (isOntimeEvent(eventData)) {
return createEvent(
eventData,
getCueCandidate(rundown.entries, rundown.flatOrder, afterId, parent),
) as CompleteEntry<T>;
const event = createEvent(eventData, getCueCandidate(rundown.entries, rundown.flatOrder, afterId, parent));
if (!event) throw new Error('Invalid event type');
return event;
}
const id = eventData.id || getUniqueId(rundown);
if (isOntimeDelay(eventData)) {
return createDelay({ duration: eventData.duration ?? 0, id }) as CompleteEntry<T>;
return createDelay({ duration: eventData.duration ?? 0, id });
}
// TODO(v4): allow user to provide a larger patch of the group entry
if (isOntimeGroup(eventData)) {
return createGroup({ id, title: eventData.title ?? '' }) as CompleteEntry<T>;
return createGroup({ id, title: eventData.title ?? '' });
}
if (isOntimeMilestone(eventData)) {
return createMilestone({ ...eventData, id }) as CompleteEntry<T>;
return createMilestone({ ...eventData, id });
}
throw new Error('Invalid event type');