diff --git a/apps/server/package.json b/apps/server/package.json index cffdebe1e..ced038929 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -27,7 +27,9 @@ "ws": "^8.13.0" }, "devDependencies": { + "@types/cors": "^2.8.17", "@types/express": "^4.17.17", + "@types/multer": "^1.4.11", "@types/node": "^18.11.18", "@types/node-osc": "^6.0.2", "@types/websocket": "^1.0.5", diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts index f0cea0cae..1bf7a9289 100644 --- a/apps/server/src/adapters/WebsocketAdapter.ts +++ b/apps/server/src/adapters/WebsocketAdapter.ts @@ -25,7 +25,7 @@ import { eventStore } from '../stores/EventStore.js'; import { dispatchFromAdapter } from '../controllers/integrationController.js'; import { logger } from '../classes/Logger.js'; -let instance; +let instance: SocketServer | null = null; export class SocketServer implements IAdapter { private readonly MAX_PAYLOAD = 1024 * 256; // 256Kb @@ -50,7 +50,7 @@ export class SocketServer implements IAdapter { this.wss.on('connection', (ws) => { let clientId = getRandomName(); this.clientIds.add(clientId); - logger.info(LogOrigin.Client, `${this.wss.clients.size} Connections with new: ${clientId}`); + logger.info(LogOrigin.Client, `${this.clientIds.size} Connections with new: ${clientId}`); // send store payload on connect ws.send( @@ -70,8 +70,8 @@ export class SocketServer implements IAdapter { ws.on('error', console.error); ws.on('close', () => { - logger.info(LogOrigin.Client, `${this.wss.clients.size} Connections with disconnected: ${clientId}`); this.clientIds.delete(clientId); + logger.info(LogOrigin.Client, `${this.clientIds.size} Connections with disconnected: ${clientId}`); }); ws.on('message', (data) => { diff --git a/apps/server/src/adapters/utils/__test__/parse.test.ts b/apps/server/src/adapters/utils/__test__/parse.test.ts index 48990c595..aba4eb060 100644 --- a/apps/server/src/adapters/utils/__test__/parse.test.ts +++ b/apps/server/src/adapters/utils/__test__/parse.test.ts @@ -33,8 +33,8 @@ describe('objectFromPath()', () => { const obj = objectFromPath(arr); expect(obj).toStrictEqual(objExpected); }); - it('empty array creates undefinde object', () => { - const arr = []; + it('empty array creates undefined object', () => { + const arr: string[] = []; const value = '1234567890'; const objExpected = null; diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 322ac2071..c351f3900 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -170,8 +170,8 @@ export const startServer = async () => { eventNext: state.eventNext, publicEventNext: state.publicEventNext, timer1: { - duration: null, - current: null, + duration: 0, + current: 0, playback: SimplePlayback.Stop, direction: SimpleDirection.CountDown, }, diff --git a/apps/server/src/classes/data-provider/DataProvider.ts b/apps/server/src/classes/data-provider/DataProvider.ts index a3074cb85..b15073a0c 100644 --- a/apps/server/src/classes/data-provider/DataProvider.ts +++ b/apps/server/src/classes/data-provider/DataProvider.ts @@ -11,6 +11,7 @@ import { UserFields, Alias, Settings, + HttpSettings, } from 'ontime-types'; import { data, db } from '../../modules/loadDb.js'; @@ -98,7 +99,7 @@ export class DataProvider { await this.persist(); } - static async setHttp(newData) { + static async setHttp(newData: HttpSettings) { data.http = { ...newData }; await this.persist(); } diff --git a/apps/server/src/classes/simple-timer/SimpleTimer.ts b/apps/server/src/classes/simple-timer/SimpleTimer.ts index baea1e712..31e31ca97 100644 --- a/apps/server/src/classes/simple-timer/SimpleTimer.ts +++ b/apps/server/src/classes/simple-timer/SimpleTimer.ts @@ -38,7 +38,8 @@ export class SimpleTimer { public start(timeNow: number): SimpleTimerState { if (this.state.playback === SimplePlayback.Pause) { - const elapsedSincePause = this.pausedAt - this.startedAt; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know these are not null in a timer that is paused + const elapsedSincePause = this.pausedAt! - this.startedAt!; this.startedAt = timeNow - elapsedSincePause; } else if (this.state.playback === SimplePlayback.Stop) { this.startedAt = timeNow; diff --git a/apps/server/src/controllers/ontimeController.ts b/apps/server/src/controllers/ontimeController.ts index 1275d31f9..9e30b1c44 100644 --- a/apps/server/src/controllers/ontimeController.ts +++ b/apps/server/src/controllers/ontimeController.ts @@ -8,7 +8,7 @@ import type { ErrorResponse, ProjectFileListResponse, } from 'ontime-types'; -import { deepmerge } from 'ontime-utils'; +import { ExcelImportOptions, deepmerge } from 'ontime-utils'; import { RequestHandler, Request, Response } from 'express'; import fs from 'fs'; @@ -44,7 +44,7 @@ import { removeFileExtension } from '../utils/removeFileExtension.js'; // Create controller for GET request to '/ontime/poll' // Returns data for current state -export const poll = async (_req, res) => { +export const poll = async (_req: Request, res: Response) => { try { const state = eventStore.poll(); res.status(200).send(state); @@ -57,7 +57,7 @@ export const poll = async (_req, res) => { // Create controller for GET request to '/ontime/db' // Returns - -export const dbDownload = async (req, res) => { +export const dbDownload = async (_req: Request, res: Response) => { const { title } = DataProvider.getProjectData(); const fileTitle = title || 'ontime data'; @@ -77,7 +77,7 @@ export const dbDownload = async (req, res) => { * @param _res * @param options */ -async function parseFile(file, _req, _res, options) { +async function parseFile(file, _req: Request, _res: Response, options: ExcelImportOptions) { if (!fs.existsSync(file)) { throw new Error('Upload failed'); } @@ -85,6 +85,10 @@ async function parseFile(file, _req, _res, options) { return result.data; } +export type ParsingOptions = { + onlyRundown?: 'true' | 'false'; +}; + /** * parse an uploaded file and apply its parsed objects * @param file @@ -93,7 +97,7 @@ async function parseFile(file, _req, _res, options) { * @param [options] * @returns {Promise} */ -const parseAndApply = async (file, _req, res, options) => { +const parseAndApply = async (file, _req: Request, res: Response, options) => { const result = await parseFile(file, _req, res, options); runtimeService.stop(); @@ -134,7 +138,7 @@ const getNetworkInterfaces = () => { // Create controller for GET request to '/ontime/info' // Returns - -export const getInfo = async (req: Request, res: Response) => { +export const getInfo = async (_req: Request, res: Response) => { const { version, serverPort } = DataProvider.getSettings(); const osc = DataProvider.getOsc(); @@ -155,14 +159,14 @@ export const getInfo = async (req: Request, res: Response) => { // Create controller for POST request to '/ontime/aliases' // Returns - -export const getAliases = async (req, res) => { +export const getAliases = async (_req: Request, res: Response) => { const aliases = DataProvider.getAliases(); res.status(200).send(aliases); }; // Create controller for POST request to '/ontime/aliases' // Returns ACK message -export const postAliases = async (req, res) => { +export const postAliases = async (req: Request, res: Response) => { if (failIsNotArray(req.body, res)) { return; } @@ -178,20 +182,20 @@ export const postAliases = async (req, res) => { await DataProvider.setAliases(newAliases); res.status(200).send(newAliases); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; // Create controller for GET request to '/ontime/userfields' // Returns - -export const getUserFields = async (req, res) => { +export const getUserFields = async (_req: Request, res: Response) => { const userFields = DataProvider.getUserFields(); res.status(200).send(userFields); }; // Create controller for POST request to '/ontime/userfields' // Returns ACK message -export const postUserFields = async (req, res) => { +export const postUserFields = async (req: Request, res: Response) => { if (failEmptyObjects(req.body, res)) { return; } @@ -201,13 +205,13 @@ export const postUserFields = async (req, res) => { await DataProvider.setUserFields(newData); res.status(200).send(newData); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; // Create controller for POST request to '/ontime/settings' // Returns - -export const getSettings = async (req, res) => { +export const getSettings = async (_req: Request, res: Response) => { const settings = DataProvider.getSettings(); res.status(200).send(settings); }; @@ -227,7 +231,7 @@ function extractPin(value: string | undefined | null, fallback: string | null): // Create controller for POST request to '/ontime/settings' // Returns ACK message -export const postSettings = async (req, res) => { +export const postSettings = async (req: Request, res: Response) => { if (failEmptyObjects(req.body, res)) { return; } @@ -264,7 +268,7 @@ export const postSettings = async (req, res) => { await DataProvider.setSettings(newData); res.status(200).send(newData); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; @@ -272,7 +276,7 @@ export const postSettings = async (req, res) => { * @description Get view Settings * @method GET */ -export const getViewSettings = async (req, res) => { +export const getViewSettings = async (_req: Request, res: Response) => { const views = DataProvider.getViewSettings(); res.status(200).send(views); }; @@ -281,7 +285,7 @@ export const getViewSettings = async (req, res) => { * @description Change view Settings * @method POST */ -export const postViewSettings = async (req, res) => { +export const postViewSettings = async (req: Request, res: Response) => { if (failEmptyObjects(req.body, res)) { return; } @@ -299,20 +303,20 @@ export const postViewSettings = async (req, res) => { await DataProvider.setViewSettings(newData); res.status(200).send(newData); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; // Create controller for GET request to '/ontime/osc' // Returns - -export const getOSC = async (req, res) => { +export const getOSC = async (_req: Request, res: Response) => { const osc = DataProvider.getOsc(); res.status(200).send(osc); }; // Create controller for POST request to '/ontime/osc' // Returns ACK message -export const postOSC = async (req, res) => { +export const postOSC = async (req: Request, res: Response) => { if (failEmptyObjects(req.body, res)) { return; } @@ -333,11 +337,11 @@ export const postOSC = async (req, res) => { res.send(oscSettings).status(200); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; -export const postOscSubscriptions = async (req, res) => { +export const postOscSubscriptions = async (req: Request, res: Response) => { if (failEmptyObjects(req.body, res)) { return; } @@ -354,18 +358,18 @@ export const postOscSubscriptions = async (req, res) => { res.send(oscSettings).status(200); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; // Create controller for GET request to '/ontime/http' -export const getHTTP = async (_req, res: Response) => { +export const getHTTP = async (_req: Request, res: Response) => { const http = DataProvider.getHttp(); res.status(200).send(http); }; // Create controller for POST request to '/ontime/http' -export const postHTTP = async (req, res) => { +export const postHTTP = async (req: Request, res: Response) => { if (failEmptyObjects(req.body, res)) { return; } @@ -386,11 +390,11 @@ export const postHTTP = async (req, res) => { res.send(httpSettings).status(200); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; -export async function patchPartialProjectFile(req, res) { +export async function patchPartialProjectFile(req: Request, res: Response) { if (failEmptyObjects(req.body, res)) { return; } @@ -414,14 +418,14 @@ export async function patchPartialProjectFile(req, res) { } res.status(200).send(); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } } /** * uploads, parses and applies the data from a given file */ -export const dbUpload = async (req, res) => { +export const dbUpload = async (req: Request, res: Response) => { if (!req.file) { res.status(400).send({ message: 'File not found' }); return; @@ -440,7 +444,7 @@ export const dbUpload = async (req, res) => { * uploads and parses an excel file * @returns parsed result */ -export async function previewExcel(req, res) { +export async function previewExcel(req, res: Response) { if (!req.file) { res.status(400).send({ message: 'File not found' }); return; @@ -452,7 +456,7 @@ export async function previewExcel(req, res) { const data = await parseFile(file, req, res, options); res.status(200).send(data); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } } @@ -475,7 +479,7 @@ export const postNew: RequestHandler = async (req, res) => { await deleteAllEvents(); res.status(201).send(newData); } catch (error) { - res.status(400).send({ message: error.toString() }); + res.status(400).send({ message: String(error) }); } }; @@ -497,7 +501,7 @@ export const listProjects: RequestHandler = async (_, res: Response { message: `Loaded project ${filename}`, }); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; @@ -537,7 +541,7 @@ export const loadProject: RequestHandler = async (req, res) => { * a 409 status if there are validation errors, * or a 500 status with an error message in case of an exception. */ -export const duplicateProjectFile: RequestHandler = async (req, res) => { +export const duplicateProjectFile: RequestHandler = async (req: Request, res: Response) => { try { const { filename } = req.params; const { newFilename } = req.body; @@ -557,7 +561,7 @@ export const duplicateProjectFile: RequestHandler = async (req, res) => { message: `Duplicated project ${filename} to ${newFilename}`, }); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; @@ -571,7 +575,7 @@ export const duplicateProjectFile: RequestHandler = async (req, res) => { * a 409 status if there are validation errors, * or a 500 status with an error message in case of an exception. */ -export const renameProjectFile: RequestHandler = async (req, res) => { +export const renameProjectFile: RequestHandler = async (req: Request, res: Response) => { try { const { newFilename } = req.body; const { filename } = req.params; @@ -599,7 +603,7 @@ export const renameProjectFile: RequestHandler = async (req, res) => { message: `Renamed project ${filename} to ${newFilename}`, }); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; @@ -612,7 +616,7 @@ export const renameProjectFile: RequestHandler = async (req, res) => { * a 409 status if there are validation errors, * or a 500 status with an error message in case of an exception. */ -export const createProjectFile: RequestHandler = async (req, res) => { +export const createProjectFile: RequestHandler = async (req: Request, res: Response) => { try { const { filename } = req.body; @@ -624,14 +628,13 @@ export const createProjectFile: RequestHandler = async (req, res) => { return res.status(409).send({ message: errors.join(', ') }); } - console.log(`----------------> Creating directory createProjectFile: ${projectFilePath}`); await writeFile(projectFilePath, JSON.stringify(dbModel)); res.status(200).send({ message: `Created project ${filename}`, }); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; @@ -645,7 +648,7 @@ export const createProjectFile: RequestHandler = async (req, res) => { * a 409 status if there are validation errors, * or a 500 status with an error message in case of an exception. */ -export const deleteProjectFile: RequestHandler = async (req, res) => { +export const deleteProjectFile: RequestHandler = async (req: Request, res: Response) => { try { const { filename } = req.params; @@ -669,7 +672,7 @@ export const deleteProjectFile: RequestHandler = async (req, res) => { message: `Deleted project ${filename}`, }); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; @@ -678,7 +681,7 @@ export const deleteProjectFile: RequestHandler = async (req, res) => { * @description SETP-1 POST Client Secrect * @returns parsed result */ -export async function uploadSheetClientFile(req, res) { +export async function uploadSheetClientFile(req, res: Response) { if (!req.file.path) { res.status(400).send({ message: 'File not found' }); return; @@ -688,7 +691,7 @@ export async function uploadSheetClientFile(req, res) { await sheet.saveClientSecrets(client); res.status(200).send('OK'); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } fs.unlink(req.file.path, (err) => { if (err) logger.error(LogOrigin.Server, err.message); @@ -698,7 +701,7 @@ export async function uploadSheetClientFile(req, res) { /** * @description STEP-1 GET Client Secrect status */ -export const getClientSecrect = async (req, res) => { +export const getClientSecrect = async (req: Request, res: Response) => { try { const clientSecrectExists = await sheet.testClientSecret(); if (clientSecrectExists) { @@ -707,31 +710,31 @@ export const getClientSecrect = async (req, res) => { res.status(500).send({ message: 'The Client ID does not exist' }); } } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; /** * @description STEP-2 GET sheet authentication url */ -export async function getAuthenticationUrl(req, res) { +export async function getAuthenticationUrl(_req: Request, res: Response) { try { const authUrl = await sheet.openAuthServer(); res.status(200).send(authUrl); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } } /** * @description STEP-2 GET sheet authentication status */ -export const getAuthentication = async (req, res) => { +export const getAuthentication = async (_req: Request, res: Response) => { try { await sheet.testAuthentication(); res.status(200).send(); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; @@ -739,7 +742,7 @@ export const getAuthentication = async (req, res) => { * @description STEP-3 POST sheet id * @returns list of worksheets */ -export const postId = async (req, res) => { +export const postId = async (req: Request, res: Response) => { try { const { id } = req.body; if (id.lenght < 40) { @@ -748,20 +751,20 @@ export const postId = async (req, res) => { const state = await sheet.testSheetId(id); res.status(200).send(state); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; /** * @description STEP-4 POST worksheet */ -export const postWorksheet = async (req, res) => { +export const postWorksheet = async (req: Request, res: Response) => { try { const { worksheet, id } = req.body; const state = await sheet.testWorksheet(worksheet, id); res.status(200).send(state); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } }; @@ -769,25 +772,25 @@ export const postWorksheet = async (req, res) => { * @description STEP-5 POST download undown to sheet * @returns parsed result */ -export async function pullSheet(req, res) { +export async function pullSheet(req: Request, res: Response) { try { const { id, options } = req.body; const data = await sheet.pull(id, options); res.status(200).send(data); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } } /** * @description STEP-5 POST upload rundown to sheet */ -export async function pushSheet(req, res) { +export async function pushSheet(req: Request, res: Response) { try { const { id, options } = req.body; await sheet.push(id, options); res.status(200).send(); } catch (error) { - res.status(500).send({ message: error.toString() }); + res.status(500).send({ message: String(error) }); } } diff --git a/apps/server/src/controllers/ontimeController.validate.ts b/apps/server/src/controllers/ontimeController.validate.ts index 7e34d0136..862392935 100644 --- a/apps/server/src/controllers/ontimeController.validate.ts +++ b/apps/server/src/controllers/ontimeController.validate.ts @@ -255,7 +255,7 @@ export const validateProjectCreate = [ * */ export const validateProjectFiles = (projectFiles: { filename?: string; newFilename?: string }): Array => { - const errors = []; + const errors: string[] = []; if (projectFiles.filename) { const projectFilePath = join(uploadsFolderPath, projectFiles.filename); diff --git a/apps/server/src/modules/loadDb.ts b/apps/server/src/modules/loadDb.ts index b0d1da656..7a324588f 100644 --- a/apps/server/src/modules/loadDb.ts +++ b/apps/server/src/modules/loadDb.ts @@ -35,7 +35,7 @@ const populateDb = () => { * @param adapterToUse * @return {Promise} */ -const parseDb = async (fileToRead, adapterToUse) => { +const parseDb = async (fileToRead: string, adapterToUse: Low) => { if (validateFile(fileToRead)) { await adapterToUse.read(); } else { diff --git a/apps/server/src/services/TimerService.ts b/apps/server/src/services/TimerService.ts index ea9104f42..7b344e316 100644 --- a/apps/server/src/services/TimerService.ts +++ b/apps/server/src/services/TimerService.ts @@ -81,7 +81,7 @@ export class TimerService { const newState = runtimeState.getState(); // handle end action if there was a timer playing - if (newState.timer.playback === Playback.Play) { + if (newState.timer.playback === Playback.Play && newState.eventNow) { if (newState.eventNow.endAction === EndAction.Stop) { runtimeState.stop(); } else if (newState.eventNow.endAction === EndAction.LoadNext) { diff --git a/apps/server/src/services/__tests__/timerUtils.test.ts b/apps/server/src/services/__tests__/timerUtils.test.ts index ce363d602..ca159a9f5 100644 --- a/apps/server/src/services/__tests__/timerUtils.test.ts +++ b/apps/server/src/services/__tests__/timerUtils.test.ts @@ -860,7 +860,7 @@ describe('getRollTimers()', () => { publicIndex: null, nextIndex: 0, publicNextIndex: 4, - timeToNext: dayInMs - now + eventlist[0].timeStart, + timeToNext: dayInMs - now + eventlist[0].timeStart!, nextEvent: eventlist[0], nextPublicEvent: eventlist[4], currentEvent: null, @@ -887,7 +887,7 @@ describe('getRollTimers()', () => { publicIndex: null, nextIndex: 0, publicNextIndex: 0, - timeToNext: dayInMs - now + singleEventList[0].timeStart, + timeToNext: dayInMs - now + singleEventList[0].timeStart!, nextEvent: singleEventList[0], nextPublicEvent: singleEventList[0], currentEvent: null, diff --git a/apps/server/src/services/integration-service/integrationUtils.ts b/apps/server/src/services/integration-service/integrationUtils.ts index b69d5a207..191884c73 100644 --- a/apps/server/src/services/integration-service/integrationUtils.ts +++ b/apps/server/src/services/integration-service/integrationUtils.ts @@ -1,10 +1,11 @@ +import { MaybeNumber } from 'ontime-types'; import { millisToString, removeLeadingZero } from 'ontime-utils'; // any value inside double curly braces {{val}} const placeholderRegex = /{{(.*?)}}/g; function formatDisplayFromString(value: string, hideZero = false): string { - let valueInNumber = null; + let valueInNumber: MaybeNumber = null; if (value !== 'null') { const parsedValue = Number(value); @@ -48,7 +49,7 @@ export function parseTemplateNested(template: string, state: object, humanReadab for (const match of matches) { const variableName = match[1]; const variableParts = variableName.split('.'); - let value = undefined; + let value: string | undefined = undefined; if (variableParts[0] === 'human') { const lookupKey = variableParts[1]; diff --git a/apps/server/src/services/message-service/MessageService.ts b/apps/server/src/services/message-service/MessageService.ts index f114058e6..9a2750545 100644 --- a/apps/server/src/services/message-service/MessageService.ts +++ b/apps/server/src/services/message-service/MessageService.ts @@ -4,7 +4,7 @@ import { throttle } from '../../utils/throttle.js'; import type { PublishFn } from '../../stores/EventStore.js'; -let instance; +let instance: MessageService | null = null; class MessageService { timer: TimerMessage; @@ -56,7 +56,7 @@ class MessageService { init(publish: PublishFn) { this.publish = publish; - this.throttledSet = throttle((key, value) => this.publish(key, value), 100); + this.throttledSet = throttle((key, value) => this.publish?.(key, value), 100); } getState(): MessageState { diff --git a/apps/server/src/services/rundown-service/RundownService.ts b/apps/server/src/services/rundown-service/RundownService.ts index da6299f86..e0fb9bcd5 100644 --- a/apps/server/src/services/rundown-service/RundownService.ts +++ b/apps/server/src/services/rundown-service/RundownService.ts @@ -104,7 +104,8 @@ export async function editEvent(patch: Partial | Partial { } type CommonParams = { persistedRundown: OntimeRundown }; -type MutationParams = T & Partial; +type MutationParams = T & CommonParams; type MutatingReturn = { newRundown: OntimeRundown; newEvent?: OntimeRundownEntry; diff --git a/apps/server/src/services/runtime-service/RuntimeService.ts b/apps/server/src/services/runtime-service/RuntimeService.ts index 644840a8f..9eb2681a5 100644 --- a/apps/server/src/services/runtime-service/RuntimeService.ts +++ b/apps/server/src/services/runtime-service/RuntimeService.ts @@ -166,6 +166,9 @@ class RuntimeService { */ startById(eventId: string): boolean { const event = getEventWithId(eventId); + if (!event) { + return false; + } const success = this.loadEvent(event); if (success) { this.start(); @@ -180,6 +183,9 @@ class RuntimeService { */ startByIndex(eventIndex: number): boolean { const event = getEventAtIndex(eventIndex); + if (!event) { + return false; + } const success = this.loadEvent(event); if (success) { this.start(); @@ -194,6 +200,9 @@ class RuntimeService { */ startByCue(cue: string): boolean { const event = getEventWithCue(cue); + if (!event) { + return false; + } const success = this.loadEvent(event); if (success) { this.start(); @@ -208,6 +217,9 @@ class RuntimeService { */ loadById(eventId: string): boolean { const event = getEventWithId(eventId); + if (!event) { + return false; + } const success = this.loadEvent(event); return success; } @@ -219,6 +231,9 @@ class RuntimeService { */ loadByIndex(eventIndex: number): boolean { const event = getEventAtIndex(eventIndex); + if (!event) { + return false; + } const success = this.loadEvent(event); return success; } @@ -230,6 +245,9 @@ class RuntimeService { */ loadByCue(cue: string): boolean { const event = getEventWithCue(cue); + if (!event) { + return false; + } const success = this.loadEvent(event); return success; } diff --git a/apps/server/src/services/timerUtils.ts b/apps/server/src/services/timerUtils.ts index f0886f085..a08bfe9c6 100644 --- a/apps/server/src/services/timerUtils.ts +++ b/apps/server/src/services/timerUtils.ts @@ -14,6 +14,11 @@ export const normaliseEndTime = (start: number, end: number) => (end < start ? e */ export function getExpectedFinish(state: RuntimeState): MaybeNumber { const { startedAt, finishedAt, duration, addedTime } = state.timer; + + if (state.eventNow === null) { + return null; + } + const { timerType, timeEnd } = state.eventNow; const { pausedAt } = state._timer; const { clock } = state; @@ -33,7 +38,8 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber { } // handle events that finish the day after - const expectedFinish = startedAt + duration + addedTime + pausedTime; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- duration exists if ther eis a timer + const expectedFinish = startedAt + duration! + addedTime + pausedTime; if (expectedFinish > dayInMs) { return expectedFinish - dayInMs; } diff --git a/apps/server/src/setup.ts b/apps/server/src/setup.ts index 404ecdfe5..7c8575def 100644 --- a/apps/server/src/setup.ts +++ b/apps/server/src/setup.ts @@ -19,13 +19,13 @@ export function getAppDataPath(): string { switch (process.platform) { case 'darwin': { - return path.join(process.env.HOME, 'Library', 'Application Support', 'Ontime'); + return path.join(process.env.HOME!, 'Library', 'Application Support', 'Ontime'); } case 'win32': { - return path.join(process.env.APPDATA, 'Ontime'); + return path.join(process.env.APPDATA!, 'Ontime'); } case 'linux': { - return path.join(process.env.HOME, '.Ontime'); + return path.join(process.env.HOME!, '.Ontime'); } default: { throw new Error('Could not resolve public folder for platform'); diff --git a/apps/server/src/stores/__tests__/runtimeState.test.ts b/apps/server/src/stores/__tests__/runtimeState.test.ts index bedbc6003..384b314a3 100644 --- a/apps/server/src/stores/__tests__/runtimeState.test.ts +++ b/apps/server/src/stores/__tests__/runtimeState.test.ts @@ -77,7 +77,7 @@ describe('mutation on runtimeState', () => { // 1. Load event load(mockEvent, [mockEvent]); let newState = getState(); - expect(newState.eventNow.id).toBe(mockEvent.id); + expect(newState.eventNow?.id).toBe(mockEvent.id); expect(newState.timer.playback).toBe(Playback.Armed); expect(newState.clock).not.toBe(666); diff --git a/apps/server/src/utils/throttle.ts b/apps/server/src/utils/throttle.ts index 275d9772b..c72853a6c 100644 --- a/apps/server/src/utils/throttle.ts +++ b/apps/server/src/utils/throttle.ts @@ -8,7 +8,7 @@ */ export function throttle(cb: (...args: T) => U, delay: number) { let shouldWait = false; - let waitingArgs; + let waitingArgs: T | null = null; const timeoutFunc = () => { if (waitingArgs == null) { shouldWait = false; diff --git a/apps/server/src/utils/upload.ts b/apps/server/src/utils/upload.ts index 58144f72c..16950e791 100644 --- a/apps/server/src/utils/upload.ts +++ b/apps/server/src/utils/upload.ts @@ -1,4 +1,5 @@ -import multer from 'multer'; +import { Request } from 'express'; +import multer, { FileFilterCallback } from 'multer'; import path from 'path'; import fs from 'fs'; @@ -6,12 +7,12 @@ import { EXCEL_MIME, JSON_MIME } from './parser.js'; import { ensureDirectory } from './fileManagement.js'; import { getAppDataPath } from '../setup.js'; -function generateNewFileName(filePath, callback) { +function generateNewFileName(filePath: string, callback: (newName: string) => void) { const baseName = path.basename(filePath, path.extname(filePath)); const extension = path.extname(filePath); let counter = 1; - const checkExistence = (newPath) => { + const checkExistence = (newPath: string) => { fs.access(newPath, fs.constants.F_OK, (err) => { if (err) { // File with the new name does not exist, use this name @@ -64,7 +65,7 @@ const storage = multer.diskStorage({ * @argument file - reference to file * @return {boolean} - file allowed */ -const filterAllowed = (req, file, cb) => { +const filterAllowed = (_req: Request, file: Express.Multer.File, cb: FileFilterCallback) => { if (file.mimetype.includes(JSON_MIME) || file.mimetype.includes(EXCEL_MIME)) { cb(null, true); } else { diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index f768b1a07..0e9b4333b 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -12,7 +12,7 @@ "experimentalDecorators": true, }, "include": [ - "src/**/*" + "src/**/*.ts", + "src/**/*.js", ], - "exclude": ["node_modules", "build"], } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 624c0f387..f6b44cc41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,9 +304,15 @@ importers: specifier: ^8.13.0 version: 8.13.0 devDependencies: + '@types/cors': + specifier: ^2.8.17 + version: 2.8.17 '@types/express': specifier: ^4.17.17 version: 4.17.17 + '@types/multer': + specifier: ^1.4.11 + version: 1.4.11 '@types/node': specifier: ^18.11.18 version: 18.11.18 @@ -3161,6 +3167,12 @@ packages: '@types/node': 18.19.3 dev: true + /@types/cors@2.8.17: + resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + dependencies: + '@types/node': 18.19.3 + dev: true + /@types/debug@4.1.12: resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: @@ -3252,6 +3264,12 @@ packages: resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} dev: true + /@types/multer@1.4.11: + resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==} + dependencies: + '@types/express': 4.17.17 + dev: true + /@types/node-osc@6.0.2: resolution: {integrity: sha512-/TxCH+NlDoI3hFA6b2O91dpnPAqBDkLb2HEIv5hMVdKnCiWTdJJv2sVnQdX38sBgnals8TjCQEGii+OcMVf2fg==} dependencies: