@@ -489,6 +489,7 @@ async function retreiveSnapshot(snapshotId: SnapshotId, cred0: Credentials): Pro
489489
490490 return readSnapshot
491491}
492+
492493async function restoreFromSnapshot (
493494 /** The snapshot data to restore */
494495 snapshot : AnySnapshot ,
@@ -497,22 +498,13 @@ async function restoreFromSnapshot(
497498) : Promise < void > {
498499 // Determine what kind of snapshot
499500
500- if ( ! _ . isObject ( snapshot ) ) throw new Meteor . Error ( 500 , `Restore input data is not an object` )
501501 // First, some special (debugging) cases:
502- // @ts -expect-error is's not really a snapshot here:
503- if ( snapshot . externalId && snapshot . segments && snapshot . type === 'mos' ) {
502+ if ( snapshotIsAMOSDataDump ( snapshot ) ) {
504503 // Special: Not a snapshot, but a datadump of a MOS rundown
505- const studioId : StudioId = Meteor . settings . manualSnapshotIngestStudioId || 'studio0'
506- const studioExists = await checkStudioExists ( studioId )
507- if ( studioExists ) {
508- await importIngestRundown ( studioId , snapshot as unknown as IngestRundown )
509- return
510- }
511- throw new Meteor . Error ( 500 , `No Studio found` )
504+ return ingestFromSnapshot ( snapshot )
512505 }
513506
514507 // Then, continue as if it's a normal snapshot:
515-
516508 if ( ! snapshot . snapshot ) throw new Meteor . Error ( 500 , `Restore input data is not a snapshot (${ _ . keys ( snapshot ) } )` )
517509
518510 if ( snapshot . snapshot . type === SnapshotType . RUNDOWNPLAYLIST ) {
@@ -525,11 +517,7 @@ async function restoreFromSnapshot(
525517 )
526518 }
527519
528- // TODO: Improve this. This matches the 'old' behaviour
529- const studios = await Studios . findFetchAsync ( { } )
530- const snapshotStudioExists = studios . find ( ( studio ) => studio . _id === playlistSnapshot . playlist . studioId )
531- const studioId = snapshotStudioExists ? playlistSnapshot . playlist . studioId : studios [ 0 ] ?. _id
532- if ( ! studioId ) throw new Meteor . Error ( 500 , `No Studio found` )
520+ const studioId = await getStudioIdFromPlaylistSnapshot ( playlistSnapshot )
533521
534522 // A snapshot of a rundownPlaylist
535523 return restoreFromRundownPlaylistSnapshot ( snapshot as RundownPlaylistSnapshot , studioId , restoreDebugData )
@@ -540,6 +528,73 @@ async function restoreFromSnapshot(
540528 throw new Meteor . Error ( 402 , `Unknown snapshot type "${ snapshot . snapshot . type } "` )
541529 }
542530}
531+ function snapshotIsAMOSDataDump ( snapshot : Record < string , any > ) : boolean {
532+ // Special: Is not a snapshot, but a datadump of a MOS rundown
533+ return snapshot . externalId && snapshot . segments && snapshot . type === 'mos'
534+ }
535+ async function getStudioIdFromPlaylistSnapshot ( playlistSnapshot : RundownPlaylistSnapshot ) : Promise < StudioId > {
536+ // TODO: Improve this. This matches the 'old' behaviour
537+ const studios = await Studios . findFetchAsync ( { } )
538+ const snapshotStudioExists = studios . find ( ( studio ) => studio . _id === playlistSnapshot . playlist . studioId )
539+ const studioId = snapshotStudioExists ? playlistSnapshot . playlist . studioId : studios [ 0 ] ?. _id
540+ if ( ! studioId ) throw new Meteor . Error ( 500 , `No Studio found` )
541+ return studioId
542+ }
543+ /** Read the ingest data from a snapshot and pipe it into blueprints */
544+ async function ingestFromSnapshot (
545+ /** The snapshot data to restore */
546+ snapshot : AnySnapshot
547+ ) : Promise < void > {
548+ // First, some special (debugging) cases:
549+ if ( snapshotIsAMOSDataDump ( snapshot ) ) {
550+ // Special: Not a snapshot, but a datadump of a MOS rundown
551+ const studioId : StudioId = Meteor . settings . manualSnapshotIngestStudioId || 'studio0'
552+ const studioExists = await checkStudioExists ( studioId )
553+ if ( studioExists ) {
554+ return importIngestRundown ( studioId , snapshot as unknown as IngestRundown )
555+ } else throw new Meteor . Error ( 500 , `No Studio found` )
556+ }
557+
558+ // Determine what kind of snapshot
559+ if ( ! snapshot . snapshot ) throw new Meteor . Error ( 500 , `Restore input data is not a snapshot (${ _ . keys ( snapshot ) } )` )
560+ if ( snapshot . snapshot . type === SnapshotType . RUNDOWNPLAYLIST ) {
561+ const playlistSnapshot = snapshot as RundownPlaylistSnapshot
562+
563+ const studioId = await getStudioIdFromPlaylistSnapshot ( playlistSnapshot )
564+
565+ // Read the ingestData from the snapshot
566+ const ingestData = playlistSnapshot . ingestData
567+
568+ const rundownData = ingestData . filter ( ( e ) => e . type === 'rundown' )
569+ const segmentData = ingestData . filter ( ( e ) => e . type === 'segment' )
570+ const partData = ingestData . filter ( ( e ) => e . type === 'part' )
571+
572+ if ( rundownData . length === 0 ) throw new Meteor . Error ( 402 , `No rundowns found in ingestData` )
573+
574+ for ( const seg of segmentData ) {
575+ seg . data . parts = partData
576+ . filter ( ( e ) => e . segmentId === seg . segmentId )
577+ . map ( ( e ) => e . data )
578+ . sort ( ( a , b ) => b . rank - a . rank )
579+ }
580+
581+ for ( let i = 0 ; i < rundownData . length ; i ++ ) {
582+ const rundown = rundownData [ i ]
583+
584+ const segmentsInRundown = segmentData . filter ( ( e ) => e . rundownId === rundown . rundownId )
585+
586+ const ingestRundown : IngestRundown = rundown . data
587+ ingestRundown . segments = segmentsInRundown . map ( ( s ) => s . data ) . sort ( ( a , b ) => b . rank - a . rank )
588+
589+ await importIngestRundown ( studioId , ingestRundown )
590+ }
591+ } else {
592+ throw new Meteor . Error (
593+ 402 ,
594+ `Unable to ingest a snapshot of type "${ snapshot . snapshot . type } ", did you mean to restore it?`
595+ )
596+ }
597+ }
543598
544599async function restoreFromRundownPlaylistSnapshot (
545600 snapshot : RundownPlaylistSnapshot ,
@@ -816,8 +871,16 @@ if (!Settings.enableUserAccounts) {
816871 if ( ! snapshot ) throw new Meteor . Error ( 400 , 'Restore Snapshot: Missing request body' )
817872
818873 const restoreDebugData = ctx . headers [ 'restore-debug-data' ] === '1'
874+ const ingestSnapshotData = ctx . headers [ 'ingest-snapshot-data' ] === '1'
819875
820- await restoreFromSnapshot ( snapshot , restoreDebugData )
876+ if ( typeof snapshot !== 'object' || snapshot === null )
877+ throw new Meteor . Error ( 500 , `Restore input data is not an object` )
878+
879+ if ( ingestSnapshotData ) {
880+ await ingestFromSnapshot ( snapshot )
881+ } else {
882+ await restoreFromSnapshot ( snapshot , restoreDebugData )
883+ }
821884
822885 ctx . response . status = 200
823886 ctx . response . body = content
0 commit comments