Skip to content

Commit

Permalink
Add studio and showStyle blueprintConfig validation support via bluep…
Browse files Browse the repository at this point in the history
…rints
  • Loading branch information
Simon Rogers committed Jul 8, 2024
1 parent 7c90645 commit 7f77b3f
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 29 deletions.
29 changes: 29 additions & 0 deletions meteor/lib/api/rest/v1/showstyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ export interface ShowStylesRestAPI {
showStyleBaseId: ShowStyleBaseId,
showStyleBase: APIShowStyleBase
): Promise<ClientAPI.ClientResponse<void>>
/**
* Gets a ShowStyle config, if the ShowStyle id exists.
*
* Throws if the specified ShowStyle does not exist.
* @param connection Connection data including client and header details
* @param event User event string
* @param showStyleBaseId ShowStyleBaseId to fetch
*/
getShowStyleConfig(
connection: Meteor.Connection,
event: string,
showStyleBaseId: ShowStyleBaseId
): Promise<ClientAPI.ClientResponse<object>>
/**
* Updates a ShowStyle configuration.
*
* Throws if the ShowStyle is in use in an active Rundown.
* @param connection Connection data including client and header details
* @param event User event string
* @param showStyleBaseId Id of the ShowStyleBase to update
* @param object Blueprint configuration object
*/
updateShowStyleConfig(
connection: Meteor.Connection,
event: string,
showStyleBaseId: ShowStyleBaseId,
config: object
): Promise<ClientAPI.ClientResponse<void>>
/**
* Removed a ShowStyleBase.
*
Expand Down Expand Up @@ -192,6 +220,7 @@ export interface APIShowStyleBase {
export interface APIShowStyleVariant {
name: string
showStyleBaseId: string
blueprintConfigPresetId?: string
config: object
rank: number
}
Expand Down
28 changes: 28 additions & 0 deletions meteor/lib/api/rest/v1/studios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,34 @@ export interface StudiosRestAPI {
studioId: StudioId,
studio: APIStudio
): Promise<ClientAPI.ClientResponse<void>>
/**
* Gets a Studio config, if it exists.
*
* Throws if the specified Studio does not exist.
* @param connection Connection data including client and header details
* @param event User event string
* @param studioId Id of the Studio to fetch
*/
getStudioConfig(
connection: Meteor.Connection,
event: string,
studioId: StudioId
): Promise<ClientAPI.ClientResponse<object>>
/**
* Updates a Studio configuration.
*
* Throws if the Studio already exists and is in use in an active Rundown.
* @param connection Connection data including client and header details
* @param event User event string
* @param studioId Id of the Studio to update
* @param object Blueprint configuration object
*/
updateStudioConfig(
connection: Meteor.Connection,
event: string,
studioId: StudioId,
config: object
): Promise<ClientAPI.ClientResponse<void>>
/**
* Deletes a Studio.
*
Expand Down
187 changes: 180 additions & 7 deletions meteor/server/api/rest/v1/showstyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
APIShowStyleVariantFrom,
showStyleBaseFrom,
showStyleVariantFrom,
validateAPIBlueprintConfigForShowStyle,
} from './typeConversion'
import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
Expand Down Expand Up @@ -60,16 +61,38 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
const showStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId)
if (!showStyleBase) throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} does not exist`)

return ClientAPI.responseSuccess(APIShowStyleBaseFrom(showStyleBase))
return ClientAPI.responseSuccess(await APIShowStyleBaseFrom(showStyleBase))
}

async addOrUpdateShowStyleBase(
_connection: Meteor.Connection,
_event: string,
showStyleBaseId: ShowStyleBaseId,
showStyleBase: APIShowStyleBase
apiShowStyleBase: APIShowStyleBase
): Promise<ClientAPI.ClientResponse<void>> {
const showStyle = await showStyleBaseFrom(showStyleBase, showStyleBaseId)
const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle(
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
)
}

const showStyle = await showStyleBaseFrom(apiShowStyleBase, showStyleBaseId)
if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleBase`)

const existingShowStyle = await ShowStyleBases.findOneAsync(showStyleBaseId)
Expand Down Expand Up @@ -108,7 +131,104 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
throw new Meteor.Error(409, `ShowStyleBase ${showStyleBaseId} has failed validation`, details)
}

return ClientAPI.responseSuccess(await runUpgradeForShowStyleBase(showStyleBaseId))
return ClientAPI.responseSuccess(
await new Promise<void>((resolve) =>
// wait for the upsert to complete before upgrade
setTimeout(async () => resolve(await runUpgradeForShowStyleBase(showStyleBaseId)), 200)
)
)
}

async getShowStyleConfig(
_connection: Meteor.Connection,
_event: string,
showStyleBaseId: ShowStyleBaseId
): Promise<ClientAPI.ClientResponse<object>> {
const showStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId)
if (!showStyleBase) throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} does not exist`)

return ClientAPI.responseSuccess((await APIShowStyleBaseFrom(showStyleBase)).config)
}

async updateShowStyleConfig(
_connection: Meteor.Connection,
_event: string,
showStyleBaseId: ShowStyleBaseId,
config: object
): 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`
)
}
}
} else throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} not found`)

const apiShowStyleBase = await APIShowStyleBaseFrom(existingShowStyleBase)
apiShowStyleBase.config = config

const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle(
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
)
}

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)
}

return ClientAPI.responseSuccess(
await new Promise<void>((resolve) =>
// wait for the upsert to complete before upgrade
setTimeout(async () => resolve(await runUpgradeForShowStyleBase(showStyleBaseId)), 200)
)
)
}

async deleteShowStyleBase(
Expand Down Expand Up @@ -185,20 +305,42 @@ class ShowStylesServerAPI implements ShowStylesRestAPI {
const variant = await ShowStyleVariants.findOneAsync(showStyleVariantId)
if (!variant) throw new Meteor.Error(404, `ShowStyleVariant ${showStyleVariantId} not found`)

return ClientAPI.responseSuccess(APIShowStyleVariantFrom(variant))
return ClientAPI.responseSuccess(await APIShowStyleVariantFrom(showStyleBase, variant))
}

async addOrUpdateShowStyleVariant(
_connection: Meteor.Connection,
_event: string,
showStyleBaseId: ShowStyleBaseId,
showStyleVariantId: ShowStyleVariantId,
showStyleVariant: APIShowStyleVariant
apiShowStyleVariant: APIShowStyleVariant
): Promise<ClientAPI.ClientResponse<void>> {
const showStyleBase = await ShowStyleBases.findOneAsync(showStyleBaseId)
if (!showStyleBase) throw new Meteor.Error(404, `ShowStyleBase ${showStyleBaseId} does not exist`)

const showStyle = showStyleVariantFrom(showStyleVariant, showStyleVariantId)
const blueprintConfigValidation = await validateAPIBlueprintConfigForShowStyle(
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
)
}

const showStyle = showStyleVariantFrom(apiShowStyleVariant, showStyleVariantId)
if (!showStyle) throw new Meteor.Error(400, `Invalid ShowStyleVariant`)

const existingShowStyle = await ShowStyleVariants.findOneAsync(showStyleVariantId)
Expand Down Expand Up @@ -335,6 +477,37 @@ export function registerRoutes(registerRoute: APIRegisterHook<ShowStylesRestAPI>
}
)

registerRoute<{ showStyleBaseId: string }, never, object>(
'get',
'/showstyles/:showStyleBaseId/config',
new Map([[404, [UserErrorMessage.ShowStyleBaseNotFound]]]),
showStylesAPIFactory,
async (serverAPI, connection, event, params, _) => {
const showStyleBaseId = protectString<ShowStyleBaseId>(params.showStyleBaseId)
logger.info(`API GET: ShowStyleBase config ${showStyleBaseId}`)

check(showStyleBaseId, String)
return await serverAPI.getShowStyleConfig(connection, event, showStyleBaseId)
}
)

registerRoute<{ showStyleBaseId: string }, object, void>(
'put',
'/showstyles/:showStyleBaseId/config',
new Map([
[404, [UserErrorMessage.ShowStyleBaseNotFound]],
[409, [UserErrorMessage.ValidationFailed]],
]),
showStylesAPIFactory,
async (serverAPI, connection, event, params, body) => {
const showStyleBaseId = protectString<ShowStyleBaseId>(params.showStyleBaseId)
logger.info(`API PUT: Update ShowStyleBase config ${showStyleBaseId}`)

check(showStyleBaseId, String)
return await serverAPI.updateShowStyleConfig(connection, event, showStyleBaseId, body)
}
)

registerRoute<{ showStyleBaseId: string }, never, void>(
'delete',
'/showstyles/:showStyleBaseId',
Expand Down
Loading

0 comments on commit 7f77b3f

Please sign in to comment.