Skip to content

Commit

Permalink
fix: Data piping for Rundown, Segment and Part
Browse files Browse the repository at this point in the history
  • Loading branch information
nytamin committed Dec 5, 2023
1 parent 70e9afd commit e830031
Show file tree
Hide file tree
Showing 43 changed files with 1,761 additions and 129 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
28 changes: 23 additions & 5 deletions packages/apps/backend/src/api-server/ApiServer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { feathers } from '@feathersjs/feathers'
import { RealTimeConnection, feathers } from '@feathersjs/feathers'
import { koa, rest, bodyParser, errorHandler, serveStatic, cors } from '@feathersjs/koa'
import socketio from '@feathersjs/socketio'
import { EventEmitter } from 'eventemitter3'
import { ServiceTypes } from '@sofie-prompter-editor/shared-model'
import { LoggerInstance } from '../lib/logger.js'
import { PublishChannels } from './PublishChannels.js'
import { PlaylistFeathersService, PlaylistService } from './services/PlaylistService.js'
import { RundownFeathersService, RundownService } from './services/RundownService.js'
import { SegmentFeathersService, SegmentService } from './services/SegmentService.js'
import { PartFeathersService, PartService } from './services/PartService.js'
import { ExampleFeathersService, ExampleService } from './services/ExampleService.js'
import { Store } from '../data-stores/Store.js'
import { SofieCoreConnection } from '../sofie-core-connection/SofieCoreConnection.js'

export type ApiServerEvents = {
connection: []
Expand All @@ -17,10 +21,18 @@ export class ApiServer extends EventEmitter<ApiServerEvents> {

public initialized: Promise<void>
public readonly playlist: PlaylistFeathersService
public readonly rundown: RundownFeathersService
public readonly segment: SegmentFeathersService
public readonly part: PartFeathersService
public readonly example: ExampleFeathersService

private log: LoggerInstance
constructor(log: LoggerInstance, port: number, private store: Store) {
constructor(
log: LoggerInstance,
port: number,
private store: Store,
private coreConnection: SofieCoreConnection | undefined
) {
super()
this.log = log.category('ApiServer')

Expand All @@ -37,19 +49,25 @@ export class ApiServer extends EventEmitter<ApiServerEvents> {
this.app.configure(rest())
this.app.configure(socketio({ cors: { origin: '*' } })) // TODO: cors

this.playlist = PlaylistService.setupService(this.app, this.store, this.log)
this.playlist = PlaylistService.setupService(this.log, this.app, this.store)
this.rundown = RundownService.setupService(this.log, this.app, this.store, this.coreConnection)
this.segment = SegmentService.setupService(this.log, this.app, this.store)
this.part = PartService.setupService(this.log, this.app, this.store)

this.example = ExampleService.setupService(this.app)

this.app.on('connection', (connection) => {
this.app.on('connection', (connection: RealTimeConnection) => {
// A new client connection has been made
this.emit('connection')

// Add the connection to the Anything channel:
this.app.channel(PublishChannels.Everyone()).join(connection)
})
this.app.on('disconnect', (_connection) => {
this.app.on('disconnect', (connection: RealTimeConnection) => {
// A client disconnected.
// Note: A disconnected client will leave all channels automatically.

this.coreConnection?.unsubscribe(connection)
})

this.playlist.on('tmpPong', (payload: string) => {
Expand Down
11 changes: 9 additions & 2 deletions packages/apps/backend/src/api-server/PublishChannels.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { RundownPlaylistId } from '@sofie-prompter-editor/shared-model'

/** Definitions of published channels */
export const PublishChannels = {
Everyone: (): string => {
Expand All @@ -11,7 +13,12 @@ export const PublishChannels = {
return `playlists`
},

OneSpecificPlaylist: (playlistId: string): string => {
return `playlists/${playlistId}`
/** All info inside one playlist */
Playlist: (playlistId: RundownPlaylistId): string => {
return `playlist/${playlistId}`
},

RundownsInPlaylist: (playlistId: RundownPlaylistId): string => {
return `playlist/${playlistId}/rundowns`
},
}
104 changes: 104 additions & 0 deletions packages/apps/backend/src/api-server/services/PartService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import EventEmitter from 'eventemitter3'
import { Application, PaginationParams, Params } from '@feathersjs/feathers'
import { ServiceTypes, Services, PartServiceDefinition as Definition } from '@sofie-prompter-editor/shared-model'
export { PlaylistServiceDefinition } from '@sofie-prompter-editor/shared-model'
import { PublishChannels } from '../PublishChannels.js'
import { CustomFeathersService } from './lib.js'
import { Store } from '../../data-stores/Store.js'
import { Lambda, observe } from 'mobx'
import { LoggerInstance } from '../../lib/logger.js'
import { NotFound, NotImplemented } from '@feathersjs/errors'

export type PartFeathersService = CustomFeathersService<Definition.Service, Definition.Events>

/** The methods exposed by this class are exposed in the API */
export class PartService extends EventEmitter<Definition.Events> implements Definition.Service {
static setupService(log: LoggerInstance, app: Application<ServiceTypes, any>, store: Store): PartFeathersService {
app.use(Services.Part, new PartService(log.category('PartService'), store), {
methods: Definition.ALL_METHODS,
serviceEvents: Definition.ALL_EVENTS,
})
const service = app.service(Services.Part) as PartFeathersService
this.setupPublications(app, service)
return service
}
private static setupPublications(app: Application<ServiceTypes, any>, service: PartFeathersService) {
service.publish('created', (data, _context) => {
return app.channel(PublishChannels.RundownsInPlaylist(data.playlistId))
})
service.publish('updated', (data, _context) => {
return app.channel(PublishChannels.RundownsInPlaylist(data.playlistId))
})
// service.publish('patched', (data, _context) => {
// return app.channel(PublishChannels.RundownsInPlaylist(data.playlistId))
// })
service.publish('removed', (data, _context) => {
return app.channel(PublishChannels.RundownsInPlaylist(data.playlistId))
})
}

private observers: Lambda[] = []
constructor(
private log: LoggerInstance,

private store: Store
) {
super()

this.observers.push(
observe(this.store.parts.parts, (change) => {
this.log.debug('observed change', change)

if (change.type === 'add') {
this.emit('created', change.newValue)
} else if (change.type === 'update') {
// const patch = diff(change.oldValue, change.newValue)
// if (patch) this.emit('patched', patch)
this.emit('updated', change.newValue)
} else if (change.type === 'delete') {
this.emit('removed', {
_id: change.oldValue._id,
playlistId: change.oldValue.playlistId,
rundownId: change.oldValue.rundownId,
segmentId: change.oldValue.segmentId,
})
}
})
)
}
destroy() {
// dispose of observers:
for (const obs of this.observers) {
obs()
}
}

public async find(_params?: Params & { paginate?: PaginationParams }): Promise<Data[]> {
return Array.from(this.store.parts.parts.values())
}
public async get(id: Id, _params?: Params): Promise<Data> {
const data = this.store.parts.parts.get(id)
if (!data) throw new NotFound(`Rundown "${id}" not found`)
return data
}
/** @deprecated not supported */
public async create(_data: Data, _params?: Params): Promise<Result> {
throw new NotImplemented(`Not supported`)
}
/** @deprecated not supported */
public async update(_id: NullId, _data: Data, _params?: Params): Promise<Result> {
throw new NotImplemented(`Not supported`)
}
/** @deprecated not supported */
// public async patch(_id: NullId, _data: PatchData, _params?: Params): Promise<Result> {
// throw new NotImplemented(`Not supported`)
// }
/** @deprecated not supported */
public async remove(_id: NullId, _params?: Params): Promise<Result> {
throw new NotImplemented(`Not supported`)
}
}
type Result = Definition.Result
type Id = Definition.Id
type NullId = Definition.NullId
type Data = Definition.Data
68 changes: 36 additions & 32 deletions packages/apps/backend/src/api-server/services/PlaylistService.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import EventEmitter from 'eventemitter3'
import { Application, PaginationParams, Params } from '@feathersjs/feathers'
import {
ServiceTypes,
Services,
PlaylistServiceDefinition as Definition,
diff,
} from '@sofie-prompter-editor/shared-model'
import { ServiceTypes, Services, PlaylistServiceDefinition as Definition } from '@sofie-prompter-editor/shared-model'
export { PlaylistServiceDefinition } from '@sofie-prompter-editor/shared-model'
import { PublishChannels } from '../PublishChannels.js'
import { CustomFeathersService } from './lib.js'
import { Store } from '../../data-stores/Store.js'
import { Lambda, observe } from 'mobx'
import { LoggerInstance } from '../../lib/logger.js'
import { Forbidden, NotFound } from '@feathersjs/errors'
import { NotImplemented, NotFound } from '@feathersjs/errors'

export type PlaylistFeathersService = CustomFeathersService<Definition.Service, Definition.Events>

/** The methods exposed by this class are exposed in the API */
export class PlaylistService extends EventEmitter<Definition.Events> implements Definition.Service {
static setupService(app: Application<ServiceTypes, any>, store: Store, log: LoggerInstance): PlaylistFeathersService {
app.use(Services.Playlist, new PlaylistService(app, store, log.category('PlaylistService')), {
static setupService(log: LoggerInstance, app: Application<ServiceTypes, any>, store: Store): PlaylistFeathersService {
app.use(Services.Playlist, new PlaylistService(log.category('PlaylistService'), app, store), {
methods: Definition.ALL_METHODS,
serviceEvents: Definition.ALL_EVENTS,
})
Expand All @@ -39,28 +34,32 @@ export class PlaylistService extends EventEmitter<Definition.Events> implements
service.publish('updated', (_data, _context) => {
return app.channel(PublishChannels.AllPlaylists())
})
service.publish('patched', (_data, _context) => {
return app.channel(PublishChannels.AllPlaylists())
})
// service.publish('patched', (_data, _context) => {
// return app.channel(PublishChannels.AllPlaylists())
// })
service.publish('removed', (_data, _context) => {
return app.channel(PublishChannels.AllPlaylists())
})

service.publish('created', (data, _context) => {
return app.channel(PublishChannels.Playlist(data._id))
})
}

private observers: Lambda[] = []
constructor(private app: Application<ServiceTypes, any>, private store: Store, private log: LoggerInstance) {
constructor(private log: LoggerInstance, private app: Application<ServiceTypes, any>, private store: Store) {
super()

this.observers.push(
observe(this.store.playlists.playlists, (change) => {
this.log.info('observed change', change)
this.log.debug('observed change', change)

if (change.type === 'add') {
this.emit('created', change.newValue)
} else if (change.type === 'update') {
const patch = diff(change.oldValue, change.newValue)
if (patch) this.emit('patched', patch)
// this.emit('updated', change.newValue)
// const patch = diff(change.oldValue, change.newValue)
// if (patch) this.emit('patched', patch)
this.emit('updated', change.newValue)
} else if (change.type === 'delete') {
this.emit('removed', change.oldValue._id)
}
Expand All @@ -82,32 +81,36 @@ export class PlaylistService extends EventEmitter<Definition.Events> implements
if (!data) throw new NotFound(`Playlist "${id}" not found`)
return data
}
/** @deprecated not supported */
public async create(_data: Data, _params?: Params): Promise<Result> {
throw new Forbidden(`Not supported`)
throw new NotImplemented(`Not supported`)
// this.store.playlists.create(data)
// return this.get(data._id)
}
/** @deprecated not supported */
public async update(_id: NullId, _data: Data, _params?: Params): Promise<Result> {
throw new Forbidden(`Not supported`)
throw new NotImplemented(`Not supported`)
// if (id === null) throw new BadRequest(`id must not be null`)
// if (id !== data._id) throw new BadRequest(`Cannot change id of playlist`)

// this.store.playlists.update(data)
// return this.get(data._id)
}
public async patch(_id: NullId, _data: PatchData, _params?: Params): Promise<Result> {
throw new Forbidden(`Not supported`)
// if (id === null) throw new BadRequest(`id must not be null`)
// const existing = await this.get(id)
// const newData: RundownPlaylist = {
// ...existing,
// ...data,
// }
// this.store.playlists.update(newData)
// return newData
}
/** @deprecated not supported */
// public async patch(_id: NullId, _data: PatchData, _params?: Params): Promise<Result> {
// throw new NotImplemented(`Not supported`)
// // if (id === null) throw new BadRequest(`id must not be null`)
// // const existing = await this.get(id)
// // const newData: RundownPlaylist = {
// // ...existing,
// // ...data,
// // }
// // this.store.playlists.update(newData)
// // return newData
// }
/** @deprecated not supported */
public async remove(_id: NullId, _params?: Params): Promise<Result> {
throw new Forbidden(`Not supported`)
throw new NotImplemented(`Not supported`)
// if (id === null) throw new BadRequest(`id must not be null`)
// const existing = await this.get(id)
// this.store.playlists.remove(id)
Expand All @@ -122,8 +125,10 @@ export class PlaylistService extends EventEmitter<Definition.Events> implements

public async subscribeToPlaylists(_: unknown, params: Params): Promise<void> {
if (!params.connection) throw new Error('No connection!')

this.app.channel(PublishChannels.AllPlaylists()).join(params.connection)
}

public async tmpPing(_payload: string): Promise<string> {
console.log('got a ping!')
setTimeout(() => {
Expand All @@ -138,4 +143,3 @@ type Result = Definition.Result
type Id = Definition.Id
type NullId = Definition.NullId
type Data = Definition.Data
type PatchData = Definition.PatchData
Loading

0 comments on commit e830031

Please sign in to comment.