mappingsContent.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. // TODO: add logging of mapping events (entity found/not found, entity updated/deleted, etc.)
  2. // TODO: split file into multiple files
  3. // TODO: make sure assets are updated when VideoUpdateParameters have only `assets` parameter set (no `new_meta` set) - if this situation can even happend
  4. // TODO: check all `db.get()` and similar calls recieve a proper type argument (aka add `.toString()`, etc. to those calls)
  5. import { SubstrateEvent } from '@dzlzv/hydra-common'
  6. import { DatabaseManager } from '@dzlzv/hydra-db-utils'
  7. import ISO6391 from 'iso-639-1';
  8. // protobuf definitions
  9. import {
  10. ChannelMetadata,
  11. ChannelCategoryMetadata,
  12. PublishedBeforeJoystream as PublishedBeforeJoystreamMetadata,
  13. License as LicenseMetadata,
  14. MediaType as MediaTypeMetadata,
  15. VideoMetadata,
  16. VideoCategoryMetadata,
  17. } from '@joystream/content-metadata-protobuf'
  18. import {
  19. Content,
  20. } from '../generated/types'
  21. import {
  22. inconsistentState,
  23. prepareBlock,
  24. prepareAssetDataObject,
  25. } from './common'
  26. // primary entities
  27. import { Block } from 'query-node/src/modules/block/block.model'
  28. import { CuratorGroup } from 'query-node/src/modules/curator-group/curator-group.model'
  29. import { Channel } from 'query-node/src/modules/channel/channel.model'
  30. import { ChannelCategory } from 'query-node/src/modules/channel-category/channel-category.model'
  31. import { Video } from 'query-node/src/modules/video/video.model'
  32. import { VideoCategory } from 'query-node/src/modules/video-category/video-category.model'
  33. // secondary entities
  34. import { Language } from 'query-node/src/modules/language/language.model'
  35. import { License } from 'query-node/src/modules/license/license.model'
  36. import { VideoMediaEncoding } from 'query-node/src/modules/video-media-encoding/video-media-encoding.model'
  37. import { VideoMediaMetadata } from 'query-node/src/modules/video-media-metadata/video-media-metadata.model'
  38. // Asset
  39. import {
  40. Asset,
  41. AssetUrl,
  42. AssetUploadStatus,
  43. AssetStorage,
  44. AssetOwner,
  45. AssetOwnerMember,
  46. } from 'query-node/src/modules/variants/variants.model'
  47. import {
  48. AssetDataObject,
  49. LiaisonJudgement
  50. } from 'query-node/src/modules/asset-data-object/asset-data-object.model'
  51. // Joystream types
  52. import {
  53. ContentParameters,
  54. NewAsset,
  55. } from '@joystream/types/augment'
  56. /////////////////// Utils //////////////////////////////////////////////////////
  57. async function readProtobuf(
  58. type: Channel | ChannelCategory | Video | VideoCategory,
  59. metadata: Uint8Array,
  60. assets: NewAsset[],
  61. db: DatabaseManager,
  62. event: SubstrateEvent,
  63. ): Promise<Partial<typeof type>> {
  64. // process channel
  65. if (type instanceof Channel) {
  66. const meta = ChannelMetadata.deserializeBinary(metadata)
  67. const metaAsObject = meta.toObject()
  68. const result = metaAsObject as any as Channel
  69. // prepare cover photo asset if needed
  70. if (metaAsObject.coverPhoto !== undefined) {
  71. result.coverPhoto = await extractAsset(metaAsObject.coverPhoto, assets, db, event)
  72. }
  73. // prepare avatar photo asset if needed
  74. if (metaAsObject.avatarPhoto !== undefined) {
  75. result.avatarPhoto = await extractAsset(metaAsObject.avatarPhoto, assets, db, event)
  76. }
  77. // prepare language if needed
  78. if (metaAsObject.language) {
  79. result.language = await prepareLanguage(metaAsObject.language, db)
  80. }
  81. return result
  82. }
  83. // process channel category
  84. if (type instanceof ChannelCategory) {
  85. return ChannelCategoryMetadata.deserializeBinary(metadata).toObject()
  86. }
  87. // process video
  88. if (type instanceof Video) {
  89. const meta = VideoMetadata.deserializeBinary(metadata)
  90. const metaAsObject = meta.toObject()
  91. const result = metaAsObject as any as Video
  92. // prepare video category if needed
  93. if (metaAsObject.category !== undefined) {
  94. result.category = await prepareVideoCategory(metaAsObject.category, db)
  95. }
  96. // prepare media meta information if needed
  97. if (metaAsObject.mediaType) {
  98. result.mediaMetadata = await prepareVideoMetadata(metaAsObject)
  99. delete metaAsObject.mediaType
  100. }
  101. // prepare license if needed
  102. if (metaAsObject.license) {
  103. result.license = await prepareLicense(metaAsObject.license)
  104. }
  105. // prepare thumbnail photo asset if needed
  106. if (metaAsObject.thumbnailPhoto !== undefined) {
  107. result.thumbnailPhoto = await extractAsset(metaAsObject.thumbnailPhoto, assets, db, event)
  108. }
  109. // prepare video asset if needed
  110. if (metaAsObject.video !== undefined) {
  111. result.media = await extractAsset(metaAsObject.video, assets, db, event)
  112. }
  113. // prepare language if needed
  114. if (metaAsObject.language) {
  115. result.language = await prepareLanguage(metaAsObject.language, db)
  116. }
  117. // prepare information about media published somewhere else before Joystream if needed.
  118. if (metaAsObject.publishedBeforeJoystream) {
  119. // TODO: is ok to just ignore `isPublished?: boolean` here?
  120. if (metaAsObject.publishedBeforeJoystream.date) {
  121. result.publishedBeforeJoystream = new Date(metaAsObject.publishedBeforeJoystream.date)
  122. } else {
  123. delete result.publishedBeforeJoystream
  124. }
  125. }
  126. return result
  127. }
  128. // process video category
  129. if (type instanceof VideoCategory) {
  130. return VideoCategoryMetadata.deserializeBinary(metadata).toObject()
  131. }
  132. // this should never happen
  133. throw `Not implemented type: ${type}`
  134. }
  135. async function convertAsset(rawAsset: NewAsset, db: DatabaseManager, event: SubstrateEvent): Promise<typeof Asset> {
  136. if (rawAsset.isUrls) {
  137. const assetUrl = new AssetUrl()
  138. assetUrl.urls = rawAsset.asUrls.toArray().map(item => item.toString())
  139. return assetUrl
  140. }
  141. // !rawAsset.isUrls && rawAsset.isUpload
  142. const contentParameters: ContentParameters = rawAsset.asUpload
  143. const block = await prepareBlock(db, event)
  144. const assetStorage = await prepareAssetDataObject(contentParameters, block)
  145. return assetStorage
  146. }
  147. async function extractAsset(
  148. assetIndex: number | undefined,
  149. assets: NewAsset[],
  150. db: DatabaseManager,
  151. event: SubstrateEvent,
  152. ): Promise<typeof Asset | undefined> {
  153. if (assetIndex === undefined) {
  154. return undefined
  155. }
  156. if (assetIndex > assets.length) {
  157. return inconsistentState()
  158. }
  159. return convertAsset(assets[assetIndex], db, event)
  160. }
  161. async function prepareLanguage(languageIso: string, db: DatabaseManager): Promise<Language> {
  162. const isValidIso = ISO6391.validate(languageIso);
  163. if (!isValidIso) {
  164. return inconsistentState()
  165. }
  166. const language = await db.get(Language, { where: { iso: languageIso }})
  167. if (language) {
  168. return language;
  169. }
  170. const newLanguage = new Language({
  171. iso: languageIso
  172. })
  173. return newLanguage
  174. }
  175. async function prepareLicense(licenseProtobuf: LicenseMetadata.AsObject): Promise<License> {
  176. const license = new License(licenseProtobuf)
  177. return license
  178. }
  179. async function prepareVideoMetadata(videoProtobuf: VideoMetadata.AsObject): Promise<VideoMediaMetadata> {
  180. const encoding = new VideoMediaEncoding(videoProtobuf.mediaType)
  181. const videoMeta = new VideoMediaMetadata({
  182. encoding,
  183. pixelWidth: videoProtobuf.mediaPixelWidth,
  184. pixelHeight: videoProtobuf.mediaPixelHeight,
  185. size: 0, // TODO: retrieve proper file size
  186. })
  187. return videoMeta
  188. }
  189. async function prepareVideoCategory(categoryId: number, db: DatabaseManager): Promise<VideoCategory> {
  190. const category = await db.get(VideoCategory, { where: { id: categoryId }})
  191. if (!category) {
  192. return inconsistentState()
  193. }
  194. return category
  195. }
  196. /////////////////// Channel ////////////////////////////////////////////////////
  197. // eslint-disable-next-line @typescript-eslint/naming-convention
  198. export async function content_ChannelCreated(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
  199. // read event data
  200. const {channelId, channelCreationParameters} = new Content.ChannelCreatedEvent(event).data
  201. // read metadata
  202. const protobufContent = await readProtobuf(
  203. new Channel(),
  204. channelCreationParameters.meta,
  205. channelCreationParameters.assets,
  206. db,
  207. event,
  208. )
  209. // create entity
  210. const channel = new Channel({
  211. id: channelId,
  212. isCensored: false,
  213. videos: [],
  214. happenedIn: await prepareBlock(db, event),
  215. ...Object(protobufContent)
  216. })
  217. // save entity
  218. await db.save<Channel>(channel)
  219. }
  220. // eslint-disable-next-line @typescript-eslint/naming-convention
  221. export async function content_ChannelUpdated(
  222. db: DatabaseManager,
  223. event: SubstrateEvent
  224. ) {
  225. // read event data
  226. const {channelId , channelUpdateParameters} = new Content.ChannelUpdatedEvent(event).data
  227. // load channel
  228. const channel = await db.get(Channel, { where: { id: channelId } })
  229. // ensure channel exists
  230. if (!channel) {
  231. return inconsistentState()
  232. }
  233. // metadata change happened?
  234. if (channelUpdateParameters.new_meta.isSome) {
  235. const protobufContent = await readProtobuf(
  236. new Channel(),
  237. channelUpdateParameters.new_meta.unwrap(), // TODO: is there any better way to get value without unwrap?
  238. channelUpdateParameters.assets.unwrapOr([]),
  239. db,
  240. event,
  241. )
  242. // update all fields read from protobuf
  243. for (let [key, value] of Object(protobufContent).entries()) {
  244. channel[key] = value
  245. }
  246. }
  247. // reward account change happened?
  248. if (channelUpdateParameters.reward_account.isSome) {
  249. // TODO: separate to function
  250. // new different reward account set
  251. if (channelUpdateParameters.reward_account.unwrap().isSome) {
  252. channel.rewardAccount = channelUpdateParameters.reward_account.unwrap().unwrap().toString()
  253. } else { // reward account removed
  254. delete channel.rewardAccount
  255. }
  256. }
  257. // save channel
  258. await db.save<Channel>(channel)
  259. }
  260. export async function content_ChannelAssetsRemoved(
  261. db: DatabaseManager,
  262. event: SubstrateEvent
  263. ) {
  264. // TODO - what should happen here?
  265. }
  266. // eslint-disable-next-line @typescript-eslint/naming-convention
  267. export async function content_ChannelCensored(
  268. db: DatabaseManager,
  269. event: SubstrateEvent
  270. ) {
  271. // read event data
  272. const {channelId} = new Content.ChannelCensoredEvent(event).data
  273. // load event
  274. const channel = await db.get(Channel, { where: { id: channelId } })
  275. // ensure channel exists
  276. if (!channel) {
  277. return inconsistentState()
  278. }
  279. // update channel
  280. channel.isCensored = true;
  281. // save channel
  282. await db.save<Channel>(channel)
  283. }
  284. // eslint-disable-next-line @typescript-eslint/naming-convention
  285. export async function content_ChannelUncensored(
  286. db: DatabaseManager,
  287. event: SubstrateEvent
  288. ) {
  289. // read event data
  290. const {channelId} = new Content.ChannelUncensoredEvent(event).data
  291. // load event
  292. const channel = await db.get(Channel, { where: { id: channelId } })
  293. // ensure channel exists
  294. if (!channel) {
  295. return inconsistentState()
  296. }
  297. // update channel
  298. channel.isCensored = false;
  299. // save channel
  300. await db.save<Channel>(channel)
  301. }
  302. /////////////////// ChannelCategory ////////////////////////////////////////////
  303. // eslint-disable-next-line @typescript-eslint/naming-convention
  304. export async function content_ChannelCategoryCreated(
  305. db: DatabaseManager,
  306. event: SubstrateEvent
  307. ) {
  308. // read event data
  309. const {channelCategoryCreationParameters} = new Content.ChannelCategoryCreatedEvent(event).data
  310. // read metadata
  311. const protobufContent = await readProtobuf(
  312. new ChannelCategory(),
  313. channelCategoryCreationParameters.meta,
  314. [],
  315. db,
  316. event,
  317. )
  318. // create new channel category
  319. const channelCategory = new ChannelCategory({
  320. id: event.params[0].value.toString(), // ChannelCategoryId
  321. channels: [],
  322. happenedIn: await prepareBlock(db, event),
  323. ...Object(protobufContent)
  324. })
  325. // save channel
  326. await db.save<ChannelCategory>(channelCategory)
  327. }
  328. // eslint-disable-next-line @typescript-eslint/naming-convention
  329. export async function content_ChannelCategoryUpdated(
  330. db: DatabaseManager,
  331. event: SubstrateEvent
  332. ) {
  333. // read event data
  334. const {channelCategoryId, channelCategoryUpdateParameters} = new Content.ChannelCategoryUpdatedEvent(event).data
  335. // load channel category
  336. const channelCategory = await db.get(ChannelCategory, { where: { id: channelCategoryId } })
  337. // ensure channel exists
  338. if (!channelCategory) {
  339. return inconsistentState()
  340. }
  341. // read metadata
  342. const protobufContent = await readProtobuf(
  343. new ChannelCategory(),
  344. channelCategoryUpdateParameters.new_meta,
  345. [],
  346. db,
  347. event,
  348. )
  349. // update all fields read from protobuf
  350. for (let [key, value] of Object(protobufContent).entries()) {
  351. channelCategory[key] = value
  352. }
  353. // save channel category
  354. await db.save<ChannelCategory>(channelCategory)
  355. }
  356. // eslint-disable-next-line @typescript-eslint/naming-convention
  357. export async function content_ChannelCategoryDeleted(
  358. db: DatabaseManager,
  359. event: SubstrateEvent
  360. ) {
  361. // read event data
  362. const {channelCategoryId} = new Content.ChannelCategoryDeletedEvent(event).data
  363. // load channel category
  364. const channelCategory = await db.get(ChannelCategory, { where: { id: channelCategoryId } })
  365. // ensure channel category exists
  366. if (!channelCategory) {
  367. return inconsistentState()
  368. }
  369. // delete channel category
  370. await db.remove<ChannelCategory>(channelCategory)
  371. }
  372. /////////////////// VideoCategory //////////////////////////////////////////////
  373. // eslint-disable-next-line @typescript-eslint/naming-convention
  374. export async function content_VideoCategoryCreated(
  375. db: DatabaseManager,
  376. event: SubstrateEvent
  377. ) {
  378. // read event data
  379. const {videoCategoryId, videoCategoryCreationParameters} = new Content.VideoCategoryCreatedEvent(event).data
  380. // read metadata
  381. const protobufContent = readProtobuf(
  382. new VideoCategory(),
  383. videoCategoryCreationParameters.meta,
  384. [],
  385. db,
  386. event
  387. )
  388. // create new video category
  389. const videoCategory = new VideoCategory({
  390. id: videoCategoryId.toString(), // ChannelId
  391. isCensored: false,
  392. videos: [],
  393. happenedIn: await prepareBlock(db, event),
  394. ...Object(protobufContent)
  395. })
  396. // save video category
  397. await db.save<VideoCategory>(videoCategory)
  398. }
  399. // eslint-disable-next-line @typescript-eslint/naming-convention
  400. export async function content_VideoCategoryUpdated(
  401. db: DatabaseManager,
  402. event: SubstrateEvent
  403. ) {
  404. // read event data
  405. const {videoCategoryId, videoCategoryUpdateParameters} = new Content.VideoCategoryUpdatedEvent(event).data
  406. // load video category
  407. const videoCategory = await db.get(VideoCategory, { where: { id: videoCategoryId } })
  408. // ensure video category exists
  409. if (!videoCategory) {
  410. return inconsistentState()
  411. }
  412. // read metadata
  413. const protobufContent = await readProtobuf(
  414. new VideoCategory(),
  415. videoCategoryUpdateParameters.new_meta,
  416. [],
  417. db,
  418. event,
  419. )
  420. // update all fields read from protobuf
  421. for (let [key, value] of Object(protobufContent).entries()) {
  422. videoCategory[key] = value
  423. }
  424. // save video category
  425. await db.save<VideoCategory>(videoCategory)
  426. }
  427. // eslint-disable-next-line @typescript-eslint/naming-convention
  428. export async function content_VideoCategoryDeleted(
  429. db: DatabaseManager,
  430. event: SubstrateEvent
  431. ) {
  432. // read event data
  433. const {videoCategoryId} = new Content.VideoCategoryDeletedEvent(event).data
  434. // load video category
  435. const videoCategory = await db.get(VideoCategory, { where: { id: videoCategoryId } })
  436. // ensure video category exists
  437. if (!videoCategory) {
  438. return inconsistentState()
  439. }
  440. // remove video category
  441. await db.remove<VideoCategory>(videoCategory)
  442. }
  443. /////////////////// Video //////////////////////////////////////////////////////
  444. // eslint-disable-next-line @typescript-eslint/naming-convention
  445. export async function content_VideoCreated(
  446. db: DatabaseManager,
  447. event: SubstrateEvent
  448. ) {
  449. // read event data
  450. const {channelId, videoId, videoCreationParameters} = new Content.VideoCreatedEvent(event).data
  451. // read metadata
  452. const protobufContent = await readProtobuf(
  453. new Video(),
  454. videoCreationParameters.meta,
  455. videoCreationParameters.assets,
  456. db,
  457. event,
  458. )
  459. // create new video
  460. const video = new Video({
  461. id: videoId,
  462. isCensored: false,
  463. channel: channelId,
  464. happenedIn: await prepareBlock(db, event),
  465. ...Object(protobufContent)
  466. })
  467. // save video
  468. await db.save<Video>(video)
  469. }
  470. // eslint-disable-next-line @typescript-eslint/naming-convention
  471. export async function content_VideoUpdated(
  472. db: DatabaseManager,
  473. event: SubstrateEvent
  474. ) {
  475. // read event data
  476. const {videoId, videoUpdateParameters} = new Content.VideoUpdatedEvent(event).data
  477. // load video
  478. const video = await db.get(Video, { where: { id: videoId } })
  479. // ensure video exists
  480. if (!video) {
  481. return inconsistentState()
  482. }
  483. // update metadata if it changed
  484. if (videoUpdateParameters.new_meta.isSome) {
  485. const protobufContent = await readProtobuf(
  486. new Video(),
  487. videoUpdateParameters.new_meta.unwrap(), // TODO: is there any better way to get value without unwrap?
  488. videoUpdateParameters.assets.unwrapOr([]),
  489. db,
  490. event,
  491. )
  492. // remember original license
  493. const originalLicense = video.license
  494. // update all fields read from protobuf
  495. for (let [key, value] of Object(protobufContent).entries()) {
  496. video[key] = value
  497. }
  498. // license has changed - delete old license
  499. if (originalLicense && video.license != originalLicense) {
  500. await db.remove<License>(originalLicense)
  501. }
  502. }
  503. // TODO: handle situation when only assets changed
  504. // save video
  505. await db.save<Video>(video)
  506. }
  507. // eslint-disable-next-line @typescript-eslint/naming-convention
  508. export async function content_VideoDeleted(
  509. db: DatabaseManager,
  510. event: SubstrateEvent
  511. ) {
  512. // read event data
  513. const {videoId} = new Content.VideoDeletedEvent(event).data
  514. // load video
  515. const video = await db.get(Video, { where: { id: videoId } })
  516. // ensure video exists
  517. if (!video) {
  518. return inconsistentState()
  519. }
  520. // remove video
  521. await db.remove<Video>(video)
  522. }
  523. // eslint-disable-next-line @typescript-eslint/naming-convention
  524. export async function content_VideoCensored(
  525. db: DatabaseManager,
  526. event: SubstrateEvent
  527. ) {
  528. // read event data
  529. const {videoId} = new Content.VideoCensoredEvent(event).data
  530. // load video
  531. const video = await db.get(Video, { where: { id: videoId } })
  532. // ensure video exists
  533. if (!video) {
  534. return inconsistentState()
  535. }
  536. // update video
  537. video.isCensored = true;
  538. // save video
  539. await db.save<Video>(video)
  540. }
  541. // eslint-disable-next-line @typescript-eslint/naming-convention
  542. export async function content_VideoUncensored(
  543. db: DatabaseManager,
  544. event: SubstrateEvent
  545. ) {
  546. // read event data
  547. const {videoId} = new Content.VideoUncensoredEvent(event).data
  548. // load video
  549. const video = await db.get(Video, { where: { id: videoId } })
  550. // ensure video exists
  551. if (!video) {
  552. return inconsistentState()
  553. }
  554. // update video
  555. video.isCensored = false;
  556. // save video
  557. await db.save<Video>(video)
  558. }
  559. // eslint-disable-next-line @typescript-eslint/naming-convention
  560. export async function content_FeaturedVideosSet(
  561. db: DatabaseManager,
  562. event: SubstrateEvent
  563. ) {
  564. // read event data
  565. const {videoId: videoIds} = new Content.FeaturedVideosSetEvent(event).data
  566. // load old featured videos
  567. const existingFeaturedVideos = await db.getMany(Video, { where: { isFeatured: true } })
  568. // comparsion utility
  569. const isSame = (videoIdA: string) => (videoIdB: string) => videoIdA == videoIdB
  570. // calculate diff sets
  571. const toRemove = existingFeaturedVideos.filter(existingFV =>
  572. !videoIds
  573. .map(item => item.toHex())
  574. .some(isSame(existingFV.id))
  575. )
  576. const toAdd = videoIds.filter(video =>
  577. !existingFeaturedVideos
  578. .map(item => item.id)
  579. .some(isSame(video.toHex()))
  580. )
  581. // mark previously featured videos as not-featured
  582. for (let video of toRemove) {
  583. video.isFeatured = false;
  584. await db.save<Video>(video)
  585. }
  586. // escape if no featured video needs to be added
  587. if (!toAdd) {
  588. return
  589. }
  590. // read videos previously not-featured videos that are meant to be featured
  591. const videosToAdd = await db.getMany(Video, { where: { id: [toAdd] } })
  592. if (videosToAdd.length != toAdd.length) {
  593. return inconsistentState()
  594. }
  595. // mark previously not-featured videos as featured
  596. for (let video of videosToAdd) {
  597. video.isFeatured = true;
  598. await db.save<Video>(video)
  599. }
  600. }
  601. /////////////////// Curator Group //////////////////////////////////////////////
  602. export async function content_CuratorGroupCreated(
  603. db: DatabaseManager,
  604. event: SubstrateEvent
  605. ) {
  606. // read event data
  607. const {curatorGroupId} = new Content.CuratorGroupCreatedEvent(event).data
  608. // create new curator group
  609. const curatorGroup = new CuratorGroup({
  610. id: curatorGroupId.toString(),
  611. curatorIds: [],
  612. isActive: false, // runtime creates inactive curator groups by default
  613. })
  614. // save curator group
  615. await db.save<CuratorGroup>(curatorGroup)
  616. }
  617. export async function content_CuratorGroupStatusSet(
  618. db: DatabaseManager,
  619. event: SubstrateEvent
  620. ) {
  621. // read event data
  622. const {curatorGroupId, bool: isActive} = new Content.CuratorGroupStatusSetEvent(event).data
  623. // load curator group
  624. const curatorGroup = await db.get(CuratorGroup, { where: { id: curatorGroupId }})
  625. // ensure curator group exists
  626. if (!curatorGroup) {
  627. return inconsistentState()
  628. }
  629. // update curator group
  630. curatorGroup.isActive = isActive.isTrue
  631. // save curator group
  632. await db.save<CuratorGroup>(curatorGroup)
  633. }
  634. export async function content_CuratorAdded(
  635. db: DatabaseManager,
  636. event: SubstrateEvent
  637. ) {
  638. // read event data
  639. const {curatorGroupId, curatorId} = new Content.CuratorAddedEvent(event).data
  640. // load curator group
  641. const curatorGroup = await db.get(CuratorGroup, { where: { id: curatorGroupId }})
  642. // ensure curator group exists
  643. if (!curatorGroup) {
  644. return inconsistentState()
  645. }
  646. // update curator group
  647. curatorGroup.curatorIds.push(curatorId)
  648. // save curator group
  649. await db.save<CuratorGroup>(curatorGroup)
  650. }
  651. export async function content_CuratorRemoved(
  652. db: DatabaseManager,
  653. event: SubstrateEvent
  654. ) {
  655. // read event data
  656. const {curatorGroupId, curatorId} = new Content.CuratorAddedEvent(event).data
  657. // load curator group
  658. const curatorGroup = await db.get(CuratorGroup, { where: { id: curatorGroupId }})
  659. // ensure curator group exists
  660. if (!curatorGroup) {
  661. return inconsistentState()
  662. }
  663. const curatorIndex = curatorGroup.curatorIds.indexOf(curatorId)
  664. // ensure curator group exists
  665. if (curatorIndex < 0) {
  666. return inconsistentState()
  667. }
  668. // update curator group
  669. curatorGroup.curatorIds.splice(curatorIndex, 1)
  670. // save curator group
  671. await db.save<CuratorGroup>(curatorGroup)
  672. }