Typescript null (#869)

* refactor: improve null checking

* fix: return result of mutation
This commit is contained in:
Carlos Valente
2024-04-05 12:07:26 +02:00
committed by GitHub
parent 9f207dd39a
commit d432f1e3ff
12 changed files with 260 additions and 229 deletions
+7 -9
View File
@@ -116,16 +116,14 @@ export class SocketServer implements IAdapter {
// Protocol specific stuff handled above
try {
const reply = dispatchFromAdapter(
type,
{
payload,
},
'ws',
);
const reply = dispatchFromAdapter(type, { payload }, 'ws');
if (reply) {
const { payload } = reply;
ws.send(type, payload);
ws.send(
JSON.stringify({
type: 'ontime-change',
payload: reply.payload,
}),
);
}
} catch (error) {
logger.error(LogOrigin.Rx, `WS IN: ${error}`);
@@ -3,10 +3,13 @@
* Google Sheets
*/
import { CustomFields, OntimeRundown } from 'ontime-types';
import { ImportMap } from 'ontime-utils';
import { extname } from 'path';
import { existsSync } from 'fs';
import { ImportMap } from 'ontime-utils';
import xlsx from 'node-xlsx';
import { parseExcel } from '../../utils/parser.js';
import { parseCustomFields, parseRundown } from '../../utils/parserFunctions.js';
import { deleteFile } from '../../utils/parserUtils.js';
@@ -29,7 +32,7 @@ export function listWorksheets() {
return excelData.map((value) => value.name);
}
export function generateRundownPreview(options: ImportMap) {
export function generateRundownPreview(options: ImportMap): { rundown: OntimeRundown; customFields: CustomFields } {
const data = excelData.find(({ name }) => name.toLowerCase() === options.worksheet.toLowerCase())?.data;
if (!data) {
@@ -39,15 +42,14 @@ export function generateRundownPreview(options: ImportMap) {
const dataFromExcel = parseExcel(data, options);
// we run the parsed data through an extra step to ensure the objects shape
const result = { rundown: [], customFields: {} };
result.rundown = parseRundown(dataFromExcel);
if (result.rundown.length < 1) {
const rundown = parseRundown(dataFromExcel);
if (rundown.length === 0) {
throw new Error(`Could not find data to import in the worksheet: ${options.worksheet}`);
}
result.customFields = parseCustomFields(dataFromExcel);
const customFields = parseCustomFields(dataFromExcel);
//clear the data
// clear the data
excelData = [];
return result;
return { rundown, customFields };
}
@@ -64,6 +64,7 @@ export class SimpleTimer {
public update(timeNow: number): SimpleTimerState {
if (this.state.playback === SimplePlayback.Start) {
// we know startedAt is not null since we are in play mode
const elapsed = timeNow - this.startedAt;
if (this.state.direction === SimpleDirection.CountDown) {
this.state.current = this.state.duration - elapsed;
+1 -1
View File
@@ -64,7 +64,7 @@ export class RestoreService {
private readonly filePath: MaybeString;
private readonly file: JSONFile<RestorePoint | null>;
private failedCreateAttempts: number;
private savedState: RestorePoint;
private savedState: RestorePoint | null;
constructor(filePath: string) {
this.filePath = filePath;
@@ -75,7 +75,7 @@ export async function getProjectFiles(): Promise<ProjectFile[]> {
const allFiles = await getFilesFromFolder(resolveProjectsDirectory);
const filteredFiles = filterProjectFiles(allFiles);
const projectFiles = [];
const projectFiles: ProjectFile[] = [];
for (const file of filteredFiles) {
const filePath = join(resolveProjectsDirectory, file);
const stats = await stat(filePath);
@@ -22,20 +22,32 @@ import { runtimeService } from '../runtime-service/RuntimeService.js';
import * as cache from './rundownCache.js';
import { getPlayableEvents } from './rundownUtils.js';
function generateEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>) {
// we discard any UI provided events and add our own
type PatchWithId = (Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) & { id: string };
type CompleteEntry<T> = T extends Partial<OntimeEvent>
? OntimeEvent
: T extends Partial<OntimeDelay>
? OntimeDelay
: T extends Partial<OntimeBlock>
? OntimeBlock
: never;
function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>>(
eventData: T,
): CompleteEntry<T> {
// we discard any UI provided IDs and add our own
const id = cache.getUniqueId();
if (isOntimeEvent(eventData)) {
return createEvent(eventData, getCueCandidate(cache.getPersistedRundown(), eventData?.after)) as OntimeEvent;
return createEvent(eventData, getCueCandidate(cache.getPersistedRundown(), eventData?.after)) as CompleteEntry<T>;
}
if (isOntimeDelay(eventData)) {
return { ...delayDef, duration: eventData.duration ?? 0, id } as OntimeDelay;
return { ...delayDef, duration: eventData.duration ?? 0, id } as CompleteEntry<T>;
}
if (isOntimeBlock(eventData)) {
return { ...blockDef, title: eventData?.title ?? '', id } as OntimeBlock;
return { ...blockDef, title: eventData?.title ?? '', id } as CompleteEntry<T>;
}
throw new Error('Invalid event type');
@@ -46,9 +58,7 @@ function generateEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> |
* @param {object} eventData
* @return {OntimeRundownEntry}
*/
export async function addEvent(
eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>,
): Promise<OntimeRundownEntry> {
export async function addEvent(eventData: PatchWithId & { after?: string }): Promise<OntimeRundownEntry> {
// if the user didnt provide an index, we add the event to start
let atIndex = 0;
if (eventData?.after !== undefined) {
@@ -62,15 +72,16 @@ export async function addEvent(
// generate a fully formed event from the patch
const eventToAdd = generateEvent(eventData);
// modify rundown
const scopedMutation = cache.mutateCache(cache.add);
const { newEvent } = await scopedMutation({ atIndex, event: eventToAdd as OntimeRundownEntry });
const { newEvent } = await scopedMutation({ atIndex, event: eventToAdd });
// notify runtime that rundown has changed
updateRuntimeOnChange();
// notify timer and external services of change
notifyChanges({ timer: [newEvent.id], external: true });
notifyChanges({ timer: [eventData.id], external: true });
return newEvent;
}
@@ -81,7 +92,11 @@ export async function addEvent(
*/
export async function deleteEvent(eventId: string) {
const scopedMutation = cache.mutateCache(cache.remove);
await scopedMutation({ eventId });
const { didMutate } = await scopedMutation({ eventId });
if (didMutate === false) {
return;
}
// notify runtime that rundown has changed
updateRuntimeOnChange();
@@ -108,14 +123,18 @@ export async function deleteAllEvents() {
* Apply patch to an element in rundown
* @param patch
*/
export async function editEvent(patch: Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) {
export async function editEvent(patch: PatchWithId) {
if (isOntimeEvent(patch) && patch?.cue === '') {
throw new Error('Cue value invalid');
}
const scopedMutation = cache.mutateCache(cache.edit);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know patch has an id
const { newEvent } = await scopedMutation({ patch, eventId: patch.id! });
const { newEvent, didMutate } = await scopedMutation({ patch, eventId: patch.id });
// short circuit if nothing changed
if (didMutate === false) {
return newEvent;
}
// notify runtime that rundown has changed
updateRuntimeOnChange();
@@ -83,7 +83,7 @@ export function generate(
let accumulatedDelay = 0;
let daySpan = 0;
let previousEnd: number;
let previousEnd: MaybeNumber = null;
for (let i = 0; i < initialRundown.length; i++) {
const currentEvent = initialRundown[i];
@@ -106,7 +106,7 @@ export function generate(
lastEnd = updatedEvent.timeEnd;
// check if we go over midnight, account for eventual gaps
const gapOverMidnight = previousEnd > updatedEvent.timeStart;
const gapOverMidnight = previousEnd !== null && previousEnd > updatedEvent.timeStart;
const durationOverMidnight = updatedEvent.timeStart > updatedEvent.timeEnd;
if (gapOverMidnight || durationOverMidnight) {
daySpan++;
@@ -135,7 +135,9 @@ export function generate(
isStale = false;
totalDelay = accumulatedDelay;
totalDuration = getTotalDuration(firstStart, lastEnd, daySpan);
if (lastEnd !== null && firstStart !== null) {
totalDuration = getTotalDuration(firstStart, lastEnd, daySpan);
}
return { rundown, order, links, totalDelay, totalDuration, assignedCustomProperties: assignedCustomFields };
}
@@ -210,6 +212,7 @@ type MutationParams<T> = T & CommonParams;
type MutatingReturn = {
newRundown: OntimeRundown;
newEvent?: OntimeRundownEntry;
didMutate: boolean;
};
type MutatingFn<T extends object> = (params: MutationParams<T>) => MutatingReturn;
@@ -227,7 +230,7 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
*/
isStale = true;
const { newEvent, newRundown } = mutation({ ...params, persistedRundown });
const { newEvent, newRundown, didMutate } = mutation({ ...params, persistedRundown });
revision = revision + 1;
persistedRundown = newRundown;
@@ -244,7 +247,7 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
DataProvider.setRundown(persistedRundown);
});
return { newEvent };
return { newEvent, newRundown, didMutate };
}
return scopedMutation;
@@ -256,7 +259,7 @@ export function add({ persistedRundown, atIndex, event }: AddArgs): Required<Mut
const newEvent: OntimeRundownEntry = { ...event };
const newRundown = insertAtIndex(atIndex, newEvent, persistedRundown);
return { newRundown, newEvent };
return { newRundown, newEvent, didMutate: true };
}
type RemoveArgs = MutationParams<{ eventId: string }>;
@@ -265,11 +268,11 @@ export function remove({ persistedRundown, eventId }: RemoveArgs): MutatingRetur
const atIndex = persistedRundown.findIndex((event) => event.id === eventId);
const newRundown = deleteAtIndex(atIndex, persistedRundown);
return { newRundown };
return { newRundown, didMutate: atIndex !== -1 };
}
export function removeAll(): { newRundown: OntimeRundown } {
return { newRundown: [] };
export function removeAll(): MutatingReturn {
return { newRundown: [], didMutate: true };
}
/**
@@ -304,7 +307,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
const eventInMemory = persistedRundown[indexAt];
if (!hasChanges(eventInMemory, patch)) {
isStale = false;
return;
return { newRundown: persistedRundown, newEvent: eventInMemory, didMutate: false };
}
const newEvent = makeEvent(eventInMemory, patch);
@@ -312,6 +315,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
const newRundown = [...persistedRundown];
newRundown[indexAt] = newEvent;
// check whether the data warrants recalculation of cache
const makeStale = isDataStale(patch);
if (!makeStale) {
@@ -319,7 +323,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
}
isStale = makeStale;
return { newRundown, newEvent };
return { newRundown, newEvent, didMutate: true };
}
type BatchEditArgs = MutationParams<{ eventIds: string[]; patch: Partial<OntimeRundownEntry> }>;
@@ -339,7 +343,7 @@ export function batchEdit({ persistedRundown, eventIds, patch }: BatchEditArgs):
newRundown.push(persistedRundown[i]);
}
}
return { newRundown };
return { newRundown, didMutate: true };
}
type ReorderArgs = MutationParams<{ eventId: string; from: number; to: number }>;
@@ -357,14 +361,14 @@ export function reorder({ persistedRundown, eventId, from, to }: ReorderArgs): R
event.revision += 1;
}
}
return { newRundown, newEvent: newRundown.at(from) };
return { newRundown, newEvent: newRundown.at(from) as OntimeRundownEntry, didMutate: true };
}
type ApplyDelayArgs = MutationParams<{ eventId: string }>;
export function applyDelay({ persistedRundown, eventId }: ApplyDelayArgs): MutatingReturn {
const newRundown = apply(eventId, persistedRundown);
return { newRundown };
return { newRundown, didMutate: true };
}
type SwapArgs = MutationParams<{ fromId: string; toId: string }>;
@@ -388,7 +392,7 @@ export function swap({ persistedRundown, fromId, toId }: SwapArgs): MutatingRetu
newRundown[indexB] = newB;
(newRundown[indexB] as OntimeEvent).revision += 1;
return { newRundown };
return { newRundown, didMutate: true };
}
/**
+7 -1
View File
@@ -25,7 +25,13 @@ const populateDb = (directory: string, filename: string): string => {
if (!existsSync(dbPath)) {
try {
const dbDirectory = resolveDbDirectory;
const newFileDirectory = join(dbDirectory, pathToStartDb.split('/').pop());
const startDbName = pathToStartDb.split('/').pop();
if (!startDbName) {
throw new Error('Invalid path to start database');
}
const newFileDirectory = join(dbDirectory, startDbName);
copyFileSync(pathToStartDb, newFileDirectory);
dbPath = newFileDirectory;
@@ -318,7 +318,6 @@ describe('test parser edge cases', () => {
// @ts-expect-error -- we know this is wrong, testing imports outside domain
const parseResponse = await parseJson(testData);
expect(console.log).toHaveBeenCalledWith('ERROR: unkown event type, skipping');
expect(parseResponse?.rundown.length).toBe(0);
});
@@ -332,7 +331,7 @@ describe('test parser edge cases', () => {
// @ts-expect-error -- we know this is wrong, testing imports outside domain
await parseJson(testData);
expect(console.log).toHaveBeenCalledWith('ERROR: unknown app version, skipping');
expect(console.log).toHaveBeenCalledWith('ERROR: unable to parse settings, missing app or version');
});
});
@@ -564,6 +563,7 @@ describe('test views import', () => {
normalColor: '#ffffffcc',
warningColor: '#FFAB33',
dangerColor: '#ED3333',
freezeEnd: false,
endMessage: '',
overrideStyles: false,
};
@@ -581,7 +581,7 @@ describe('test views import', () => {
},
} as DatabaseModel;
const parsed = parseViewSettings(testData);
expect(parsed).toStrictEqual({});
expect(parsed).toStrictEqual(dbModel.viewSettings);
});
});
@@ -591,7 +591,7 @@ describe('test import of v2 datamodel', () => {
rundown: [
{ type: SupportedEvent.Block, title: 'block-title', id: 'block-id' },
{ type: SupportedEvent.Delay, duration: 0 },
{ type: SupportedEvent.Event, title: 'block-title', id: 'block-id' },
{ type: SupportedEvent.Event, title: 'event-title', id: 'event-id' },
],
project: {
title: '',
+16 -5
View File
@@ -273,15 +273,26 @@ export const parseJson = async (jsonData: Partial<DatabaseModel>): Promise<Datab
return null;
}
let settings;
// check settings first to make sure we can parse it
try {
settings = parseSettings(jsonData);
} catch (error) {
// if we cant parse, return an empty project
console.log('ERROR: unable to parse settings, missing app or version');
return dbModel;
}
const returnData: DatabaseModel = {
rundown: parseRundown(jsonData),
project: parseProject(jsonData) ?? dbModel.project,
settings: parseSettings(jsonData) ?? dbModel.settings,
viewSettings: parseViewSettings(jsonData) ?? dbModel.viewSettings,
project: parseProject(jsonData),
settings,
viewSettings: parseViewSettings(jsonData),
urlPresets: parseUrlPresets(jsonData),
customFields: parseCustomFields(jsonData),
osc: parseOsc(jsonData) ?? dbModel.osc,
http: parseHttp(jsonData) ?? dbModel.http,
osc: parseOsc(jsonData),
http: parseHttp(jsonData),
};
return returnData;
+156 -166
View File
@@ -15,6 +15,9 @@ import {
isOntimeCycle,
HttpSubscription,
URLPreset,
OntimeEvent,
OntimeBlock,
OntimeDelay,
} from 'ontime-types';
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
@@ -22,135 +25,118 @@ import { dbModel } from '../models/dataModel.js';
import { createEvent } from './parser.js';
/**
* Parse events array of an entry
* @param {object} data - data object
* @returns {object} - event object data
* Parse rundown array of an entry
*/
export const parseRundown = (data: Partial<DatabaseModel>): OntimeRundown => {
let newRundown: OntimeRundown = [];
if ('rundown' in data) {
console.log('Found rundown definition, importing...');
const rundown = [];
try {
let eventIndex = 0;
const ids = [];
for (const event of data.rundown) {
// double check unique ids
if (ids.includes(event?.id)) {
console.log('ERROR: ID collision on import, skipping');
continue;
}
if (isOntimeEvent(event)) {
eventIndex += 1;
const parsedEvent = createEvent(event, eventIndex.toString());
if (event != null) {
rundown.push(parsedEvent);
ids.push(parsedEvent.id);
}
} else if (isOntimeDelay(event)) {
rundown.push({
...delayDef,
duration: event.duration,
id: event.id || generateId(),
});
} else if (isOntimeBlock(event)) {
rundown.push({ ...blockDef, title: event.title, id: event.id || generateId() });
} else {
console.log('ERROR: unkown event type, skipping');
}
}
} catch (error) {
console.log(`Error ${error}`);
}
// write to db
newRundown = rundown;
console.log(`Uploaded file with ${newRundown.length} entries`);
if (!data.rundown) {
return [];
}
return newRundown;
console.log('Found rundown, importing...');
const rundown: OntimeRundown = [];
let eventIndex = 0;
const ids: string[] = [];
for (const event of data.rundown) {
if (ids.includes(event.id)) {
console.log('ERROR: ID collision on import, skipping');
continue;
}
const id = event.id || generateId();
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
if (isOntimeEvent(event)) {
newEvent = createEvent(event, eventIndex.toString());
// skip if event is invalid
if (newEvent == null) {
continue;
}
eventIndex += 1;
} else if (isOntimeDelay(event)) {
newEvent = { ...delayDef, duration: event.duration, id };
} else if (isOntimeBlock(event)) {
newEvent = { ...blockDef, title: event.title, id };
} else {
console.log('ERROR: unknown event type, skipping');
continue;
}
if (newEvent) {
rundown.push(newEvent);
ids.push(id);
}
}
console.log(`Uploaded rundown with ${rundown.length} entries`);
return rundown;
};
/**
* Parse event portion of an entry
* @param {object} data - data object
* @returns {object} - event object data
*/
export const parseProject = (data: Partial<DatabaseModel>): ProjectData => {
let newProjectData: Partial<ProjectData> = {};
// we are adding this here to aid transition, should be removed once enough time has past that users have fully migrated
if ('project' in data) {
console.log('Found project data, importing...');
const project = data.project;
// filter known properties and write to db
newProjectData = {
...dbModel.project,
title: project.title || dbModel.project.title,
description: project.description || dbModel.project.description,
publicUrl: project.publicUrl || dbModel.project.publicUrl,
publicInfo: project.publicInfo || dbModel.project.publicInfo,
backstageUrl: project.backstageUrl || dbModel.project.backstageUrl,
backstageInfo: project.backstageInfo || dbModel.project.backstageInfo,
};
if (!data.project) {
return { ...dbModel.project };
}
return newProjectData as ProjectData;
console.log('Found project data, importing...');
return {
title: data.project.title ?? dbModel.project.title,
description: data.project.description ?? dbModel.project.description,
publicUrl: data.project.publicUrl ?? dbModel.project.publicUrl,
publicInfo: data.project.publicInfo ?? dbModel.project.publicInfo,
backstageUrl: data.project.backstageUrl ?? dbModel.project.backstageUrl,
backstageInfo: data.project.backstageInfo ?? dbModel.project.backstageInfo,
};
};
/**
* Parse settings portion of an entry
* @param {object} data - data object
* @returns {object} - event object data
*/
export const parseSettings = (data): Settings => {
let newSettings: Partial<Settings> = {};
if ('settings' in data) {
console.log('Found settings definition, importing...');
const s = data.settings;
// skip if file definition is missing
if (s?.app !== 'ontime' || s?.version == null) {
console.log('ERROR: unknown app version, skipping');
} else {
const settings = {
version: dbModel.settings.version,
serverPort: s.serverPort ?? dbModel.settings.serverPort,
editorKey: s.editorKey ?? null,
operatorKey: s.operatorKey ?? null,
timeFormat: s.timeFormat ?? '24',
language: s.language ?? 'en',
};
// write to db
newSettings = {
...dbModel.settings,
...settings,
};
}
export const parseSettings = (data: Partial<DatabaseModel>): Settings => {
if (!data.settings) {
return { ...dbModel.settings };
}
return newSettings as Settings;
// skip if file definition is missing
if (data.settings?.app !== 'ontime' || data.settings?.version == null) {
throw new Error('ERROR: unable to parse settings, missing app or version');
}
console.log('Found settings, importing...');
return {
app: dbModel.settings.app,
version: dbModel.settings.version,
serverPort: data.settings.serverPort ?? dbModel.settings.serverPort,
editorKey: data.settings.editorKey ?? null,
operatorKey: data.settings.operatorKey ?? null,
timeFormat: data.settings.timeFormat ?? '24',
language: data.settings.language ?? 'en',
};
};
/**
* Parse settings portion of an entry
* @param {object} data - data object
* @returns {object} - event object data
* Parse view settings portion of an entry
*/
export const parseViewSettings = (data: Partial<DatabaseModel>): ViewSettings => {
let newViews: Partial<ViewSettings> = {};
if ('viewSettings' in data) {
console.log('Found view definition, importing...');
const v = data.viewSettings;
const viewSettings = {
overrideStyles: v.overrideStyles ?? dbModel.viewSettings.overrideStyles,
normalColor: v.normalColor ?? dbModel.viewSettings.normalColor,
warningColor: v.warningColor ?? dbModel.viewSettings.warningColor,
dangerColor: v.dangerColor ?? dbModel.viewSettings.dangerColor,
endMessage: v.endMessage ?? dbModel.viewSettings.endMessage,
};
newViews = { ...viewSettings };
if (!data.viewSettings) {
return { ...dbModel.viewSettings };
}
return newViews as ViewSettings;
console.log('Found view settings, importing...');
return {
dangerColor: data.viewSettings.dangerColor ?? dbModel.viewSettings.dangerColor,
endMessage: data.viewSettings.endMessage ?? dbModel.viewSettings.endMessage,
freezeEnd: data.viewSettings.freezeEnd ?? dbModel.viewSettings.freezeEnd,
normalColor: data.viewSettings.normalColor ?? dbModel.viewSettings.normalColor,
overrideStyles: data.viewSettings.overrideStyles ?? dbModel.viewSettings.overrideStyles,
warningColor: data.viewSettings.warningColor ?? dbModel.viewSettings.warningColor,
};
};
/**
@@ -170,20 +156,20 @@ export function sanitiseOscSubscriptions(subscriptions?: OscSubscription[]): Osc
/**
* Parse osc portion of an entry
*/
export const parseOsc = (data: { osc?: Partial<OSCSettings> }): OSCSettings => {
if ('osc' in data) {
console.log('Found OSC definition, importing...');
const loadedConfig = data.osc || {};
return {
portIn: loadedConfig.portIn ?? dbModel.osc.portIn,
portOut: loadedConfig.portOut ?? dbModel.osc.portOut,
targetIP: loadedConfig.targetIP ?? dbModel.osc.targetIP,
enabledIn: loadedConfig.enabledIn ?? dbModel.osc.enabledIn,
enabledOut: loadedConfig.enabledOut ?? dbModel.osc.enabledOut,
subscriptions: sanitiseOscSubscriptions(loadedConfig.subscriptions),
};
export const parseOsc = (data: Partial<DatabaseModel>): OSCSettings => {
if (!data.osc) {
return { ...dbModel.osc };
}
console.log('Found OSC settings, importing...');
return {
portIn: data.osc.portIn ?? dbModel.osc.portIn,
portOut: data.osc.portOut ?? dbModel.osc.portOut,
targetIP: data.osc.targetIP ?? dbModel.osc.targetIP,
enabledIn: data.osc.enabledIn ?? dbModel.osc.enabledIn,
enabledOut: data.osc.enabledOut ?? dbModel.osc.enabledOut,
subscriptions: sanitiseOscSubscriptions(data.osc.subscriptions),
};
};
/**
@@ -206,66 +192,70 @@ export function sanitiseHttpSubscriptions(subscriptions?: HttpSubscription[]): H
/**
* Parse Http portion of an entry
* @param {object} data - data object
* @param {boolean} enforce - whether to create a definition if one is missing
* @returns {object} - event object data
*/
export const parseHttp = (data: { http?: Partial<HttpSettings> }): HttpSettings => {
if ('http' in data) {
console.log('Found HTTP definition, importing...');
// TODO: this can be improved by only merging known keys
const loadedConfig = data?.http || {};
return {
enabledOut: loadedConfig.enabledOut ?? dbModel.http.enabledOut,
subscriptions: sanitiseHttpSubscriptions(loadedConfig.subscriptions),
};
export const parseHttp = (data: Partial<DatabaseModel>): HttpSettings => {
if (!data.http) {
return { ...dbModel.http };
}
console.log('Found HTTP settings, importing...');
return {
enabledOut: data.http.enabledOut ?? dbModel.http.enabledOut,
subscriptions: sanitiseHttpSubscriptions(data.http.subscriptions),
};
};
/**
* Parse URL preset portion of an entry
* @param {object} data - data object
* @returns {object} - event object data
*/
export const parseUrlPresets = (data: Partial<DatabaseModel>): URLPreset[] => {
const newPresets: URLPreset[] = [];
if ('urlPresets' in data) {
console.log('Found URL presets definition, importing...');
try {
for (const preset of data.urlPresets) {
const newPreset = {
enabled: preset.enabled ?? false,
alias: preset.alias ?? '',
pathAndParams: preset.pathAndParams ?? '',
};
newPresets.push(newPreset);
}
console.log(`Uploaded ${newPresets.length} preset(s)`);
} catch (error) {
console.log(`Error: ${error}`);
}
if (!data.urlPresets) {
return [];
}
console.log('Found URL presets, importing...');
const newPresets: URLPreset[] = [];
for (const preset of data.urlPresets) {
const newPreset = {
enabled: preset.enabled ?? false,
alias: preset.alias ?? '',
pathAndParams: preset.pathAndParams ?? '',
};
newPresets.push(newPreset);
}
console.log(`Uploaded ${newPresets.length} preset(s)`);
return newPresets;
};
/**
* Parse customFields entry
* @param {object} data - data object
* @returns {object} - event object data
*/
export const parseCustomFields = (data: Partial<DatabaseModel>): CustomFields => {
let newCustomFields: CustomFields = { ...dbModel.customFields };
if ('customFields' in data) {
console.log('Found Custom Fields definition, importing...');
try {
//TODO: validate
newCustomFields = { ...dbModel.customFields, ...data.customFields };
} catch (error) {
console.log(`Error: ${error}`);
}
if (typeof data.customFields !== 'object') {
return { ...dbModel.customFields };
}
return { ...newCustomFields };
console.log('Found Custom Fields, importing...');
const newCustomFields: CustomFields = {};
for (const fieldLabel in data.customFields) {
const field = data.customFields[fieldLabel];
if (!field.label || !field.type || !field.colour) {
console.log('ERROR: missing required field, skipping');
continue;
}
newCustomFields[field.label] = {
type: field.type,
colour: field.colour,
label: field.label,
};
}
return newCustomFields;
};
+5 -5
View File
@@ -15,10 +15,9 @@ export const makeString = (val: unknown, fallback = ''): string => {
/**
* @description Delete file from system
* @param {string} file - reference to file
*/
export const deleteFile = async (file) => {
unlink(file, (error) => {
export const deleteFile = async (filePath: string) => {
unlink(filePath, (error) => {
if (error) {
console.error('Could not delete file:', error);
}
@@ -67,11 +66,12 @@ export function mergeObject<T extends object>(a: T, b: Partial<T>): T {
* @description Removes undefined
* @param {object} obj
*/
export const removeUndefined = (obj: object) => {
export const removeUndefined = <T extends Record<string, unknown>>(obj: T): Partial<T> => {
return Object.keys(obj).reduce((patched, key) => {
if (typeof obj[key] !== 'undefined') {
// @ts-expect-error -- not sure how to type this
patched[key] = obj[key];
}
return patched;
}, {});
}, {} as Partial<T>);
};