Browse Source

query node - mappings contentOwner improvements + other minor improvements

ondratra 3 years ago
parent
commit
2c93c61cb8

+ 1 - 5
query-node/mappings/src/common.ts

@@ -46,11 +46,7 @@ export async function prepareDataObject(
     typeId: contentParameters.type_id.toNumber(),
     // `size` is masked by `size` special name in `Struct` so there needs to be `.get('size') as unknown as u64` to retrieve proper value
     size: (contentParameters.get('size') as unknown as u64).toBn(),
-
-    // TODO: liason & judgement information can't be retrieved now - it's neither event parameter or extrinsic parameter
-    liaisonId: new BN(0),
-    liaisonJudgement: LiaisonJudgement.PENDING,
-
+    liaisonJudgement: LiaisonJudgement.PENDING, // judgement is pending at start; liaison id is set when content is accepted/rejected
     ipfsContentId: contentParameters.ipfs_content_id.toHex(),
     joystreamContentId: contentParameters.content_id.toHex(),
   })

+ 31 - 23
query-node/mappings/src/content/channel.ts

@@ -5,7 +5,11 @@ import ISO6391 from 'iso-639-1';
 import { AccountId } from "@polkadot/types/interfaces";
 import { Option } from '@polkadot/types/codec';
 import { Content } from '../../../generated/types'
-import { readProtobuf } from './utils'
+import {
+  readProtobuf,
+  readProtobufWithAssets,
+  convertContentActorToOwner,
+} from './utils'
 
 import {
   DataObject,
@@ -25,13 +29,15 @@ export async function content_ChannelCreated(db: DatabaseManager, event: Substra
   const {channelId, channelCreationParameters, contentActor} = new Content.ChannelCreatedEvent(event).data
 
   // read metadata
-  const protobufContent = await readProtobuf(
+  const protobufContent = await readProtobufWithAssets(
     new Channel(),
-    channelCreationParameters.meta,
-    channelCreationParameters.assets,
-    db,
-    event.blockNumber,
-    contentActor,
+    {
+      metadata: channelCreationParameters.meta,
+      db,
+      blockNumber: event.blockNumber,
+      assets: channelCreationParameters.assets,
+      contentOwner: convertContentActorToOwner(contentActor, channelId.toBn()),
+    }
   )
 
   // create entity
@@ -75,13 +81,15 @@ export async function content_ChannelUpdated(
 
   //  update metadata if it was changed
   if (newMetadata) {
-    const protobufContent = await readProtobuf(
+    const protobufContent = await readProtobufWithAssets(
       new Channel(),
-      newMetadata,
-      channelUpdateParameters.assets.unwrapOr([]),
-      db,
-      event.blockNumber,
-      contentActor,
+      {
+        metadata: newMetadata,
+        db,
+        blockNumber: event.blockNumber,
+        assets: channelUpdateParameters.assets.unwrapOr([]),
+        contentOwner: convertContentActorToOwner(contentActor, channelId),
+      }
     )
 
     // update all fields read from protobuf
@@ -191,11 +199,11 @@ export async function content_ChannelCategoryCreated(
   // read metadata
   const protobufContent = await readProtobuf(
     new ChannelCategory(),
-    channelCategoryCreationParameters.meta,
-    [],
-    db,
-    event.blockNumber,
-    contentActor,
+    {
+      metadata: channelCategoryCreationParameters.meta,
+      db,
+      blockNumber: event.blockNumber,
+    }
   )
 
   // create new channel category
@@ -236,11 +244,11 @@ export async function content_ChannelCategoryUpdated(
   // read metadata
   const protobufContent = await readProtobuf(
     new ChannelCategory(),
-    channelCategoryUpdateParameters.new_meta,
-    [],
-    db,
-    event.blockNumber,
-    contentActor,
+    {
+      metadata: channelCategoryUpdateParameters.new_meta,
+      db,
+      blockNumber: event.blockNumber,
+    }
   )
 
   // update all fields read from protobuf

+ 113 - 52
query-node/mappings/src/content/utils.ts

@@ -51,10 +51,12 @@ import {
 import {
   DataObjectOwner,
   DataObjectOwnerMember,
+  DataObjectOwnerChannel,
 } from 'query-node/src/modules/variants/variants.model'
 
 // Joystream types
 import {
+  ChannelId,
   ContentParameters,
   NewAsset,
   ContentActor,
@@ -76,65 +78,101 @@ function isAssetInStorage(dataObject: AssetStorageOrUrls): dataObject is DataObj
   return true
 }
 
+export interface IReadProtobufArguments{
+  metadata: Uint8Array
+  db: DatabaseManager
+  blockNumber: number
+}
+
+export interface IReadProtobufArgumentsWithAssets extends IReadProtobufArguments {
+  assets: NewAsset[] // assets provided in event
+  contentOwner: typeof DataObjectOwner
+}
+
 /*
   Reads information from the event and protobuf metadata and constructs changeset that's fit to be used when saving to db.
 */
 export async function readProtobuf(
-  type: Channel | ChannelCategory | Video | VideoCategory,
-  metadata: Uint8Array,
-  assets: NewAsset[], // assets provided in event
-  db: DatabaseManager,
-  blockNumber: number,
-  actor: ContentActor,
+  type: ChannelCategory | VideoCategory,
+  parameters: IReadProtobufArguments,
+): Promise<Partial<typeof type>> {
+  // process channel category
+  if (type instanceof ChannelCategory) {
+    return ChannelCategoryMetadata.deserializeBinary(parameters.metadata).toObject()
+  }
+
+  // process video category
+  if (type instanceof VideoCategory) {
+    return VideoCategoryMetadata.deserializeBinary(parameters.metadata).toObject()
+  }
+
+  // this should never happen
+  throw `Not implemented type: ${type}`
+}
+
+/*
+  Reads information from the event and protobuf metadata and constructs changeset that's fit to be used when saving to db.
+  In addition it handles any assets associated with the metadata.
+*/
+export async function readProtobufWithAssets(
+  type: Channel | Video,
+  parameters: IReadProtobufArgumentsWithAssets,
 ): Promise<Partial<typeof type>> {
   // process channel
   if (type instanceof Channel) {
-    const meta = ChannelMetadata.deserializeBinary(metadata)
+    const meta = ChannelMetadata.deserializeBinary(parameters.metadata)
     const metaAsObject = meta.toObject()
     const result = metaAsObject as any as Channel
 
     // prepare cover photo asset if needed
     if (metaAsObject.coverPhoto !== undefined) {
-      const asset = await extractAsset(metaAsObject.coverPhoto, assets, db, blockNumber, actor)
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.coverPhoto,
+        assets: parameters.assets,
+        db: parameters.db,
+        blockNumber: parameters.blockNumber,
+        contentOwner: parameters.contentOwner,
+      })
       integrateAsset('coverPhoto', result, asset) // changes `result` inline!
       delete metaAsObject.coverPhoto
     }
 
     // prepare avatar photo asset if needed
     if (metaAsObject.avatarPhoto !== undefined) {
-      const asset = await extractAsset(metaAsObject.avatarPhoto, assets, db, blockNumber, actor)
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.avatarPhoto,
+        assets: parameters.assets,
+        db: parameters.db,
+        blockNumber: parameters.blockNumber,
+        contentOwner: parameters.contentOwner,
+      })
       integrateAsset('avatarPhoto', result, asset) // changes `result` inline!
       delete metaAsObject.avatarPhoto
     }
 
     // prepare language if needed
     if (metaAsObject.language) {
-      result.language = await prepareLanguage(metaAsObject.language, db)
+      result.language = await prepareLanguage(metaAsObject.language, parameters.db)
     }
 
     return result
   }
 
-  // process channel category
-  if (type instanceof ChannelCategory) {
-    return ChannelCategoryMetadata.deserializeBinary(metadata).toObject()
-  }
-
   // process video
   if (type instanceof Video) {
-    const meta = VideoMetadata.deserializeBinary(metadata)
+    const meta = VideoMetadata.deserializeBinary(parameters.metadata)
     const metaAsObject = meta.toObject()
     const result = metaAsObject as any as Video
 
     // prepare video category if needed
     if (metaAsObject.category !== undefined) {
-      result.category = await prepareVideoCategory(metaAsObject.category, db)
+      result.category = await prepareVideoCategory(metaAsObject.category, parameters.db)
     }
 
     // prepare media meta information if needed
     if (metaAsObject.mediaType) {
       // prepare video file size if poosible
-      const videoSize = await extractVideoSize(assets, metaAsObject.video)
+      const videoSize = await extractVideoSize(parameters.assets, metaAsObject.video)
 
       result.mediaMetadata = await prepareVideoMetadata(metaAsObject, videoSize)
       delete metaAsObject.mediaType
@@ -147,21 +185,35 @@ export async function readProtobuf(
 
     // prepare thumbnail photo asset if needed
     if (metaAsObject.thumbnailPhoto !== undefined) {
-      const asset = await extractAsset(metaAsObject.thumbnailPhoto, assets, db, blockNumber, actor)
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.thumbnailPhoto,
+        assets: parameters.assets,
+        db: parameters.db,
+        blockNumber: parameters.blockNumber,
+        //actor: actor,
+        contentOwner: parameters.contentOwner,
+      })
       integrateAsset('thumbnail', result, asset) // changes `result` inline!
       delete metaAsObject.thumbnailPhoto
     }
 
     // prepare video asset if needed
     if (metaAsObject.video !== undefined) {
-      const asset = await extractAsset(metaAsObject.video, assets, db, blockNumber, actor)
+      const asset = await extractAsset({
+        assetIndex: metaAsObject.video,
+        assets: parameters.assets,
+        db: parameters.db,
+        blockNumber: parameters.blockNumber,
+        //actor: actor,
+        contentOwner: parameters.contentOwner,
+      })
       integrateAsset('media', result, asset) // changes `result` inline!
       delete metaAsObject.video
     }
 
     // prepare language if needed
     if (metaAsObject.language) {
-      result.language = await prepareLanguage(metaAsObject.language, db)
+      result.language = await prepareLanguage(metaAsObject.language, parameters.db)
     }
 
     // prepare information about media published somewhere else before Joystream if needed.
@@ -173,29 +225,23 @@ export async function readProtobuf(
     return result
   }
 
-  // process video category
-  if (type instanceof VideoCategory) {
-    return VideoCategoryMetadata.deserializeBinary(metadata).toObject()
-  }
-
   // this should never happen
   throw `Not implemented type: ${type}`
 }
 
-export function convertContentActorToOwner(actor: ContentActor): typeof DataObjectOwner {
-  if (actor.isMember) {
+export function convertContentActorToOwner(contentActor: ContentActor, channelId: BN): typeof DataObjectOwner {
+  if (contentActor.isMember) {
     const owner = new DataObjectOwnerMember()
-    owner.member = actor.asMember.toBn()
+    owner.member = contentActor.asMember.toBn()
 
     return owner
   }
 
-  if (actor.isLead) {
-    // TODO: who will be owner?
-  }
+  if (contentActor.isLead || contentActor.isCurator) {
+    const owner = new DataObjectOwnerChannel()
+    owner.channel = channelId
 
-  if (actor.isCurator) {
-    // TODO: who will be owner?
+    return owner
   }
 
   throw 'Not-implemented ContentActor type used'
@@ -211,45 +257,60 @@ function handlePublishedBeforeJoystream(video: Video, publishedAtString?: string
   video.publishedBeforeJoystream = undefined // plan deletion (will have effect when saved to db)
 }
 
+interface IConvertAssetParameters {
+  rawAsset: NewAsset
+  db: DatabaseManager
+  blockNumber: number
+  contentOwner: typeof DataObjectOwner
+}
+
 /*
   Converts event asset into data object or list of URLs fit to be saved to db.
 */
-async function convertAsset(rawAsset: NewAsset, db: DatabaseManager, blockNumber: number, actor: ContentActor): Promise<AssetStorageOrUrls> {
+async function convertAsset(parameters: IConvertAssetParameters): Promise<AssetStorageOrUrls> {
   // is asset describing list of URLs?
-  if (rawAsset.isUrls) {
-    const urls = rawAsset.asUrls.toArray().map(item => item.toString())
+  if (parameters.rawAsset.isUrls) {
+    const urls = parameters.rawAsset.asUrls.toArray().map(item => item.toString())
 
     return urls
   }
 
-  // !rawAsset.isUrls && rawAsset.isUpload // asset is in storage
+  // !parameters.rawAsset.isUrls && parameters.rawAsset.isUpload // asset is in storage
 
   // prepare data object
-  const contentParameters: ContentParameters = rawAsset.asUpload
-  const owner = convertContentActorToOwner(actor)
-  const dataObject = await prepareDataObject(contentParameters, blockNumber, owner)
-
+  const contentParameters: ContentParameters = parameters.rawAsset.asUpload
+  const dataObject = await prepareDataObject(contentParameters, parameters.blockNumber, parameters.contentOwner)
 
   return dataObject
 }
 
+interface IExtractAssetParameters {
+  assetIndex: number
+  assets: NewAsset[]
+  db: DatabaseManager
+  blockNumber: number
+  contentOwner: typeof DataObjectOwner
+}
+
 /*
   Selects asset from provided set of assets and prepares asset data fit to be saved to db.
 */
-async function extractAsset(
-  assetIndex: number,
-  assets: NewAsset[],
-  db: DatabaseManager,
-  blockNumber: number,
-  actor: ContentActor,
-): Promise<AssetStorageOrUrls> {
+async function extractAsset(parameters: IExtractAssetParameters): Promise<AssetStorageOrUrls> {
   // ensure asset index is valid
-  if (assetIndex > assets.length) {
-    return inconsistentState(`Non-existing asset extraction requested`, {assetsProvided: assets.length, assetIndex})
+  if (parameters.assetIndex > parameters.assets.length) {
+    return inconsistentState(`Non-existing asset extraction requested`, {
+      assetsProvided: parameters.assets.length,
+      assetIndex: parameters.assetIndex,
+    })
   }
 
   // convert asset to data object record
-  return convertAsset(assets[assetIndex], db, blockNumber, actor)
+  return convertAsset({
+    rawAsset: parameters.assets[parameters.assetIndex],
+    db: parameters.db,
+    blockNumber: parameters.blockNumber,
+    contentOwner: parameters.contentOwner,
+  })
 }
 
 /*

+ 37 - 23
query-node/mappings/src/content/video.ts

@@ -1,5 +1,6 @@
 import { SubstrateEvent } from '@dzlzv/hydra-common'
 import { DatabaseManager } from '@dzlzv/hydra-db-utils'
+import BN from 'bn.js'
 
 import {
   Content,
@@ -10,7 +11,11 @@ import {
   logger,
 } from '../common'
 
-import { readProtobuf } from './utils'
+import {
+  convertContentActorToOwner,
+  readProtobuf,
+  readProtobufWithAssets
+} from './utils'
 
 // primary entities
 import { Video } from 'query-node/src/modules/video/video.model'
@@ -19,6 +24,11 @@ import { VideoCategory } from 'query-node/src/modules/video-category/video-categ
 // secondary entities
 import { License } from 'query-node/src/modules/license/license.model'
 
+// Joystream types
+import {
+  ChannelId,
+} from '@joystream/types/augment'
+
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function content_VideoCategoryCreated(
   db: DatabaseManager,
@@ -34,11 +44,11 @@ export async function content_VideoCategoryCreated(
   // read metadata
   const protobufContent = readProtobuf(
     new VideoCategory(),
-    videoCategoryCreationParameters.meta,
-    [],
-    db,
-    event.blockNumber,
-    contentActor,
+    {
+      metadata: videoCategoryCreationParameters.meta,
+      db,
+      blockNumber: event.blockNumber,
+    }
   )
 
   // create new video category
@@ -80,11 +90,11 @@ export async function content_VideoCategoryUpdated(
   // read metadata
   const protobufContent = await readProtobuf(
     new VideoCategory(),
-    videoCategoryUpdateParameters.new_meta,
-    [],
-    db,
-    event.blockNumber,
-    contentActor,
+    {
+      metadata: videoCategoryUpdateParameters.new_meta,
+      db,
+      blockNumber: event.blockNumber,
+    }
   )
 
   // update all fields read from protobuf
@@ -138,13 +148,15 @@ export async function content_VideoCreated(
   } = new Content.VideoCreatedEvent(event).data
 
   // read metadata
-  const protobufContent = await readProtobuf(
+  const protobufContent = await readProtobufWithAssets(
     new Video(),
-    videoCreationParameters.meta,
-    videoCreationParameters.assets,
-    db,
-    event.blockNumber,
-    contentActor,
+    {
+      metadata: videoCreationParameters.meta,
+      db,
+      blockNumber: event.blockNumber,
+      assets: videoCreationParameters.assets,
+      contentOwner: convertContentActorToOwner(contentActor, channelId.toBn()),
+    }
   )
 
   // create new video
@@ -188,13 +200,15 @@ export async function content_VideoUpdated(
 
   // update metadata if it was changed
   if (newMetadata) {
-    const protobufContent = await readProtobuf(
+    const protobufContent = await readProtobufWithAssets(
       new Video(),
-      newMetadata,
-      videoUpdateParameters.assets.unwrapOr([]),
-      db,
-      event.blockNumber,
-      contentActor,
+      {
+        metadata: newMetadata,
+        db,
+        blockNumber: event.blockNumber,
+        assets: videoUpdateParameters.assets.unwrapOr([]),
+        contentOwner: convertContentActorToOwner(contentActor, new BN(video.channel.id)),
+      }
     )
 
     // remember original license

+ 6 - 3
query-node/mappings/src/storage.ts

@@ -61,7 +61,7 @@ export async function data_directory_ContentRemoved(db: DatabaseManager, event:
 
 export async function data_directory_ContentAccepted(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const {contentId} = new DataDirectory.ContentAcceptedEvent(event).data
+  const {contentId, storageProviderId} = new DataDirectory.ContentAcceptedEvent(event).data
 
   // load asset
   const dataObject = await db.get(DataObject, { where: { joystreamContentId: contentId }})
@@ -72,6 +72,7 @@ export async function data_directory_ContentAccepted(db: DatabaseManager, event:
   }
 
   // update object
+  dataObject.liaisonId = storageProviderId
   dataObject.liaisonJudgement = LiaisonJudgement.ACCEPTED
 
   // save object
@@ -83,7 +84,7 @@ export async function data_directory_ContentAccepted(db: DatabaseManager, event:
 
 export async function data_directory_ContentRejected(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const {contentId} = new DataDirectory.ContentRejectedEvent(event).data
+  const {contentId, storageProviderId} = new DataDirectory.ContentRejectedEvent(event).data
 
   // load asset
   const dataObject = await db.get(DataObject, { where: { joystreamContentId: contentId }})
@@ -94,6 +95,7 @@ export async function data_directory_ContentRejected(db: DatabaseManager, event:
   }
 
   // update object
+  dataObject.liaisonId = storageProviderId
   dataObject.liaisonJudgement = LiaisonJudgement.REJECTED
 
   // save object
@@ -127,12 +129,13 @@ function convertStorageObjectOwner(objectOwner: StorageObjectOwner): typeof Data
     return owner
   }
 
-  if (objectOwner.isWorkingGroup) {
+  if (objectOwner.isCouncil) {
     return new DataObjectOwnerCouncil()
   }
 
   if (objectOwner.isWorkingGroup) {
     const owner = new DataObjectOwnerWorkingGroup()
+    owner.workingGroup = objectOwner.asWorkingGroup.toNumber()
 
     return owner
   }

+ 4 - 7
query-node/schema.graphql

@@ -96,7 +96,7 @@ type DataObject @entity {
   size: BigInt!
 
   "Storage provider id of the liaison"
-  liaisonId: BigInt!
+  liaisonId: BigInt # liaison is unset until storage provider accepts or rejects the content
 
   "Storage provider as liaison judgment"
   liaisonJudgement: LiaisonJudgement!
@@ -118,6 +118,7 @@ union DataObjectOwner = DataObjectOwnerMember
 "Asset owned by a member"
 type DataObjectOwnerMember @variant {
   # use `BigInt` instead of `Membership` before variant relations are featured in Hydra
+  # TODO: setup proper relations
   #"Member identifier"
   #memberId: Membership!
   "Member identifier"
@@ -153,12 +154,8 @@ type DataObjectOwnerCouncil @variant {
 
 "Asset owned by a WorkingGroup"
 type DataObjectOwnerWorkingGroup @variant {
-  # TODO: working group doesn't seem to have id but rather `isStorage`, `isContent`, ... shoudl we care about that?
-  #"Working group identifier"
-  #workingGroup: BigInt!
-
-  "Variant needs to have at least one property. This value is not used."
-  dummy: Int
+  "Working group identifier"
+  workingGroup: Int!
 }
 
 #### High Level Derivative Entities ####