mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 14:39:06 +00:00
Feat/change event api (#553)
* feat: change attribute for given event --------- Co-authored-by: Ary <arylmoraesn@gmail.com> Co-authored-by: arc-alex <ac@omnivox.dk>
This commit is contained in:
committed by
GitHub
parent
78d5d442cf
commit
a01046b479
@@ -15,13 +15,13 @@ export class OscServer implements IAdapter {
|
|||||||
this.osc.on('error', console.error);
|
this.osc.on('error', console.error);
|
||||||
|
|
||||||
this.osc.on('message', (msg) => {
|
this.osc.on('message', (msg) => {
|
||||||
// message should look like /ontime/{path} {args} where
|
// message should look like /ontime/{path}/{params?} {args} where
|
||||||
// ontime: fixed message for app
|
// ontime: fixed message for app
|
||||||
// path: command to be called
|
// path: command to be called
|
||||||
// args: extra data, only used on some API entries (delay, goto)
|
// args: extra data, only used on some API entries (delay, goto)
|
||||||
|
|
||||||
// split message
|
// split message
|
||||||
const [, address, path] = msg[0].split('/');
|
const [, address, path, ...params] = msg[0].split('/');
|
||||||
const args = msg[1];
|
const args = msg[1];
|
||||||
|
|
||||||
// get first part before (ontime)
|
// get first part before (ontime)
|
||||||
@@ -37,7 +37,14 @@ export class OscServer implements IAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const reply = dispatchFromAdapter(path, args, 'osc');
|
const reply = dispatchFromAdapter(
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
payload: args,
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
'osc',
|
||||||
|
);
|
||||||
if (reply) {
|
if (reply) {
|
||||||
const { topic, payload } = reply;
|
const { topic, payload } = reply;
|
||||||
this.osc.emit(topic, payload);
|
this.osc.emit(topic, payload);
|
||||||
|
|||||||
@@ -123,7 +123,13 @@ export class SocketServer implements IAdapter {
|
|||||||
|
|
||||||
// Protocol specific stuff handled above
|
// Protocol specific stuff handled above
|
||||||
try {
|
try {
|
||||||
const reply = dispatchFromAdapter(type, payload, 'ws');
|
const reply = dispatchFromAdapter(
|
||||||
|
type,
|
||||||
|
{
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
'ws',
|
||||||
|
);
|
||||||
if (reply) {
|
if (reply) {
|
||||||
const { topic, payload } = reply;
|
const { topic, payload } = reply;
|
||||||
ws.send(topic, payload);
|
ws.send(topic, payload);
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export const startIntegrations = async (config?: { osc: OSCSettings }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { success, message } = oscIntegration.init(osc);
|
const { success, message } = oscIntegration.init(osc);
|
||||||
logger.info(LogOrigin.Rx, message);
|
logger.info(LogOrigin.Tx, message);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
integrationService.register(oscIntegration);
|
integrationService.register(oscIntegration);
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { LogOrigin, OntimeEvent } from 'ontime-types';
|
||||||
|
import { EventLoader } from '../classes/event-loader/EventLoader.js';
|
||||||
|
import { editEvent } from '../services/rundown-service/RundownService.js';
|
||||||
|
import { coerceString, coerceNumber, coerceBoolean } from '../utils/coerceType.js';
|
||||||
|
import { logger } from '../classes/Logger.js';
|
||||||
|
|
||||||
|
const whitelistedPayload = {
|
||||||
|
title: coerceString,
|
||||||
|
subtitle: coerceString,
|
||||||
|
presenter: coerceString,
|
||||||
|
note: coerceString,
|
||||||
|
cue: coerceString,
|
||||||
|
|
||||||
|
duration: coerceNumber,
|
||||||
|
|
||||||
|
isPublic: coerceBoolean,
|
||||||
|
skip: coerceBoolean,
|
||||||
|
|
||||||
|
colour: coerceString,
|
||||||
|
user0: coerceString,
|
||||||
|
user1: coerceString,
|
||||||
|
user2: coerceString,
|
||||||
|
user3: coerceString,
|
||||||
|
user4: coerceString,
|
||||||
|
user5: coerceString,
|
||||||
|
user6: coerceString,
|
||||||
|
user7: coerceString,
|
||||||
|
user8: coerceString,
|
||||||
|
user9: coerceString,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parse(field: string, value: unknown) {
|
||||||
|
if (!whitelistedPayload.hasOwnProperty(field)) {
|
||||||
|
throw new Error(`Field ${field} not permitted`);
|
||||||
|
}
|
||||||
|
const parserFn = whitelistedPayload[field];
|
||||||
|
return parserFn(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a property of the event with the given id
|
||||||
|
* @param {string} eventId
|
||||||
|
* @param {keyof OntimeEvent} propertyName
|
||||||
|
* @param {OntimeEvent[typeof propertyName]} newValue
|
||||||
|
*/
|
||||||
|
export function updateEvent(
|
||||||
|
eventId: string,
|
||||||
|
propertyName: keyof OntimeEvent,
|
||||||
|
newValue: OntimeEvent[typeof propertyName],
|
||||||
|
) {
|
||||||
|
const event = EventLoader.getEventWithId(eventId);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
let propertiesToUpdate = { [propertyName]: newValue };
|
||||||
|
|
||||||
|
// Handles the special case for duration
|
||||||
|
// needs to be converted to milliseconds
|
||||||
|
if (propertyName === 'duration') {
|
||||||
|
propertiesToUpdate.duration = (newValue as number) * 1000;
|
||||||
|
propertiesToUpdate.timeEnd = event.timeStart + propertiesToUpdate.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
editEvent({ id: eventId, ...propertiesToUpdate }).then(() => {
|
||||||
|
logger.info(LogOrigin.Playback, `Updated ${propertyName} of event with ID ${eventId} to ${newValue}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(`Event with ID ${eventId} not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,26 @@
|
|||||||
|
import { LogOrigin, OntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { messageService } from '../services/message-service/MessageService.js';
|
import { messageService } from '../services/message-service/MessageService.js';
|
||||||
import { PlaybackService } from '../services/PlaybackService.js';
|
import { PlaybackService } from '../services/PlaybackService.js';
|
||||||
import { eventStore } from '../stores/EventStore.js';
|
import { eventStore } from '../stores/EventStore.js';
|
||||||
|
import { parse, updateEvent } from './integrationController.config.js';
|
||||||
|
import { isKeyOfType } from 'ontime-types/src/utils/guards.js';
|
||||||
|
import { event } from '../models/eventsDefinition.js';
|
||||||
|
|
||||||
export function dispatchFromAdapter(type: string, payload: unknown, source?: 'osc' | 'ws') {
|
export function dispatchFromAdapter(
|
||||||
switch (type.toLowerCase()) {
|
type: string,
|
||||||
|
args: {
|
||||||
|
payload: unknown;
|
||||||
|
params?: Array<string>;
|
||||||
|
},
|
||||||
|
source?: 'osc' | 'ws',
|
||||||
|
) {
|
||||||
|
const payload = args.payload;
|
||||||
|
const typeComponents = type.toLowerCase().split('/');
|
||||||
|
const mainType = typeComponents[0];
|
||||||
|
const params = args.params || [];
|
||||||
|
|
||||||
|
switch (mainType) {
|
||||||
case 'test-ontime': {
|
case 'test-ontime': {
|
||||||
return { topic: 'hello' };
|
return { topic: 'hello' };
|
||||||
}
|
}
|
||||||
@@ -222,6 +239,23 @@ export function dispatchFromAdapter(type: string, payload: unknown, source?: 'os
|
|||||||
return { topic: 'timer', payload: timer };
|
return { topic: 'timer', payload: timer };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ontime/change/{eventID}/{propertyName}
|
||||||
|
case 'change': {
|
||||||
|
if (params.length < 2) {
|
||||||
|
throw new Error(`To few parameters`);
|
||||||
|
}
|
||||||
|
if (payload === undefined) {
|
||||||
|
throw new Error(`Undefined payload`);
|
||||||
|
}
|
||||||
|
const eventID = params[0];
|
||||||
|
const propertyName = params[1] as keyof OntimeEvent;
|
||||||
|
if (!isKeyOfType(propertyName, event)) {
|
||||||
|
throw new Error(`Cannot update unknown event property ${propertyName}`);
|
||||||
|
}
|
||||||
|
const parsedPayload = parse(propertyName, payload);
|
||||||
|
return updateEvent(eventID, propertyName, parsedPayload);
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Unhandled message ${type}`);
|
throw new Error(`Unhandled message ${type}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export const postOscSubscriptions = async (req, res) => {
|
|||||||
|
|
||||||
// TODO: this update could be more granular, checking that relevant data was changed
|
// TODO: this update could be more granular, checking that relevant data was changed
|
||||||
const { message } = oscIntegration.init(oscSettings);
|
const { message } = oscIntegration.init(oscSettings);
|
||||||
logger.info(LogOrigin.Rx, message);
|
logger.info(LogOrigin.Tx, message);
|
||||||
|
|
||||||
res.send(oscSettings).status(200);
|
res.send(oscSettings).status(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -316,7 +316,7 @@ export const postOSC = async (req, res) => {
|
|||||||
|
|
||||||
// TODO: this update could be more granular, checking that relevant data was changed
|
// TODO: this update could be more granular, checking that relevant data was changed
|
||||||
const { message } = oscIntegration.init(oscSettings);
|
const { message } = oscIntegration.init(oscSettings);
|
||||||
logger.info(LogOrigin.Rx, message);
|
logger.info(LogOrigin.Tx, message);
|
||||||
|
|
||||||
res.send(oscSettings).status(200);
|
res.send(oscSettings).status(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* @description Converts a value to a number if possible, throws otherwise
|
||||||
|
* @param {unknown} value - Value to be converted to a string.
|
||||||
|
* @returns {string} - The converted value as a string.
|
||||||
|
* @throws {Error} Throws an error if the value is null or undefined.
|
||||||
|
*/
|
||||||
|
export function coerceString(value: unknown): string {
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error('Invalid value received');
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Converts a value to a number if possible, throws otherwise
|
||||||
|
* @param {unknown} value - Value to be converted to a boolean.
|
||||||
|
* @returns {boolean} - The converted value as a boolean.
|
||||||
|
* @throws {Error} Throws an error if the value is null or undefined.
|
||||||
|
*/
|
||||||
|
export function coerceBoolean(value: unknown): boolean {
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error('Invalid value received');
|
||||||
|
}
|
||||||
|
return Boolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Converts a value to a number if possible, throws otherwise
|
||||||
|
* @param {unknown} value - Value to be converted to a number.
|
||||||
|
* @returns {number} - The converted value as a number.
|
||||||
|
* @throws {Error} Throws an error if the value is null, undefined or not a valid number.
|
||||||
|
*/
|
||||||
|
export function coerceNumber(value: unknown): number {
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error('Invalid value received');
|
||||||
|
}
|
||||||
|
const parsedValue = Number(value);
|
||||||
|
if (isNaN(parsedValue)) {
|
||||||
|
throw new Error('Invalid value received');
|
||||||
|
}
|
||||||
|
return parsedValue;
|
||||||
|
}
|
||||||
@@ -14,3 +14,9 @@ export function isOntimeDelay(event: MaybeEvent): event is OntimeDelay {
|
|||||||
export function isOntimeBlock(event: MaybeEvent): event is OntimeBlock {
|
export function isOntimeBlock(event: MaybeEvent): event is OntimeBlock {
|
||||||
return event?.type === SupportedEvent.Block;
|
return event?.type === SupportedEvent.Block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AnyKeys<T> = keyof T;
|
||||||
|
|
||||||
|
export function isKeyOfType<T extends object>(key: PropertyKey, obj: T): key is AnyKeys<T> {
|
||||||
|
return key in obj;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user