mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 01:13:55 +00:00
Remove serverport from project file (#1957)
* feat: get server port from app satate or env optional startup port from env will always override function for parsing port from env populate default port in app state add test for migration * bump version
This commit is contained in:
committed by
GitHub
parent
1fe58e21be
commit
c1fcdf7065
@@ -10,6 +10,9 @@ import { parseUrlPresets } from '../url-presets/urlPresets.parser.js';
|
||||
import { parseViewSettings } from '../view-settings/viewSettings.parser.js';
|
||||
import { parseCustomFields } from '../custom-fields/customFields.parser.js';
|
||||
import * as v3 from './migration/db.migration.v3.js';
|
||||
import * as v4 from './migration/db.migration.v4.js';
|
||||
import { portManager } from '../../classes/port-manager/PortManager.js';
|
||||
import { getErrorMessage } from 'ontime-utils';
|
||||
|
||||
type ParsingError = {
|
||||
context: string;
|
||||
@@ -21,21 +24,45 @@ type ParsingError = {
|
||||
* @param {object} jsonData - project file to be parsed
|
||||
* @returns {object} parsed object
|
||||
*/
|
||||
export function parseDatabaseModel(jsonData: Partial<DatabaseModel>): {
|
||||
export function parseDatabaseModel(
|
||||
jsonData: Partial<DatabaseModel>,
|
||||
initialLoad = false,
|
||||
): {
|
||||
data: DatabaseModel;
|
||||
errors: ParsingError[];
|
||||
migrated: boolean;
|
||||
} {
|
||||
|
||||
let migrated = false;
|
||||
let migratedData = jsonData;
|
||||
const errors: ParsingError[] = [];
|
||||
|
||||
if (v3.shouldUseThisMigration(jsonData)) {
|
||||
try {
|
||||
migrated = true;
|
||||
logger.warning(LogOrigin.Server, 'The imported project is from v3, trying to migrate');
|
||||
migratedData = v3.migrateAllData(jsonData);
|
||||
} catch (_error) {
|
||||
} catch (error) {
|
||||
logger.error(LogOrigin.Server, 'Failed to migrate the data');
|
||||
errors.push({ context: 'v3 migration', message: getErrorMessage(error) });
|
||||
migratedData = jsonData;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (v4.shouldMigrateServerPort(migratedData)) {
|
||||
try {
|
||||
migrated = true;
|
||||
logger.warning(LogOrigin.Server, 'Migrating serverPort from settings to AppState');
|
||||
const { db, serverPort } = v4.migrateServerPort(migratedData);
|
||||
if (initialLoad && serverPort) portManager.migratePortFromProjectFile(serverPort);
|
||||
migratedData = db;
|
||||
} catch (error) {
|
||||
logger.error(LogOrigin.Server, 'Failed to migrate serverPort');
|
||||
errors.push({ context: 'v4 migration', message: getErrorMessage(error) });
|
||||
migratedData = jsonData;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +70,6 @@ export function parseDatabaseModel(jsonData: Partial<DatabaseModel>): {
|
||||
// this may throw
|
||||
const settings = parseSettings(migratedData);
|
||||
|
||||
const errors: ParsingError[] = [];
|
||||
const makeEmitError = (context: string) => (message: string) => {
|
||||
logger.error(LogOrigin.Server, `Error parsing ${context}: ${message}`);
|
||||
errors.push({ context, message });
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
} from 'ontime-utils';
|
||||
|
||||
import { is } from '../../../utils/is.js';
|
||||
import { ONTIME_VERSION } from '../../../ONTIME_VERSION.js';
|
||||
import { getPartialProject } from '../../../models/dataModel.js';
|
||||
|
||||
// the methodology of the migrations is to just change the necessary keys to match with v4
|
||||
@@ -69,12 +68,12 @@ type old_Settings = {
|
||||
* migrates a settings from v3 to v4
|
||||
* - update the version number
|
||||
*/
|
||||
export function migrateSettings(jsonData: object): Settings | undefined {
|
||||
export function migrateSettings(jsonData: object): (Settings & { serverPort: number }) | undefined {
|
||||
if (is.objectWithKeys(jsonData, ['settings']) && is.object(jsonData.settings)) {
|
||||
const { serverPort, editorKey, operatorKey, timeFormat, language } = structuredClone(
|
||||
jsonData.settings,
|
||||
) as old_Settings;
|
||||
return { version: ONTIME_VERSION, serverPort, editorKey, operatorKey, timeFormat, language };
|
||||
return { version: '4.0.0', serverPort, editorKey, operatorKey, timeFormat, language };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { DatabaseModel, Settings } from 'ontime-types';
|
||||
import { is } from '../../../utils/is.js';
|
||||
|
||||
export function shouldMigrateServerPort(jsonData: object): boolean {
|
||||
return (
|
||||
is.objectWithKeys(jsonData, ['settings']) &&
|
||||
is.object(jsonData.settings) &&
|
||||
is.objectWithKeys(jsonData.settings, ['version', 'serverPort']) &&
|
||||
typeof jsonData.settings.version === 'string' &&
|
||||
jsonData.settings.version.split('.')[0] === '4' &&
|
||||
Number(jsonData.settings.version.split('.')[1]) <= 4
|
||||
);
|
||||
}
|
||||
|
||||
export function migrateServerPort(jsonData: Partial<DatabaseModel>): {
|
||||
db: Partial<DatabaseModel>;
|
||||
serverPort?: number;
|
||||
} {
|
||||
const db = structuredClone(jsonData);
|
||||
const settings = db.settings as Partial<Settings & { serverPort: number }>;
|
||||
const editorKey = settings?.editorKey;
|
||||
const operatorKey = settings?.operatorKey;
|
||||
const timeFormat = settings?.timeFormat;
|
||||
const language = settings?.language;
|
||||
const version = '4.5.0';
|
||||
db.settings = {
|
||||
version,
|
||||
editorKey,
|
||||
operatorKey,
|
||||
timeFormat,
|
||||
language,
|
||||
app: 'ontime',
|
||||
} as Settings;
|
||||
return { db, serverPort: settings?.serverPort };
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AutomationSettings,
|
||||
CustomFields,
|
||||
DatabaseModel,
|
||||
EndAction,
|
||||
OntimeView,
|
||||
ProjectData,
|
||||
@@ -14,8 +15,7 @@ import {
|
||||
ViewSettings,
|
||||
} from 'ontime-types';
|
||||
import * as v3 from './db.migration.v3.js';
|
||||
|
||||
import { ONTIME_VERSION } from '../../../ONTIME_VERSION.js';
|
||||
import * as v4 from './db.migration.v4.js';
|
||||
|
||||
describe('v3 to v4', () => {
|
||||
const oldDb = {
|
||||
@@ -175,8 +175,8 @@ describe('v3 to v4', () => {
|
||||
};
|
||||
|
||||
test('migrate settings', () => {
|
||||
const expectSettings: Settings = {
|
||||
version: ONTIME_VERSION,
|
||||
const expectSettings: Settings & { serverPort: number } = {
|
||||
version: '4.0.0',
|
||||
serverPort: 4001,
|
||||
editorKey: null,
|
||||
operatorKey: null,
|
||||
@@ -493,3 +493,101 @@ describe('v3 to v4', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('v4 remove server port', () => {
|
||||
const demoDb = {
|
||||
rundowns: {},
|
||||
project: {
|
||||
title: 'Eurovision Song Contest',
|
||||
description: 'Turin 2022',
|
||||
url: 'www.github.com/cpvalente/ontime',
|
||||
info: 'Rehearsal Schedule - Turin 2022\nAll performers to wear full costumes for 1st rehearsal',
|
||||
logo: null,
|
||||
custom: [],
|
||||
},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: '4.0.0',
|
||||
serverPort: 4001,
|
||||
editorKey: null,
|
||||
operatorKey: null,
|
||||
timeFormat: '24',
|
||||
language: 'en',
|
||||
},
|
||||
viewSettings: {
|
||||
dangerColor: '#ff7300',
|
||||
normalColor: '#ffffffcc',
|
||||
overrideStyles: false,
|
||||
warningColor: '#ffa528',
|
||||
},
|
||||
customFields: {
|
||||
song: {
|
||||
label: 'Song',
|
||||
type: 'text',
|
||||
colour: '#339E4E',
|
||||
},
|
||||
artist: {
|
||||
label: 'Artist',
|
||||
type: 'text',
|
||||
colour: '#3E75E8',
|
||||
},
|
||||
},
|
||||
urlPresets: [
|
||||
{
|
||||
enabled: true,
|
||||
alias: 'clock',
|
||||
target: 'timer',
|
||||
search:
|
||||
'timer?showLeadingZeros=true&timerType=clock&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
alias: 'minimal',
|
||||
target: 'timer',
|
||||
search:
|
||||
'timer?showLeadingZeros=true&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true',
|
||||
},
|
||||
],
|
||||
automation: {
|
||||
enabledAutomations: false,
|
||||
enabledOscIn: true,
|
||||
oscPortIn: 8888,
|
||||
triggers: [],
|
||||
automations: {},
|
||||
},
|
||||
};
|
||||
|
||||
it('should migrate if server port exists', () => {
|
||||
expect(v4.shouldMigrateServerPort(demoDb)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should not migrate if the version is newer', () => {
|
||||
expect(
|
||||
v4.shouldMigrateServerPort({ settings: { version: '5.0.0', serverPort: 4001 } } as unknown as DatabaseModel),
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should not migrate if there is no server port', () => {
|
||||
expect(v4.shouldMigrateServerPort({ settings: { version: '4.0.0' } } as DatabaseModel)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('remove server port from project', () => {
|
||||
const { db: result, serverPort } = v4.migrateServerPort(demoDb as DatabaseModel);
|
||||
expect(result).not.toHaveProperty('settings.serverPort');
|
||||
expect(serverPort).toBe(4001);
|
||||
expect(result.automation).toMatchObject(demoDb.automation);
|
||||
expect(result.customFields).toMatchObject(demoDb.customFields);
|
||||
expect(result.project).toMatchObject(demoDb.project);
|
||||
expect(result.rundowns).toMatchObject(demoDb.rundowns);
|
||||
expect(result.settings).toMatchObject({
|
||||
app: 'ontime',
|
||||
version: '4.5.0',
|
||||
editorKey: null,
|
||||
operatorKey: null,
|
||||
timeFormat: '24',
|
||||
language: 'en',
|
||||
});
|
||||
expect(result.urlPresets).toMatchObject(demoDb.urlPresets);
|
||||
expect(result.viewSettings).toMatchObject(demoDb.viewSettings);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getTimezoneLabel } from '../../utils/time.js';
|
||||
import { password, routerPrefix } from '../../externals.js';
|
||||
import { hashPassword } from '../../utils/hash.js';
|
||||
import { ONTIME_VERSION } from '../../ONTIME_VERSION.js';
|
||||
import { portManager } from '../../classes/port-manager/PortManager.js';
|
||||
|
||||
const startedAt = new Date();
|
||||
|
||||
@@ -37,7 +38,8 @@ export async function getSessionStats(): Promise<SessionStats> {
|
||||
* Adds business logic to gathering data for the info endpoint
|
||||
*/
|
||||
export async function getInfo(): Promise<GetInfo> {
|
||||
const { version, serverPort } = getDataProvider().getSettings();
|
||||
const { version } = getDataProvider().getSettings();
|
||||
const { port } = portManager.getPort();
|
||||
|
||||
// get nif and inject localhost
|
||||
const ni = getNetworkInterfaces();
|
||||
@@ -46,7 +48,7 @@ export async function getInfo(): Promise<GetInfo> {
|
||||
return {
|
||||
networkInterfaces: ni,
|
||||
version,
|
||||
serverPort,
|
||||
serverPort: port,
|
||||
publicDir: publicDir.root,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ describe('parseSettings()', () => {
|
||||
expect(result).toBeTypeOf('object');
|
||||
expect(result).toMatchObject({
|
||||
version: expect.any(String),
|
||||
serverPort: 4001,
|
||||
editorKey: null,
|
||||
operatorKey: null,
|
||||
timeFormat: '24',
|
||||
|
||||
@@ -18,7 +18,6 @@ export function parseSettings(data: Partial<DatabaseModel>): Settings {
|
||||
|
||||
return {
|
||||
version: defaultSettings.version,
|
||||
serverPort: data.settings.serverPort ?? defaultSettings.serverPort,
|
||||
editorKey: data.settings.editorKey ?? defaultSettings.editorKey,
|
||||
operatorKey: data.settings.operatorKey ?? defaultSettings.operatorKey,
|
||||
timeFormat: data.settings.timeFormat ?? defaultSettings.timeFormat,
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
import express from 'express';
|
||||
import { matchedData } from 'express-validator';
|
||||
import type { Request, Response } from 'express';
|
||||
import { deepEqual } from 'fast-equals';
|
||||
import express from "express";
|
||||
import { matchedData } from "express-validator";
|
||||
import type { Request, Response } from "express";
|
||||
import { deepEqual } from "fast-equals";
|
||||
|
||||
import { ErrorResponse, RefetchKey, Settings } from 'ontime-types';
|
||||
import { getErrorMessage, obfuscate } from 'ontime-utils';
|
||||
import { ErrorResponse, PortInfo, RefetchKey, Settings } from "ontime-types";
|
||||
import { getErrorMessage, obfuscate } from "ontime-utils";
|
||||
|
||||
import { validateSettings, validateWelcomeDialog } from './settings.validation.js';
|
||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||
import * as appState from '../../services/app-state-service/AppStateService.js';
|
||||
import { isDocker } from '../../setup/environment.js';
|
||||
import { sendRefetch } from '../../adapters/WebsocketAdapter.js';
|
||||
import {
|
||||
validateSettings,
|
||||
validateWelcomeDialog,
|
||||
validateServerPort,
|
||||
} from "./settings.validation.js";
|
||||
import { getDataProvider } from "../../classes/data-provider/DataProvider.js";
|
||||
import * as appState from "../../services/app-state-service/AppStateService.js";
|
||||
import { sendRefetch } from "../../adapters/WebsocketAdapter.js";
|
||||
import { portManager } from "../../classes/port-manager/PortManager.js";
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
router.post('/welcomedialog', validateWelcomeDialog, async (req: Request, res: Response) => {
|
||||
router.post("/welcomedialog", validateWelcomeDialog, async (req: Request, res: Response) => {
|
||||
const show = await appState.setShowWelcomeDialog(req.body.show);
|
||||
res.status(200).json({ show });
|
||||
});
|
||||
|
||||
router.get('/', (_req: Request, res: Response<Settings>) => {
|
||||
router.get("/", (_req: Request, res: Response<Settings>) => {
|
||||
const settings = getDataProvider().getSettings();
|
||||
const obfuscatedSettings = { ...settings };
|
||||
if (settings.editorKey) {
|
||||
@@ -33,26 +37,52 @@ router.get('/', (_req: Request, res: Response<Settings>) => {
|
||||
res.status(200).json(obfuscatedSettings);
|
||||
});
|
||||
|
||||
router.post('/', validateSettings, async (req: Request, res: Response<Settings | ErrorResponse>) => {
|
||||
router.post(
|
||||
"/",
|
||||
validateSettings,
|
||||
async (req: Request, res: Response<Settings | ErrorResponse>) => {
|
||||
try {
|
||||
const data = matchedData<Settings>(req);
|
||||
const settings = getDataProvider().getSettings();
|
||||
|
||||
data.version = settings.version;
|
||||
|
||||
if (!deepEqual(data, settings)) {
|
||||
await getDataProvider().setSettings(data);
|
||||
sendRefetch(RefetchKey.Settings);
|
||||
}
|
||||
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(400).json({ message });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.get("/serverport", (_req: Request, res: Response<PortInfo | ErrorResponse>) => {
|
||||
try {
|
||||
const data = matchedData<Settings>(req);
|
||||
const settings = getDataProvider().getSettings();
|
||||
|
||||
if (isDocker && settings.serverPort !== data.serverPort) {
|
||||
res.status(403).json({ message: 'Can`t change port when running inside docker' });
|
||||
return;
|
||||
}
|
||||
|
||||
data.version = settings.version;
|
||||
|
||||
if (!deepEqual(data, settings)) {
|
||||
await getDataProvider().setSettings(data);
|
||||
sendRefetch(RefetchKey.Settings);
|
||||
}
|
||||
|
||||
res.status(200).json(data);
|
||||
const { port, pendingRestart } = portManager.getPort();
|
||||
res.status(200).json({ port, pendingRestart });
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(400).json({ message });
|
||||
res.status(500).json({ message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/serverport",
|
||||
validateServerPort,
|
||||
async (req: Request, res: Response<PortInfo | ErrorResponse>) => {
|
||||
try {
|
||||
const { serverPort } = matchedData<{ serverPort: number }>(req);
|
||||
portManager.changePort(serverPort);
|
||||
const { port, pendingRestart } = portManager.getPort();
|
||||
|
||||
res.status(200).json({ port, pendingRestart });
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(400).json({ message });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -26,7 +26,11 @@ export const validateSettings = [
|
||||
pinValidator('operatorKey'),
|
||||
body('timeFormat').isString().isIn(['12', '24']).withMessage('Time format can only be "12" or "24"'),
|
||||
body('language').isString().trim().notEmpty(),
|
||||
body('serverPort').isPort().withMessage('Invalid value found for server port').toInt(),
|
||||
|
||||
requestValidationFunction,
|
||||
];
|
||||
|
||||
export const validateServerPort = [
|
||||
body('serverPort').isPort().withMessage('Invalid value found for server port').toInt(),
|
||||
requestValidationFunction,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user