mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 15:39:11 +00:00
Typescript null (#869)
* refactor: improve null checking * fix: return result of mutation
This commit is contained in:
@@ -116,16 +116,14 @@ export class SocketServer implements IAdapter {
|
|||||||
|
|
||||||
// Protocol specific stuff handled above
|
// Protocol specific stuff handled above
|
||||||
try {
|
try {
|
||||||
const reply = dispatchFromAdapter(
|
const reply = dispatchFromAdapter(type, { payload }, 'ws');
|
||||||
type,
|
|
||||||
{
|
|
||||||
payload,
|
|
||||||
},
|
|
||||||
'ws',
|
|
||||||
);
|
|
||||||
if (reply) {
|
if (reply) {
|
||||||
const { payload } = reply;
|
ws.send(
|
||||||
ws.send(type, payload);
|
JSON.stringify({
|
||||||
|
type: 'ontime-change',
|
||||||
|
payload: reply.payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(LogOrigin.Rx, `WS IN: ${error}`);
|
logger.error(LogOrigin.Rx, `WS IN: ${error}`);
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
* Google Sheets
|
* Google Sheets
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { CustomFields, OntimeRundown } from 'ontime-types';
|
||||||
|
import { ImportMap } from 'ontime-utils';
|
||||||
|
|
||||||
import { extname } from 'path';
|
import { extname } from 'path';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { ImportMap } from 'ontime-utils';
|
|
||||||
import xlsx from 'node-xlsx';
|
import xlsx from 'node-xlsx';
|
||||||
|
|
||||||
import { parseExcel } from '../../utils/parser.js';
|
import { parseExcel } from '../../utils/parser.js';
|
||||||
import { parseCustomFields, parseRundown } from '../../utils/parserFunctions.js';
|
import { parseCustomFields, parseRundown } from '../../utils/parserFunctions.js';
|
||||||
import { deleteFile } from '../../utils/parserUtils.js';
|
import { deleteFile } from '../../utils/parserUtils.js';
|
||||||
@@ -29,7 +32,7 @@ export function listWorksheets() {
|
|||||||
return excelData.map((value) => value.name);
|
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;
|
const data = excelData.find(({ name }) => name.toLowerCase() === options.worksheet.toLowerCase())?.data;
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -39,15 +42,14 @@ export function generateRundownPreview(options: ImportMap) {
|
|||||||
const dataFromExcel = parseExcel(data, options);
|
const dataFromExcel = parseExcel(data, options);
|
||||||
|
|
||||||
// we run the parsed data through an extra step to ensure the objects shape
|
// we run the parsed data through an extra step to ensure the objects shape
|
||||||
const result = { rundown: [], customFields: {} };
|
const rundown = parseRundown(dataFromExcel);
|
||||||
result.rundown = parseRundown(dataFromExcel);
|
if (rundown.length === 0) {
|
||||||
if (result.rundown.length < 1) {
|
|
||||||
throw new Error(`Could not find data to import in the worksheet: ${options.worksheet}`);
|
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 = [];
|
excelData = [];
|
||||||
|
|
||||||
return result;
|
return { rundown, customFields };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export class SimpleTimer {
|
|||||||
|
|
||||||
public update(timeNow: number): SimpleTimerState {
|
public update(timeNow: number): SimpleTimerState {
|
||||||
if (this.state.playback === SimplePlayback.Start) {
|
if (this.state.playback === SimplePlayback.Start) {
|
||||||
|
// we know startedAt is not null since we are in play mode
|
||||||
const elapsed = timeNow - this.startedAt;
|
const elapsed = timeNow - this.startedAt;
|
||||||
if (this.state.direction === SimpleDirection.CountDown) {
|
if (this.state.direction === SimpleDirection.CountDown) {
|
||||||
this.state.current = this.state.duration - elapsed;
|
this.state.current = this.state.duration - elapsed;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class RestoreService {
|
|||||||
private readonly filePath: MaybeString;
|
private readonly filePath: MaybeString;
|
||||||
private readonly file: JSONFile<RestorePoint | null>;
|
private readonly file: JSONFile<RestorePoint | null>;
|
||||||
private failedCreateAttempts: number;
|
private failedCreateAttempts: number;
|
||||||
private savedState: RestorePoint;
|
private savedState: RestorePoint | null;
|
||||||
|
|
||||||
constructor(filePath: string) {
|
constructor(filePath: string) {
|
||||||
this.filePath = filePath;
|
this.filePath = filePath;
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export async function getProjectFiles(): Promise<ProjectFile[]> {
|
|||||||
const allFiles = await getFilesFromFolder(resolveProjectsDirectory);
|
const allFiles = await getFilesFromFolder(resolveProjectsDirectory);
|
||||||
const filteredFiles = filterProjectFiles(allFiles);
|
const filteredFiles = filterProjectFiles(allFiles);
|
||||||
|
|
||||||
const projectFiles = [];
|
const projectFiles: ProjectFile[] = [];
|
||||||
for (const file of filteredFiles) {
|
for (const file of filteredFiles) {
|
||||||
const filePath = join(resolveProjectsDirectory, file);
|
const filePath = join(resolveProjectsDirectory, file);
|
||||||
const stats = await stat(filePath);
|
const stats = await stat(filePath);
|
||||||
|
|||||||
@@ -22,20 +22,32 @@ import { runtimeService } from '../runtime-service/RuntimeService.js';
|
|||||||
import * as cache from './rundownCache.js';
|
import * as cache from './rundownCache.js';
|
||||||
import { getPlayableEvents } from './rundownUtils.js';
|
import { getPlayableEvents } from './rundownUtils.js';
|
||||||
|
|
||||||
function generateEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>) {
|
type PatchWithId = (Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) & { id: string };
|
||||||
// we discard any UI provided events and add our own
|
|
||||||
|
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();
|
const id = cache.getUniqueId();
|
||||||
|
|
||||||
if (isOntimeEvent(eventData)) {
|
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)) {
|
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)) {
|
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');
|
throw new Error('Invalid event type');
|
||||||
@@ -46,9 +58,7 @@ function generateEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> |
|
|||||||
* @param {object} eventData
|
* @param {object} eventData
|
||||||
* @return {OntimeRundownEntry}
|
* @return {OntimeRundownEntry}
|
||||||
*/
|
*/
|
||||||
export async function addEvent(
|
export async function addEvent(eventData: PatchWithId & { after?: string }): Promise<OntimeRundownEntry> {
|
||||||
eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>,
|
|
||||||
): Promise<OntimeRundownEntry> {
|
|
||||||
// if the user didnt provide an index, we add the event to start
|
// if the user didnt provide an index, we add the event to start
|
||||||
let atIndex = 0;
|
let atIndex = 0;
|
||||||
if (eventData?.after !== undefined) {
|
if (eventData?.after !== undefined) {
|
||||||
@@ -62,15 +72,16 @@ export async function addEvent(
|
|||||||
|
|
||||||
// generate a fully formed event from the patch
|
// generate a fully formed event from the patch
|
||||||
const eventToAdd = generateEvent(eventData);
|
const eventToAdd = generateEvent(eventData);
|
||||||
|
|
||||||
// modify rundown
|
// modify rundown
|
||||||
const scopedMutation = cache.mutateCache(cache.add);
|
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
|
// notify runtime that rundown has changed
|
||||||
updateRuntimeOnChange();
|
updateRuntimeOnChange();
|
||||||
|
|
||||||
// notify timer and external services of change
|
// notify timer and external services of change
|
||||||
notifyChanges({ timer: [newEvent.id], external: true });
|
notifyChanges({ timer: [eventData.id], external: true });
|
||||||
|
|
||||||
return newEvent;
|
return newEvent;
|
||||||
}
|
}
|
||||||
@@ -81,7 +92,11 @@ export async function addEvent(
|
|||||||
*/
|
*/
|
||||||
export async function deleteEvent(eventId: string) {
|
export async function deleteEvent(eventId: string) {
|
||||||
const scopedMutation = cache.mutateCache(cache.remove);
|
const scopedMutation = cache.mutateCache(cache.remove);
|
||||||
await scopedMutation({ eventId });
|
const { didMutate } = await scopedMutation({ eventId });
|
||||||
|
|
||||||
|
if (didMutate === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// notify runtime that rundown has changed
|
// notify runtime that rundown has changed
|
||||||
updateRuntimeOnChange();
|
updateRuntimeOnChange();
|
||||||
@@ -108,14 +123,18 @@ export async function deleteAllEvents() {
|
|||||||
* Apply patch to an element in rundown
|
* Apply patch to an element in rundown
|
||||||
* @param patch
|
* @param patch
|
||||||
*/
|
*/
|
||||||
export async function editEvent(patch: Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) {
|
export async function editEvent(patch: PatchWithId) {
|
||||||
if (isOntimeEvent(patch) && patch?.cue === '') {
|
if (isOntimeEvent(patch) && patch?.cue === '') {
|
||||||
throw new Error('Cue value invalid');
|
throw new Error('Cue value invalid');
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopedMutation = cache.mutateCache(cache.edit);
|
const scopedMutation = cache.mutateCache(cache.edit);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know patch has an id
|
const { newEvent, didMutate } = await scopedMutation({ patch, eventId: patch.id });
|
||||||
const { newEvent } = await scopedMutation({ patch, eventId: patch.id! });
|
|
||||||
|
// short circuit if nothing changed
|
||||||
|
if (didMutate === false) {
|
||||||
|
return newEvent;
|
||||||
|
}
|
||||||
|
|
||||||
// notify runtime that rundown has changed
|
// notify runtime that rundown has changed
|
||||||
updateRuntimeOnChange();
|
updateRuntimeOnChange();
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function generate(
|
|||||||
|
|
||||||
let accumulatedDelay = 0;
|
let accumulatedDelay = 0;
|
||||||
let daySpan = 0;
|
let daySpan = 0;
|
||||||
let previousEnd: number;
|
let previousEnd: MaybeNumber = null;
|
||||||
|
|
||||||
for (let i = 0; i < initialRundown.length; i++) {
|
for (let i = 0; i < initialRundown.length; i++) {
|
||||||
const currentEvent = initialRundown[i];
|
const currentEvent = initialRundown[i];
|
||||||
@@ -106,7 +106,7 @@ export function generate(
|
|||||||
lastEnd = updatedEvent.timeEnd;
|
lastEnd = updatedEvent.timeEnd;
|
||||||
|
|
||||||
// check if we go over midnight, account for eventual gaps
|
// 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;
|
const durationOverMidnight = updatedEvent.timeStart > updatedEvent.timeEnd;
|
||||||
if (gapOverMidnight || durationOverMidnight) {
|
if (gapOverMidnight || durationOverMidnight) {
|
||||||
daySpan++;
|
daySpan++;
|
||||||
@@ -135,7 +135,9 @@ export function generate(
|
|||||||
|
|
||||||
isStale = false;
|
isStale = false;
|
||||||
totalDelay = accumulatedDelay;
|
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 };
|
return { rundown, order, links, totalDelay, totalDuration, assignedCustomProperties: assignedCustomFields };
|
||||||
}
|
}
|
||||||
@@ -210,6 +212,7 @@ type MutationParams<T> = T & CommonParams;
|
|||||||
type MutatingReturn = {
|
type MutatingReturn = {
|
||||||
newRundown: OntimeRundown;
|
newRundown: OntimeRundown;
|
||||||
newEvent?: OntimeRundownEntry;
|
newEvent?: OntimeRundownEntry;
|
||||||
|
didMutate: boolean;
|
||||||
};
|
};
|
||||||
type MutatingFn<T extends object> = (params: MutationParams<T>) => MutatingReturn;
|
type MutatingFn<T extends object> = (params: MutationParams<T>) => MutatingReturn;
|
||||||
|
|
||||||
@@ -227,7 +230,7 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
|||||||
*/
|
*/
|
||||||
isStale = true;
|
isStale = true;
|
||||||
|
|
||||||
const { newEvent, newRundown } = mutation({ ...params, persistedRundown });
|
const { newEvent, newRundown, didMutate } = mutation({ ...params, persistedRundown });
|
||||||
|
|
||||||
revision = revision + 1;
|
revision = revision + 1;
|
||||||
persistedRundown = newRundown;
|
persistedRundown = newRundown;
|
||||||
@@ -244,7 +247,7 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
|||||||
DataProvider.setRundown(persistedRundown);
|
DataProvider.setRundown(persistedRundown);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { newEvent };
|
return { newEvent, newRundown, didMutate };
|
||||||
}
|
}
|
||||||
|
|
||||||
return scopedMutation;
|
return scopedMutation;
|
||||||
@@ -256,7 +259,7 @@ export function add({ persistedRundown, atIndex, event }: AddArgs): Required<Mut
|
|||||||
const newEvent: OntimeRundownEntry = { ...event };
|
const newEvent: OntimeRundownEntry = { ...event };
|
||||||
const newRundown = insertAtIndex(atIndex, newEvent, persistedRundown);
|
const newRundown = insertAtIndex(atIndex, newEvent, persistedRundown);
|
||||||
|
|
||||||
return { newRundown, newEvent };
|
return { newRundown, newEvent, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveArgs = MutationParams<{ eventId: string }>;
|
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 atIndex = persistedRundown.findIndex((event) => event.id === eventId);
|
||||||
const newRundown = deleteAtIndex(atIndex, persistedRundown);
|
const newRundown = deleteAtIndex(atIndex, persistedRundown);
|
||||||
|
|
||||||
return { newRundown };
|
return { newRundown, didMutate: atIndex !== -1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAll(): { newRundown: OntimeRundown } {
|
export function removeAll(): MutatingReturn {
|
||||||
return { newRundown: [] };
|
return { newRundown: [], didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +307,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
|
|||||||
const eventInMemory = persistedRundown[indexAt];
|
const eventInMemory = persistedRundown[indexAt];
|
||||||
if (!hasChanges(eventInMemory, patch)) {
|
if (!hasChanges(eventInMemory, patch)) {
|
||||||
isStale = false;
|
isStale = false;
|
||||||
return;
|
return { newRundown: persistedRundown, newEvent: eventInMemory, didMutate: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const newEvent = makeEvent(eventInMemory, patch);
|
const newEvent = makeEvent(eventInMemory, patch);
|
||||||
@@ -312,6 +315,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
|
|||||||
const newRundown = [...persistedRundown];
|
const newRundown = [...persistedRundown];
|
||||||
newRundown[indexAt] = newEvent;
|
newRundown[indexAt] = newEvent;
|
||||||
|
|
||||||
|
// check whether the data warrants recalculation of cache
|
||||||
const makeStale = isDataStale(patch);
|
const makeStale = isDataStale(patch);
|
||||||
|
|
||||||
if (!makeStale) {
|
if (!makeStale) {
|
||||||
@@ -319,7 +323,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
|
|||||||
}
|
}
|
||||||
|
|
||||||
isStale = makeStale;
|
isStale = makeStale;
|
||||||
return { newRundown, newEvent };
|
return { newRundown, newEvent, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type BatchEditArgs = MutationParams<{ eventIds: string[]; patch: Partial<OntimeRundownEntry> }>;
|
type BatchEditArgs = MutationParams<{ eventIds: string[]; patch: Partial<OntimeRundownEntry> }>;
|
||||||
@@ -339,7 +343,7 @@ export function batchEdit({ persistedRundown, eventIds, patch }: BatchEditArgs):
|
|||||||
newRundown.push(persistedRundown[i]);
|
newRundown.push(persistedRundown[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { newRundown };
|
return { newRundown, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReorderArgs = MutationParams<{ eventId: string; from: number; to: number }>;
|
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;
|
event.revision += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { newRundown, newEvent: newRundown.at(from) };
|
return { newRundown, newEvent: newRundown.at(from) as OntimeRundownEntry, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApplyDelayArgs = MutationParams<{ eventId: string }>;
|
type ApplyDelayArgs = MutationParams<{ eventId: string }>;
|
||||||
|
|
||||||
export function applyDelay({ persistedRundown, eventId }: ApplyDelayArgs): MutatingReturn {
|
export function applyDelay({ persistedRundown, eventId }: ApplyDelayArgs): MutatingReturn {
|
||||||
const newRundown = apply(eventId, persistedRundown);
|
const newRundown = apply(eventId, persistedRundown);
|
||||||
return { newRundown };
|
return { newRundown, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type SwapArgs = MutationParams<{ fromId: string; toId: string }>;
|
type SwapArgs = MutationParams<{ fromId: string; toId: string }>;
|
||||||
@@ -388,7 +392,7 @@ export function swap({ persistedRundown, fromId, toId }: SwapArgs): MutatingRetu
|
|||||||
newRundown[indexB] = newB;
|
newRundown[indexB] = newB;
|
||||||
(newRundown[indexB] as OntimeEvent).revision += 1;
|
(newRundown[indexB] as OntimeEvent).revision += 1;
|
||||||
|
|
||||||
return { newRundown };
|
return { newRundown, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,13 @@ const populateDb = (directory: string, filename: string): string => {
|
|||||||
if (!existsSync(dbPath)) {
|
if (!existsSync(dbPath)) {
|
||||||
try {
|
try {
|
||||||
const dbDirectory = resolveDbDirectory;
|
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);
|
copyFileSync(pathToStartDb, newFileDirectory);
|
||||||
dbPath = newFileDirectory;
|
dbPath = newFileDirectory;
|
||||||
|
|||||||
@@ -318,7 +318,6 @@ describe('test parser edge cases', () => {
|
|||||||
|
|
||||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||||
const parseResponse = await parseJson(testData);
|
const parseResponse = await parseJson(testData);
|
||||||
expect(console.log).toHaveBeenCalledWith('ERROR: unkown event type, skipping');
|
|
||||||
expect(parseResponse?.rundown.length).toBe(0);
|
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
|
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||||
await parseJson(testData);
|
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',
|
normalColor: '#ffffffcc',
|
||||||
warningColor: '#FFAB33',
|
warningColor: '#FFAB33',
|
||||||
dangerColor: '#ED3333',
|
dangerColor: '#ED3333',
|
||||||
|
freezeEnd: false,
|
||||||
endMessage: '',
|
endMessage: '',
|
||||||
overrideStyles: false,
|
overrideStyles: false,
|
||||||
};
|
};
|
||||||
@@ -581,7 +581,7 @@ describe('test views import', () => {
|
|||||||
},
|
},
|
||||||
} as DatabaseModel;
|
} as DatabaseModel;
|
||||||
const parsed = parseViewSettings(testData);
|
const parsed = parseViewSettings(testData);
|
||||||
expect(parsed).toStrictEqual({});
|
expect(parsed).toStrictEqual(dbModel.viewSettings);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -591,7 +591,7 @@ describe('test import of v2 datamodel', () => {
|
|||||||
rundown: [
|
rundown: [
|
||||||
{ type: SupportedEvent.Block, title: 'block-title', id: 'block-id' },
|
{ type: SupportedEvent.Block, title: 'block-title', id: 'block-id' },
|
||||||
{ type: SupportedEvent.Delay, duration: 0 },
|
{ type: SupportedEvent.Delay, duration: 0 },
|
||||||
{ type: SupportedEvent.Event, title: 'block-title', id: 'block-id' },
|
{ type: SupportedEvent.Event, title: 'event-title', id: 'event-id' },
|
||||||
],
|
],
|
||||||
project: {
|
project: {
|
||||||
title: '',
|
title: '',
|
||||||
|
|||||||
@@ -273,15 +273,26 @@ export const parseJson = async (jsonData: Partial<DatabaseModel>): Promise<Datab
|
|||||||
return null;
|
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 = {
|
const returnData: DatabaseModel = {
|
||||||
rundown: parseRundown(jsonData),
|
rundown: parseRundown(jsonData),
|
||||||
project: parseProject(jsonData) ?? dbModel.project,
|
project: parseProject(jsonData),
|
||||||
settings: parseSettings(jsonData) ?? dbModel.settings,
|
settings,
|
||||||
viewSettings: parseViewSettings(jsonData) ?? dbModel.viewSettings,
|
viewSettings: parseViewSettings(jsonData),
|
||||||
urlPresets: parseUrlPresets(jsonData),
|
urlPresets: parseUrlPresets(jsonData),
|
||||||
customFields: parseCustomFields(jsonData),
|
customFields: parseCustomFields(jsonData),
|
||||||
osc: parseOsc(jsonData) ?? dbModel.osc,
|
osc: parseOsc(jsonData),
|
||||||
http: parseHttp(jsonData) ?? dbModel.http,
|
http: parseHttp(jsonData),
|
||||||
};
|
};
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import {
|
|||||||
isOntimeCycle,
|
isOntimeCycle,
|
||||||
HttpSubscription,
|
HttpSubscription,
|
||||||
URLPreset,
|
URLPreset,
|
||||||
|
OntimeEvent,
|
||||||
|
OntimeBlock,
|
||||||
|
OntimeDelay,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
|
||||||
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
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';
|
import { createEvent } from './parser.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse events array of an entry
|
* Parse rundown array of an entry
|
||||||
* @param {object} data - data object
|
|
||||||
* @returns {object} - event object data
|
|
||||||
*/
|
*/
|
||||||
export const parseRundown = (data: Partial<DatabaseModel>): OntimeRundown => {
|
export const parseRundown = (data: Partial<DatabaseModel>): OntimeRundown => {
|
||||||
let newRundown: OntimeRundown = [];
|
if (!data.rundown) {
|
||||||
if ('rundown' in data) {
|
return [];
|
||||||
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`);
|
|
||||||
}
|
}
|
||||||
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
|
* Parse event portion of an entry
|
||||||
* @param {object} data - data object
|
|
||||||
* @returns {object} - event object data
|
|
||||||
*/
|
*/
|
||||||
export const parseProject = (data: Partial<DatabaseModel>): ProjectData => {
|
export const parseProject = (data: Partial<DatabaseModel>): ProjectData => {
|
||||||
let newProjectData: Partial<ProjectData> = {};
|
if (!data.project) {
|
||||||
// we are adding this here to aid transition, should be removed once enough time has past that users have fully migrated
|
return { ...dbModel.project };
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
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
|
* Parse settings portion of an entry
|
||||||
* @param {object} data - data object
|
|
||||||
* @returns {object} - event object data
|
|
||||||
*/
|
*/
|
||||||
export const parseSettings = (data): Settings => {
|
export const parseSettings = (data: Partial<DatabaseModel>): Settings => {
|
||||||
let newSettings: Partial<Settings> = {};
|
if (!data.settings) {
|
||||||
if ('settings' in data) {
|
return { ...dbModel.settings };
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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
|
* Parse view settings portion of an entry
|
||||||
* @param {object} data - data object
|
|
||||||
* @returns {object} - event object data
|
|
||||||
*/
|
*/
|
||||||
export const parseViewSettings = (data: Partial<DatabaseModel>): ViewSettings => {
|
export const parseViewSettings = (data: Partial<DatabaseModel>): ViewSettings => {
|
||||||
let newViews: Partial<ViewSettings> = {};
|
if (!data.viewSettings) {
|
||||||
if ('viewSettings' in data) {
|
return { ...dbModel.viewSettings };
|
||||||
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 };
|
|
||||||
}
|
}
|
||||||
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
|
* Parse osc portion of an entry
|
||||||
*/
|
*/
|
||||||
export const parseOsc = (data: { osc?: Partial<OSCSettings> }): OSCSettings => {
|
export const parseOsc = (data: Partial<DatabaseModel>): OSCSettings => {
|
||||||
if ('osc' in data) {
|
if (!data.osc) {
|
||||||
console.log('Found OSC definition, importing...');
|
return { ...dbModel.osc };
|
||||||
|
|
||||||
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),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
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
|
* 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 => {
|
export const parseHttp = (data: Partial<DatabaseModel>): HttpSettings => {
|
||||||
if ('http' in data) {
|
if (!data.http) {
|
||||||
console.log('Found HTTP definition, importing...');
|
return { ...dbModel.http };
|
||||||
|
|
||||||
// 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),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* Parse URL preset portion of an entry
|
||||||
* @param {object} data - data object
|
|
||||||
* @returns {object} - event object data
|
|
||||||
*/
|
*/
|
||||||
export const parseUrlPresets = (data: Partial<DatabaseModel>): URLPreset[] => {
|
export const parseUrlPresets = (data: Partial<DatabaseModel>): URLPreset[] => {
|
||||||
const newPresets: URLPreset[] = [];
|
if (!data.urlPresets) {
|
||||||
if ('urlPresets' in data) {
|
return [];
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return newPresets;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse customFields entry
|
* Parse customFields entry
|
||||||
* @param {object} data - data object
|
|
||||||
* @returns {object} - event object data
|
|
||||||
*/
|
*/
|
||||||
export const parseCustomFields = (data: Partial<DatabaseModel>): CustomFields => {
|
export const parseCustomFields = (data: Partial<DatabaseModel>): CustomFields => {
|
||||||
let newCustomFields: CustomFields = { ...dbModel.customFields };
|
if (typeof data.customFields !== 'object') {
|
||||||
|
return { ...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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ export const makeString = (val: unknown, fallback = ''): string => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Delete file from system
|
* @description Delete file from system
|
||||||
* @param {string} file - reference to file
|
|
||||||
*/
|
*/
|
||||||
export const deleteFile = async (file) => {
|
export const deleteFile = async (filePath: string) => {
|
||||||
unlink(file, (error) => {
|
unlink(filePath, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Could not delete file:', 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
|
* @description Removes undefined
|
||||||
* @param {object} obj
|
* @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) => {
|
return Object.keys(obj).reduce((patched, key) => {
|
||||||
if (typeof obj[key] !== 'undefined') {
|
if (typeof obj[key] !== 'undefined') {
|
||||||
|
// @ts-expect-error -- not sure how to type this
|
||||||
patched[key] = obj[key];
|
patched[key] = obj[key];
|
||||||
}
|
}
|
||||||
return patched;
|
return patched;
|
||||||
}, {});
|
}, {} as Partial<T>);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user