video.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. eslint-disable @typescript-eslint/naming-convention
  3. */
  4. import { EventContext, StoreContext } from '@joystream/hydra-common'
  5. import { In } from 'typeorm'
  6. import { Content } from '../generated/types'
  7. import { deserializeMetadata, inconsistentState, logger } from '../common'
  8. import { processVideoMetadata } from './utils'
  9. import { Channel, Video, VideoCategory } from 'query-node/dist/model'
  10. import { VideoMetadata, VideoCategoryMetadata } from '@joystream/metadata-protobuf'
  11. import { integrateMeta } from '@joystream/metadata-protobuf/utils'
  12. import _ from 'lodash'
  13. export async function content_VideoCategoryCreated({ store, event }: EventContext & StoreContext): Promise<void> {
  14. // read event data
  15. const [, videoCategoryId, videoCategoryCreationParameters] = new Content.VideoCategoryCreatedEvent(event).params
  16. // read metadata
  17. const metadata = (await deserializeMetadata(VideoCategoryMetadata, videoCategoryCreationParameters.meta)) || {}
  18. // create new video category
  19. const videoCategory = new VideoCategory({
  20. // main data
  21. id: videoCategoryId.toString(),
  22. videos: [],
  23. createdInBlock: event.blockNumber,
  24. // fill in auto-generated fields
  25. createdAt: new Date(event.blockTimestamp),
  26. updatedAt: new Date(event.blockTimestamp),
  27. })
  28. integrateMeta(videoCategory, metadata, ['name'])
  29. // save video category
  30. await store.save<VideoCategory>(videoCategory)
  31. // emit log event
  32. logger.info('Video category has been created', { id: videoCategoryId })
  33. }
  34. export async function content_VideoCategoryUpdated({ store, event }: EventContext & StoreContext): Promise<void> {
  35. // read event data
  36. const [, videoCategoryId, videoCategoryUpdateParameters] = new Content.VideoCategoryUpdatedEvent(event).params
  37. // load video category
  38. const videoCategory = await store.get(VideoCategory, {
  39. where: { id: videoCategoryId.toString() },
  40. })
  41. // ensure video category exists
  42. if (!videoCategory) {
  43. return inconsistentState('Non-existing video category update requested', videoCategoryId)
  44. }
  45. // read metadata
  46. const newMeta = deserializeMetadata(VideoCategoryMetadata, videoCategoryUpdateParameters.new_meta) || {}
  47. integrateMeta(videoCategory, newMeta, ['name'])
  48. // set last update time
  49. videoCategory.updatedAt = new Date(event.blockTimestamp)
  50. // save video category
  51. await store.save<VideoCategory>(videoCategory)
  52. // emit log event
  53. logger.info('Video category has been updated', { id: videoCategoryId })
  54. }
  55. export async function content_VideoCategoryDeleted({ store, event }: EventContext & StoreContext): Promise<void> {
  56. // read event data
  57. const [, videoCategoryId] = new Content.VideoCategoryDeletedEvent(event).params
  58. // load video category
  59. const videoCategory = await store.get(VideoCategory, {
  60. where: { id: videoCategoryId.toString() },
  61. })
  62. // ensure video category exists
  63. if (!videoCategory) {
  64. return inconsistentState('Non-existing video category deletion requested', videoCategoryId)
  65. }
  66. // remove video category
  67. await store.remove<VideoCategory>(videoCategory)
  68. // emit log event
  69. logger.info('Video category has been deleted', { id: videoCategoryId })
  70. }
  71. /// //////////////// Video //////////////////////////////////////////////////////
  72. export async function content_VideoCreated(ctx: EventContext & StoreContext): Promise<void> {
  73. const { store, event } = ctx
  74. // read event data
  75. const [, channelId, videoId, videoCreationParameters] = new Content.VideoCreatedEvent(event).params
  76. // load channel
  77. const channel = await store.get(Channel, { where: { id: channelId.toString() } })
  78. // ensure channel exists
  79. if (!channel) {
  80. return inconsistentState('Trying to add video to non-existing channel', channelId)
  81. }
  82. const video = new Video({
  83. id: videoId.toString(),
  84. channel,
  85. isCensored: false,
  86. isFeatured: false,
  87. createdInBlock: event.blockNumber,
  88. createdAt: new Date(event.blockTimestamp),
  89. updatedAt: new Date(event.blockTimestamp),
  90. })
  91. // deserialize & process metadata
  92. if (videoCreationParameters.meta.isSome) {
  93. const metadata = deserializeMetadata(VideoMetadata, videoCreationParameters.meta.unwrap()) || {}
  94. await processVideoMetadata(ctx, video, metadata, videoCreationParameters.assets.unwrapOr(undefined))
  95. }
  96. // save video
  97. await store.save<Video>(video)
  98. // emit log event
  99. logger.info('Video has been created', { id: videoId })
  100. }
  101. export async function content_VideoUpdated(ctx: EventContext & StoreContext): Promise<void> {
  102. const { event, store } = ctx
  103. // read event data
  104. const [, videoId, videoUpdateParameters] = new Content.VideoUpdatedEvent(event).params
  105. // load video
  106. const video = await store.get(Video, {
  107. where: { id: videoId.toString() },
  108. relations: ['channel', 'license'],
  109. })
  110. // ensure video exists
  111. if (!video) {
  112. return inconsistentState('Non-existing video update requested', videoId)
  113. }
  114. // prepare changed metadata
  115. const newMetadataBytes = videoUpdateParameters.new_meta.unwrapOr(null)
  116. // update metadata if it was changed
  117. if (newMetadataBytes) {
  118. const newMetadata = deserializeMetadata(VideoMetadata, newMetadataBytes) || {}
  119. await processVideoMetadata(ctx, video, newMetadata, videoUpdateParameters.assets_to_upload.unwrapOr(undefined))
  120. }
  121. // set last update time
  122. video.updatedAt = new Date(event.blockTimestamp)
  123. // save video
  124. await store.save<Video>(video)
  125. // emit log event
  126. logger.info('Video has been updated', { id: videoId })
  127. }
  128. export async function content_VideoDeleted({ store, event }: EventContext & StoreContext): Promise<void> {
  129. // read event data
  130. const [, videoId] = new Content.VideoDeletedEvent(event).params
  131. // load video
  132. const video = await store.get(Video, { where: { id: videoId.toString() } })
  133. // ensure video exists
  134. if (!video) {
  135. return inconsistentState('Non-existing video deletion requested', videoId)
  136. }
  137. // remove video
  138. await store.remove<Video>(video)
  139. // emit log event
  140. logger.info('Video has been deleted', { id: videoId })
  141. }
  142. export async function content_VideoCensorshipStatusUpdated({
  143. store,
  144. event,
  145. }: EventContext & StoreContext): Promise<void> {
  146. // read event data
  147. const [, videoId, isCensored] = new Content.VideoCensorshipStatusUpdatedEvent(event).params
  148. // load video
  149. const video = await store.get(Video, { where: { id: videoId.toString() } })
  150. // ensure video exists
  151. if (!video) {
  152. return inconsistentState('Non-existing video censoring requested', videoId)
  153. }
  154. // update video
  155. video.isCensored = isCensored.isTrue
  156. // set last update time
  157. video.updatedAt = new Date(event.blockTimestamp)
  158. // save video
  159. await store.save<Video>(video)
  160. // emit log event
  161. logger.info('Video censorship status has been updated', { id: videoId, isCensored: isCensored.isTrue })
  162. }
  163. export async function content_FeaturedVideosSet({ store, event }: EventContext & StoreContext): Promise<void> {
  164. // read event data
  165. const [, videoIds] = new Content.FeaturedVideosSetEvent(event).params
  166. // load old featured videos
  167. const existingFeaturedVideos = await store.getMany(Video, { where: { isFeatured: true } })
  168. // comparsion utility
  169. const isSame = (videoIdA: string) => (videoIdB: string) => videoIdA === videoIdB
  170. // calculate diff sets
  171. const videosToRemove = existingFeaturedVideos.filter(
  172. (existingFV) => !videoIds.map((videoId) => videoId.toString()).some(isSame(existingFV.id))
  173. )
  174. const videoIdsToAdd = videoIds.filter(
  175. (videoId) => !existingFeaturedVideos.map((existingFV) => existingFV.id).some(isSame(videoId.toString()))
  176. )
  177. // mark previously featured videos as not-featured
  178. await Promise.all(
  179. videosToRemove.map(async (video) => {
  180. video.isFeatured = false
  181. // set last update time
  182. video.updatedAt = new Date(event.blockTimestamp)
  183. await store.save<Video>(video)
  184. })
  185. )
  186. // read previously not-featured videos that are meant to be featured
  187. const videosToAdd = await store.getMany(Video, {
  188. where: {
  189. id: In(videoIdsToAdd.map((item) => item.toString())),
  190. },
  191. })
  192. if (videosToAdd.length !== videoIdsToAdd.length) {
  193. // Do not throw, as this is not validated by the runtime
  194. console.warn(
  195. 'Non-existing video(s) in featuredVideos set:',
  196. _.difference(
  197. videoIdsToAdd.map((v) => v.toString()),
  198. videosToAdd.map((v) => v.id)
  199. )
  200. )
  201. }
  202. // mark previously not-featured videos as featured
  203. await Promise.all(
  204. videosToAdd.map(async (video) => {
  205. video.isFeatured = true
  206. // set last update time
  207. video.updatedAt = new Date(event.blockTimestamp)
  208. await store.save<Video>(video)
  209. })
  210. )
  211. // emit log event
  212. const addedVideoIds = videosToAdd.map((v) => v.id)
  213. const removedVideoIds = videosToRemove.map((v) => v.id)
  214. logger.info('Featured videos have been updated', { addedVideoIds, removedVideoIds })
  215. }