diff --git a/apps/server/src/api-data/db/db.controller.ts b/apps/server/src/api-data/db/db.controller.ts index 64c140bab..496cef013 100644 --- a/apps/server/src/api-data/db/db.controller.ts +++ b/apps/server/src/api-data/db/db.controller.ts @@ -9,15 +9,11 @@ import { import type { Request, Response } from 'express'; import fs from 'fs'; -import { join } from 'path'; -import { DataProvider } from '../../classes/data-provider/DataProvider.js'; import { failEmptyObjects } from '../../utils/routerUtils.js'; import { resolveDbPath, resolveProjectsDirectory } from '../../setup/index.js'; import * as projectService from '../../services/project-service/ProjectService.js'; -import { runtimeService } from '../../services/runtime-service/RuntimeService.js'; -import { setRundown } from '../../services/rundown-service/RundownService.js'; import { ensureJsonExtension } from '../../utils/fileManagement.js'; import { generateUniqueFileName } from '../../utils/generateUniqueFilename.js'; import { appStateService } from '../../services/app-state-service/AppStateService.js'; @@ -31,23 +27,10 @@ export async function patchPartialProjectFile(req: Request, res: Response = { - project: req.body?.project, - settings: req.body?.settings, - viewSettings: req.body?.viewSettings, - osc: req.body?.osc, - urlPresets: req.body?.urlPresets, - customFields: req.body?.customFields, - }; + const { rundown, project, settings, viewSettings, urlPresets, customFields, osc, http } = req.body; + const patchDb: DatabaseModel = { rundown, project, settings, viewSettings, urlPresets, customFields, osc, http }; - const maybeRundown = req.body?.rundown; - await DataProvider.mergeIntoData(patchDb); - if (maybeRundown !== undefined) { - // it is likely cheaper to invalidate cache than to calculate diff - runtimeService.stop(); - await setRundown(maybeRundown); - } - const newData = DataProvider.getData(); + const newData = await projectService.applyDataModel(patchDb); res.status(200).send(newData); } catch (error) { res.status(400).send({ message: String(error) }); @@ -93,9 +76,7 @@ export async function createProjectFile(req: Request, res: Response<{ filename: } export async function projectDownload(_req: Request, res: Response) { - const { title } = DataProvider.getProjectData(); - const fileTitle = title || 'ontime data'; - + const fileTitle = projectService.getProjectTitle(); res.download(resolveDbPath, `${fileTitle}.json`, (err) => { if (err) { res.status(500).send({ @@ -116,9 +97,14 @@ export async function postProjectFile(req: Request, res: Response) { try { - const filename = req.body.filename; - const filePath = join(resolveProjectsDirectory, filename); - - if (!fs.existsSync(filePath)) { + const name = req.body.filename; + if (!projectService.doesProjectExist(name)) { return res.status(404).send({ message: 'File not found' }); } - await projectService.applyProjectFile(filePath); + + await projectService.applyProjectFile(name); + res.status(201).send({ - message: `Loaded project ${filename}`, + message: `Loaded project ${name}`, }); } catch (error) { res.status(500).send({ message: String(error) }); diff --git a/apps/server/src/classes/data-provider/DataProvider.ts b/apps/server/src/classes/data-provider/DataProvider.ts index 06a00f02f..edf65fd3b 100644 --- a/apps/server/src/classes/data-provider/DataProvider.ts +++ b/apps/server/src/classes/data-provider/DataProvider.ts @@ -25,7 +25,7 @@ export class DataProvider { static async setProjectData(newData: Partial) { data.project = { ...data.project, ...newData }; - await this.persist(); + this.persist(); return data.project; } @@ -35,7 +35,7 @@ export class DataProvider { static async setCustomFields(newData: CustomFields): Promise { data.customFields = { ...newData }; - await this.persist(); + this.persist(); return data.customFields; } @@ -45,7 +45,8 @@ export class DataProvider { static async setRundown(newData: OntimeRundown) { data.rundown = [...newData]; - await this.persist(); + this.persist(); + return data.rundown; } static getSettings(): Readonly { @@ -54,7 +55,8 @@ export class DataProvider { static async setSettings(newData: Settings) { data.settings = { ...newData }; - await this.persist(); + this.persist(); + return data.settings; } static getOsc(): OSCSettings { @@ -71,7 +73,8 @@ export class DataProvider { static async setUrlPresets(newData: URLPreset[]) { data.urlPresets = newData; - await this.persist(); + this.persist(); + return data.urlPresets; } static getViewSettings() { @@ -80,18 +83,19 @@ export class DataProvider { static async setViewSettings(newData: ViewSettings) { data.viewSettings = { ...newData }; - await this.persist(); + this.persist(); + return data.viewSettings; } static async setOsc(newData: OSCSettings): Promise { data.osc = { ...newData }; - await this.persist(); + this.persist(); return data.osc; } static async setHttp(newData: HttpSettings): Promise { data.http = { ...newData }; - await this.persist(); + this.persist(); return data.http; } @@ -116,6 +120,9 @@ export class DataProvider { data.urlPresets = mergedData.urlPresets; data.customFields = mergedData.customFields; data.rundown = mergedData.rundown; - await this.persist(); + + this.persist(); + + return data; } } diff --git a/apps/server/src/services/project-service/ProjectService.ts b/apps/server/src/services/project-service/ProjectService.ts index 80816b50b..ef8012e2f 100644 --- a/apps/server/src/services/project-service/ProjectService.ts +++ b/apps/server/src/services/project-service/ProjectService.ts @@ -2,9 +2,9 @@ import { DatabaseModel, GetInfo, ProjectData, ProjectFile, ProjectFileListRespon import { copyFile, rename, stat, writeFile } from 'fs/promises'; import { existsSync } from 'fs'; -import { basename, join } from 'path'; +import { join } from 'path'; -import { notifyChanges, setRundown } from '../rundown-service/RundownService.js'; +import { initRundown } from '../rundown-service/RundownService.js'; import { DataProvider } from '../../classes/data-provider/DataProvider.js'; import { runtimeService } from '../runtime-service/RuntimeService.js'; import { getNetworkInterfaces } from '../../utils/networkInterfaces.js'; @@ -33,22 +33,31 @@ type Options = { /** * Handles a file from the upload folder and applies its data */ -export async function applyProjectFile(filePath: string, options?: Options) { +export async function applyProjectFile(name: string, options?: Options) { + const filePath = join(resolveProjectsDirectory, name); const data = parseProjectFile(filePath); - // move file to project folder - const filename = basename(filePath); - const newFilePath = join(resolveProjectsDirectory, filename); - await rename(filePath, newFilePath); - // change LowDB to point to new file - await switchDb(filename); + await switchDb(name); // apply data model await applyDataModel(data, options); // persist the project selection - await appStateService.updateDatabaseConfig(filename); + await appStateService.updateDatabaseConfig(name); +} + +/** + * Copies a file from upload folder to the projects folder + * @param filePath + * @param name + * @returns + */ +export async function handleUploadedFile(filePath: string, name: string) { + const newFilePath = join(resolveProjectsDirectory, name); + await rename(filePath, newFilePath); + await deleteFile(filePath); + return name; } /** @@ -196,18 +205,27 @@ export function extractPin(value: string | undefined | null, fallback: string | /** * applies a partial database model */ -export async function applyDataModel(data: Partial, options?: Options) { +export async function applyDataModel(data: Partial, _options?: Options) { runtimeService.stop(); - const newRundown = data.rundown || []; - const { rundown, ...rest } = data; - if (options?.onlyRundown === 'true') { - setRundown(newRundown ?? []); - } else { - await DataProvider.mergeIntoData(rest); - setRundown(rundown ?? []); + // TODO: allow partial project merge from options + const { rundown, customFields, ...rest } = data; + const newData = await DataProvider.mergeIntoData(rest); + + if (rundown != null) { + initRundown(rundown, customFields ?? {}); } - notifyChanges({ timer: true, external: true }); + + return newData; +} + +/** + * Checks whether a project of a given name exists + * @param name + */ +export function doesProjectExist(name: string): boolean { + const projectFilePath = join(resolveProjectsDirectory, name); + return existsSync(projectFilePath); } /** @@ -240,3 +258,11 @@ export const validateProjectFiles = (projectFiles: { filename?: string; newFilen return errors; }; + +/** + * Get current project title or fallback + */ +export function getProjectTitle(): string { + const { title } = DataProvider.getProjectData(); + return title || 'ontime data'; +} diff --git a/apps/server/src/services/rundown-service/RundownService.ts b/apps/server/src/services/rundown-service/RundownService.ts index 528044a25..189e23130 100644 --- a/apps/server/src/services/rundown-service/RundownService.ts +++ b/apps/server/src/services/rundown-service/RundownService.ts @@ -210,7 +210,7 @@ function updateRuntimeOnChange() { /** * Notify services of changes in the rundown */ -export function notifyChanges(options: { timer?: boolean | string[]; external?: boolean }) { +function notifyChanges(options: { timer?: boolean | string[]; external?: boolean }) { if (options.timer) { const playableEvents = getPlayableEvents(); @@ -243,12 +243,3 @@ export async function initRundown(rundown: OntimeRundown, customFields: CustomFi // notify timer of change notifyChanges({ timer: true }); } - -/** - * Overrides the rundown with the given - * @param rundown - */ -export async function setRundown(rundown: OntimeRundown) { - await cache.setRundown(rundown); - notifyChanges({ timer: true }); -}