From 088927bbbb81a41607322c84d67b4e909ef1095f Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:08:08 +0100 Subject: [PATCH] refactor: organise API around resources (#798) --- apps/client/src/common/api/aliases.ts | 21 + apps/client/src/common/api/apiUtils.ts | 46 -- .../api/{apiConstants.ts => constants.ts} | 6 +- apps/client/src/common/api/customFields.ts | 38 ++ apps/client/src/common/api/db.ts | 155 ++++++ apps/client/src/common/api/eventsApi.ts | 103 ---- apps/client/src/common/api/external.ts | 19 + apps/client/src/common/api/http.ts | 21 + apps/client/src/common/api/ontimeApi.ts | 397 --------------- apps/client/src/common/api/osc.ts | 21 + apps/client/src/common/api/project.ts | 21 + apps/client/src/common/api/projectDataApi.ts | 21 - apps/client/src/common/api/rundown.ts | 86 ++++ apps/client/src/common/api/settings.ts | 21 + apps/client/src/common/api/sheets.ts | 67 +++ .../{utils/fileDownload.ts => api/utils.ts} | 57 ++- apps/client/src/common/api/viewSettings.ts | 21 + .../src/common/hooks-query/useAliases.ts | 4 +- .../src/common/hooks-query/useCustomFields.ts | 4 +- .../src/common/hooks-query/useHttpSettings.ts | 6 +- apps/client/src/common/hooks-query/useInfo.ts | 4 +- .../src/common/hooks-query/useOscSettings.ts | 6 +- .../src/common/hooks-query/useProjectData.ts | 4 +- .../src/common/hooks-query/useProjectList.ts | 4 +- .../src/common/hooks-query/useRundown.ts | 6 +- .../src/common/hooks-query/useSettings.ts | 4 +- .../src/common/hooks-query/useViewSettings.ts | 4 +- .../client/src/common/hooks/useEventAction.ts | 6 +- apps/client/src/common/hooks/useMemoisedFn.ts | 2 +- apps/client/src/common/utils/socket.ts | 2 +- apps/client/src/common/utils/time.ts | 2 +- .../panel/about-panel/CheckUpdatesButton.tsx | 10 +- .../integrations-panel/HttpIntegrations.tsx | 2 +- .../integrations-panel/OscIntegrations.tsx | 2 +- .../panel/log-panel/NetworkInterfaces.tsx | 2 +- .../panel/project-panel/ProjectCreateForm.tsx | 8 +- .../panel/project-panel/ProjectListItem.tsx | 4 +- .../panel/project-panel/ProjectPanel.tsx | 12 +- .../CustomFieldForm.tsx | 2 +- .../ProjectSettingsPanel.tsx | 2 +- .../panel/sources-panel/SourcesPanel.tsx | 4 +- .../panel/sources-panel/useGoogleSheet.ts | 8 +- .../modals/settings-modal/AliasesForm.tsx | 4 +- .../modals/settings-modal/AppSettings.tsx | 4 +- .../modals/settings-modal/ProjectDataForm.tsx | 4 +- .../settings-modal/ViewSettingsForm.tsx | 4 +- .../src/features/rundown/useEventSelection.ts | 2 +- .../features/viewers/backstage/Backstage.tsx | 2 +- .../src/features/viewers/clock/Clock.tsx | 2 +- .../features/viewers/countdown/Countdown.tsx | 2 +- .../viewers/lower-thirds/LowerThird.tsx | 2 +- .../viewers/minimal-timer/MinimalTimer.tsx | 2 +- .../src/features/viewers/public/Public.tsx | 2 +- .../features/viewers/studio/StudioClock.tsx | 2 +- .../src/features/viewers/timer/Timer.tsx | 2 +- apps/server/src/adapters/OscAdapter.ts | 2 +- apps/server/src/adapters/WebsocketAdapter.ts | 2 +- .../api-data/aliases/aliases.controller.ts | 31 ++ .../src/api-data/aliases/aliases.router.ts | 8 + .../api-data/aliases/aliases.validation.ts | 18 + .../custom-fields/customFields.controller.ts | 49 ++ .../custom-fields/customFields.router.ts | 14 + .../custom-fields/customFields.validation.ts} | 29 -- apps/server/src/api-data/db/db.controller.ts | 285 +++++++++++ apps/server/src/api-data/db/db.middleware.ts | 32 ++ apps/server/src/api-data/db/db.router.ts | 44 ++ apps/server/src/api-data/db/db.validation.ts | 106 ++++ .../src/api-data/http/http.controller.ts | 29 ++ apps/server/src/api-data/http/http.router.ts | 9 + .../src/api-data/http/http.validation.ts | 21 + apps/server/src/api-data/index.ts | 25 + .../server/src/api-data/osc/osc.controller.ts | 29 ++ apps/server/src/api-data/osc/osc.router.ts | 8 + .../server/src/api-data/osc/osc.validation.ts | 25 + .../api-data/project/project.controller.ts | 32 ++ .../src/api-data/project/project.router.ts | 9 + .../api-data/project/project.validation.ts | 18 + .../rundown/rundown.controller.ts} | 88 ++-- .../rundown/rundown.router.ts} | 28 +- .../rundown/rundown.validation.ts} | 0 .../api-data/settings/settings.controller.ts | 54 ++ .../src/api-data/settings/settings.router.ts | 8 + .../api-data/settings/settings.validation.ts | 19 + .../sheets/sheets.controller.ts} | 4 +- .../src/api-data/sheets/sheets.middleware.ts | 18 + .../src/api-data/sheets/sheets.router.ts | 25 + .../sheets/sheets.validation.ts} | 0 .../view-settings/viewSettings.controller.ts | 33 ++ .../view-settings/viewSettings.router.ts | 9 + .../view-settings/viewSettings.validation.ts | 20 + .../integration.controller.ts} | 3 +- .../src/api-integration/integration.router.ts | 65 +++ .../integration.utils.ts} | 1 + .../api-integration/integration.validation.ts | 0 apps/server/src/app.ts | 14 +- .../src/controllers/ontimeController.ts | 477 ------------------ .../controllers/ontimeController.validate.ts | 168 ------ .../src/controllers/projectController.ts | 80 --- apps/server/src/routes/apiRouter.ts | 44 -- apps/server/src/routes/ontimeRouter.ts | 128 ----- apps/server/src/routes/projectRouter.ts | 32 -- apps/server/src/utils/backend.types.ts | 1 - .../src/utils/generateUniqueFilename.ts | 6 +- apps/server/src/utils/upload.ts | 40 +- 104 files changed, 1741 insertions(+), 1718 deletions(-) create mode 100644 apps/client/src/common/api/aliases.ts delete mode 100644 apps/client/src/common/api/apiUtils.ts rename apps/client/src/common/api/{apiConstants.ts => constants.ts} (91%) create mode 100644 apps/client/src/common/api/customFields.ts create mode 100644 apps/client/src/common/api/db.ts delete mode 100644 apps/client/src/common/api/eventsApi.ts create mode 100644 apps/client/src/common/api/external.ts create mode 100644 apps/client/src/common/api/http.ts delete mode 100644 apps/client/src/common/api/ontimeApi.ts create mode 100644 apps/client/src/common/api/osc.ts create mode 100644 apps/client/src/common/api/project.ts delete mode 100644 apps/client/src/common/api/projectDataApi.ts create mode 100644 apps/client/src/common/api/rundown.ts create mode 100644 apps/client/src/common/api/settings.ts create mode 100644 apps/client/src/common/api/sheets.ts rename apps/client/src/common/{utils/fileDownload.ts => api/utils.ts} (53%) create mode 100644 apps/client/src/common/api/viewSettings.ts create mode 100644 apps/server/src/api-data/aliases/aliases.controller.ts create mode 100644 apps/server/src/api-data/aliases/aliases.router.ts create mode 100644 apps/server/src/api-data/aliases/aliases.validation.ts create mode 100644 apps/server/src/api-data/custom-fields/customFields.controller.ts create mode 100644 apps/server/src/api-data/custom-fields/customFields.router.ts rename apps/server/src/{controllers/projectController.validate.ts => api-data/custom-fields/customFields.validation.ts} (56%) create mode 100644 apps/server/src/api-data/db/db.controller.ts create mode 100644 apps/server/src/api-data/db/db.middleware.ts create mode 100644 apps/server/src/api-data/db/db.router.ts create mode 100644 apps/server/src/api-data/db/db.validation.ts create mode 100644 apps/server/src/api-data/http/http.controller.ts create mode 100644 apps/server/src/api-data/http/http.router.ts create mode 100644 apps/server/src/api-data/http/http.validation.ts create mode 100644 apps/server/src/api-data/index.ts create mode 100644 apps/server/src/api-data/osc/osc.controller.ts create mode 100644 apps/server/src/api-data/osc/osc.router.ts create mode 100644 apps/server/src/api-data/osc/osc.validation.ts create mode 100644 apps/server/src/api-data/project/project.controller.ts create mode 100644 apps/server/src/api-data/project/project.router.ts create mode 100644 apps/server/src/api-data/project/project.validation.ts rename apps/server/src/{controllers/rundownController.ts => api-data/rundown/rundown.controller.ts} (50%) rename apps/server/src/{routes/rundownRouter.ts => api-data/rundown/rundown.router.ts} (51%) rename apps/server/src/{controllers/rundownController.validate.ts => api-data/rundown/rundown.validation.ts} (100%) create mode 100644 apps/server/src/api-data/settings/settings.controller.ts create mode 100644 apps/server/src/api-data/settings/settings.router.ts create mode 100644 apps/server/src/api-data/settings/settings.validation.ts rename apps/server/src/{controllers/sheetsController.ts => api-data/sheets/sheets.controller.ts} (94%) create mode 100644 apps/server/src/api-data/sheets/sheets.middleware.ts create mode 100644 apps/server/src/api-data/sheets/sheets.router.ts rename apps/server/src/{controllers/sheetController.validate.ts => api-data/sheets/sheets.validation.ts} (100%) create mode 100644 apps/server/src/api-data/view-settings/viewSettings.controller.ts create mode 100644 apps/server/src/api-data/view-settings/viewSettings.router.ts create mode 100644 apps/server/src/api-data/view-settings/viewSettings.validation.ts rename apps/server/src/{controllers/integrationController.ts => api-integration/integration.controller.ts} (98%) create mode 100644 apps/server/src/api-integration/integration.router.ts rename apps/server/src/{controllers/integrationController.config.ts => api-integration/integration.utils.ts} (99%) create mode 100644 apps/server/src/api-integration/integration.validation.ts delete mode 100644 apps/server/src/controllers/ontimeController.ts delete mode 100644 apps/server/src/controllers/ontimeController.validate.ts delete mode 100644 apps/server/src/controllers/projectController.ts delete mode 100644 apps/server/src/routes/apiRouter.ts delete mode 100644 apps/server/src/routes/ontimeRouter.ts delete mode 100644 apps/server/src/routes/projectRouter.ts delete mode 100644 apps/server/src/utils/backend.types.ts diff --git a/apps/client/src/common/api/aliases.ts b/apps/client/src/common/api/aliases.ts new file mode 100644 index 000000000..7eacb2e29 --- /dev/null +++ b/apps/client/src/common/api/aliases.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; +import { Alias } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const aliasesPath = `${apiEntryUrl}/aliases`; + +/** + * HTTP request to retrieve aliases + */ +export async function getAliases(): Promise { + const res = await axios.get(aliasesPath); + return res.data; +} + +/** + * HTTP request to mutate aliases + */ +export async function postAliases(data: Alias[]): Promise { + return axios.post(aliasesPath, data); +} diff --git a/apps/client/src/common/api/apiUtils.ts b/apps/client/src/common/api/apiUtils.ts deleted file mode 100644 index 815454ab7..000000000 --- a/apps/client/src/common/api/apiUtils.ts +++ /dev/null @@ -1,46 +0,0 @@ -import axios, { AxiosError } from 'axios'; -import { LogLevel } from 'ontime-types'; -import { generateId, millisToString } from 'ontime-utils'; - -import { ontimeQueryClient } from '../queryClient'; -import { addLog } from '../stores/logger'; -import { nowInMillis } from '../utils/time'; - -export function maybeAxiosError(error: unknown) { - if (axios.isAxiosError(error)) { - const statusText = (error as AxiosError).response?.statusText ?? ''; - let data = (error as AxiosError).response?.data ?? ''; - if (typeof data === 'object') { - if ('message' in data) { - data = JSON.stringify(data.message); - } else { - data = JSON.stringify(data); - } - } - return `${statusText}: ${data}`; - } else { - if (typeof error !== 'string') { - return JSON.stringify(error); - } - return error; - } -} - -export function logAxiosError(prepend: string, error: unknown) { - const message = `${prepend}: ${maybeAxiosError(error)}`; - - addLog({ - id: generateId(), - origin: 'SERVER', - time: millisToString(nowInMillis()), - level: LogLevel.Error, - text: message, - }); -} - -/** - * Utility function invalidates react-query caches - */ -export async function invalidateAllCaches() { - await ontimeQueryClient.invalidateQueries(); -} diff --git a/apps/client/src/common/api/apiConstants.ts b/apps/client/src/common/api/constants.ts similarity index 91% rename from apps/client/src/common/api/apiConstants.ts rename to apps/client/src/common/api/constants.ts index 290ccda4c..9b437eb37 100644 --- a/apps/client/src/common/api/apiConstants.ts +++ b/apps/client/src/common/api/constants.ts @@ -1,4 +1,4 @@ -// REST stuff +// keys in tanstack store export const ALIASES = ['aliases']; export const APP_INFO = ['appinfo']; export const APP_SETTINGS = ['appSettings']; @@ -12,16 +12,20 @@ export const SHEET_STATE = ['sheetState']; export const CUSTOM_FIELDS = ['customFields']; export const VIEW_SETTINGS = ['viewSettings']; +// resolve location const location = window.location; const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; export const isProduction = import.meta.env.MODE === 'production'; export const isDev = !isProduction; +// resolve port const STATIC_PORT = 4001; export const serverPort = isProduction ? location.port : STATIC_PORT; export const serverURL = `${location.protocol}//${location.hostname}:${serverPort}`; export const websocketUrl = `${socketProtocol}://${location.hostname}:${serverPort}/ws`; +export const apiEntryUrl = `${serverURL}/data`; + export const projectDataURL = `${serverURL}/project`; export const rundownURL = `${serverURL}/events`; export const ontimeURL = `${serverURL}/ontime`; diff --git a/apps/client/src/common/api/customFields.ts b/apps/client/src/common/api/customFields.ts new file mode 100644 index 000000000..ff60290ed --- /dev/null +++ b/apps/client/src/common/api/customFields.ts @@ -0,0 +1,38 @@ +import axios from 'axios'; +import { CustomField, CustomFieldLabel, CustomFields } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const customFieldsPath = `${apiEntryUrl}/custom-fields`; + +/** + * Requests list of known custom fields + */ +export async function getCustomFields(): Promise { + const res = await axios.get(customFieldsPath); + return res.data; +} + +/** + * Sets list of known custom fields + */ +export async function postCustomField(newField: CustomField): Promise { + const res = await axios.post(customFieldsPath, { ...newField }); + return res.data; +} + +/** + * Edits single custom field + */ +export async function editCustomField(label: CustomFieldLabel, newField: CustomField): Promise { + const res = await axios.put(`${customFieldsPath}/${label}`, { ...newField }); + return res.data; +} + +/** + * Deletes single custom field + */ +export async function deleteCustomField(label: CustomFieldLabel): Promise { + const res = await axios.delete(`${customFieldsPath}/${label}`); + return res.data; +} diff --git a/apps/client/src/common/api/db.ts b/apps/client/src/common/api/db.ts new file mode 100644 index 000000000..03a4b2937 --- /dev/null +++ b/apps/client/src/common/api/db.ts @@ -0,0 +1,155 @@ +import axios, { AxiosResponse } from 'axios'; +import { + CustomFields, + DatabaseModel, + GetInfo, + MessageResponse, + OntimeRundown, + ProjectData, + ProjectFileListResponse, +} from 'ontime-types'; +import { ImportMap } from 'ontime-utils'; + +import { apiEntryUrl } from './constants'; +import fileDownload from './utils'; + +const dbPath = `${apiEntryUrl}/db`; + +/** + * HTTP request to download db in JSON format + */ +export async function downloadRundown(fileName?: string) { + return fileDownload( + dbPath, + { name: fileName ?? 'rundown', type: 'json' }, + { type: 'application/json;charset=utf-8;' }, + ); +} + +/** + * HTTP request to download db in CSV format + */ +export async function downloadCSV(fileName?: string) { + return fileDownload(dbPath, { name: fileName ?? 'rundown', type: 'csv' }, { type: 'text/csv;charset=utf-8;' }); +} + +/** + * HTTP request to upload project file + */ +export async function uploadProjectFile(file: File): Promise { + const formData = new FormData(); + formData.append('project', file); + const response = await axios.post(`${dbPath}/upload`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + return response.data; +} + +/** + * Make patch changes to the objects in the db + */ +export async function patchData(patchDb: Partial): Promise> { + return await axios.patch(dbPath, patchDb); +} + +/** + * HTTP request to create a project file + */ +export async function createProject( + project: Partial< + ProjectData & { + filename: string; + } + >, +): Promise { + const res = await axios.post(`${dbPath}/new`, project); + return res.data; +} + +/** + * HTTP request to get the list of available project files + */ +export async function getProjects(): Promise { + const res = await axios.get(`${dbPath}/all`); + return res.data; +} + +/** + * HTTP request to load a project file + */ +export async function loadProject(filename: string): Promise { + const res = await axios.post(`${dbPath}/load`, { + filename, + }); + return res.data; +} + +/** + * HTTP request to duplicate a project file + */ +export async function duplicateProject(filename: string, newFilename: string): Promise { + const url = `${dbPath}/${filename}/duplicate`; + const decodedUrl = decodeURIComponent(url); + const res = await axios.post(decodedUrl, { + newFilename, + }); + return res.data; +} + +/** + * HTTP request to rename a project file + */ +export async function renameProject(filename: string, newFilename: string): Promise { + const url = `${dbPath}/${filename}/rename`; + const decodedUrl = decodeURIComponent(url); + const res = await axios.put(decodedUrl, { + newFilename, + }); + return res.data; +} + +/** + * HTTP request to delete a project file + */ +export async function deleteProject(filename: string): Promise { + const url = `${dbPath}/${filename}`; + const decodedUrl = decodeURIComponent(url); + const res = await axios.delete(decodedUrl); + return res.data; +} + +/** + * HTTP request to retrieve application info + */ +export async function getInfo(): Promise { + const res = await axios.get(`${dbPath}/info`); + return res.data; +} + +type PreviewSpreadsheetResponse = { + rundown: OntimeRundown; + customFields: CustomFields; +}; + +/** + * Make patch changes to the objects in the db + */ +export async function importSpreadsheetPreview(file: File, options: ImportMap): Promise { + const formData = new FormData(); + formData.append('spreadsheet', file); + formData.append('options', JSON.stringify(options)); + + const response: AxiosResponse = await axios.post( + `${dbPath}/spreadsheet/preview`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }, + ); + + return response.data; +} diff --git a/apps/client/src/common/api/eventsApi.ts b/apps/client/src/common/api/eventsApi.ts deleted file mode 100644 index 88c02a932..000000000 --- a/apps/client/src/common/api/eventsApi.ts +++ /dev/null @@ -1,103 +0,0 @@ -import axios from 'axios'; -import { OntimeEvent, OntimeRundown, OntimeRundownEntry, RundownCached } from 'ontime-types'; - -import { rundownURL } from './apiConstants'; - -/** - * @description HTTP request to fetch all events - * @return {Promise} - */ -export async function fetchCachedRundown(): Promise { - const res = await axios.get(`${rundownURL}/cached`); - return res.data; -} - -/** - * @deprecated use fetchCachedRundown instead - * @description HTTP request to fetch all events - * @return {Promise} - */ -export async function fetchRundown(): Promise { - const res = await axios.get(rundownURL); - return res.data; -} - -/** - * @description HTTP request to post new event - * @return {Promise} - */ -export async function requestPostEvent(data: Partial) { - return axios.post(rundownURL, data); -} - -/** - * @description HTTP request to put new event - * @return {Promise} - */ -export async function requestPutEvent(data: Partial) { - return axios.put(rundownURL, data); -} - -type BatchEditEntry = { - data: Partial; - ids: string[]; -}; - -/** - * @description HTTP request to put multiple events - * @returns {Promise} - */ -export async function requestBatchPutEvents(data: BatchEditEntry) { - return axios.put(`${rundownURL}/batchEdit`, data); -} - -export type ReorderEntry = { - eventId: string; - from: number; - to: number; -}; - -/** - * @description HTTP request to reorder events - * @return {Promise} - */ -export async function requestReorderEvent(data: ReorderEntry) { - return axios.patch(`${rundownURL}/reorder`, data); -} - -/** - * @description HTTP request to request application of delay - * @return {Promise} - */ -export async function requestApplyDelay(eventId: string) { - return axios.patch(`${rundownURL}/applydelay/${eventId}`); -} - -export type SwapEntry = { - from: string; - to: string; -}; - -/** - * @description HTTP request to swap two events - * @return {Promise} - */ -export async function requestEventSwap(data: SwapEntry) { - return axios.patch(`${rundownURL}/swap`, data); -} - -/** - * @description HTTP request to delete given event - * @return {Promise} - */ -export async function requestDelete(eventId: string) { - return axios.delete(`${rundownURL}/${eventId}`); -} - -/** - * @description HTTP request to delete all events - * @return {Promise} - */ -export async function requestDeleteAll() { - return axios.delete(`${rundownURL}/all`); -} diff --git a/apps/client/src/common/api/external.ts b/apps/client/src/common/api/external.ts new file mode 100644 index 000000000..e84e49a15 --- /dev/null +++ b/apps/client/src/common/api/external.ts @@ -0,0 +1,19 @@ +import axios from 'axios'; + +import { apiRepoLatest } from '../../externals'; + +export type HasUpdate = { + url: string; + version: string; +}; + +/** + * HTTP request to get the latest version and url from github + */ +export async function getLatestVersion(): Promise { + const res = await axios.get(`${apiRepoLatest}`); + return { + url: res.data.html_url as string, + version: res.data.tag_name as string, + }; +} diff --git a/apps/client/src/common/api/http.ts b/apps/client/src/common/api/http.ts new file mode 100644 index 000000000..e479e3c4f --- /dev/null +++ b/apps/client/src/common/api/http.ts @@ -0,0 +1,21 @@ +import axios, { AxiosResponse } from 'axios'; +import { HttpSettings } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const httpPath = `${apiEntryUrl}/http`; + +/** + * HTTP request to retrieve http settings + */ +export async function getHTTP(): Promise { + const res = await axios.get(httpPath); + return res.data; +} + +/** + * HTTP request to mutate http settings + */ +export async function postHTTP(data: HttpSettings): Promise> { + return axios.post(httpPath, data); +} diff --git a/apps/client/src/common/api/ontimeApi.ts b/apps/client/src/common/api/ontimeApi.ts deleted file mode 100644 index d9456ce26..000000000 --- a/apps/client/src/common/api/ontimeApi.ts +++ /dev/null @@ -1,397 +0,0 @@ -import axios, { AxiosResponse } from 'axios'; -import { - Alias, - AuthenticationStatus, - CustomField, - CustomFieldLabel, - CustomFields, - DatabaseModel, - GetInfo, - HttpSettings, - MessageResponse, - OntimeRundown, - OSCSettings, - ProjectData, - ProjectFileListResponse, - Settings, - ViewSettings, -} from 'ontime-types'; -import { ImportMap } from 'ontime-utils'; - -import { apiRepoLatest } from '../../externals'; -import fileDownload from '../utils/fileDownload'; - -import { ontimeURL, projectDataURL } from './apiConstants'; - -/** - * @description HTTP request to retrieve application settings - * @return {Promise} - */ -export async function getSettings(): Promise { - const res = await axios.get(`${ontimeURL}/settings`); - return res.data; -} - -/** - * @description HTTP request to mutate application settings - * @return {Promise} - */ -export async function postSettings(data: Settings) { - return axios.post(`${ontimeURL}/settings`, data); -} - -/** - * @description HTTP request to retrieve application info - * @return {Promise} - */ -export async function getInfo(): Promise { - const res = await axios.get(`${ontimeURL}/info`); - return res.data; -} - -/** - * @description HTTP request to retrieve view settings - * @return {Promise} - */ -export async function getView(): Promise { - const res = await axios.get(`${ontimeURL}/views`); - return res.data; -} - -/** - * @description HTTP request to mutate view settings - * @return {Promise} - */ -export async function postViewSettings(data: ViewSettings) { - return axios.post(`${ontimeURL}/views`, data); -} - -/** - * @description HTTP request to retrieve aliases - * @return {Promise} - */ -export async function getAliases(): Promise { - const res = await axios.get(`${ontimeURL}/aliases`); - return res.data; -} - -/** - * @description HTTP request to mutate aliases - * @return {Promise} - */ -export async function postAliases(data: Alias[]) { - return axios.post(`${ontimeURL}/aliases`, data); -} - -/** - * @description HTTP request to retrieve osc settings - * @return {Promise} - */ -export async function getOSC(): Promise { - const res = await axios.get(`${ontimeURL}/osc`); - return res.data; -} - -/** - * @description HTTP request to mutate osc settings - * @return {Promise} - */ -export async function postOSC(data: OSCSettings): Promise> { - return axios.post(`${ontimeURL}/osc`, data); -} - -/** - * @description HTTP request to retrieve http settings - * @return {Promise} - */ -export async function getHTTP(): Promise { - const res = await axios.get(`${ontimeURL}/http`); - return res.data; -} - -/** - * @description HTTP request to mutate http settings - * @return {Promise} - */ -export async function postHTTP(data: HttpSettings): Promise> { - return axios.post(`${ontimeURL}/http`, data); -} - -/** - * @description HTTP request to download db in CSV format - */ -export const downloadCSV = (fileName?: string) => { - return fileDownload(ontimeURL, { name: fileName ?? 'rundown', type: 'csv' }, { type: 'text/csv;charset=utf-8;' }); -}; - -/** - * @description HTTP request to download db in JSON format - */ -export const downloadRundown = (fileName?: string) => { - return fileDownload( - ontimeURL, - { name: fileName ?? 'rundown', type: 'json' }, - { type: 'application/json;charset=utf-8;' }, - ); -}; - -/** - * @description HTTP request to upload project file - * @return {Promise} - */ -export async function importProjectFile(file: File): Promise { - const formData = new FormData(); - formData.append('userFile', file); - const response = await axios.post(`${ontimeURL}/db`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - return response.data; -} - -// TODO: should this be extracted to shared code? -export type ProjectFileImportOptions = { - onlyRundown: boolean; -}; - -/** - * @description HTTP request to upload events db - * @return {Promise} - */ -export const uploadProjectFile = async ( - file: File, - setProgress: (value: number) => void, - options?: Partial, -) => { - const formData = new FormData(); - formData.append('userFile', file); - - const onlyRundown = Boolean(options?.onlyRundown); - - await axios - .post(`${ontimeURL}/db?onlyRundown=${onlyRundown}`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - onUploadProgress: (progressEvent) => { - const complete = progressEvent?.total ? Math.round((progressEvent.loaded * 100) / progressEvent.total) : 0; - setProgress(complete); - }, - }) - .then((response) => response.data.id); -}; - -/** - * @description Make patch changes to the objects in the db - * @return {Promise} - */ -export async function patchData(patchDb: Partial): Promise { - return await axios.patch(`${ontimeURL}/db`, patchDb); -} - -type PreviewSpreadsheetResponse = { - rundown: OntimeRundown; - customFields: CustomFields; -}; - -/** - * @description Make patch changes to the objects in the db - * @return {Promise} - returns parsed rundown and customFields - */ -export async function importSpreadsheetPreview(file: File, options: ImportMap): Promise { - const formData = new FormData(); - formData.append('userFile', file); - formData.append('options', JSON.stringify(options)); - - const response: AxiosResponse = await axios.post( - `${ontimeURL}/spreadsheet/preview`, - formData, - { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }, - ); - - return response.data; -} - -export type HasUpdate = { - url: string; - version: string; -}; - -/** - * @description HTTP request to get the latest version and url from github - * @return {Promise} - */ -export async function getLatestVersion(): Promise { - const res = await axios.get(`${apiRepoLatest}`); - return { - url: res.data.html_url as string, - version: res.data.tag_name as string, - }; -} - -/** - * @description HTTP request to get the list of available project files - */ -export async function getProjects(): Promise { - const res = await axios.get(`${ontimeURL}/projects`); - return res.data; -} - -/** - * @description HTTP request to load a project file - */ -export async function loadProject(filename: string): Promise { - const res = await axios.post(`${ontimeURL}/load-project`, { - filename, - }); - return res.data; -} - -/** - * @description HTTP request to initiate the authentication service with google - */ -export const requestConnection = async ( - file: File, - sheetId: string, -): Promise<{ - verification_url: string; - user_code: string; -}> => { - const formData = new FormData(); - formData.append('client_secret', file); - - const response = await axios.post(`${ontimeURL}/sheet/${sheetId}/connect`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return response.data; -}; - -/** - * @description HTTP request to verify whether we are authenticated with Google Sheet service - */ -export const verifyAuthenticationStatus = async (): Promise<{ authenticated: AuthenticationStatus }> => { - const response = await axios.get(`${ontimeURL}/sheet/connect`); - return response.data; -}; - -/** - * @description HTTP request to revoke authentication to google sheet - */ -export const revokeAuthentication = async (): Promise<{ authenticated: AuthenticationStatus }> => { - const response = await axios.post(`${ontimeURL}/sheet/revoke`); - return response.data; -}; - -/** - * @description HTTP request to upload preview the contents of a google sheet as rundown - */ -export const previewRundown = async ( - sheetId: string, - options: ImportMap, -): Promise<{ - rundown: OntimeRundown; - customFields: CustomFields; -}> => { - const response = await axios.post(`${ontimeURL}/sheet/${sheetId}/read`, { options }); - return response.data; -}; - -/** - * @description HTTP request to upload the rundown to a google sheet - */ -export const uploadRundown = async (sheetId: string, options: ImportMap): Promise => { - const response = await axios.post(`${ontimeURL}/sheet/${sheetId}/write`, { options }); - return response.data; -}; - -/** - * @description HTTP request to rename a project file - */ -export async function renameProject(filename: string, newFilename: string): Promise { - const url = `${ontimeURL}/project/${filename}/rename`; - const decodedUrl = decodeURIComponent(url); - const res = await axios.put(decodedUrl, { - newFilename, - }); - return res.data; -} - -/** - * @description HTTP request to duplicate a project file - */ -export async function duplicateProject(filename: string, newFilename: string): Promise { - const url = `${ontimeURL}/project/${filename}/duplicate`; - const decodedUrl = decodeURIComponent(url); - const res = await axios.post(decodedUrl, { - newFilename, - }); - return res.data; -} - -/** - * @description HTTP request to delete a project file - */ -export async function deleteProject(filename: string): Promise { - const url = `${ontimeURL}/project/${filename}`; - const decodedUrl = decodeURIComponent(url); - const res = await axios.delete(decodedUrl); - return res.data; -} - -/** - * @description HTTP request to create a project file - */ -export async function createProject( - project: Partial< - ProjectData & { - filename: string; - } - >, -): Promise { - const res = await axios.post(`${ontimeURL}/project`, project); - return res.data; -} - -/** - * Requests list of known custom fields - */ -export async function getCustomFields(): Promise { - const res = await axios.get(`${projectDataURL}/custom-field`); - return res.data; -} - -/** - * Sets list of known custom fields - */ -export async function postCustomField(newField: CustomField): Promise { - const res = await axios.post(`${projectDataURL}/custom-field`, { - ...newField, - }); - return res.data; -} - -/** - * Edits single custom field - */ -export async function editCustomField(label: CustomFieldLabel, newField: CustomField): Promise { - const res = await axios.put(`${projectDataURL}/custom-field/${label}`, { - ...newField, - }); - return res.data; -} - -/** - * Deletes single custom field - */ -export async function deleteCustomField(label: CustomFieldLabel): Promise { - const res = await axios.delete(`${projectDataURL}/custom-field/${label}`); - return res.data; -} diff --git a/apps/client/src/common/api/osc.ts b/apps/client/src/common/api/osc.ts new file mode 100644 index 000000000..e3f0aa834 --- /dev/null +++ b/apps/client/src/common/api/osc.ts @@ -0,0 +1,21 @@ +import axios, { AxiosResponse } from 'axios'; +import { OSCSettings } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const oscPath = `${apiEntryUrl}/osc`; + +/** + * HTTP request to retrieve osc settings + */ +export async function getOSC(): Promise { + const res = await axios.get(oscPath); + return res.data; +} + +/** + * HTTP request to mutate osc settings + */ +export async function postOSC(data: OSCSettings): Promise> { + return axios.post(oscPath, data); +} diff --git a/apps/client/src/common/api/project.ts b/apps/client/src/common/api/project.ts new file mode 100644 index 000000000..11b14d267 --- /dev/null +++ b/apps/client/src/common/api/project.ts @@ -0,0 +1,21 @@ +import axios, { AxiosResponse } from 'axios'; +import { ProjectData } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const projectPath = `${apiEntryUrl}/project`; + +/** + * HTTP request to fetch project data + */ +export async function getProjectData(): Promise { + const res = await axios.get(projectPath); + return res.data; +} + +/** + * HTTP request to mutate project data + */ +export async function postProjectData(data: ProjectData): Promise> { + return axios.post(projectPath, data); +} diff --git a/apps/client/src/common/api/projectDataApi.ts b/apps/client/src/common/api/projectDataApi.ts deleted file mode 100644 index b89bebffb..000000000 --- a/apps/client/src/common/api/projectDataApi.ts +++ /dev/null @@ -1,21 +0,0 @@ -import axios from 'axios'; -import { ProjectData } from 'ontime-types'; - -import { projectDataURL } from './apiConstants'; - -/** - * @description HTTP request to fetch project data - * @return {Promise} - */ -export async function getProjectData(): Promise { - const res = await axios.get(projectDataURL); - return res.data; -} - -/** - * @description HTTP request to mutate project data - * @return {Promise} - */ -export async function postProjectData(data: ProjectData) { - return axios.post(projectDataURL, data); -} diff --git a/apps/client/src/common/api/rundown.ts b/apps/client/src/common/api/rundown.ts new file mode 100644 index 000000000..2849f274d --- /dev/null +++ b/apps/client/src/common/api/rundown.ts @@ -0,0 +1,86 @@ +import axios, { AxiosResponse } from 'axios'; +import { MessageResponse, OntimeEvent, OntimeRundownEntry, RundownCached } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const rundownPath = `${apiEntryUrl}/rundown`; + +/** + * HTTP request to fetch all events + */ +export async function fetchNormalisedRundown(): Promise { + const res = await axios.get(`${rundownPath}/normalised`); + return res.data; +} + +/** + * HTTP request to post new event + */ +export async function requestPostEvent(data: Partial): Promise> { + return axios.post(rundownPath, data); +} + +/** + * HTTP request to put new event + */ +export async function requestPutEvent(data: Partial): Promise> { + return axios.put(rundownPath, data); +} + +type BatchEditEntry = { + data: Partial; + ids: string[]; +}; + +/** + * HTTP request to put multiple events + */ +export async function requestBatchPutEvents(data: BatchEditEntry): Promise> { + return axios.put(`${rundownPath}/batch`, data); +} + +export type ReorderEntry = { + eventId: string; + from: number; + to: number; +}; + +/** + * HTTP request to reorder events + */ +export async function requestReorderEvent(data: ReorderEntry): Promise> { + return axios.patch(`${rundownPath}/reorder`, data); +} + +export type SwapEntry = { + from: string; + to: string; +}; + +/** + * HTTP request to swap two events + */ +export async function requestEventSwap(data: SwapEntry): Promise> { + return axios.patch(`${rundownPath}/swap`, data); +} + +/** + * HTTP request to request application of delay + */ +export async function requestApplyDelay(eventId: string): Promise> { + return axios.patch(`${rundownPath}/applydelay/${eventId}`); +} + +/** + * HTTP request to delete given event + */ +export async function requestDelete(eventId: string): Promise> { + return axios.delete(`${rundownPath}/${eventId}`); +} + +/** + * HTTP request to delete all events + */ +export async function requestDeleteAll(): Promise> { + return axios.delete(`${rundownPath}/all`); +} diff --git a/apps/client/src/common/api/settings.ts b/apps/client/src/common/api/settings.ts new file mode 100644 index 000000000..a4a9aa1c2 --- /dev/null +++ b/apps/client/src/common/api/settings.ts @@ -0,0 +1,21 @@ +import axios, { AxiosResponse } from 'axios'; +import { Settings } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const settingsPath = `${apiEntryUrl}/settings`; + +/** + * HTTP request to retrieve application settings + */ +export async function getSettings(): Promise { + const res = await axios.get(settingsPath); + return res.data; +} + +/** + * HTTP request to mutate application settings + */ +export async function postSettings(data: Settings): Promise> { + return axios.post(settingsPath, data); +} diff --git a/apps/client/src/common/api/sheets.ts b/apps/client/src/common/api/sheets.ts new file mode 100644 index 000000000..4e43a33c1 --- /dev/null +++ b/apps/client/src/common/api/sheets.ts @@ -0,0 +1,67 @@ +import axios from 'axios'; +import { AuthenticationStatus, CustomFields, OntimeRundown } from 'ontime-types'; +import { ImportMap } from 'ontime-utils'; + +import { apiEntryUrl } from './constants'; + +const sheetsPath = `${apiEntryUrl}/sheets`; + +/** + * HTTP request to verify whether we are authenticated with Google Sheet service + */ +export const verifyAuthenticationStatus = async (): Promise<{ authenticated: AuthenticationStatus }> => { + const response = await axios.get(`${apiEntryUrl}/connect`); + return response.data; +}; + +/** + * HTTP request to initiate the authentication service with google + */ +export const requestConnection = async ( + file: File, + sheetId: string, +): Promise<{ + verification_url: string; + user_code: string; +}> => { + const formData = new FormData(); + formData.append('client_secret', file); + + const response = await axios.post(`${sheetsPath}/${sheetId}/connect`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return response.data; +}; + +/** + * HTTP request to revoke authentication to google sheet + */ +export const revokeAuthentication = async (): Promise<{ authenticated: AuthenticationStatus }> => { + const response = await axios.post(`${sheetsPath}/revoke`); + return response.data; +}; + +/** + * HTTP request to upload preview the contents of a google sheet as rundown + */ +export const previewRundown = async ( + sheetId: string, + options: ImportMap, +): Promise<{ + rundown: OntimeRundown; + customFields: CustomFields; +}> => { + const response = await axios.post(`${sheetsPath}/${sheetId}/read`, { options }); + return response.data; +}; + +/** + * HTTP request to upload the rundown to a google sheet + */ +export const uploadRundown = async (sheetId: string, options: ImportMap): Promise => { + const response = await axios.post(`${sheetsPath}/${sheetId}/write`, { options }); + return response.data; +}; diff --git a/apps/client/src/common/utils/fileDownload.ts b/apps/client/src/common/api/utils.ts similarity index 53% rename from apps/client/src/common/utils/fileDownload.ts rename to apps/client/src/common/api/utils.ts index 79cf83d81..623f0a0b2 100644 --- a/apps/client/src/common/utils/fileDownload.ts +++ b/apps/client/src/common/api/utils.ts @@ -1,6 +1,60 @@ -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; +import { LogLevel } from 'ontime-types'; +import { generateId, millisToString } from 'ontime-utils'; import { makeCSV, makeTable } from '../../features/cuesheet/cuesheetUtils'; +import { ontimeQueryClient } from '../queryClient'; +import { addLog } from '../stores/logger'; +import { nowInMillis } from '../utils/time'; + +/** + * Utility unrwap a potential axios error + * @param error + * @returns + */ +export function maybeAxiosError(error: unknown) { + if (axios.isAxiosError(error)) { + const statusText = (error as AxiosError).response?.statusText ?? ''; + let data = (error as AxiosError).response?.data ?? ''; + if (typeof data === 'object') { + if ('message' in data) { + data = JSON.stringify(data.message); + } else { + data = JSON.stringify(data); + } + } + return `${statusText}: ${data}`; + } else { + if (typeof error !== 'string') { + return JSON.stringify(error); + } + return error; + } +} + +/** + * Utility unrwaps a potential axios error and sends to logger + * @param prepend + * @param error + */ +export function logAxiosError(prepend: string, error: unknown) { + const message = `${prepend}: ${maybeAxiosError(error)}`; + + addLog({ + id: generateId(), + origin: 'SERVER', + time: millisToString(nowInMillis()), + level: LogLevel.Error, + text: message, + }); +} + +/** + * Utility function invalidates react-query caches + */ +export async function invalidateAllCaches() { + await ontimeQueryClient.invalidateQueries(); +} type FileOptions = { name: string; @@ -61,3 +115,4 @@ export default async function fileDownload(url: string, fileOptions: FileOptions URL.revokeObjectURL(downloadUrl); return; } + diff --git a/apps/client/src/common/api/viewSettings.ts b/apps/client/src/common/api/viewSettings.ts new file mode 100644 index 000000000..f835f29e7 --- /dev/null +++ b/apps/client/src/common/api/viewSettings.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; +import { ViewSettings } from 'ontime-types'; + +import { apiEntryUrl } from './constants'; + +const viewSettingsPath = `${apiEntryUrl}/view-settings`; + +/** + * HTTP request to retrieve view settings + */ +export async function getView(): Promise { + const res = await axios.get(viewSettingsPath); + return res.data; +} + +/** + * HTTP request to mutate view settings + */ +export async function postViewSettings(data: ViewSettings) { + return axios.post(viewSettingsPath, data); +} diff --git a/apps/client/src/common/hooks-query/useAliases.ts b/apps/client/src/common/hooks-query/useAliases.ts index cb54e320f..4503ffd7d 100644 --- a/apps/client/src/common/hooks-query/useAliases.ts +++ b/apps/client/src/common/hooks-query/useAliases.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { ALIASES } from '../api/apiConstants'; -import { getAliases } from '../api/ontimeApi'; +import { getAliases } from '../api/aliases'; +import { ALIASES } from '../api/constants'; export default function useAliases() { const { data, status, isFetching, isError, refetch } = useQuery({ diff --git a/apps/client/src/common/hooks-query/useCustomFields.ts b/apps/client/src/common/hooks-query/useCustomFields.ts index f74d14896..47e0a255a 100644 --- a/apps/client/src/common/hooks-query/useCustomFields.ts +++ b/apps/client/src/common/hooks-query/useCustomFields.ts @@ -2,8 +2,8 @@ import { useQuery } from '@tanstack/react-query'; import { CustomFields } from 'ontime-types'; import { queryRefetchInterval } from '../../ontimeConfig'; -import { CUSTOM_FIELDS } from '../api/apiConstants'; -import { getCustomFields } from '../api/ontimeApi'; +import { CUSTOM_FIELDS } from '../api/constants'; +import { getCustomFields } from '../api/customFields'; const placeholder: CustomFields = {}; diff --git a/apps/client/src/common/hooks-query/useHttpSettings.ts b/apps/client/src/common/hooks-query/useHttpSettings.ts index bdfdcea41..ce9089088 100644 --- a/apps/client/src/common/hooks-query/useHttpSettings.ts +++ b/apps/client/src/common/hooks-query/useHttpSettings.ts @@ -1,9 +1,9 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { HTTP_SETTINGS } from '../api/apiConstants'; -import { logAxiosError } from '../api/apiUtils'; -import { getHTTP, postHTTP } from '../api/ontimeApi'; +import { HTTP_SETTINGS } from '../api/constants'; +import { getHTTP, postHTTP } from '../api/http'; +import { logAxiosError } from '../api/utils'; import { httpPlaceholder } from '../models/Http'; import { ontimeQueryClient } from '../queryClient'; diff --git a/apps/client/src/common/hooks-query/useInfo.ts b/apps/client/src/common/hooks-query/useInfo.ts index c600e0def..680988500 100644 --- a/apps/client/src/common/hooks-query/useInfo.ts +++ b/apps/client/src/common/hooks-query/useInfo.ts @@ -2,8 +2,8 @@ import { useQuery } from '@tanstack/react-query'; import { GetInfo } from 'ontime-types'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { APP_INFO } from '../api/apiConstants'; -import { getInfo } from '../api/ontimeApi'; +import { APP_INFO } from '../api/constants'; +import { getInfo } from '../api/db'; import { ontimePlaceholderInfo } from '../models/Info'; export default function useInfo() { diff --git a/apps/client/src/common/hooks-query/useOscSettings.ts b/apps/client/src/common/hooks-query/useOscSettings.ts index 11c154ae1..fb3834fec 100644 --- a/apps/client/src/common/hooks-query/useOscSettings.ts +++ b/apps/client/src/common/hooks-query/useOscSettings.ts @@ -1,9 +1,9 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { OSC_SETTINGS } from '../api/apiConstants'; -import { logAxiosError } from '../api/apiUtils'; -import { getOSC, postOSC } from '../api/ontimeApi'; +import { OSC_SETTINGS } from '../api/constants'; +import { getOSC, postOSC } from '../api/osc'; +import { logAxiosError } from '../api/utils'; import { oscPlaceholderSettings } from '../models/OscSettings'; import { ontimeQueryClient } from '../queryClient'; diff --git a/apps/client/src/common/hooks-query/useProjectData.ts b/apps/client/src/common/hooks-query/useProjectData.ts index 4c65eb1d8..bcc1f10e5 100644 --- a/apps/client/src/common/hooks-query/useProjectData.ts +++ b/apps/client/src/common/hooks-query/useProjectData.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { PROJECT_DATA } from '../api/apiConstants'; -import { getProjectData } from '../api/projectDataApi'; +import { PROJECT_DATA } from '../api/constants'; +import { getProjectData } from '../api/project'; import { projectDataPlaceholder } from '../models/ProjectData'; export default function useProjectData() { diff --git a/apps/client/src/common/hooks-query/useProjectList.ts b/apps/client/src/common/hooks-query/useProjectList.ts index a4f587185..cfdf450be 100644 --- a/apps/client/src/common/hooks-query/useProjectList.ts +++ b/apps/client/src/common/hooks-query/useProjectList.ts @@ -2,8 +2,8 @@ import { useQuery } from '@tanstack/react-query'; import { ProjectFileListResponse } from 'ontime-types'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { PROJECT_LIST } from '../api/apiConstants'; -import { getProjects } from '../api/ontimeApi'; +import { PROJECT_LIST } from '../api/constants'; +import { getProjects } from '../api/db'; const placeholderProjectList: ProjectFileListResponse = { files: [], diff --git a/apps/client/src/common/hooks-query/useRundown.ts b/apps/client/src/common/hooks-query/useRundown.ts index 5f05f074f..639f491ce 100644 --- a/apps/client/src/common/hooks-query/useRundown.ts +++ b/apps/client/src/common/hooks-query/useRundown.ts @@ -3,8 +3,8 @@ import { useQuery } from '@tanstack/react-query'; import { NormalisedRundown, OntimeRundown, RundownCached } from 'ontime-types'; import { queryRefetchInterval } from '../../ontimeConfig'; -import { RUNDOWN } from '../api/apiConstants'; -import { fetchCachedRundown } from '../api/eventsApi'; +import { RUNDOWN } from '../api/constants'; +import { fetchNormalisedRundown } from '../api/rundown'; // revision is -1 so that the remote revision is higher const cachedRundownPlaceholder = { order: [] as string[], rundown: {} as NormalisedRundown, revision: -1 }; @@ -12,7 +12,7 @@ const cachedRundownPlaceholder = { order: [] as string[], rundown: {} as Normali export default function useRundown() { const { data, status, isError, refetch, isFetching } = useQuery({ queryKey: RUNDOWN, - queryFn: fetchCachedRundown, + queryFn: fetchNormalisedRundown, placeholderData: cachedRundownPlaceholder, retry: 5, retryDelay: (attempt) => attempt * 2500, diff --git a/apps/client/src/common/hooks-query/useSettings.ts b/apps/client/src/common/hooks-query/useSettings.ts index 2bb284b57..330305112 100644 --- a/apps/client/src/common/hooks-query/useSettings.ts +++ b/apps/client/src/common/hooks-query/useSettings.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { APP_SETTINGS } from '../api/apiConstants'; -import { getSettings } from '../api/ontimeApi'; +import { APP_SETTINGS } from '../api/constants'; +import { getSettings } from '../api/settings'; import { ontimePlaceholderSettings } from '../models/OntimeSettings'; export default function useSettings() { diff --git a/apps/client/src/common/hooks-query/useViewSettings.ts b/apps/client/src/common/hooks-query/useViewSettings.ts index 3c6abb01c..3673ae59d 100644 --- a/apps/client/src/common/hooks-query/useViewSettings.ts +++ b/apps/client/src/common/hooks-query/useViewSettings.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; -import { VIEW_SETTINGS } from '../api/apiConstants'; -import { getView } from '../api/ontimeApi'; +import { VIEW_SETTINGS } from '../api/constants'; +import { getView } from '../api/viewSettings'; import { viewsSettingsPlaceholder } from '../models/ViewSettings.type'; export default function useViewSettings() { diff --git a/apps/client/src/common/hooks/useEventAction.ts b/apps/client/src/common/hooks/useEventAction.ts index 996e4999c..7e6caa2e7 100644 --- a/apps/client/src/common/hooks/useEventAction.ts +++ b/apps/client/src/common/hooks/useEventAction.ts @@ -3,8 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { isOntimeEvent, MaybeString, OntimeEvent, OntimeRundownEntry, RundownCached } from 'ontime-types'; import { getLinkedTimes, getPreviousEventNormal, reorderArray, swapEventData } from 'ontime-utils'; -import { RUNDOWN } from '../api/apiConstants'; -import { logAxiosError } from '../api/apiUtils'; +import { RUNDOWN } from '../api/constants'; import { ReorderEntry, requestApplyDelay, @@ -16,7 +15,8 @@ import { requestPutEvent, requestReorderEvent, SwapEntry, -} from '../api/eventsApi'; +} from '../api/rundown'; +import { logAxiosError } from '../api/utils'; import { useEditorSettings } from '../stores/editorSettings'; import { forgivingStringToMillis } from '../utils/dateConfig'; diff --git a/apps/client/src/common/hooks/useMemoisedFn.ts b/apps/client/src/common/hooks/useMemoisedFn.ts index d5507e97c..d0f416e57 100644 --- a/apps/client/src/common/hooks/useMemoisedFn.ts +++ b/apps/client/src/common/hooks/useMemoisedFn.ts @@ -6,7 +6,7 @@ import { useMemo, useRef } from 'react'; -import { isDev } from '../api/apiConstants'; +import { isDev } from '../api/constants'; type noop = (this: any, ...args: any[]) => any; diff --git a/apps/client/src/common/utils/socket.ts b/apps/client/src/common/utils/socket.ts index a7aeee43f..3639c5721 100644 --- a/apps/client/src/common/utils/socket.ts +++ b/apps/client/src/common/utils/socket.ts @@ -1,6 +1,6 @@ import { Log, RuntimeStore } from 'ontime-types'; -import { isProduction, RUNTIME, websocketUrl } from '../api/apiConstants'; +import { isProduction, RUNTIME, websocketUrl } from '../api/constants'; import { ontimeQueryClient } from '../queryClient'; import { socketClientName } from '../stores/connectionName'; import { addLog } from '../stores/logger'; diff --git a/apps/client/src/common/utils/time.ts b/apps/client/src/common/utils/time.ts index 37c96f87d..f41af9e3b 100644 --- a/apps/client/src/common/utils/time.ts +++ b/apps/client/src/common/utils/time.ts @@ -2,7 +2,7 @@ import { MaybeNumber, Settings, TimeFormat } from 'ontime-types'; import { formatFromMillis } from 'ontime-utils'; import { FORMAT_12, FORMAT_24 } from '../../viewerConfig'; -import { APP_SETTINGS } from '../api/apiConstants'; +import { APP_SETTINGS } from '../api/constants'; import { ontimeQueryClient } from '../queryClient'; /** diff --git a/apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx b/apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx index e0b9eaeb4..744937e63 100644 --- a/apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx +++ b/apps/client/src/features/app-settings/panel/about-panel/CheckUpdatesButton.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Button } from '@chakra-ui/react'; -import { getLatestVersion, HasUpdate } from '../../../../common/api/ontimeApi'; +import { getLatestVersion, HasUpdate } from '../../../../common/api/external'; import ExternalLink from '../../../../common/components/external-link/ExternalLink'; import style from '../Panel.module.scss'; @@ -52,7 +52,13 @@ export default function CheckUpdatesButton(props: CheckUpdatesButtonProps) { return ( <> - diff --git a/apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx b/apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx index 02526a88b..3f3575338 100644 --- a/apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx +++ b/apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx @@ -5,7 +5,7 @@ import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { HttpSettings } from 'ontime-types'; import { generateId } from 'ontime-utils'; -import { maybeAxiosError } from '../../../../common/api/apiUtils'; +import { maybeAxiosError } from '../../../../common/api/utils'; import { useHttpSettings, usePostHttpSettings } from '../../../../common/hooks-query/useHttpSettings'; import { isKeyEscape } from '../../../../common/utils/keyEvent'; import { startsWithHttp } from '../../../../common/utils/regex'; diff --git a/apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx b/apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx index a37687516..955aa62d0 100644 --- a/apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx +++ b/apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx @@ -5,7 +5,7 @@ import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { OSCSettings } from 'ontime-types'; import { generateId } from 'ontime-utils'; -import { maybeAxiosError } from '../../../../common/api/apiUtils'; +import { maybeAxiosError } from '../../../../common/api/utils'; import useOscSettings, { useOscSettingsMutation } from '../../../../common/hooks-query/useOscSettings'; import { isKeyEscape } from '../../../../common/utils/keyEvent'; import { isIPAddress, isOnlyNumbers, startsWithSlash } from '../../../../common/utils/regex'; diff --git a/apps/client/src/features/app-settings/panel/log-panel/NetworkInterfaces.tsx b/apps/client/src/features/app-settings/panel/log-panel/NetworkInterfaces.tsx index 1ecbdd576..c1915c7b8 100644 --- a/apps/client/src/features/app-settings/panel/log-panel/NetworkInterfaces.tsx +++ b/apps/client/src/features/app-settings/panel/log-panel/NetworkInterfaces.tsx @@ -1,4 +1,4 @@ -import { serverPort } from '../../../../common/api/apiConstants'; +import { serverPort } from '../../../../common/api/constants'; import AppLink from '../../../../common/components/app-link/AppLink'; import useInfo from '../../../../common/hooks-query/useInfo'; diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectCreateForm.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectCreateForm.tsx index 767e003f4..e4f3dbe16 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectCreateForm.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectCreateForm.tsx @@ -1,9 +1,11 @@ import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { Button, Input, Textarea } from '@chakra-ui/react'; +import { useQueryClient } from '@tanstack/react-query'; -import { maybeAxiosError } from '../../../../common/api/apiUtils'; -import { createProject } from '../../../../common/api/ontimeApi'; +import { PROJECT_LIST } from '../../../../common/api/constants'; +import { createProject } from '../../../../common/api/db'; +import { maybeAxiosError } from '../../../../common/api/utils'; import * as Panel from '../PanelUtils'; import style from './ProjectPanel.module.scss'; @@ -25,6 +27,7 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) { const { onClose } = props; const [error, setError] = useState(null); + const queryClient = useQueryClient(); const { handleSubmit, @@ -53,6 +56,7 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) { ...values, filename, }); + await queryClient.invalidateQueries({ queryKey: PROJECT_LIST }); onClose(); } catch (error) { setError(maybeAxiosError(error)); diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx index 0f560dcb8..38d353582 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx @@ -2,7 +2,6 @@ import { useState } from 'react'; import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal'; -import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/apiUtils'; import { deleteProject, downloadCSV, @@ -10,7 +9,8 @@ import { duplicateProject, loadProject, renameProject, -} from '../../../../common/api/ontimeApi'; +} from '../../../../common/api/db'; +import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/utils'; import ProjectForm, { ProjectFormValues } from './ProjectForm'; diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx index 4a1328203..29a4fde58 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.tsx @@ -2,8 +2,8 @@ import { ChangeEvent, useRef, useState } from 'react'; import { Button, Input } from '@chakra-ui/react'; import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/apiUtils'; -import { importProjectFile } from '../../../../common/api/ontimeApi'; +import { uploadProjectFile } from '../../../../common/api/db'; +import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/utils'; import { validateProjectFile } from '../../../../common/utils/uploadUtils'; import * as Panel from '../PanelUtils'; @@ -37,7 +37,7 @@ export default function ProjectPanel() { try { validateProjectFile(selectedFile); - await importProjectFile(selectedFile); + await uploadProjectFile(selectedFile); } catch (error) { const errorMessage = maybeAxiosError(error); setError(`Error uploading file: ${errorMessage}`); @@ -48,6 +48,10 @@ export default function ProjectPanel() { setLoading(null); }; + const handleCloseForm = () => { + setIsCreatingProject(false); + }; + return ( <> Project @@ -85,7 +89,7 @@ export default function ProjectPanel() { {error && {error}} - {isCreatingProject && setIsCreatingProject(false)} />} + {isCreatingProject && } diff --git a/apps/client/src/features/app-settings/panel/project-settings-panel/CustomFieldForm.tsx b/apps/client/src/features/app-settings/panel/project-settings-panel/CustomFieldForm.tsx index f519d925f..081c62182 100644 --- a/apps/client/src/features/app-settings/panel/project-settings-panel/CustomFieldForm.tsx +++ b/apps/client/src/features/app-settings/panel/project-settings-panel/CustomFieldForm.tsx @@ -4,7 +4,7 @@ import { Button, Input } from '@chakra-ui/react'; import { CustomField } from 'ontime-types'; import { isAlphanumeric } from 'ontime-utils'; -import { maybeAxiosError } from '../../../../common/api/apiUtils'; +import { maybeAxiosError } from '../../../../common/api/utils'; import SwatchSelect from '../../../../common/components/input/colour-input/SwatchSelect'; import * as Panel from '../PanelUtils'; diff --git a/apps/client/src/features/app-settings/panel/project-settings-panel/ProjectSettingsPanel.tsx b/apps/client/src/features/app-settings/panel/project-settings-panel/ProjectSettingsPanel.tsx index bd8f5ff91..d72e311d9 100644 --- a/apps/client/src/features/app-settings/panel/project-settings-panel/ProjectSettingsPanel.tsx +++ b/apps/client/src/features/app-settings/panel/project-settings-panel/ProjectSettingsPanel.tsx @@ -3,7 +3,7 @@ import { Alert, AlertDescription, AlertIcon, Button } from '@chakra-ui/react'; import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; import { CustomField, CustomFieldLabel } from 'ontime-types'; -import { deleteCustomField, editCustomField, postCustomField } from '../../../../common/api/ontimeApi'; +import { deleteCustomField, editCustomField, postCustomField } from '../../../../common/api/customFields'; import ExternalLink from '../../../../common/components/external-link/ExternalLink'; import useCustomFields from '../../../../common/hooks-query/useCustomFields'; import * as Panel from '../PanelUtils'; diff --git a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx index 9f552b251..7caa5b37a 100644 --- a/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx +++ b/apps/client/src/features/app-settings/panel/sources-panel/SourcesPanel.tsx @@ -4,8 +4,8 @@ import { IoCloudOutline } from '@react-icons/all-files/io5/IoCloudOutline'; import { IoDownloadOutline } from '@react-icons/all-files/io5/IoDownloadOutline'; import { ImportMap, unpackError } from 'ontime-utils'; -import { maybeAxiosError } from '../../../../common/api/apiUtils'; -import { importSpreadsheetPreview } from '../../../../common/api/ontimeApi'; +import { importSpreadsheetPreview } from '../../../../common/api/db'; +import { maybeAxiosError } from '../../../../common/api/utils'; import { validateSpreadsheetImport } from '../../../../common/utils/uploadUtils'; import * as Panel from '../PanelUtils'; diff --git a/apps/client/src/features/app-settings/panel/sources-panel/useGoogleSheet.ts b/apps/client/src/features/app-settings/panel/sources-panel/useGoogleSheet.ts index 22586e8d1..f3d19f13d 100644 --- a/apps/client/src/features/app-settings/panel/sources-panel/useGoogleSheet.ts +++ b/apps/client/src/features/app-settings/panel/sources-panel/useGoogleSheet.ts @@ -2,16 +2,16 @@ import { useQueryClient } from '@tanstack/react-query'; import { AuthenticationStatus, CustomFields, OntimeRundown } from 'ontime-types'; import { ImportMap } from 'ontime-utils'; -import { CUSTOM_FIELDS, RUNDOWN } from '../../../../common/api/apiConstants'; -import { maybeAxiosError } from '../../../../common/api/apiUtils'; +import { CUSTOM_FIELDS, RUNDOWN } from '../../../../common/api/constants'; +import { patchData } from '../../../../common/api/db'; import { - patchData, previewRundown, requestConnection, revokeAuthentication, uploadRundown, verifyAuthenticationStatus, -} from '../../../../common/api/ontimeApi'; +} from '../../../../common/api/sheets'; +import { maybeAxiosError } from '../../../../common/api/utils'; import { useSheetStore } from './useSheetStore'; diff --git a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx index 7937e8b5e..4b14bf64e 100644 --- a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx +++ b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx @@ -5,8 +5,8 @@ import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline'; import { IoRemove } from '@react-icons/all-files/io5/IoRemove'; import { Alias } from 'ontime-types'; -import { logAxiosError } from '../../../common/api/apiUtils'; -import { postAliases } from '../../../common/api/ontimeApi'; +import { postAliases } from '../../../common/api/aliases'; +import { logAxiosError } from '../../../common/api/utils'; import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn'; import useAliases from '../../../common/hooks-query/useAliases'; import { useEmitLog } from '../../../common/stores/logger'; diff --git a/apps/client/src/features/modals/settings-modal/AppSettings.tsx b/apps/client/src/features/modals/settings-modal/AppSettings.tsx index 51d653096..458b984db 100644 --- a/apps/client/src/features/modals/settings-modal/AppSettings.tsx +++ b/apps/client/src/features/modals/settings-modal/AppSettings.tsx @@ -3,8 +3,8 @@ import { useForm } from 'react-hook-form'; import { Input, Select } from '@chakra-ui/react'; import type { Settings } from 'ontime-types'; -import { logAxiosError } from '../../../common/api/apiUtils'; -import { postSettings } from '../../../common/api/ontimeApi'; +import { postSettings } from '../../../common/api/settings'; +import { logAxiosError } from '../../../common/api/utils'; import useSettings from '../../../common/hooks-query/useSettings'; import { isOnlyNumbers } from '../../../common/utils/regex'; import ModalLoader from '../modal-loader/ModalLoader'; diff --git a/apps/client/src/features/modals/settings-modal/ProjectDataForm.tsx b/apps/client/src/features/modals/settings-modal/ProjectDataForm.tsx index bbb64c1fd..28f33b4da 100644 --- a/apps/client/src/features/modals/settings-modal/ProjectDataForm.tsx +++ b/apps/client/src/features/modals/settings-modal/ProjectDataForm.tsx @@ -3,8 +3,8 @@ import { useForm } from 'react-hook-form'; import { Input, Textarea } from '@chakra-ui/react'; import { ProjectData } from 'ontime-types'; -import { logAxiosError } from '../../../common/api/apiUtils'; -import { postProjectData } from '../../../common/api/projectDataApi'; +import { postProjectData } from '../../../common/api/project'; +import { logAxiosError } from '../../../common/api/utils'; import useProjectData from '../../../common/hooks-query/useProjectData'; import ModalLoader from '../modal-loader/ModalLoader'; import { inputProps } from '../modalHelper'; diff --git a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx index 0b738acd3..74b9f4cc6 100644 --- a/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx +++ b/apps/client/src/features/modals/settings-modal/ViewSettingsForm.tsx @@ -3,8 +3,8 @@ import { useForm } from 'react-hook-form'; import { Alert, AlertDescription, AlertIcon, AlertTitle, Input, Switch } from '@chakra-ui/react'; import { ViewSettings } from 'ontime-types'; -import { logAxiosError } from '../../../common/api/apiUtils'; -import { postViewSettings } from '../../../common/api/ontimeApi'; +import { logAxiosError } from '../../../common/api/utils'; +import { postViewSettings } from '../../../common/api/viewSettings'; import { PopoverPickerRHF } from '../../../common/components/input/popover-picker/PopoverPicker'; import useInfo from '../../../common/hooks-query/useInfo'; import useViewSettings from '../../../common/hooks-query/useViewSettings'; diff --git a/apps/client/src/features/rundown/useEventSelection.ts b/apps/client/src/features/rundown/useEventSelection.ts index 52fbcb869..d60606f1b 100644 --- a/apps/client/src/features/rundown/useEventSelection.ts +++ b/apps/client/src/features/rundown/useEventSelection.ts @@ -2,7 +2,7 @@ import { MouseEvent } from 'react'; import { isOntimeEvent, OntimeEvent, RundownCached } from 'ontime-types'; import { create } from 'zustand'; -import { RUNDOWN } from '../../common/api/apiConstants'; +import { RUNDOWN } from '../../common/api/constants'; import { ontimeQueryClient } from '../../common/queryClient'; import { isMacOS } from '../../common/utils/deviceUtils'; diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index 5a38da74e..103769b16 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -5,7 +5,7 @@ import { AnimatePresence, motion } from 'framer-motion'; import { CustomFields, Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types'; import { millisToString, removeLeadingZero } from 'ontime-utils'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import ProgressBar from '../../../common/components/progress-bar/ProgressBar'; import Schedule from '../../../common/components/schedule/Schedule'; diff --git a/apps/client/src/features/viewers/clock/Clock.tsx b/apps/client/src/features/viewers/clock/Clock.tsx index 88d3d7fa7..11f6f2200 100644 --- a/apps/client/src/features/viewers/clock/Clock.tsx +++ b/apps/client/src/features/viewers/clock/Clock.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Settings, ViewSettings } from 'ontime-types'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import { getClockOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx index 8a36603c1..c4be635d1 100644 --- a/apps/client/src/features/viewers/countdown/Countdown.tsx +++ b/apps/client/src/features/viewers/countdown/Countdown.tsx @@ -3,7 +3,7 @@ import { useSearchParams } from 'react-router-dom'; import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types'; import { millisToString, removeLeadingZero } from 'ontime-utils'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import { getCountdownOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx index b20e05b83..d33a462b8 100644 --- a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx +++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { CustomFields, Message, OntimeEvent, ViewSettings } from 'ontime-types'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index b5f637825..92fdf93c5 100644 --- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -3,7 +3,7 @@ import { useSearchParams } from 'react-router-dom'; import { Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types'; import { millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import { MINIMAL_TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx index 6c22f6552..00ab311d9 100644 --- a/apps/client/src/features/viewers/public/Public.tsx +++ b/apps/client/src/features/viewers/public/Public.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { CustomFields, Message, OntimeEvent, ProjectData, Settings, ViewSettings } from 'ontime-types'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import Schedule from '../../../common/components/schedule/Schedule'; import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext'; diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx index 29a1ee46e..7b4ddd1f3 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.tsx +++ b/apps/client/src/features/viewers/studio/StudioClock.tsx @@ -4,7 +4,7 @@ import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime- import { isOntimeEvent, Playback } from 'ontime-types'; import { millisToString, removeSeconds } from 'ontime-utils'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import { getStudioClockOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index 365cb9767..8c59679ab 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -13,7 +13,7 @@ import { } from 'ontime-types'; import { millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils'; -import { overrideStylesURL } from '../../../common/api/apiConstants'; +import { overrideStylesURL } from '../../../common/api/constants'; import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import TitleCard from '../../../common/components/title-card/TitleCard'; diff --git a/apps/server/src/adapters/OscAdapter.ts b/apps/server/src/adapters/OscAdapter.ts index b58c9226c..be48f93a2 100644 --- a/apps/server/src/adapters/OscAdapter.ts +++ b/apps/server/src/adapters/OscAdapter.ts @@ -3,9 +3,9 @@ import { LogOrigin, OSCSettings } from 'ontime-types'; import { Server } from 'node-osc'; import { IAdapter } from './IAdapter.js'; -import { dispatchFromAdapter, type ChangeOptions } from '../controllers/integrationController.js'; import { logger } from '../classes/Logger.js'; import { objectFromPath } from './utils/parse.js'; +import { type ChangeOptions, dispatchFromAdapter } from '../api-integration/integration.controller.js'; export class OscServer implements IAdapter { private readonly osc: Server; diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts index 1bf7a9289..079f8aa54 100644 --- a/apps/server/src/adapters/WebsocketAdapter.ts +++ b/apps/server/src/adapters/WebsocketAdapter.ts @@ -22,8 +22,8 @@ import type { Server } from 'http'; import getRandomName from '../utils/getRandomName.js'; import { IAdapter } from './IAdapter.js'; import { eventStore } from '../stores/EventStore.js'; -import { dispatchFromAdapter } from '../controllers/integrationController.js'; import { logger } from '../classes/Logger.js'; +import { dispatchFromAdapter } from '../api-integration/integration.controller.js'; let instance: SocketServer | null = null; diff --git a/apps/server/src/api-data/aliases/aliases.controller.ts b/apps/server/src/api-data/aliases/aliases.controller.ts new file mode 100644 index 000000000..c10097ff4 --- /dev/null +++ b/apps/server/src/api-data/aliases/aliases.controller.ts @@ -0,0 +1,31 @@ +import type { Alias, ErrorResponse } from 'ontime-types'; + +import { Request, Response } from 'express'; + +import { DataProvider } from '../../classes/data-provider/DataProvider.js'; +import { failIsNotArray } from '../../utils/routerUtils.js'; + +export async function getAliases(_req: Request, res: Response) { + const aliases = DataProvider.getAliases(); + res.status(200).send(aliases); +} + +export async function postAliases(req: Request, res: Response) { + if (failIsNotArray(req.body, res)) { + return; + } + try { + const newAliases: Alias[] = []; + req.body.forEach((a) => { + newAliases.push({ + enabled: a.enabled, + alias: a.alias, + pathAndParams: a.pathAndParams, + }); + }); + await DataProvider.setAliases(newAliases); + res.status(200).send(newAliases); + } catch (error) { + res.status(400).send({ message: String(error) }); + } +} diff --git a/apps/server/src/api-data/aliases/aliases.router.ts b/apps/server/src/api-data/aliases/aliases.router.ts new file mode 100644 index 000000000..6c40079d4 --- /dev/null +++ b/apps/server/src/api-data/aliases/aliases.router.ts @@ -0,0 +1,8 @@ +import express from 'express'; +import { getAliases, postAliases } from './aliases.controller.js'; +import { validateAliases } from './aliases.validation.js'; + +export const router = express.Router(); + +router.get('/', getAliases); +router.post('/', validateAliases, postAliases); diff --git a/apps/server/src/api-data/aliases/aliases.validation.ts b/apps/server/src/api-data/aliases/aliases.validation.ts new file mode 100644 index 000000000..15f096bfe --- /dev/null +++ b/apps/server/src/api-data/aliases/aliases.validation.ts @@ -0,0 +1,18 @@ +import { body, validationResult } from 'express-validator'; +import { Request, Response, NextFunction } from 'express'; + +/** + * @description Validates object for POST /ontime/aliases + */ +export const validateAliases = [ + body().isArray(), + body('*.enabled').isBoolean(), + body('*.alias').isString().trim(), + body('*.pathAndParams').isString().trim(), + + (req: Request, res: Response, next: NextFunction) => { + const errors = validationResult(req); + if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() }); + next(); + }, +]; diff --git a/apps/server/src/api-data/custom-fields/customFields.controller.ts b/apps/server/src/api-data/custom-fields/customFields.controller.ts new file mode 100644 index 000000000..42c428c08 --- /dev/null +++ b/apps/server/src/api-data/custom-fields/customFields.controller.ts @@ -0,0 +1,49 @@ +import { CustomField, CustomFields } from 'ontime-types'; + +import type { Request, Response } from 'express'; + +import { + createCustomField, + editCustomField, + getCustomFields as getCustomFieldsFromCache, + removeCustomField, +} from '../../services/rundown-service/rundownCache.js'; + +export async function getCustomFields(_req: Request, res: Response) { + const customFields = getCustomFieldsFromCache(); + res.json(customFields); +} + +// Expects { label: