From 2498e591566084687357c59eb0a5ab28b06ad4fc Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Fri, 6 Jun 2025 21:08:30 +0200 Subject: [PATCH] refactor: migrate custom fields to transactions refactor: extract functions to api domain refactor: strict custom field parsing refactor: remove rundown cache utilities refactor: directory restructure --- apps/client/src/common/api/customFields.ts | 10 +- .../custom-fields/CustomFieldEntry.tsx | 6 +- .../custom-fields/CustomFields.tsx | 10 +- .../rundown/event-editor/EventEditor.tsx | 5 +- .../__tests__/automation.dao.test.ts | 39 +- .../__tests__/automation.utils.test.ts | 54 +- .../automation/automation.controller.ts | 7 +- .../src/api-data/automation/automation.dao.ts | 17 +- .../api-data/automation/automation.parser.ts | 2 +- .../api-data/automation/automation.utils.ts | 24 +- .../__tests__/customFields.parser.test.ts} | 99 +--- .../custom-fields/customFields.controller.ts | 49 -- .../custom-fields/customFields.parser.ts | 64 +++ .../custom-fields/customFields.router.ts | 47 +- .../custom-fields/customFields.validation.ts | 16 +- .../api-data/db/__tests__/db.parser.test.ts | 56 +++ apps/server/src/api-data/db/db.middleware.ts | 3 +- apps/server/src/api-data/db/db.parser.ts | 49 ++ .../excel/__tests__/excel.parser.test.ts} | 451 ++++++----------- .../excel/__tests__/mockData.ts} | 0 .../src/api-data/excel/excel.middleware.ts | 3 +- .../excel/excel.parser.ts} | 170 +++---- .../src/api-data/excel/excel.service.ts | 7 +- apps/server/src/api-data/index.ts | 2 +- .../__tests__/projectData.parser.test.ts | 10 + .../projectData.controller.ts} | 2 +- .../projectData.dao.ts} | 1 + .../project-data/projectData.parser.ts | 27 + .../projectData.router.ts} | 4 +- .../projectData.validation.ts} | 0 .../rundown/__mocks__/rundown.mocks.ts | 12 +- .../rundown/__tests__/rundown.dao.test.ts | 171 ++++++- .../rundown/__tests__/rundown.parser.test.ts | 95 +++- .../rundown/__tests__/rundown.utils.test.ts | 86 +++- .../src/api-data/rundown/rundown.dao.ts | 230 +++++++-- .../src/api-data/rundown/rundown.parser.ts | 223 ++++++++- .../src/api-data/rundown/rundown.service.ts | 134 ++++- .../src/api-data/rundown/rundown.types.ts | 14 +- .../src/api-data/rundown/rundown.utils.ts | 60 ++- .../__tests__/settings.parser.test.ts | 22 + .../src/api-data/settings/settings.parser.ts | 25 + .../src/api-data/sheets/sheets.controller.ts | 14 +- .../src/api-data/sheets/sheets.middleware.ts | 3 +- .../__tests__/urlPresets.parser.test.ts | 47 ++ .../api-data/url-presets/urlPresets.parser.ts | 30 ++ .../__tests__/viewSettings.parser.ts | 10 + .../view-settings/viewSettings.parser.ts | 25 + .../__tests__/DataProvider.utils.test.ts | 84 ---- apps/server/src/models/demoProject.ts | 66 +-- .../project-service/ProjectService.ts | 18 +- .../rundown-service/RundownService.ts | 74 --- .../__mocks__/rundown.mocks.ts | 54 -- .../__tests__/rundownCache.test.ts | 127 ----- .../__tests__/rundownCache.utils.test.ts | 188 ------- .../__tests__/rundownUtils.test.ts | 24 - .../services/rundown-service/rundownCache.ts | 462 ------------------ .../rundown-service/rundownCache.utils.ts | 251 ---------- .../services/rundown-service/rundownUtils.ts | 188 ------- .../runtime-service/RuntimeService.ts | 122 +++-- .../runtime-service/rundownService.utils.ts | 96 +++- .../services/sheet-service/SheetService.ts | 25 +- apps/server/src/stores/runtimeState.ts | 12 +- .../src/utils/__tests__/parserUtils.test.ts | 38 +- .../src/utils/__tests__/varUtils.test.ts | 11 - apps/server/src/utils/fileManagement.ts | 11 +- apps/server/src/utils/generateCrashReport.ts | 6 +- apps/server/src/utils/parserFunctions.ts | 164 ------- apps/server/src/utils/parserUtils.ts | 13 +- apps/server/src/utils/varUtils.ts | 3 - .../src/definitions/core/CustomFields.type.ts | 8 +- packages/types/src/index.ts | 2 +- packages/utils/index.ts | 3 +- .../customFieldLabelToKey.ts | 21 - .../src/customField-utils/customFieldUtils.ts | 19 + packages/utils/src/rundown/rundown.utils.ts | 15 + 75 files changed, 2060 insertions(+), 2480 deletions(-) rename apps/server/src/{utils/__tests__/parserFunctions.test.ts => api-data/custom-fields/__tests__/customFields.parser.test.ts} (61%) delete mode 100644 apps/server/src/api-data/custom-fields/customFields.controller.ts create mode 100644 apps/server/src/api-data/custom-fields/customFields.parser.ts create mode 100644 apps/server/src/api-data/db/__tests__/db.parser.test.ts create mode 100644 apps/server/src/api-data/db/db.parser.ts rename apps/server/src/{utils/__tests__/parser.test.ts => api-data/excel/__tests__/excel.parser.test.ts} (74%) rename apps/server/src/{utils/__tests__/parser.mock-data.ts => api-data/excel/__tests__/mockData.ts} (100%) rename apps/server/src/{utils/parser.ts => api-data/excel/excel.parser.ts} (78%) create mode 100644 apps/server/src/api-data/project-data/__tests__/projectData.parser.test.ts rename apps/server/src/api-data/{project/project.controller.ts => project-data/projectData.controller.ts} (96%) rename apps/server/src/api-data/{project/project.dao.ts => project-data/projectData.dao.ts} (99%) create mode 100644 apps/server/src/api-data/project-data/projectData.parser.ts rename apps/server/src/api-data/{project/project.router.ts => project-data/projectData.router.ts} (70%) rename apps/server/src/api-data/{project/project.validation.ts => project-data/projectData.validation.ts} (100%) create mode 100644 apps/server/src/api-data/settings/__tests__/settings.parser.test.ts create mode 100644 apps/server/src/api-data/settings/settings.parser.ts create mode 100644 apps/server/src/api-data/url-presets/__tests__/urlPresets.parser.test.ts create mode 100644 apps/server/src/api-data/url-presets/urlPresets.parser.ts create mode 100644 apps/server/src/api-data/view-settings/__tests__/viewSettings.parser.ts create mode 100644 apps/server/src/api-data/view-settings/viewSettings.parser.ts delete mode 100644 apps/server/src/services/rundown-service/RundownService.ts delete mode 100644 apps/server/src/services/rundown-service/__mocks__/rundown.mocks.ts delete mode 100644 apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts delete mode 100644 apps/server/src/services/rundown-service/__tests__/rundownCache.utils.test.ts delete mode 100644 apps/server/src/services/rundown-service/__tests__/rundownUtils.test.ts delete mode 100644 apps/server/src/services/rundown-service/rundownCache.ts delete mode 100644 apps/server/src/services/rundown-service/rundownCache.utils.ts delete mode 100644 apps/server/src/services/rundown-service/rundownUtils.ts delete mode 100644 apps/server/src/utils/__tests__/varUtils.test.ts delete mode 100644 apps/server/src/utils/parserFunctions.ts delete mode 100644 apps/server/src/utils/varUtils.ts delete mode 100644 packages/utils/src/customField-utils/customFieldLabelToKey.ts create mode 100644 packages/utils/src/customField-utils/customFieldUtils.ts create mode 100644 packages/utils/src/rundown/rundown.utils.ts diff --git a/apps/client/src/common/api/customFields.ts b/apps/client/src/common/api/customFields.ts index ff60290ed..02ce4d15e 100644 --- a/apps/client/src/common/api/customFields.ts +++ b/apps/client/src/common/api/customFields.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { CustomField, CustomFieldLabel, CustomFields } from 'ontime-types'; +import { CustomField, CustomFieldKey, CustomFields } from 'ontime-types'; import { apiEntryUrl } from './constants'; @@ -24,15 +24,15 @@ export async function postCustomField(newField: CustomField): Promise { - const res = await axios.put(`${customFieldsPath}/${label}`, { ...newField }); +export async function editCustomField(key: CustomFieldKey, newField: CustomField): Promise { + const res = await axios.put(`${customFieldsPath}/${key}`, { ...newField }); return res.data; } /** * Deletes single custom field */ -export async function deleteCustomField(label: CustomFieldLabel): Promise { - const res = await axios.delete(`${customFieldsPath}/${label}`); +export async function deleteCustomField(key: CustomFieldKey): Promise { + const res = await axios.delete(`${customFieldsPath}/${key}`); return res.data; } diff --git a/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx b/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx index b99435319..351beadec 100644 --- a/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx +++ b/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { IoPencil, IoTrash } from 'react-icons/io5'; import { IconButton } from '@chakra-ui/react'; -import { CustomField, CustomFieldLabel } from 'ontime-types'; +import { CustomField, CustomFieldKey } from 'ontime-types'; import CopyTag from '../../../../../common/components/copy-tag/CopyTag'; import Swatch from '../../../../../common/components/input/colour-input/Swatch'; @@ -17,8 +17,8 @@ interface CustomFieldEntryProps { label: string; fieldKey: string; type: 'string' | 'image'; - onEdit: (label: CustomFieldLabel, patch: CustomField) => Promise; - onDelete: (label: CustomFieldLabel) => Promise; + onEdit: (key: CustomFieldKey, patch: CustomField) => Promise; + onDelete: (key: CustomFieldKey) => Promise; } export default function CustomFieldEntry(props: CustomFieldEntryProps) { diff --git a/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFields.tsx b/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFields.tsx index 151ff95bd..243a2d6f0 100644 --- a/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFields.tsx +++ b/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFields.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { IoAdd } from 'react-icons/io5'; import { Button } from '@chakra-ui/react'; -import { CustomField, CustomFieldLabel } from 'ontime-types'; +import { CustomField, CustomFieldKey } from 'ontime-types'; import { deleteCustomField, editCustomField, postCustomField } from '../../../../../common/api/customFields'; import Info from '../../../../../common/components/info/Info'; @@ -31,14 +31,14 @@ export default function CustomFields() { setIsAdding(false); }; - const handleEditField = async (label: CustomFieldLabel, customField: CustomField) => { - await editCustomField(label, customField); + const handleEditField = async (key: CustomFieldKey, customField: CustomField) => { + await editCustomField(key, customField); refetch(); }; - const handleDelete = async (label: string) => { + const handleDelete = async (key: CustomFieldKey) => { try { - await deleteCustomField(label); + await deleteCustomField(key); refetch(); } catch (_error) { /** we do not handle errors here */ diff --git a/apps/client/src/features/rundown/event-editor/EventEditor.tsx b/apps/client/src/features/rundown/event-editor/EventEditor.tsx index a04ab0418..867d4a016 100644 --- a/apps/client/src/features/rundown/event-editor/EventEditor.tsx +++ b/apps/client/src/features/rundown/event-editor/EventEditor.tsx @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { CustomFieldLabel, OntimeEvent } from 'ontime-types'; +import { OntimeEvent } from 'ontime-types'; import AppLink from '../../../common/components/link/app-link/AppLink'; import { useEntryActions } from '../../../common/hooks/useEntryAction'; @@ -14,7 +14,8 @@ import EventEditorEmpty from './EventEditorEmpty'; import style from './EventEditor.module.scss'; -export type EditorUpdateFields = 'cue' | 'title' | 'note' | 'colour' | CustomFieldLabel; +// any of the titles + custom field labels +export type EditorUpdateFields = 'cue' | 'title' | 'note' | 'colour' | string; interface EventEditorProps { event: OntimeEvent; diff --git a/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts b/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts index 20d869444..d5e497be5 100644 --- a/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts +++ b/apps/server/src/api-data/automation/__tests__/automation.dao.test.ts @@ -1,4 +1,6 @@ -import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation } from 'ontime-types'; +import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation, EntryId } from 'ontime-types'; + +import { makeRundown } from '../../rundown/__mocks__/rundown.mocks.js'; import { addTrigger, @@ -12,6 +14,7 @@ import { getAutomationTriggers, getAutomations, } from '../automation.dao.js'; + import { makeOSCAction, makeHTTPAction } from './testUtils.js'; beforeAll(() => { @@ -186,11 +189,9 @@ describe('editAutomation()', async () => { }); describe('deleteAutomation()', () => { - // saving the ID of the added automation - let firstAutomation: Automation; beforeEach(async () => { await deleteAll(); - firstAutomation = await addAutomation({ + await addAutomation({ title: 'test-osc', filterRule: 'all', filters: [], @@ -198,35 +199,15 @@ describe('deleteAutomation()', () => { }); }); - it('should remove m automation from the list', async () => { + it('should remove an automation from the list', async () => { const automations = getAutomations(); expect(Object.keys(automations).length).toEqual(1); - await deleteAutomation(Object.keys(automations)[0]); + const rundown = makeRundown({}); + const timedEventOrder: EntryId[] = []; + + await deleteAutomation(rundown, timedEventOrder, Object.keys(automations)[0]); const removed = getAutomations(); expect(Object.keys(removed).length).toEqual(0); }); - - it('should not remove an automation which is in use', async () => { - const automations = getAutomations(); - await addTrigger({ - title: 'test-automation', - trigger: TimerLifeCycle.onLoad, - automationId: firstAutomation.id, - }); - - const automationKeys = Object.keys(automations); - const automationId = automationKeys[0]; - expect(automationId).toEqual(firstAutomation.id); - expect(automationKeys.length).toEqual(1); - expect(automations[automationId]).toMatchObject({ - id: automationId, - title: 'test-osc', - filterRule: 'all', - filters: expect.any(Array), - outputs: expect.any(Array), - }); - - await expect(deleteAutomation(automationId)).rejects.toThrowError(); - }); }); diff --git a/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts b/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts index 6c7ad2b71..7dc03a253 100644 --- a/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts +++ b/apps/server/src/api-data/automation/__tests__/automation.utils.test.ts @@ -1,4 +1,6 @@ -import { parseTemplateNested, stringToOSCArgs } from '../automation.utils.js'; +import { TimerLifeCycle } from 'ontime-types'; +import { makeOntimeEvent, makeRundown } from '../../rundown/__mocks__/rundown.mocks.js'; +import { isAutomationUsed, parseTemplateNested, stringToOSCArgs } from '../automation.utils.js'; describe('parseTemplateNested()', () => { it('parses string with a single-level variable name', () => { @@ -245,3 +247,53 @@ describe('test stringToOSCArgs()', () => { expect(stringToOSCArgs(test)).toStrictEqual(expected); }); }); + +describe('isAutomationUsed()', () => { + it('returns the first event which uses an automation', () => { + const rundown = makeRundown({ + entries: { + '1': makeOntimeEvent({ + id: '1', + triggers: [ + { + id: 'trigger-1', + title: 'Trigger 1', + trigger: TimerLifeCycle.onClock, + automationId: 'test-automation', + }, + ], + }), + }, + }); + + const timedEventOrder = ['1']; + const automationId = 'test-automation'; + + const result = isAutomationUsed(rundown, timedEventOrder, automationId); + expect(result).toBe('1'); + }); + + it('returns returns undefined if there are no matches', () => { + const rundown = makeRundown({ + entries: { + '1': makeOntimeEvent({ + id: '1', + triggers: [ + { + id: 'trigger-1', + title: 'Trigger 1', + trigger: TimerLifeCycle.onClock, + automationId: 'test-automation', + }, + ], + }), + }, + }); + + const timedEventOrder = ['1']; + const automationId = 'does-not-exist'; + + const result = isAutomationUsed(rundown, timedEventOrder, automationId); + expect(result).toBeUndefined(); + }); +}); diff --git a/apps/server/src/api-data/automation/automation.controller.ts b/apps/server/src/api-data/automation/automation.controller.ts index 4d121c523..b7790ffe5 100644 --- a/apps/server/src/api-data/automation/automation.controller.ts +++ b/apps/server/src/api-data/automation/automation.controller.ts @@ -5,6 +5,8 @@ import type { Request, Response } from 'express'; import { oscServer } from '../../adapters/OscAdapter.js'; +import { getCurrentRundown, getRundownMetadata } from '../rundown/rundown.dao.js'; + import * as automationDao from './automation.dao.js'; import * as automationService from './automation.service.js'; import { parseOutput } from './automation.validation.js'; @@ -106,7 +108,10 @@ export async function editAutomation(req: Request, res: Response) { try { - await automationDao.deleteAutomation(req.params.id); + const rundown = getCurrentRundown(); + const { timedEventOrder } = getRundownMetadata(); + + await automationDao.deleteAutomation(rundown, timedEventOrder, req.params.id); res.status(204).send(); } catch (error) { const message = getErrorMessage(error); diff --git a/apps/server/src/api-data/automation/automation.dao.ts b/apps/server/src/api-data/automation/automation.dao.ts index a2ddadf41..e882675cc 100644 --- a/apps/server/src/api-data/automation/automation.dao.ts +++ b/apps/server/src/api-data/automation/automation.dao.ts @@ -2,14 +2,17 @@ import type { Automation, AutomationDTO, AutomationSettings, + EntryId, NormalisedAutomation, + Rundown, Trigger, TriggerDTO, } from 'ontime-types'; import { deleteAtIndex, generateId } from 'ontime-utils'; import { getDataProvider } from '../../classes/data-provider/DataProvider.js'; -import { getTimedEvents } from '../../services/rundown-service/rundownUtils.js'; + +import { isAutomationUsed } from './automation.utils.js'; /** * Gets a copy of the stored automation settings @@ -133,7 +136,7 @@ export async function editAutomation(id: string, newAutomation: AutomationDTO): /** * Deletes a automation given its ID */ -export async function deleteAutomation(id: string): Promise { +export async function deleteAutomation(rundown: Rundown, timedEventOrder: EntryId[], id: string): Promise { const automations = getAutomations(); // ignore request if automation does not exist if (!Object.hasOwn(automations, id)) { @@ -149,13 +152,9 @@ export async function deleteAutomation(id: string): Promise { } // prevent deleting a automation that is in use in events - const events = getTimedEvents().filter( - (event) => event.triggers && event.triggers.some((trigger) => trigger.automationId === id), - ); - if (events.length) { - throw new Error( - `Unable to delete automation used in event: ${events[0].id}${events.length > 1 ? ` and ${events.length - 1} more` : ''}`, - ); + const isInUse = isAutomationUsed(rundown, timedEventOrder, id); + if (isInUse) { + throw new Error(`Unable to delete automation used in event with ID ${isInUse}`); } delete automations[id]; diff --git a/apps/server/src/api-data/automation/automation.parser.ts b/apps/server/src/api-data/automation/automation.parser.ts index 27fb635c5..9d56217df 100644 --- a/apps/server/src/api-data/automation/automation.parser.ts +++ b/apps/server/src/api-data/automation/automation.parser.ts @@ -1,7 +1,7 @@ import { DatabaseModel, AutomationSettings, NormalisedAutomation, Trigger } from 'ontime-types'; import { dbModel } from '../../models/dataModel.js'; -import type { ErrorEmitter } from '../../utils/parser.js'; +import type { ErrorEmitter } from '../../utils/parserUtils.js'; interface LegacyData extends Partial { http?: unknown; diff --git a/apps/server/src/api-data/automation/automation.utils.ts b/apps/server/src/api-data/automation/automation.utils.ts index 188d50c9d..fddc54eca 100644 --- a/apps/server/src/api-data/automation/automation.utils.ts +++ b/apps/server/src/api-data/automation/automation.utils.ts @@ -1,4 +1,4 @@ -import { FilterRule, MaybeNumber, OntimeAction } from 'ontime-types'; +import { EntryId, FilterRule, isOntimeEvent, MaybeNumber, OntimeAction, Rundown } from 'ontime-types'; import { millisToString, removeLeadingZero, splitWhitespace, getPropertyFromPath } from 'ontime-utils'; import type { OscArgOrArrayInput, OscArgInput } from 'osc-min'; @@ -195,3 +195,25 @@ export function isBooleanEquals(a: boolean, b: string): boolean { } return false; } + +/** + * Checks is an automation is used in a rundown + * TODO(v4): this currently only checks the current rundown, we will need to check all rundowns in the future + */ +export function isAutomationUsed( + rundown: Rundown, + timedEventOrder: EntryId[], + automationId: string, +): EntryId | undefined { + for (let i = 0; i < timedEventOrder.length; i++) { + const eventId = timedEventOrder[i]; + const event = rundown.entries[eventId]; + if (isOntimeEvent(event) && event.triggers) { + for (const trigger of event.triggers) { + if (trigger.automationId === automationId) { + return eventId; + } + } + } + } +} diff --git a/apps/server/src/utils/__tests__/parserFunctions.test.ts b/apps/server/src/api-data/custom-fields/__tests__/customFields.parser.test.ts similarity index 61% rename from apps/server/src/utils/__tests__/parserFunctions.test.ts rename to apps/server/src/api-data/custom-fields/__tests__/customFields.parser.test.ts index ba3437684..c88ed5ccb 100644 --- a/apps/server/src/utils/__tests__/parserFunctions.test.ts +++ b/apps/server/src/api-data/custom-fields/__tests__/customFields.parser.test.ts @@ -1,101 +1,6 @@ -import { CustomFields, Settings, URLPreset } from 'ontime-types'; +import { CustomFields } from 'ontime-types'; -import { - parseCustomFields, - parseProject, - parseSettings, - parseUrlPresets, - parseViewSettings, - sanitiseCustomFields, -} from '../parserFunctions.js'; - -describe('parseProject()', () => { - it('returns an a base model if nothing is given', () => { - const errorEmitter = vi.fn(); - const result = parseProject({}, errorEmitter); - expect(result).toBeTypeOf('object'); - expect(errorEmitter).toHaveBeenCalledOnce(); - }); - - it('test migration with adding the logo field v3.8.0', () => { - const errorEmitter = vi.fn(); - const result = parseProject( - { - //@ts-expect-error -- checking migration when the logo field is added - project: { - title: 'title', - description: 'description', - publicUrl: 'publicUrl', - publicInfo: 'publicInfo', - backstageUrl: 'backstageUrl', - backstageInfo: 'backstageInfo', - custom: [], - }, - }, - errorEmitter, - ); - expect(result).toStrictEqual({ - title: 'title', - description: 'description', - publicUrl: 'publicUrl', - publicInfo: 'publicInfo', - backstageUrl: 'backstageUrl', - backstageInfo: 'backstageInfo', - projectLogo: null, - custom: [], - }); - }); -}); - -describe('parseSettings()', () => { - it('throws if settings object does not exist', () => { - expect(() => parseSettings({})).toThrow(); - }); - - it('returns an a base model as long as we have the app version', () => { - const result = parseSettings({ settings: { version: '1' } as Settings }); - expect(result).toBeTypeOf('object'); - expect(result).toMatchObject({ - version: expect.any(String), - serverPort: 4001, - editorKey: null, - operatorKey: null, - timeFormat: '24', - language: 'en', - }); - }); -}); - -describe('parseViewSettings()', () => { - it('returns an a base model if nothing is given', () => { - const errorEmitter = vi.fn(); - const result = parseViewSettings({}, errorEmitter); - expect(result).toBeTypeOf('object'); - expect(errorEmitter).toHaveBeenCalledOnce(); - }); -}); - -describe('parseUrlPresets()', () => { - it('returns an a base model if nothing is given', () => { - const errorEmitter = vi.fn(); - const result = parseUrlPresets({}, errorEmitter); - expect(result).toBeTypeOf('object'); - expect(errorEmitter).toHaveBeenCalledOnce(); - }); - - it('parses data, skipping invalid results', () => { - const errorEmitter = vi.fn(); - const urlPresets = [{ enabled: true, alias: 'alias', pathAndParams: 'ss' }] as URLPreset[]; - const result = parseUrlPresets({ urlPresets }, errorEmitter); - expect(result.length).toEqual(1); - expect(result.at(0)).toMatchObject({ - enabled: true, - alias: 'alias', - pathAndParams: 'ss', - }); - expect(errorEmitter).not.toHaveBeenCalled(); - }); -}); +import { parseCustomFields, sanitiseCustomFields } from '../customFields.parser.js'; describe('parseCustomFields()', () => { it('returns an a base model if nothing is given', () => { diff --git a/apps/server/src/api-data/custom-fields/customFields.controller.ts b/apps/server/src/api-data/custom-fields/customFields.controller.ts deleted file mode 100644 index aaf6d6b67..000000000 --- a/apps/server/src/api-data/custom-fields/customFields.controller.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { CustomField, CustomFields, ErrorResponse } from 'ontime-types'; - -import type { Request, Response } from 'express'; - -import { getErrorMessage } from 'ontime-utils'; - -import { createCustomField, editCustomField, removeCustomField } from '../../services/rundown-service/rundownCache.js'; - -import { getProjectCustomFields } from '../rundown/rundown.dao.js'; - -export async function getCustomFields(_req: Request, res: Response) { - const customFields = getProjectCustomFields(); - res.json(customFields); -} - -export async function postCustomField(req: Request, res: Response) { - try { - const newField = req.body as CustomField; - const allFields = await createCustomField(newField); - res.status(201).send(allFields); - } catch (error) { - const message = getErrorMessage(error); - res.status(400).send({ message }); - } -} - -export async function putCustomField(req: Request, res: Response) { - try { - const oldLabel = req.params.label; - const { colour, type, label } = req.body; - const newFields = await editCustomField(oldLabel, { label, colour, type }); - res.status(200).send(newFields); - } catch (error) { - const message = getErrorMessage(error); - res.status(400).send({ message }); - } -} - -// Expects { label: