mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 13:23:35 +00:00
feat: ontime actions in automation
This commit is contained in:
committed by
Carlos Valente
parent
636f78e21e
commit
5fb30c75c4
@@ -3,16 +3,19 @@ import {
|
||||
AutomationFilter,
|
||||
AutomationOutput,
|
||||
HTTPOutput,
|
||||
OntimeAction,
|
||||
OSCOutput,
|
||||
SecondarySource,
|
||||
timerLifecycleValues,
|
||||
} from 'ontime-types';
|
||||
import { parseUserTime } from 'ontime-utils';
|
||||
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { body, oneOf, param, validationResult } from 'express-validator';
|
||||
|
||||
import * as assert from '../../utils/assert.js';
|
||||
|
||||
import { isFilterOperator, isFilterRule } from './automation.utils.js';
|
||||
import { isFilterOperator, isFilterRule, isOntimeActionAction } from './automation.utils.js';
|
||||
|
||||
export const paramContainsId = [
|
||||
param('id').exists(),
|
||||
@@ -131,43 +134,13 @@ function validateFilters(filters: Array<unknown>): filters is AutomationFilter[]
|
||||
|
||||
function validateOutput(output: Array<unknown>): output is AutomationOutput[] {
|
||||
output.forEach((payload) => {
|
||||
assert.isObject(payload);
|
||||
assert.hasKeys(payload, ['type']);
|
||||
const { type } = payload;
|
||||
assert.isString(type);
|
||||
|
||||
if (type === 'osc') {
|
||||
validateOSCOutput(payload);
|
||||
} else if (type === 'http') {
|
||||
validateHttpOutput(payload);
|
||||
} else {
|
||||
throw new Error('Invalid automation');
|
||||
}
|
||||
parseOutput(payload);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateOSCOutput(payload: object): payload is OSCOutput {
|
||||
assert.hasKeys(payload, ['targetIP', 'targetPort', 'address', 'args']);
|
||||
const { targetIP, targetPort, address, args } = payload;
|
||||
assert.isString(targetIP);
|
||||
assert.isNumber(targetPort);
|
||||
assert.isString(address);
|
||||
if (typeof args !== 'string' && typeof args !== 'number') {
|
||||
throw new Error('Invalid automation');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateHttpOutput(payload: object): payload is HTTPOutput {
|
||||
assert.hasKeys(payload, ['url']);
|
||||
const { url } = payload;
|
||||
assert.isString(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
export const validateTestPayload = [
|
||||
body('type').exists().isIn(['osc', 'http']),
|
||||
body('type').exists().isIn(['osc', 'http', 'ontime']),
|
||||
|
||||
// validation for OSC message
|
||||
oneOf([
|
||||
@@ -182,9 +155,143 @@ export const validateTestPayload = [
|
||||
// validation for HTTP message
|
||||
body('url').if(body('type').equals('http')).isURL({ require_tld: false }).trim(),
|
||||
|
||||
// validation for Ontime actions
|
||||
body('action').if(body('type').equals('ontime')).isString().trim(),
|
||||
body('text').if(body('type').equals('ontime')).optional().isString().trim(),
|
||||
body('time').if(body('type').equals('ontime')).optional().isString().trim(),
|
||||
body('visible').if(body('type').equals('ontime')).optional().isString().trim(),
|
||||
body('secondarySource').if(body('type').equals('ontime')).optional().isString().trim(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Sanitises an output object
|
||||
* @Throws if the output is invalid
|
||||
*/
|
||||
export function parseOutput(maybeOutput: unknown): AutomationOutput {
|
||||
assert.isObject(maybeOutput);
|
||||
assert.hasKeys(maybeOutput, ['type']);
|
||||
|
||||
const { type } = maybeOutput;
|
||||
assert.isString(type);
|
||||
|
||||
if (type === 'osc') {
|
||||
return parseOSCOutput(maybeOutput);
|
||||
} else if (type === 'http') {
|
||||
return parseHTTPOutput(maybeOutput);
|
||||
} else if (type === 'ontime') {
|
||||
return parseOntimeAction(maybeOutput);
|
||||
} else {
|
||||
throw new Error('Invalid automation output');
|
||||
}
|
||||
}
|
||||
|
||||
function parseOSCOutput(maybeOSCOutput: object): OSCOutput {
|
||||
assert.hasKeys(maybeOSCOutput, ['targetIP', 'targetPort', 'address', 'args']);
|
||||
assert.isString(maybeOSCOutput.targetIP);
|
||||
assert.isNumber(maybeOSCOutput.targetPort);
|
||||
assert.isString(maybeOSCOutput.address);
|
||||
assert.isString(maybeOSCOutput.args);
|
||||
|
||||
return {
|
||||
type: 'osc',
|
||||
targetIP: maybeOSCOutput.targetIP,
|
||||
targetPort: maybeOSCOutput.targetPort,
|
||||
address: maybeOSCOutput.address,
|
||||
args: maybeOSCOutput.args,
|
||||
};
|
||||
}
|
||||
|
||||
function parseHTTPOutput(maybeHTTPOutput: object): HTTPOutput {
|
||||
assert.hasKeys(maybeHTTPOutput, ['url']);
|
||||
assert.isString(maybeHTTPOutput.url);
|
||||
|
||||
return {
|
||||
type: 'http',
|
||||
url: maybeHTTPOutput.url,
|
||||
};
|
||||
}
|
||||
|
||||
function parseOntimeAction(maybeOntimeAction: object): OntimeAction {
|
||||
assert.hasKeys(maybeOntimeAction, ['action']);
|
||||
assert.isString(maybeOntimeAction.action);
|
||||
|
||||
if (!isOntimeActionAction(maybeOntimeAction.action)) {
|
||||
throw new Error('Invalid Ontime action');
|
||||
}
|
||||
|
||||
// we know we have a valid action, deal with special cases
|
||||
|
||||
if (maybeOntimeAction.action === 'aux-set') {
|
||||
assert.hasKeys(maybeOntimeAction, ['time']);
|
||||
assert.isString(maybeOntimeAction.time);
|
||||
|
||||
return {
|
||||
type: 'ontime',
|
||||
action: 'aux-set',
|
||||
time: parseUserTime(maybeOntimeAction.time),
|
||||
};
|
||||
}
|
||||
|
||||
if (maybeOntimeAction.action === 'message-set') {
|
||||
assert.hasKeys(maybeOntimeAction, ['text', 'visible']);
|
||||
assert.isString(maybeOntimeAction.text);
|
||||
assert.isString(maybeOntimeAction.visible);
|
||||
|
||||
return {
|
||||
type: 'ontime',
|
||||
action: 'message-set',
|
||||
text: indeterminateText(maybeOntimeAction.text),
|
||||
visible: indeterminateBooleanString(maybeOntimeAction.visible),
|
||||
};
|
||||
}
|
||||
|
||||
if (maybeOntimeAction.action === 'message-secondary') {
|
||||
assert.hasKeys(maybeOntimeAction, ['secondarySource']);
|
||||
assert.isString(maybeOntimeAction.secondarySource);
|
||||
|
||||
return {
|
||||
type: 'ontime',
|
||||
action: 'message-secondary',
|
||||
secondarySource: chooseSecondarySource(maybeOntimeAction.secondarySource),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'ontime',
|
||||
action: maybeOntimeAction.action,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to parse a text which may be indeterminate
|
||||
* "some text" -> string
|
||||
* "" -> undefined
|
||||
*/
|
||||
function indeterminateText(value: string): string | undefined {
|
||||
return value === '' ? undefined : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to parse boolean values in transit
|
||||
* "true" -> true
|
||||
* "false" -> false
|
||||
* "" | "null" -> undefined
|
||||
*/
|
||||
function indeterminateBooleanString(value: string): boolean | undefined {
|
||||
return value === '' ? undefined : value === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to validate the secondary source
|
||||
*/
|
||||
function chooseSecondarySource(value: string): SecondarySource {
|
||||
if (value === 'aux') return 'aux';
|
||||
if (value === 'external') return 'external';
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user