Browse Source

query node - mappings asset owner

ondratra 4 years ago
parent
commit
47ee6ab769

+ 28 - 15
query-node/mappings/src/common.ts

@@ -8,11 +8,6 @@ import { Network } from 'query-node/src/modules/enums/enums'
 // Asset
 import {
   DataObjectOwner,
-  DataObjectOwnerMember,
-  DataObjectOwnerChannel,
-  DataObjectOwnerDao,
-  DataObjectOwnerCouncil,
-  DataObjectOwnerWorkingGroup,
 } from 'query-node/src/modules/variants/variants.model'
 import {
   DataObject,
@@ -24,6 +19,9 @@ import {
 
 const currentNetwork = Network.BABYLON
 
+/*
+  Reports that insurmountable inconsistent state has been encountered and throws an exception.
+*/
 export function inconsistentState(extraInfo: string, data?: unknown): never {
   const errorMessage = 'Inconsistent state: ' + extraInfo
 
@@ -34,18 +32,25 @@ export function inconsistentState(extraInfo: string, data?: unknown): never {
   throw errorMessage
 }
 
-export async function prepareDataObject(contentParameters: ContentParameters, blockNumber: number): Promise<DataObject> {
-  const dataObjectOwner = new DataObjectOwnerMember() // TODO: proper owner
-  dataObjectOwner.memberId = new BN(0)
-
+/*
+  Prepares data object from content parameters.
+*/
+export async function prepareDataObject(
+  contentParameters: ContentParameters,
+  blockNumber: number,
+  owner: typeof DataObjectOwner,
+): Promise<DataObject> {
   const dataObject = new DataObject({
-    owner: dataObjectOwner,
+    owner,
     addedAt: blockNumber,
     typeId: contentParameters.type_id.toNumber(),
-    // `size` is masked by `size` special name in struct so there needs to be `.get('size') as u64`
+    // `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(),
-    liaisonId: new BN(0), // TODO: proper id
-    liaisonJudgement: LiaisonJudgement.PENDING, // TODO: proper judgement
+
+    // TODO: liason & judgement information can't be retrieved now - it's neither event parameter or extrinsic parameter
+    liaisonId: new BN(0),
+    liaisonJudgement: LiaisonJudgement.PENDING,
+
     ipfsContentId: contentParameters.ipfs_content_id.toHex(),
     joystreamContentId: contentParameters.content_id.toHex(),
   })
@@ -56,15 +61,23 @@ export async function prepareDataObject(contentParameters: ContentParameters, bl
 /////////////////// Logger /////////////////////////////////////////////////////
 
 /*
-Logger will not be needed in the future when Hydra v3 will be released.
-*/
+  Simple logger enabling error and informational reporting.
 
+  `Logger` class will not be needed in the future when Hydra v3 will be released.
+  Hydra will provide logger instance and relevant code using `Logger` should be refactored.
+*/
 class Logger {
 
+  /*
+    Log significant event.
+  */
   info(message: string, data?: unknown) {
     console.log(message, data)
   }
 
+  /*
+    Log significant error.
+  */
   error(message: string, data?: unknown) {
     console.error(message, data)
   }

+ 20 - 7
query-node/mappings/src/content/channel.ts

@@ -22,7 +22,7 @@ import { ChannelCategory } from 'query-node/src/modules/channel-category/channel
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function content_ChannelCreated(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const {channelId, channelCreationParameters} = new Content.ChannelCreatedEvent(event).data
+  const {channelId, channelCreationParameters, contentActor} = new Content.ChannelCreatedEvent(event).data
 
   // read metadata
   const protobufContent = await readProtobuf(
@@ -30,7 +30,8 @@ export async function content_ChannelCreated(db: DatabaseManager, event: Substra
     channelCreationParameters.meta,
     channelCreationParameters.assets,
     db,
-    event,
+    event.blockNumber,
+    contentActor,
   )
 
   // create entity
@@ -55,7 +56,11 @@ export async function content_ChannelUpdated(
   event: SubstrateEvent
 ) {
   // read event data
-  const {channelId , channelUpdateParameters} = new Content.ChannelUpdatedEvent(event).data
+  const {
+    channelId,
+    channelUpdateParameters,
+    contentActor,
+  } = new Content.ChannelUpdatedEvent(event).data
 
   // load channel
   const channel = await db.get(Channel, { where: { id: channelId } })
@@ -75,7 +80,8 @@ export async function content_ChannelUpdated(
       newMetadata,
       channelUpdateParameters.assets.unwrapOr([]),
       db,
-      event,
+      event.blockNumber,
+      contentActor,
     )
 
     // update all fields read from protobuf
@@ -180,6 +186,7 @@ export async function content_ChannelCategoryCreated(
 ) {
   // read event data
   const {channelCategoryCreationParameters} = new Content.ChannelCategoryCreatedEvent(event).data
+  const {actor: contentActor} = new Content.CreateChannelCategoryCall(event).args
 
   // read metadata
   const protobufContent = await readProtobuf(
@@ -187,7 +194,8 @@ export async function content_ChannelCategoryCreated(
     channelCategoryCreationParameters.meta,
     [],
     db,
-    event,
+    event.blockNumber,
+    contentActor,
   )
 
   // create new channel category
@@ -211,7 +219,11 @@ export async function content_ChannelCategoryUpdated(
   event: SubstrateEvent
 ) {
   // read event data
-  const {channelCategoryId, channelCategoryUpdateParameters} = new Content.ChannelCategoryUpdatedEvent(event).data
+  const {
+    channelCategoryId,
+    channelCategoryUpdateParameters,
+    contentActor,
+  } = new Content.ChannelCategoryUpdatedEvent(event).data
 
   // load channel category
   const channelCategory = await db.get(ChannelCategory, { where: { id: channelCategoryId } })
@@ -227,7 +239,8 @@ export async function content_ChannelCategoryUpdated(
     channelCategoryUpdateParameters.new_meta,
     [],
     db,
-    event,
+    event.blockNumber,
+    contentActor,
   )
 
   // update all fields read from protobuf

+ 39 - 10
query-node/mappings/src/content/utils.ts

@@ -1,6 +1,7 @@
 // TODO: check all `db.get()` and similar calls recieve a proper type argument (aka add `.toString()`, etc. to those calls)
 // TODO: can we rely on db having "foreign keys"? When item is deleted will automaticly be all relations to it unset?
 //       Similarly, will saving item also save all its related items no-yet-saved in db, or do they need to saved individually?
+//       Also, is the assumption that `db.save(MyType, {myProperty: undefined})` unsets value in db correct?
 
 import { SubstrateEvent } from '@dzlzv/hydra-common'
 import { DatabaseManager } from '@dzlzv/hydra-db-utils'
@@ -47,11 +48,16 @@ import {
   DataObject,
   LiaisonJudgement
 } from 'query-node/src/modules/data-object/data-object.model'
+import {
+  DataObjectOwner,
+  DataObjectOwnerMember,
+} from 'query-node/src/modules/variants/variants.model'
 
 // Joystream types
 import {
   ContentParameters,
   NewAsset,
+  ContentActor,
 } from '@joystream/types/augment'
 
 /*
@@ -78,7 +84,8 @@ export async function readProtobuf(
   metadata: Uint8Array,
   assets: NewAsset[], // assets provided in event
   db: DatabaseManager,
-  event: SubstrateEvent,
+  blockNumber: number,
+  actor: ContentActor,
 ): Promise<Partial<typeof type>> {
   // process channel
   if (type instanceof Channel) {
@@ -88,14 +95,14 @@ export async function readProtobuf(
 
     // prepare cover photo asset if needed
     if (metaAsObject.coverPhoto !== undefined) {
-      const asset = await extractAsset(metaAsObject.coverPhoto, assets, db, event)
+      const asset = await extractAsset(metaAsObject.coverPhoto, assets, db, blockNumber, actor)
       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, event)
+      const asset = await extractAsset(metaAsObject.avatarPhoto, assets, db, blockNumber, actor)
       integrateAsset('avatarPhoto', result, asset) // changes `result` inline!
       delete metaAsObject.avatarPhoto
     }
@@ -140,14 +147,14 @@ export async function readProtobuf(
 
     // prepare thumbnail photo asset if needed
     if (metaAsObject.thumbnailPhoto !== undefined) {
-      const asset = await extractAsset(metaAsObject.thumbnailPhoto, assets, db, event)
+      const asset = await extractAsset(metaAsObject.thumbnailPhoto, assets, db, blockNumber, actor)
       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, event)
+      const asset = await extractAsset(metaAsObject.video, assets, db, blockNumber, actor)
       integrateAsset('media', result, asset) // changes `result` inline!
       delete metaAsObject.video
     }
@@ -175,6 +182,25 @@ export async function readProtobuf(
   throw `Not implemented type: ${type}`
 }
 
+export function convertContentActorToOwner(actor: ContentActor): typeof DataObjectOwner {
+  if (actor.isMember) {
+    const owner = new DataObjectOwnerMember()
+    owner.member = actor.asMember.toBn()
+
+    return owner
+  }
+
+  if (actor.isLead) {
+    // TODO: who will be owner?
+  }
+
+  if (actor.isCurator) {
+    // TODO: who will be owner?
+  }
+
+  throw 'Not-implemented ContentActor type used'
+}
+
 function handlePublishedBeforeJoystream(video: Video, publishedAtString?: string) {
   // published elsewhere before Joystream
   if (publishedAtString) {
@@ -188,7 +214,7 @@ function handlePublishedBeforeJoystream(video: Video, publishedAtString?: string
 /*
   Converts event asset into data object or list of URLs fit to be saved to db.
 */
-async function convertAsset(rawAsset: NewAsset, db: DatabaseManager, event: SubstrateEvent): Promise<AssetStorageOrUrls> {
+async function convertAsset(rawAsset: NewAsset, db: DatabaseManager, blockNumber: number, actor: ContentActor): Promise<AssetStorageOrUrls> {
   // is asset describing list of URLs?
   if (rawAsset.isUrls) {
     const urls = rawAsset.asUrls.toArray().map(item => item.toString())
@@ -200,7 +226,9 @@ async function convertAsset(rawAsset: NewAsset, db: DatabaseManager, event: Subs
 
   // prepare data object
   const contentParameters: ContentParameters = rawAsset.asUpload
-  const dataObject = await prepareDataObject(contentParameters, event.blockNumber)
+  const owner = convertContentActorToOwner(actor)
+  const dataObject = await prepareDataObject(contentParameters, blockNumber, owner)
+
 
   return dataObject
 }
@@ -212,7 +240,8 @@ async function extractAsset(
   assetIndex: number,
   assets: NewAsset[],
   db: DatabaseManager,
-  event: SubstrateEvent,
+  blockNumber: number,
+  actor: ContentActor,
 ): Promise<AssetStorageOrUrls> {
   // ensure asset index is valid
   if (assetIndex > assets.length) {
@@ -220,7 +249,7 @@ async function extractAsset(
   }
 
   // convert asset to data object record
-  return convertAsset(assets[assetIndex], db, event)
+  return convertAsset(assets[assetIndex], db, blockNumber, actor)
 }
 
 /*
@@ -312,7 +341,7 @@ async function prepareLanguage(languageIso: string, db: DatabaseManager): Promis
 }
 
 async function prepareLicense(licenseProtobuf: LicenseMetadata.AsObject): Promise<License> {
-  // NOTE: Deletion of any previous license should take place in appropriate even handling function
+  // NOTE: Deletion of any previous license should take place in appropriate event handling function
   //       and not here even it might appear so.
 
   // crete new license

+ 29 - 8
query-node/mappings/src/content/video.ts

@@ -25,7 +25,11 @@ export async function content_VideoCategoryCreated(
   event: SubstrateEvent
 ) {
   // read event data
-  const {videoCategoryId, videoCategoryCreationParameters} = new Content.VideoCategoryCreatedEvent(event).data
+  const {
+    videoCategoryId,
+    videoCategoryCreationParameters,
+    contentActor,
+  } = new Content.VideoCategoryCreatedEvent(event).data
 
   // read metadata
   const protobufContent = readProtobuf(
@@ -33,7 +37,8 @@ export async function content_VideoCategoryCreated(
     videoCategoryCreationParameters.meta,
     [],
     db,
-    event
+    event.blockNumber,
+    contentActor,
   )
 
   // create new video category
@@ -58,7 +63,11 @@ export async function content_VideoCategoryUpdated(
   event: SubstrateEvent
 ) {
   // read event data
-  const {videoCategoryId, videoCategoryUpdateParameters} = new Content.VideoCategoryUpdatedEvent(event).data
+  const {
+    videoCategoryId,
+    videoCategoryUpdateParameters,
+    contentActor,
+  } = new Content.VideoCategoryUpdatedEvent(event).data
 
   // load video category
   const videoCategory = await db.get(VideoCategory, { where: { id: videoCategoryId } })
@@ -74,7 +83,8 @@ export async function content_VideoCategoryUpdated(
     videoCategoryUpdateParameters.new_meta,
     [],
     db,
-    event,
+    event.blockNumber,
+    contentActor,
   )
 
   // update all fields read from protobuf
@@ -120,7 +130,12 @@ export async function content_VideoCreated(
   event: SubstrateEvent
 ) {
   // read event data
-  const {channelId, videoId, videoCreationParameters} = new Content.VideoCreatedEvent(event).data
+  const {
+    channelId,
+    videoId,
+    videoCreationParameters,
+    contentActor,
+  } = new Content.VideoCreatedEvent(event).data
 
   // read metadata
   const protobufContent = await readProtobuf(
@@ -128,7 +143,8 @@ export async function content_VideoCreated(
     videoCreationParameters.meta,
     videoCreationParameters.assets,
     db,
-    event,
+    event.blockNumber,
+    contentActor,
   )
 
   // create new video
@@ -153,7 +169,11 @@ export async function content_VideoUpdated(
   event: SubstrateEvent
 ) {
   // read event data
-  const {videoId, videoUpdateParameters} = new Content.VideoUpdatedEvent(event).data
+  const {
+    videoId,
+    videoUpdateParameters,
+    contentActor,
+  } = new Content.VideoUpdatedEvent(event).data
 
   // load video
   const video = await db.get(Video, { where: { id: videoId } })
@@ -173,7 +193,8 @@ export async function content_VideoUpdated(
       newMetadata,
       videoUpdateParameters.assets.unwrapOr([]),
       db,
-      event,
+      event.blockNumber,
+      contentActor,
     )
 
     // remember original license

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

@@ -7,26 +7,40 @@ import {
   prepareDataObject,
 } from './common'
 
-import { DataDirectory } from '../../generated/types'
+import {
+  DataDirectory,
+} from '../../generated/types'
 import {
   ContentId,
   ContentParameters,
+  StorageObjectOwner,
 } from '@joystream/types/augment'
 import { LiaisonJudgement } from 'query-node/src/modules/enums/enums'
 
+import {
+  DataObjectOwner,
+  DataObjectOwnerMember,
+  DataObjectOwnerChannel,
+  DataObjectOwnerDao,
+  DataObjectOwnerCouncil,
+  DataObjectOwnerWorkingGroup,
+} from 'query-node/src/modules/variants/variants.model'
 import { DataObject } from 'query-node/src/modules/data-object/data-object.model'
 
 export async function data_directory_ContentAdded(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
   // read event data
-  const {contentParameters} = new DataDirectory.ContentAddedEvent(event).data
-  // TODO: resolve handling of Vec<ContentParameters> - currently only the first item is handleu
+  const {contentParameters, storageObjectOwner} = new DataDirectory.ContentAddedEvent(event).data
 
-  const dataObject = await prepareDataObject(contentParameters[0], event.blockNumber)
+  // save all content objects
+  for (let parameters of contentParameters) {
+    const owner = convertStorageObjectOwner(storageObjectOwner)
+    const dataObject = await prepareDataObject(parameters, event.blockNumber, owner)
 
-  await db.save<DataObject>(dataObject)
+    await db.save<DataObject>(dataObject)
+  }
 
   // emit log event
-  logger.info("Storage content has beed added", {id: dataObject.joystreamContentId})
+  logger.info("Storage content has beed added", {ids: contentParameters.map(item => item.content_id.toString())})
 }
 
 export async function data_directory_ContentRemoved(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
@@ -88,3 +102,40 @@ export async function data_directory_ContentRejected(db: DatabaseManager, event:
   // emit log event
   logger.info("Storage content has been rejected", {id: contentId})
 }
+
+/////////////////// Helpers ////////////////////////////////////////////////////
+
+function convertStorageObjectOwner(objectOwner: StorageObjectOwner): typeof DataObjectOwner {
+  if (objectOwner.isMember) {
+    const owner = new DataObjectOwnerMember()
+    owner.member = objectOwner.asMember.toBn()
+
+    return owner
+  }
+
+  if (objectOwner.isChannel) {
+    const owner = new DataObjectOwnerChannel()
+    owner.channel = objectOwner.asChannel.toBn()
+
+    return owner
+  }
+
+  if (objectOwner.isDao) {
+    const owner = new DataObjectOwnerDao()
+    owner.dao = objectOwner.asDao.toBn()
+
+    return owner
+  }
+
+  if (objectOwner.isWorkingGroup) {
+    return new DataObjectOwnerCouncil()
+  }
+
+  if (objectOwner.isWorkingGroup) {
+    const owner = new DataObjectOwnerWorkingGroup()
+
+    return owner
+  }
+
+  throw 'Not-implemented StorageObjectOwner type used'
+}

+ 15 - 8
query-node/schema.graphql

@@ -121,37 +121,44 @@ type DataObjectOwnerMember @variant {
   #"Member identifier"
   #memberId: Membership!
   "Member identifier"
-  memberId: BigInt!
+  member: BigInt!
 
   "Variant needs to have at least one property. This value is not used."
-  dummy: Int!
+  dummy: Int
 }
 
 "Asset owned by a channel"
 type DataObjectOwnerChannel @variant {
+  # use `BigInt` instead of `Channel` before variant relations are featured in Hydra
+  #"Channel identifier"
+  #channel: Channel!
   "Channel identifier"
-  channel: Channel!
+  channel: BigInt!
 
   "Variant needs to have at least one property. This value is not used."
-  dummy: Int!
+  dummy: Int
 }
 
 "Asset owned by a DAO"
 type DataObjectOwnerDao @variant {
   "DAO identifier"
-  daoId: BigInt!
+  dao: BigInt!
 }
 
 "Asset owned by the Council"
 type DataObjectOwnerCouncil @variant {
   "Variant needs to have at least one property. This value is not used."
-  dummy: Int!
+  dummy: Int
 }
 
 "Asset owned by a WorkingGroup"
 type DataObjectOwnerWorkingGroup @variant {
-  "Working group identifier"
-  workingGroupId: BigInt!
+  # 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
 }
 
 #### High Level Derivative Entities ####