123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- import { DerivedPropertiesManager } from '../classes'
- import { IExecutor, IListener, IChangePair } from '../interfaces'
- import { DatabaseManager } from '@joystream/hydra-common'
- import { Channel, ChannelCategory, Video, VideoCategory, StorageDataObject } from 'query-node/dist/model'
- import { videoRelationsForCountersBare } from '../../content/utils'
- export type IVideoDerivedEntites = 'channel' | 'channel.category' | 'category'
- export type IAvcChange = 1 | -1 | [1 | -1, IVideoDerivedEntites[]]
- export type IAvcChannelChange = number
- /*
- Decides if video is considered active.
- */
- function isVideoActive(video: Video): boolean {
- return !!video.isPublic && !video.isCensored && !!video.thumbnailPhoto?.isAccepted && !!video.media?.isAccepted
- }
- /*
- Compares original and updated videos and calculates if video active status changed.
- */
- function hasVideoChanged(
- oldValue: Video | undefined,
- newValue: Video | undefined
- ): IChangePair<IAvcChange> | undefined {
- // at least one video should always exists but due to TS type limitations
- // (can't define at least one-of-two parameters required) this safety condition needs to be here
- if (!oldValue && !newValue) {
- return undefined
- }
- // video is being created?
- if (!oldValue) {
- return {
- old: undefined,
- new: (Number(isVideoActive(newValue as Video)) as IAvcChange) || undefined,
- }
- }
- // video is being deleted?
- if (!newValue) {
- return {
- old: (-Number(isVideoActive(oldValue)) as IAvcChange) || undefined,
- new: undefined,
- }
- }
- // calculate active status
- const originalState = isVideoActive(oldValue)
- const newState = isVideoActive(newValue)
- // escape if no change and video is not active
- if (originalState === newState && !newState) {
- return undefined
- }
- // active status stays unchanged but relation(s) changed, return list of changed relations
- if (originalState === newState) {
- return {
- old: [
- -1,
- [
- oldValue.channel && oldValue.channel.id !== newValue.channel?.id && 'channel',
- oldValue.channel?.category &&
- oldValue.channel.category?.id !== newValue.channel?.category?.id &&
- 'channel.category',
- oldValue.category && oldValue.category.id !== newValue.category?.id && 'category',
- ].filter((item) => item) as IVideoDerivedEntites[],
- ],
- new: [
- 1,
- [
- newValue.channel && oldValue.channel?.id !== newValue.channel.id && 'channel',
- newValue.channel?.category &&
- oldValue.channel?.category?.id !== newValue.channel.category?.id &&
- 'channel.category',
- newValue.category && oldValue.category?.id !== newValue.category.id && 'category',
- ].filter((item) => item) as IVideoDerivedEntites[],
- ],
- }
- }
- // calculate change
- const change = Number(newState) - Number(originalState)
- return {
- old: (-change as IAvcChange) || undefined,
- new: (change as IAvcChange) || undefined,
- }
- }
- /*
- Listener for video events.
- */
- class VideoUpdateListener implements IListener<Video, IAvcChange> {
- getRelationDependencies(): string[] {
- return ['thumbnailPhoto', 'media']
- }
- hasValueChanged(oldValue: Video | undefined, newValue: Video): IChangePair<IAvcChange> | undefined
- hasValueChanged(oldValue: Video, newValue: Video | undefined): IChangePair<IAvcChange> | undefined
- hasValueChanged(oldValue: Video, newValue: Video): IChangePair<IAvcChange> | undefined {
- return hasVideoChanged(oldValue, newValue)
- }
- }
- /*
- Listener for channel's category update.
- */
- class ChannelsCategoryChangeListener implements IListener<Channel, IAvcChannelChange> {
- getRelationDependencies(): string[] {
- return ['category']
- }
- hasValueChanged(oldValue: Channel | undefined, newValue: Channel): IChangePair<IAvcChannelChange> | undefined
- hasValueChanged(oldValue: Channel, newValue: Channel | undefined): IChangePair<IAvcChannelChange> | undefined
- hasValueChanged(oldValue: Channel, newValue: Channel): IChangePair<IAvcChannelChange> | undefined {
- return {
- old: -oldValue?.activeVideosCounter || undefined,
- new: newValue?.activeVideosCounter || undefined,
- }
- }
- }
- /*
- Listener for thumbnail photo events.
- */
- class StorageDataObjectChangeListener_ThumbnailPhoto implements IListener<StorageDataObject, IAvcChange> {
- getRelationDependencies(): string[] {
- return [
- 'videoThumbnail',
- 'videoThumbnail.thumbnailPhoto',
- 'videoThumbnail.media',
- 'videoThumbnail.category',
- 'videoThumbnail.channel',
- 'videoThumbnail.channel.category',
- ]
- }
- hasValueChanged(
- oldValue: StorageDataObject | undefined,
- newValue: StorageDataObject
- ): IChangePair<IAvcChange> | undefined
- hasValueChanged(
- oldValue: StorageDataObject,
- newValue: StorageDataObject | undefined
- ): IChangePair<IAvcChange> | undefined
- hasValueChanged(oldValue: StorageDataObject, newValue: StorageDataObject): IChangePair<IAvcChange> | undefined {
- const oldVideo = oldValue?.videoThumbnail
- const newVideo = newValue?.videoThumbnail
- return hasVideoChanged(oldVideo, newVideo)
- }
- }
- /*
- Listener for media events.
- */
- class StorageDataObjectChangeListener_Media implements IListener<StorageDataObject, IAvcChange> {
- getRelationDependencies(): string[] {
- return [
- 'videoMedia',
- 'videoMedia.thumbnailPhoto',
- 'videoMedia.media',
- 'videoMedia.category',
- 'videoMedia.channel',
- 'videoMedia.channel.category',
- ]
- }
- hasValueChanged(
- oldValue: StorageDataObject | undefined,
- newValue: StorageDataObject
- ): IChangePair<IAvcChange> | undefined
- hasValueChanged(
- oldValue: StorageDataObject,
- newValue: StorageDataObject | undefined
- ): IChangePair<IAvcChange> | undefined
- hasValueChanged(oldValue: StorageDataObject, newValue: StorageDataObject): IChangePair<IAvcChange> | undefined {
- const oldVideo = oldValue?.videoMedia
- const newVideo = newValue?.videoMedia
- return hasVideoChanged(oldVideo as Video, newVideo as Video)
- }
- }
- /*
- Adapter for generalizing AVC executor.
- */
- interface IAvcExecutorAdapter<Entity> {
- (item: Entity): Video
- }
- /*
- Active video counter executor reflecting changes to channels, channel cateories, or video categories.
- */
- class ActiveVideoCounterExecutor<
- Entity extends Video | StorageDataObject,
- DerivedEntity extends VideoCategory | Channel | ChannelCategory = VideoCategory | Channel | ChannelCategory
- > implements IExecutor<Entity, IAvcChange, DerivedEntity> {
- private adapter: IAvcExecutorAdapter<Entity>
- constructor(adapter: IAvcExecutorAdapter<Entity>) {
- this.adapter = adapter
- }
- async loadDerivedEntities(store: DatabaseManager, entity: Entity): Promise<DerivedEntity[]> {
- // TODO: find way to reliably decide if channel, etc. are loaded and throw error if not
- const targetEntity = this.adapter(entity)
- // this expects entity has loaded channel, channel category, and video category
- return [targetEntity.channel, targetEntity.channel?.category, targetEntity.category].filter(
- (item) => item
- ) as DerivedEntity[]
- }
- async saveDerivedEntities(store: DatabaseManager, entities: DerivedEntity[]): Promise<void> {
- await Promise.all(entities.map((entity) => store.save(entity)))
- }
- updateOldValue(entity: DerivedEntity, change: IAvcChange): DerivedEntity {
- entity = this.updateValueCommon(entity, change)
- return entity
- }
- updateNewValue(entity: DerivedEntity, change: IAvcChange): DerivedEntity {
- entity = this.updateValueCommon(entity, change)
- return entity
- }
- private updateValueCommon(entity: DerivedEntity, change: IAvcChange): DerivedEntity {
- if (typeof change === 'number') {
- entity.activeVideosCounter += change
- return entity
- }
- const [counterChange, entitiesToChange] = change
- const shouldChange =
- false ||
- (entity instanceof Channel && entitiesToChange.includes('channel')) ||
- (entity instanceof ChannelCategory && entitiesToChange.includes('channel.category')) ||
- (entity instanceof VideoCategory && entitiesToChange.includes('category'))
- if (shouldChange) {
- entity.activeVideosCounter += counterChange
- }
- return entity
- }
- }
- /*
- Executor reflecting changes to channel's category.
- */
- class ChannelCategoryActiveVideoCounterExecutor implements IExecutor<Channel, IAvcChannelChange, ChannelCategory> {
- async loadDerivedEntities(store: DatabaseManager, channel: Channel): Promise<ChannelCategory[]> {
- // TODO: find way to reliably decide if channel, etc. are loaded and throw error if not
- // this expects entity has category
- return [channel.category].filter((item) => item) as ChannelCategory[]
- }
- async saveDerivedEntities(store: DatabaseManager, [entity]: ChannelCategory[]): Promise<void> {
- await store.save(entity)
- }
- updateOldValue(entity: ChannelCategory, change: IAvcChannelChange): ChannelCategory {
- entity.activeVideosCounter += change
- return entity
- }
- updateNewValue(entity: ChannelCategory, change: IAvcChannelChange): ChannelCategory {
- entity.activeVideosCounter += change
- return entity
- }
- }
- export function createVideoManager(store: DatabaseManager): DerivedPropertiesManager<Video, IAvcChange> {
- const manager = new DerivedPropertiesManager<Video, IAvcChange>(store, Video, videoRelationsForCountersBare)
- // listen to video change
- const listener = new VideoUpdateListener()
- const executors = [new ActiveVideoCounterExecutor<Video>((video) => video)]
- manager.registerListener(listener, executors)
- return manager
- }
- export function createChannelManager(store: DatabaseManager): DerivedPropertiesManager<Channel, IAvcChannelChange> {
- const manager = new DerivedPropertiesManager<Channel, IAvcChannelChange>(store, Channel)
- // listen to change of channel's category
- const channelListener = new ChannelsCategoryChangeListener()
- const channelExecutors = [new ChannelCategoryActiveVideoCounterExecutor()]
- manager.registerListener(channelListener, channelExecutors)
- return manager
- }
- export function createStorageDataObjectManager(
- store: DatabaseManager
- ): DerivedPropertiesManager<StorageDataObject, IAvcChange> {
- const manager = new DerivedPropertiesManager<StorageDataObject, IAvcChange>(store, StorageDataObject)
- // listen to change of channel's category
- const storageDataObjectListener1 = new StorageDataObjectChangeListener_ThumbnailPhoto()
- const storageDataObjectListener2 = new StorageDataObjectChangeListener_Media()
- const storageDataObjectExecutors = (adapter) => [new ActiveVideoCounterExecutor<StorageDataObject>(adapter)]
- manager.registerListener(
- storageDataObjectListener1,
- storageDataObjectExecutors((storageDataObject) => storageDataObject.videoThumbnail)
- )
- manager.registerListener(
- storageDataObjectListener2,
- storageDataObjectExecutors((storageDataObject) => storageDataObject.videoMedia)
- )
- return manager
- }
|