mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 17:33:55 +00:00
Apply project (#843)
This commit is contained in:
@@ -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<Databa
|
||||
}
|
||||
|
||||
try {
|
||||
const patchDb: Partial<DatabaseModel> = {
|
||||
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<MessageRespons
|
||||
|
||||
try {
|
||||
const options = req.query;
|
||||
const filePath = req.file.path;
|
||||
await projectService.applyProjectFile(filePath, options);
|
||||
res.status(201).send({ message: 'ok' });
|
||||
const { filename, path } = req.file;
|
||||
|
||||
await projectService.handleUploadedFile(path, filename);
|
||||
await projectService.applyProjectFile(filename, options);
|
||||
|
||||
res.status(201).send({
|
||||
message: `Loaded project ${filename}`,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).send({ message: `Failed parsing ${error}` });
|
||||
}
|
||||
@@ -141,15 +127,15 @@ export async function listProjects(_req: Request, res: Response<ProjectFileListR
|
||||
*/
|
||||
export async function loadProject(req: Request, res: Response<MessageResponse | ErrorResponse>) {
|
||||
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) });
|
||||
|
||||
@@ -25,7 +25,7 @@ export class DataProvider {
|
||||
|
||||
static async setProjectData(newData: Partial<ProjectData>) {
|
||||
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<CustomFields> {
|
||||
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<Settings> {
|
||||
@@ -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<OSCSettings> {
|
||||
data.osc = { ...newData };
|
||||
await this.persist();
|
||||
this.persist();
|
||||
return data.osc;
|
||||
}
|
||||
|
||||
static async setHttp(newData: HttpSettings): Promise<HttpSettings> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DatabaseModel>, options?: Options) {
|
||||
export async function applyDataModel(data: Partial<DatabaseModel>, _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';
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user