Skip to content

Commit

Permalink
Consolidate validation method, updates from Julusian comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Rogers committed Oct 9, 2024
1 parent 978a09f commit cb880eb
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 180 deletions.
14 changes: 14 additions & 0 deletions meteor/server/api/rest/v1/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand Down
141 changes: 36 additions & 105 deletions meteor/server/api/rest/v1/showstyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -43,9 +43,15 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
async addShowStyleBase(
_connection: Meteor.Connection,
_event: string,
showStyleBase: APIShowStyleBase
apiShowStyleBase: APIShowStyleBase
): Promise<ClientAPI.ClientResponse<string>> {
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)
Expand Down Expand Up @@ -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`)
Expand Down Expand Up @@ -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<void>((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<void>((resolve) => setTimeout(() => resolve(), 200))
return ClientAPI.responseSuccess(await runUpgradeForShowStyleBase(showStyleBaseId))
}

async getShowStyleConfig(
Expand All @@ -158,26 +136,23 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
): Promise<ClientAPI.ClientResponse<void>> {
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<Pick<Rundown, 'playlistId'>>
const playlists = (await RundownPlaylists.findFetchAsync(
{ _id: { $in: rundowns.map((r) => r.playlistId) } },
{
projection: {
activationId: 1,
},
}
)) as Array<Pick<DBRundownPlaylist, 'activationId'>>
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<Pick<Rundown, 'playlistId'>>
const playlists = (await RundownPlaylists.findFetchAsync(
{ _id: { $in: rundowns.map((r) => r.playlistId) } },
{
projection: {
activationId: 1,
},
}
)) as Array<Pick<DBRundownPlaylist, 'activationId'>>
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`)

Expand All @@ -188,47 +163,19 @@ 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`)

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<void>((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<void>((resolve) => setTimeout(() => resolve(), 200))
return ClientAPI.responseSuccess(await runUpgradeForShowStyleBase(showStyleBaseId))
}

async deleteShowStyleBase(
Expand Down Expand Up @@ -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`)
Expand Down
83 changes: 12 additions & 71 deletions meteor/server/api/rest/v1/studios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand All @@ -38,19 +39,7 @@ class StudiosServerAPI implements StudiosRestAPI {
apiStudio: APIStudio
): Promise<ClientAPI.ClientResponse<string>> {
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`)
Expand Down Expand Up @@ -91,19 +80,7 @@ class StudiosServerAPI implements StudiosRestAPI {
apiStudio: APIStudio
): Promise<ClientAPI.ClientResponse<void>> {
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`)
Expand All @@ -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<void>((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<void>((resolve) => setTimeout(() => resolve(), 200))
return ClientAPI.responseSuccess(await runUpgradeForStudio(studioId))
}

async getStudioConfig(
Expand Down Expand Up @@ -171,43 +136,19 @@ 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`)

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<void>((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<void>((resolve) => setTimeout(() => resolve(), 200))
return ClientAPI.responseSuccess(await runUpgradeForStudio(studioId))
}

async deleteStudio(
Expand Down
10 changes: 6 additions & 4 deletions meteor/server/api/rest/v1/typeConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down

0 comments on commit cb880eb

Please sign in to comment.