Merge remote-tracking branch 'origin/refactor-project-data' into excel-import-preview

This commit is contained in:
cv
2023-09-02 14:58:20 +02:00
41 changed files with 192 additions and 186 deletions
+2 -2
View File
@@ -13,7 +13,7 @@ import { LogOrigin, OSCSettings } from 'ontime-types';
// Import Routes
import { router as rundownRouter } from './routes/rundownRouter.js';
import { router as eventDataRouter } from './routes/eventDataRouter.js';
import { router as projectRouter } from './routes/projectRouter.js';
import { router as ontimeRouter } from './routes/ontimeRouter.js';
import { router as playbackRouter } from './routes/playbackRouter.js';
@@ -55,7 +55,7 @@ app.use(express.json({ limit: '1mb' }));
// Implement route endpoints
app.use('/events', rundownRouter);
app.use('/eventdata', eventDataRouter);
app.use('/project', projectRouter);
app.use('/ontime', ontimeRouter);
app.use('/playback', playbackRouter);
@@ -2,7 +2,7 @@
* Class Event Provider is a mediator for handling the local db
* and adds logic specific to ontime data
*/
import { EventData, OntimeRundown, ViewSettings } from 'ontime-types';
import { ProjectData, OntimeRundown, ViewSettings } from 'ontime-types';
import { data, db } from '../../modules/loadDb.js';
import { safeMerge } from './DataProvider.utils.js';
@@ -12,14 +12,14 @@ export class DataProvider {
return data;
}
static async setEventData(newData: Partial<EventData>) {
data.eventData = { ...data.eventData, ...newData };
static async setProjectData(newData: Partial<ProjectData>) {
data.project = { ...data.project, ...newData };
await this.persist();
return data.eventData;
return data.project;
}
static getEventData() {
return data.eventData;
static getProjectData() {
return data.project;
}
static async setRundown(newData: OntimeRundown) {
@@ -97,7 +97,7 @@ export class DataProvider {
static async mergeIntoData(newData) {
const mergedData = safeMerge(data, newData);
data.eventData = mergedData.eventData;
data.project = mergedData.project;
data.settings = mergedData.settings;
data.viewSettings = mergedData.viewSettings;
data.osc = mergedData.osc;
@@ -4,11 +4,11 @@
* @param {object} newData
*/
export function safeMerge(existing, newData) {
const { rundown, eventData, settings, viewSettings, osc, http, aliases, userFields } = newData || {};
const { rundown, project, settings, viewSettings, osc, http, aliases, userFields } = newData || {};
return {
...existing,
rundown: rundown ?? existing.rundown,
eventData: { ...existing.eventData, ...eventData },
project: { ...existing.project, ...project },
settings: { ...existing.settings, ...settings },
viewSettings: { ...existing.viewSettings, ...viewSettings },
aliases: aliases ?? existing.aliases,
@@ -3,7 +3,7 @@ import { safeMerge } from '../DataProvider.utils.js';
describe('safeMerge', () => {
const existing = {
rundown: [],
eventData: {
project: {
title: 'existing title',
publicUrl: 'existing public URL',
backstageUrl: 'existing backstageUrl',
@@ -62,15 +62,15 @@ describe('safeMerge', () => {
expect(mergedData.rundown).toEqual(newData.rundown);
});
it('merges the event key', () => {
it('merges the project key', () => {
const newData = {
eventData: {
project: {
title: 'new title',
publicInfo: 'new public info',
},
};
const mergedData = safeMerge(existing, newData);
expect(mergedData.eventData).toEqual({
expect(mergedData.project).toEqual({
title: 'new title',
publicUrl: 'existing public URL',
publicInfo: 'new public info',
@@ -1,4 +1,4 @@
import { Alias, EventData, LogOrigin } from 'ontime-types';
import { Alias, LogOrigin, ProjectData } from 'ontime-types';
import { RequestHandler } from 'express';
import fs from 'fs';
@@ -31,7 +31,7 @@ export const poll = async (req, res) => {
// Create controller for GET request to '/ontime/db'
// Returns -
export const dbDownload = async (req, res) => {
const { title } = DataProvider.getEventData();
const { title } = DataProvider.getProjectData();
const fileTitle = title || 'ontime data';
res.download(resolveDbPath, `${fileTitle}.json`, (err) => {
@@ -368,7 +368,7 @@ export const dbUpload = async (req, res) => {
// Create controller for POST request to '/ontime/new'
export const postNew: RequestHandler = async (req, res) => {
try {
const newEventData: EventData = {
const newProjectData: ProjectData = {
title: req.body?.title ?? '',
description: req.body?.description ?? '',
publicUrl: req.body?.publicUrl ?? '',
@@ -376,7 +376,7 @@ export const postNew: RequestHandler = async (req, res) => {
backstageUrl: req.body?.backstageUrl ?? '',
backstageInfo: req.body?.backstageInfo ?? '',
};
const newData = await DataProvider.setEventData(newEventData);
const newData = await DataProvider.setProjectData(newProjectData);
await deleteAllEvents();
res.status(201).send(newData);
} catch (error) {
@@ -1,24 +1,24 @@
import { RequestHandler } from 'express';
import { EventData } from 'ontime-types';
import { ProjectData } from 'ontime-types';
import { removeUndefined } from '../utils/parserUtils.js';
import { failEmptyObjects } from '../utils/routerUtils.js';
import { DataProvider } from '../classes/data-provider/DataProvider.js';
// Create controller for GET request to 'event'
export const getEventData: RequestHandler = async (req, res) => {
res.json(DataProvider.getEventData());
// Create controller for GET request to 'project'
export const getProject: RequestHandler = async (req, res) => {
res.json(DataProvider.getProjectData());
};
// Create controller for POST request to 'event'
export const postEventData: RequestHandler = async (req, res) => {
// Create controller for POST request to 'project'
export const postProject: RequestHandler = async (req, res) => {
if (failEmptyObjects(req.body, res)) {
return;
}
try {
const newEvent: Partial<EventData> = removeUndefined({
const newEvent: Partial<ProjectData> = removeUndefined({
title: req.body?.title,
description: req.body?.description,
publicUrl: req.body?.publicUrl,
@@ -27,7 +27,7 @@ export const postEventData: RequestHandler = async (req, res) => {
backstageInfo: req.body?.backstageInfo,
endMessage: req.body?.endMessage,
});
const newData = await DataProvider.setEventData(newEvent);
const newData = await DataProvider.setProjectData(newEvent);
res.status(200).send(newData);
} catch (error) {
res.status(400).send(error);
@@ -1,6 +1,6 @@
import { body, validationResult } from 'express-validator';
export const eventDataSanitizer = [
export const projectSanitiser = [
body('title').optional().isString().trim(),
body('description').optional().isString().trim(),
body('publicUrl').optional().isString().trim(),
+1 -1
View File
@@ -2,7 +2,7 @@ import { DatabaseModel } from 'ontime-types';
export const dbModel: DatabaseModel = {
rundown: [],
eventData: {
project: {
title: '',
description: '',
publicUrl: '',
-11
View File
@@ -1,11 +0,0 @@
import express from 'express';
import { getEventData, postEventData } from '../controllers/eventDataController.js';
import { eventDataSanitizer } from '../controllers/eventDataController.validate.js';
export const router = express.Router();
// create route between controller and 'GET /event' endpoint
router.get('/', getEventData);
// create route between controller and 'POST /event' endpoint
router.post('/', eventDataSanitizer, postEventData);
+2 -2
View File
@@ -28,7 +28,7 @@ import {
validateUserFields,
viewValidator,
} from '../controllers/ontimeController.validate.js';
import { eventDataSanitizer } from '../controllers/eventDataController.validate.js';
import { projectSanitiser } from '../controllers/projectController.validate.js';
export const router = express.Router();
@@ -81,4 +81,4 @@ router.post('/osc', validateOSC, postOSC);
router.post('/osc-subscriptions', validateOscSubscription, postOscSubscriptions);
// create route between controller and '/ontime/new' endpoint
router.post('/new', eventDataSanitizer, postNew);
router.post('/new', projectSanitiser, postNew);
+11
View File
@@ -0,0 +1,11 @@
import express from 'express';
import { getProject, postProject } from '../controllers/projectController.js';
import { projectSanitiser } from '../controllers/projectController.validate.js';
export const router = express.Router();
// create route between controller and 'GET /project' endpoint
router.get('/', getProject);
// create route between controller and 'POST /project' endpoint
router.post('/', projectSanitiser, postProject);
+10 -10
View File
@@ -1,6 +1,6 @@
import { vi } from 'vitest';
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
import { EndAction, TimerType } from 'ontime-types';
import { dbModel } from '../../models/dataModel.js';
import { parseExcel, parseJson, validateEvent } from '../parser.js';
@@ -192,7 +192,7 @@ describe('test json parser with valid def', () => {
user9: '',
},
],
eventData: {
project: {
title: 'This is a test definition',
url: 'www.carlosvalente.com',
publicInfo: 'WiFi: demoproject \nPassword: ontimeproject',
@@ -246,7 +246,7 @@ describe('test json parser with valid def', () => {
});
it('loaded event settings', () => {
const eventTitle = parseResponse?.eventData?.title;
const eventTitle = parseResponse?.project?.title;
expect(eventTitle).toBe('This is a test definition');
});
@@ -402,10 +402,10 @@ describe('test corrupt data', () => {
expect(parsedDef.rundown.length).toBe(0);
});
it('handles missing event data', async () => {
const emptyEventData = {
it('handles missing project data', async () => {
const emptyProjectData = {
rundown: [{}, {}, {}, {}, {}, {}, {}, {}],
eventData: {},
project: {},
settings: {
app: 'ontime',
version: 2,
@@ -415,8 +415,8 @@ describe('test corrupt data', () => {
},
};
const parsedDef = await parseJson(emptyEventData);
expect(parsedDef.eventData).toStrictEqual(dbModel.eventData);
const parsedDef = await parseJson(emptyProjectData);
expect(parsedDef.project).toStrictEqual(dbModel.project);
});
it('handles missing settings', async () => {
@@ -629,7 +629,7 @@ describe('test parseExcel function', () => {
[],
];
const expectedParsedEvent = {
const expectedParsedProjectData = {
title: 'Test Event',
publicUrl: 'www.public.com',
backstageUrl: 'www.backstage.com',
@@ -682,7 +682,7 @@ describe('test parseExcel function', () => {
];
const parsedData = await parseExcel(testdata);
expect(parsedData.eventData).toStrictEqual(expectedParsedEvent);
expect(parsedData.project).toStrictEqual(expectedParsedProjectData);
expect(parsedData.rundown).toBeDefined();
expect(parsedData.rundown[0]).toMatchObject(expectedParsedRundown[0]);
expect(parsedData.rundown[1]).toMatchObject(expectedParsedRundown[1]);
+19 -12
View File
@@ -7,7 +7,6 @@ import { generateId, calculateDuration } from 'ontime-utils';
import {
DatabaseModel,
EndAction,
EventData,
OntimeEvent,
OntimeRundown,
SupportedEvent,
@@ -19,7 +18,7 @@ import { dbModel } from '../models/dataModel.js';
import { deleteFile, makeString } from './parserUtils.js';
import {
parseAliases,
parseEventData,
parseProject,
parseOsc,
parseRundown,
parseSettings,
@@ -37,8 +36,9 @@ export const JSON_MIME = 'application/json';
* @returns {object} - parsed object
*/
export const parseExcel = async (excelData) => {
const eventData: Partial<EventData> = {
const projectData: Partial<ProjectData> = {
title: '',
description: '',
publicUrl: '',
backstageUrl: '',
};
@@ -71,6 +71,7 @@ export const parseExcel = async (excelData) => {
.filter((e) => e.length > 0)
.forEach((row) => {
let eventTitleNext = false;
let projectTitleNext = false;
let publicUrlNext = false;
let publicInfoNext = false;
let backstageUrlNext = false;
@@ -81,19 +82,22 @@ export const parseExcel = async (excelData) => {
row.forEach((column, j) => {
// check flags
if (eventTitleNext) {
eventData.title = column;
projectData.title = column;
eventTitleNext = false;
} else if (projectTitleNext) {
projectData.description = column;
projectTitleNext = false;
} else if (publicUrlNext) {
eventData.publicUrl = column;
projectData.publicUrl = column;
publicUrlNext = false;
} else if (publicInfoNext) {
eventData.publicInfo = column;
projectData.publicInfo = column;
publicInfoNext = false;
} else if (backstageUrlNext) {
eventData.backstageUrl = column;
projectData.backstageUrl = column;
backstageUrlNext = false;
} else if (backstageInfoNext) {
eventData.backstageInfo = column;
projectData.backstageInfo = column;
backstageInfoNext = false;
} else if (j === timeStartIndex) {
event.timeStart = parseExcelDate(column);
@@ -154,9 +158,12 @@ export const parseExcel = async (excelData) => {
// look for keywords
// need to make sure it is a string first
switch (col) {
case 'event name':
case 'project name':
eventTitleNext = true;
break;
case 'project description':
projectTitleNext = true;
break;
case 'public url':
publicUrlNext = true;
break;
@@ -273,7 +280,7 @@ export const parseExcel = async (excelData) => {
});
return {
rundown,
eventData,
project: projectData,
settings: {
app: 'ontime',
version: 2,
@@ -299,7 +306,7 @@ export const parseJson = async (jsonData, enforce = false): Promise<DatabaseMode
// parse Events
returnData.rundown = parseRundown(jsonData);
// parse Event
returnData.eventData = parseEventData(jsonData, enforce);
returnData.project = parseProject(jsonData, enforce);
// Settings handled partially
returnData.settings = parseSettings(jsonData, enforce);
// View settings handled partially
@@ -396,7 +403,7 @@ export const fileHandler = async (file): Promise<ResponseOK | ResponseError> =>
const dataFromExcel = await parseExcel(excelData.data);
res.data = {};
res.data.rundown = parseRundown(dataFromExcel);
res.data.eventData = parseEventData(dataFromExcel, true);
res.data.project = parseProject(dataFromExcel, true);
res.data.userFields = parseUserFields(dataFromExcel);
res.message = 'success';
} else {
+17 -17
View File
@@ -2,11 +2,11 @@ import { generateId } from 'ontime-utils';
import {
Alias,
EndAction,
EventData,
OntimeRundown,
OSCSettings,
OscSubscription,
OscSubscriptionOptions,
ProjectData,
Settings,
TimerLifeCycle,
TimerType,
@@ -91,26 +91,26 @@ export const parseRundown = (data): OntimeRundown => {
* @param {boolean} enforce - whether to create a definition if one is missing
* @returns {object} - event object data
*/
export const parseEventData = (data, enforce): EventData => {
let newEventData: Partial<EventData> = {};
if ('eventData' in data) {
console.log('Found event data, importing...');
const e = data.eventData;
export const parseProject = (data, enforce): ProjectData => {
let newProjectData: Partial<ProjectData> = {};
if ('project' in data) {
console.log('Found project data, importing...');
const project = data.project;
// filter known properties and write to db
newEventData = {
...dbModel.eventData,
title: e.title || dbModel.eventData.title,
description: e.description || dbModel.eventData.description,
publicUrl: e.publicUrl || dbModel.eventData.publicUrl,
publicInfo: e.publicInfo || dbModel.eventData.publicInfo,
backstageUrl: e.backstageUrl || dbModel.eventData.backstageUrl,
backstageInfo: e.backstageInfo || dbModel.eventData.backstageInfo,
newProjectData = {
...dbModel.project,
title: project.title || dbModel.project.title,
description: project.description || dbModel.project.description,
publicUrl: project.publicUrl || dbModel.project.publicUrl,
publicInfo: project.publicInfo || dbModel.project.publicInfo,
backstageUrl: project.backstageUrl || dbModel.project.backstageUrl,
backstageInfo: project.backstageInfo || dbModel.project.backstageInfo,
};
} else if (enforce) {
newEventData = { ...dbModel.eventData };
console.log('Created event object in db');
newProjectData = { ...dbModel.project };
console.log('Created project object in db');
}
return newEventData as EventData;
return newProjectData as ProjectData;
};
/**