video.ts 10 KB

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