diff --git a/meteor/server/api/rest/v1/index.ts b/meteor/server/api/rest/v1/index.ts index 786f18cded..3bb60c79e5 100644 --- a/meteor/server/api/rest/v1/index.ts +++ b/meteor/server/api/rest/v1/index.ts @@ -1,6 +1,7 @@ import KoaRouter from '@koa/router' import { interpollateTranslation, translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage' import { UserError, UserErrorMessage } from '@sofie-automation/corelib/dist/error' +import { IConfigMessage, NoteSeverity } from '@sofie-automation/blueprints-integration' import Koa from 'koa' import bodyParser from 'koa-bodyparser' import { Meteor } from 'meteor/meteor' @@ -92,6 +93,19 @@ function extractErrorDetails(e: unknown): string[] | undefined { } } +export const checkValidation = (method: string, configValidationMsgs: IConfigMessage[]): void => { + const configValidationOK = configValidationMsgs.reduce((acc, msg) => acc && msg.level === NoteSeverity.INFO, true) + if (!configValidationOK) { + const details = JSON.stringify( + configValidationMsgs.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), + null, + 2 + ) + logger.error(`${method} failed blueprint config validation with errors: ${details}`) + throw new Meteor.Error(409, `${method} has failed blueprint config validation`, details) + } +} + interface APIRequestError { status: number message: string diff --git a/meteor/server/api/rest/v1/showstyles.ts b/meteor/server/api/rest/v1/showstyles.ts index 52cc5ec477..bddaeac44e 100644 --- a/meteor/server/api/rest/v1/showstyles.ts +++ b/meteor/server/api/rest/v1/showstyles.ts @@ -25,9 +25,9 @@ import { import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' import { runUpgradeForShowStyleBase, validateConfigForShowStyleBase } from '../../../migration/upgrades' -import { NoteSeverity } from '@sofie-automation/blueprints-integration' import { DBShowStyleVariant } from '@sofie-automation/corelib/dist/dataModel/ShowStyleVariant' import { assertNever } from '@sofie-automation/corelib/dist/lib' +import { checkValidation } from '.' class ShowStylesServerAPI implements ShowStylesRestAPI { async getShowStyleBases( @@ -43,9 +43,15 @@ class ShowStylesServerAPI implements ShowStylesRestAPI { async addShowStyleBase( _connection: Meteor.Connection, _event: string, - showStyleBase: APIShowStyleBase + apiShowStyleBase: APIShowStyleBase ): Promise> { - const showStyle = await showStyleBaseFrom(showStyleBase) + const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle( + apiShowStyleBase, + protectString(apiShowStyleBase.blueprintId) + ) + checkValidation(`addShowStyleBase`, blueprintConfigValidation) + + const showStyle = await showStyleBaseFrom(apiShowStyleBase) if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleBase`) const showStyleId = showStyle._id await ShowStyleBases.insertAsync(showStyle) @@ -74,23 +80,7 @@ class ShowStylesServerAPI implements ShowStylesRestAPI { apiShowStyleBase, protectString(apiShowStyleBase.blueprintId) ) - const blueprintConfigValidationOK = blueprintConfigValidation.reduce( - (acc, msg) => acc && msg.level === NoteSeverity.INFO, - true - ) - if (!blueprintConfigValidationOK) { - const details = JSON.stringify( - blueprintConfigValidation.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`addOrUpdateShowStyleBase failed blueprint config validation with errors: ${details}`) - throw new Meteor.Error( - 409, - `ShowStyleBase ${showStyleBaseId} has failed blueprint config validation`, - details - ) - } + checkValidation(`addOrUpdateShowStyleBase ${showStyleBaseId}`, blueprintConfigValidation) const showStyle = await showStyleBaseFrom(apiShowStyleBase, showStyleBaseId) if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleBase`) @@ -120,23 +110,11 @@ class ShowStylesServerAPI implements ShowStylesRestAPI { await ShowStyleBases.upsertAsync(showStyleBaseId, showStyle) const validation = await validateConfigForShowStyleBase(showStyleBaseId) - const validateOK = validation.messages.reduce((acc, msg) => acc && msg.level === NoteSeverity.INFO, true) - if (!validateOK) { - const details = JSON.stringify( - validation.messages.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`addOrUpdateShowStyleBase failed validation with errors: ${details}`) - throw new Meteor.Error(409, `ShowStyleBase ${showStyleBaseId} has failed validation`, details) - } + checkValidation(`addOrUpdateShowStyleBase ${showStyleBaseId}`, validation.messages) - return ClientAPI.responseSuccess( - await new Promise((resolve) => - // wait for the upsert to complete before upgrade - setTimeout(async () => resolve(await runUpgradeForShowStyleBase(showStyleBaseId)), 200) - ) - ) + // wait for the upsert to complete before upgrade + await new Promise((resolve) => setTimeout(() => resolve(), 200)) + return ClientAPI.responseSuccess(await runUpgradeForShowStyleBase(showStyleBaseId)) } async getShowStyleConfig( @@ -158,26 +136,23 @@ class ShowStylesServerAPI implements ShowStylesRestAPI { ): Promise> { const existingShowStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId) if (existingShowStyleBase) { - const existingShowStyle = await ShowStyleBases.findOneAsync(showStyleBaseId) - if (existingShowStyle) { - const rundowns = (await Rundowns.findFetchAsync( - { showStyleBaseId }, - { projection: { playlistId: 1 } } - )) as Array> - const playlists = (await RundownPlaylists.findFetchAsync( - { _id: { $in: rundowns.map((r) => r.playlistId) } }, - { - projection: { - activationId: 1, - }, - } - )) as Array> - if (playlists.some((playlist) => playlist.activationId !== undefined)) { - throw new Meteor.Error( - 412, - `Cannot update ShowStyleBase ${showStyleBaseId} as it is in use by an active Playlist` - ) + const rundowns = (await Rundowns.findFetchAsync( + { showStyleBaseId }, + { projection: { playlistId: 1 } } + )) as Array> + const playlists = (await RundownPlaylists.findFetchAsync( + { _id: { $in: rundowns.map((r) => r.playlistId) } }, + { + projection: { + activationId: 1, + }, } + )) as Array> + if (playlists.some((playlist) => playlist.activationId !== undefined)) { + throw new Meteor.Error( + 412, + `Cannot update ShowStyleBase ${showStyleBaseId} as it is in use by an active Playlist` + ) } } else throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} not found`) @@ -188,23 +163,7 @@ class ShowStylesServerAPI implements ShowStylesRestAPI { apiShowStyleBase, protectString(apiShowStyleBase.blueprintId) ) - const blueprintConfigValidationOK = blueprintConfigValidation.reduce( - (acc, msg) => acc && msg.level === NoteSeverity.INFO, - true - ) - if (!blueprintConfigValidationOK) { - const details = JSON.stringify( - blueprintConfigValidation.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`updateShowStyleBase failed blueprint config validation with errors: ${details}`) - throw new Meteor.Error( - 409, - `ShowStyleBase ${showStyleBaseId} has failed blueprint config validation`, - details - ) - } + checkValidation(`updateShowStyleConfig ${showStyleBaseId}`, blueprintConfigValidation) const showStyle = await showStyleBaseFrom(apiShowStyleBase, showStyleBaseId) if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleBase`) @@ -212,23 +171,11 @@ class ShowStylesServerAPI implements ShowStylesRestAPI { await ShowStyleBases.upsertAsync(showStyleBaseId, showStyle) const validation = await validateConfigForShowStyleBase(showStyleBaseId) - const validateOK = validation.messages.reduce((acc, msg) => acc && msg.level === NoteSeverity.INFO, true) - if (!validateOK) { - const details = JSON.stringify( - validation.messages.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`addOrUpdateShowStyleBase failed validation with errors: ${details}`) - throw new Meteor.Error(409, `ShowStyleBase ${showStyleBaseId} has failed validation`, details) - } + checkValidation(`updateShowStyleConfig ${showStyleBaseId}`, validation.messages) - return ClientAPI.responseSuccess( - await new Promise((resolve) => - // wait for the upsert to complete before upgrade - setTimeout(async () => resolve(await runUpgradeForShowStyleBase(showStyleBaseId)), 200) - ) - ) + // wait for the upsert to complete before upgrade + await new Promise((resolve) => setTimeout(() => resolve(), 200)) + return ClientAPI.responseSuccess(await runUpgradeForShowStyleBase(showStyleBaseId)) } async deleteShowStyleBase( @@ -322,23 +269,7 @@ class ShowStylesServerAPI implements ShowStylesRestAPI { apiShowStyleVariant, showStyleBase.blueprintId ) - const blueprintConfigValidationOK = blueprintConfigValidation.reduce( - (acc, msg) => acc && msg.level === NoteSeverity.INFO, - true - ) - if (!blueprintConfigValidationOK) { - const details = JSON.stringify( - blueprintConfigValidation.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`addOrUpdateShowStyleVariant failed blueprint config validation with errors: ${details}`) - throw new Meteor.Error( - 409, - `ShowStyleBase ${showStyleBaseId} variant has failed blueprint config validation`, - details - ) - } + checkValidation(`addOrUpdateShowStyleVariant ${showStyleVariantId}`, blueprintConfigValidation) const showStyle = showStyleVariantFrom(apiShowStyleVariant, showStyleVariantId) if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleVariant`) diff --git a/meteor/server/api/rest/v1/studios.ts b/meteor/server/api/rest/v1/studios.ts index b33379a769..845c2466ff 100644 --- a/meteor/server/api/rest/v1/studios.ts +++ b/meteor/server/api/rest/v1/studios.ts @@ -19,6 +19,7 @@ import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { PeripheralDevice } from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice' import { StudioContentWriteAccess } from '../../../security/studio' import { ServerPlayoutAPI } from '../../playout/playout' +import { checkValidation } from '.' class StudiosServerAPI implements StudiosRestAPI { constructor(private context: ServerAPIContext) {} @@ -38,19 +39,7 @@ class StudiosServerAPI implements StudiosRestAPI { apiStudio: APIStudio ): Promise> { const blueprintConfigValidation = await validateAPIBlueprintConfigForStudio(apiStudio) - const blueprintConfigValidationOK = blueprintConfigValidation.reduce( - (acc, msg) => acc && msg.level === NoteSeverity.INFO, - true - ) - if (!blueprintConfigValidationOK) { - const details = JSON.stringify( - blueprintConfigValidation.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`addStudio failed blueprint config validation with errors: ${details}`) - throw new Meteor.Error(409, `Studio has failed blueprint config validation`, details) - } + checkValidation(`addStudio`, blueprintConfigValidation) const newStudio = await studioFrom(apiStudio) if (!newStudio) throw new Meteor.Error(400, `Invalid Studio`) @@ -91,19 +80,7 @@ class StudiosServerAPI implements StudiosRestAPI { apiStudio: APIStudio ): Promise> { const blueprintConfigValidation = await validateAPIBlueprintConfigForStudio(apiStudio) - const blueprintConfigValidationOK = blueprintConfigValidation.reduce( - (acc, msg) => acc && msg.level === NoteSeverity.INFO, - true - ) - if (!blueprintConfigValidationOK) { - const details = JSON.stringify( - blueprintConfigValidation.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`addOrUpdateStudio failed blueprint config validation with errors: ${details}`) - throw new Meteor.Error(409, `Studio ${studioId} has failed blueprint config validation`, details) - } + checkValidation(`addOrUpdateStudio ${studioId}`, blueprintConfigValidation) const newStudio = await studioFrom(apiStudio, studioId) if (!newStudio) throw new Meteor.Error(400, `Invalid Studio`) @@ -126,23 +103,11 @@ class StudiosServerAPI implements StudiosRestAPI { await Studios.upsertAsync(studioId, newStudio) const validation = await validateConfigForStudio(studioId) - const validateOK = validation.messages.reduce((acc, msg) => acc && msg.level === NoteSeverity.INFO, true) - if (!validateOK) { - const details = JSON.stringify( - validation.messages.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`addOrUpdateStudio failed validation with errors: ${details}`) - throw new Meteor.Error(409, `Studio ${studioId} has failed validation`, details) - } + checkValidation(`addOrUpdateStudio ${studioId}`, validation.messages) - return ClientAPI.responseSuccess( - await new Promise((resolve) => - // wait for the upsert to complete before upgrade - setTimeout(async () => resolve(await runUpgradeForStudio(studioId)), 200) - ) - ) + // wait for the upsert to complete before upgrade + await new Promise((resolve) => setTimeout(() => resolve(), 200)) + return ClientAPI.responseSuccess(await runUpgradeForStudio(studioId)) } async getStudioConfig( @@ -171,19 +136,7 @@ class StudiosServerAPI implements StudiosRestAPI { apiStudio.config = config const blueprintConfigValidation = await validateAPIBlueprintConfigForStudio(apiStudio) - const blueprintConfigValidationOK = blueprintConfigValidation.reduce( - (acc, msg) => acc && msg.level === NoteSeverity.INFO, - true - ) - if (!blueprintConfigValidationOK) { - const details = JSON.stringify( - blueprintConfigValidation.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`updateStudioConfig failed blueprint config validation with errors: ${details}`) - throw new Meteor.Error(409, `Studio ${studioId} has failed blueprint config validation`, details) - } + checkValidation(`updateStudioConfig ${studioId}`, blueprintConfigValidation) const newStudio = await studioFrom(apiStudio, studioId) if (!newStudio) throw new Meteor.Error(400, `Invalid Studio`) @@ -191,23 +144,11 @@ class StudiosServerAPI implements StudiosRestAPI { await Studios.upsertAsync(studioId, newStudio) const validation = await validateConfigForStudio(studioId) - const validateOK = validation.messages.reduce((acc, msg) => acc && msg.level === NoteSeverity.INFO, true) - if (!validateOK) { - const details = JSON.stringify( - validation.messages.filter((msg) => msg.level < NoteSeverity.INFO).map((msg) => msg.message.key), - null, - 2 - ) - logger.error(`updateStudioConfig failed validation with errors: ${details}`) - throw new Meteor.Error(409, `Studio ${studioId} has failed validation`, details) - } + checkValidation(`updateStudioConfig ${studioId}`, validation.messages) - return ClientAPI.responseSuccess( - await new Promise((resolve) => - // wait for the upsert to complete before upgrade - setTimeout(async () => resolve(await runUpgradeForStudio(studioId)), 200) - ) - ) + // wait for the upsert to complete before upgrade + await new Promise((resolve) => setTimeout(() => resolve(), 200)) + return ClientAPI.responseSuccess(await runUpgradeForStudio(studioId)) } async deleteStudio( diff --git a/meteor/server/api/rest/v1/typeConversion.ts b/meteor/server/api/rest/v1/typeConversion.ts index c7eda619f4..660d7fb874 100644 --- a/meteor/server/api/rest/v1/typeConversion.ts +++ b/meteor/server/api/rest/v1/typeConversion.ts @@ -505,8 +505,9 @@ export async function ShowStyleBaseBlueprintConfigFromAPI( if (!apiShowStyleBase.blueprintConfigPresetId) throw new Meteor.Error(500, `ShowStyleBase ${apiShowStyleBase.name} is missing config preset`) - if (typeof blueprintManifest.blueprintConfigFromAPI !== 'function') - throw new Meteor.Error(500, `Blueprint ${blueprintManifest.blueprintId} does not support this config flow`) + if (typeof blueprintManifest.blueprintConfigFromAPI !== 'function') { + return apiShowStyleBase.config as IBlueprintConfig + } const blueprintContext = new CommonContext( 'BlueprintConfigFromAPI', @@ -565,8 +566,9 @@ export async function StudioBlueprintConfigFromAPI( if (!apiStudio.blueprintConfigPresetId) throw new Meteor.Error(500, `Studio ${apiStudio.name} is missing config preset`) - if (typeof blueprintManifest.blueprintConfigFromAPI !== 'function') - throw new Meteor.Error(500, `Blueprint ${blueprintManifest.blueprintId} does not support this config flow`) + if (typeof blueprintManifest.blueprintConfigFromAPI !== 'function') { + return apiStudio.config as IBlueprintConfig + } const blueprintContext = new CommonContext( 'BlueprintConfigFromAPI',