mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 20:33:47 +00:00
feat: custom data for projects (#1571)
This commit is contained in:
committed by
Carlos Valente
parent
eed6373dbf
commit
696c016c90
@@ -58,6 +58,7 @@ export async function createProjectFile(req: Request, res: Response<{ filename:
|
||||
backstageUrl: req.body?.backstageUrl ?? '',
|
||||
backstageInfo: req.body?.backstageInfo ?? '',
|
||||
projectLogo: req.body?.projectLogo ?? null,
|
||||
custom: req.body?.custom ?? [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -203,7 +204,7 @@ export async function loadProject(req: Request, res: Response<MessageResponse |
|
||||
/**
|
||||
* Loads the demo project
|
||||
*/
|
||||
export async function loadDemo(req: Request, res: Response<MessageResponse | ErrorResponse>) {
|
||||
export async function loadDemo(_req: Request, res: Response<MessageResponse | ErrorResponse>) {
|
||||
try {
|
||||
const projectName = await projectService.loadDemoProject();
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export const validateNewProject = [
|
||||
body('backstageInfo').optional().isString().trim(),
|
||||
body('projectLogo').optional().isString().trim(),
|
||||
body('endMessage').optional().isString().trim(),
|
||||
body('custom').optional().isArray(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
|
||||
@@ -5,11 +5,11 @@ import type { Request, Response } from 'express';
|
||||
|
||||
import { removeUndefined } from '../../utils/parserUtils.js';
|
||||
import { failEmptyObjects } from '../../utils/routerUtils.js';
|
||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||
import { editCurrentProjectData } from '../../services/project-service/ProjectService.js';
|
||||
import * as projectDao from './project.dao.js';
|
||||
|
||||
export function getProjectData(_req: Request, res: Response<ProjectData>) {
|
||||
res.json(getDataProvider().getProjectData());
|
||||
res.json(projectDao.getProjectData());
|
||||
}
|
||||
|
||||
export async function postProjectData(req: Request, res: Response<ProjectData | ErrorResponse>) {
|
||||
@@ -27,6 +27,7 @@ export async function postProjectData(req: Request, res: Response<ProjectData |
|
||||
backstageInfo: req.body?.backstageInfo,
|
||||
endMessage: req.body?.endMessage,
|
||||
projectLogo: req.body?.projectLogo,
|
||||
custom: req.body?.custom,
|
||||
});
|
||||
|
||||
const updatedData = await editCurrentProjectData(newData);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ProjectData } from 'ontime-types';
|
||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||
|
||||
/**
|
||||
* Gets a copy of the stored project data
|
||||
*/
|
||||
export function getProjectData(): ProjectData {
|
||||
return structuredClone(getDataProvider().getProjectData());
|
||||
}
|
||||
@@ -10,6 +10,9 @@ export const projectSanitiser = [
|
||||
body('backstageInfo').optional().isString().trim(),
|
||||
body('endMessage').optional().isString().trim(),
|
||||
body('projectLogo').optional({ nullable: true }).isString().trim(),
|
||||
body('custom').optional().isArray(),
|
||||
body('custom.*.title').optional().isString().trim().notEmpty(),
|
||||
body('custom.*.value').optional().isString().trim().notEmpty(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
|
||||
@@ -63,7 +63,7 @@ function getData(): Readonly<DatabaseModel> {
|
||||
}
|
||||
|
||||
async function setProjectData(newData: Partial<ProjectData>): ReadonlyPromise<ProjectData> {
|
||||
db.data.project = { ...db.data.project, ...newData };
|
||||
db.data.project = { ...structuredClone(db.data.project), ...structuredClone(newData) }; // Performing deep copy as we're updating / merging data
|
||||
await persist();
|
||||
return db.data.project;
|
||||
}
|
||||
|
||||
@@ -4,24 +4,27 @@ import { DatabaseModel } from 'ontime-types';
|
||||
* Merges a partial ontime project into a given ontime project
|
||||
*/
|
||||
export function safeMerge(existing: DatabaseModel, newData: Partial<DatabaseModel>): DatabaseModel {
|
||||
const deepExisting = structuredClone(existing);
|
||||
const deepNewData = structuredClone(newData);
|
||||
|
||||
const {
|
||||
rundown = existing.rundown,
|
||||
rundown = deepExisting.rundown,
|
||||
project = {},
|
||||
settings = {},
|
||||
viewSettings = {},
|
||||
urlPresets = existing.urlPresets,
|
||||
customFields = existing.customFields,
|
||||
automation = existing.automation,
|
||||
} = newData;
|
||||
urlPresets = deepExisting.urlPresets,
|
||||
customFields = deepExisting.customFields,
|
||||
automation = deepExisting.automation,
|
||||
} = deepNewData;
|
||||
|
||||
return {
|
||||
...existing,
|
||||
...deepExisting,
|
||||
rundown,
|
||||
project: { ...existing.project, ...project },
|
||||
settings: { ...existing.settings, ...settings },
|
||||
viewSettings: { ...existing.viewSettings, ...viewSettings },
|
||||
urlPresets: urlPresets ?? existing.urlPresets,
|
||||
customFields: customFields ?? existing.customFields,
|
||||
automation: { ...existing.automation, ...automation },
|
||||
project: { ...deepExisting.project, ...project },
|
||||
settings: { ...deepExisting.settings, ...settings },
|
||||
viewSettings: { ...deepExisting.viewSettings, ...viewSettings },
|
||||
urlPresets: urlPresets ?? deepExisting.urlPresets,
|
||||
customFields: customFields ?? deepExisting.customFields,
|
||||
automation: { ...deepExisting.automation, ...automation },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ describe('safeMerge', () => {
|
||||
publicInfo: 'existing backstageInfo',
|
||||
backstageInfo: 'existing backstageInfo',
|
||||
projectLogo: null,
|
||||
custom: [
|
||||
{
|
||||
title: 'existing custom title',
|
||||
value: 'existing custom value',
|
||||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
@@ -62,6 +68,12 @@ describe('safeMerge', () => {
|
||||
project: {
|
||||
title: 'new title',
|
||||
publicInfo: 'new public info',
|
||||
custom: [
|
||||
{
|
||||
title: 'new custom title',
|
||||
value: 'new custom value',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
// @ts-expect-error -- just testing
|
||||
@@ -74,6 +86,12 @@ describe('safeMerge', () => {
|
||||
backstageUrl: 'existing backstageUrl',
|
||||
backstageInfo: 'existing backstageInfo',
|
||||
projectLogo: null,
|
||||
custom: [
|
||||
{
|
||||
title: 'new custom title',
|
||||
value: 'new custom value',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,6 +125,7 @@ describe('safeMerge', () => {
|
||||
backstageUrl: '',
|
||||
backstageInfo: '',
|
||||
projectLogo: null,
|
||||
custom: [],
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
|
||||
@@ -11,6 +11,7 @@ export const dbModel: DatabaseModel = {
|
||||
backstageUrl: '',
|
||||
backstageInfo: '',
|
||||
projectLogo: null,
|
||||
custom: [],
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
|
||||
@@ -413,6 +413,7 @@ export const demoDb: DatabaseModel = {
|
||||
backstageUrl: 'www.github.com/cpvalente/ontime',
|
||||
backstageInfo: 'Rehearsal Schedule - Turin 2022\nAll performers to wear full costumes for 1st rehearsal',
|
||||
projectLogo: null,
|
||||
custom: [],
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
|
||||
@@ -314,17 +314,13 @@ export async function patchCurrentProject(data: Partial<DatabaseModel>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the title of a project
|
||||
* it handles invalidating the necessary data
|
||||
* Patches the current project data
|
||||
* Handles deleting the local logo if the logo has been removed
|
||||
*/
|
||||
export async function editCurrentProjectData(newData: Partial<ProjectData>) {
|
||||
const currentProjectData = getDataProvider().getProjectData();
|
||||
const updatedProjectData = await getDataProvider().setProjectData(newData);
|
||||
|
||||
if (currentProjectData.title !== updatedProjectData.title) {
|
||||
// something
|
||||
}
|
||||
|
||||
// Delete the old logo if the logo has been removed
|
||||
if (!updatedProjectData.projectLogo && currentProjectData.projectLogo) {
|
||||
const filePath = join(publicDir.logoDir, currentProjectData.projectLogo);
|
||||
|
||||
@@ -65,6 +65,7 @@ describe('parseProject()', () => {
|
||||
publicInfo: 'publicInfo',
|
||||
backstageUrl: 'backstageUrl',
|
||||
backstageInfo: 'backstageInfo',
|
||||
custom: [],
|
||||
},
|
||||
},
|
||||
errorEmitter,
|
||||
@@ -77,6 +78,7 @@ describe('parseProject()', () => {
|
||||
backstageUrl: 'backstageUrl',
|
||||
backstageInfo: 'backstageInfo',
|
||||
projectLogo: null,
|
||||
custom: [],
|
||||
});
|
||||
expect(errorEmitter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -114,6 +114,7 @@ export function parseProject(data: Partial<DatabaseModel>, emitError?: ErrorEmit
|
||||
backstageUrl: data.project.backstageUrl ?? dbModel.project.backstageUrl,
|
||||
backstageInfo: data.project.backstageInfo ?? dbModel.project.backstageInfo,
|
||||
projectLogo: data.project.projectLogo ?? dbModel.project.projectLogo,
|
||||
custom: data.project.custom ?? dbModel.project.custom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user