Browse Source

Merge branch 'babylon' of https://github.com/Joystream/joystream into babylon

iorveth 4 years ago
parent
commit
d6e2971f1b
52 changed files with 5291 additions and 149 deletions
  1. 11 9
      .github/workflows/run-network-tests.yml
  2. 8 0
      .gitignore
  3. 66 9
      cli/src/base/ContentDirectoryCommandBase.ts
  4. 27 16
      cli/src/base/WorkingGroupsCommandBase.ts
  5. 1 1
      cli/src/commands/content-directory/entity.ts
  6. 1 1
      cli/src/commands/media/createChannel.ts
  7. 57 0
      cli/src/commands/media/curateContent.ts
  8. 27 7
      cli/src/commands/media/updateChannel.ts
  9. 25 5
      cli/src/commands/media/updateVideo.ts
  10. 1 0
      content-directory-schemas/.npmignore
  11. 1 1
      content-directory-schemas/examples/createChannel.ts
  12. 68 0
      content-directory-schemas/examples/createChannelWithoutTransaction.ts
  13. 47 0
      content-directory-schemas/examples/updateChannelTitleWithoutTransaction.ts
  14. 0 7
      content-directory-schemas/inputs/classes/CurationStatusClass.json
  15. 3 3
      content-directory-schemas/inputs/schemas/ChannelSchema.json
  16. 0 27
      content-directory-schemas/inputs/schemas/CurationStatusSchema.json
  17. 3 3
      content-directory-schemas/inputs/schemas/VideoSchema.json
  18. 3 1
      content-directory-schemas/package.json
  19. 12 1
      content-directory-schemas/scripts/schemasToTS.ts
  20. 73 43
      content-directory-schemas/src/helpers/InputParser.ts
  21. 32 8
      content-directory-schemas/src/helpers/extrinsics.ts
  22. 1 0
      content-directory-schemas/src/index.ts
  23. 1 1
      content-directory-schemas/tsconfig.json
  24. 59 0
      query-node/.env
  25. 45 0
      query-node/README.md
  26. 1 0
      query-node/bootstrap/index.ts
  27. 41 0
      query-node/bootstrap/members.ts
  28. 1827 0
      query-node/bootstrap/package-lock.json
  29. 19 0
      query-node/bootstrap/package.json
  30. 18 0
      query-node/bootstrap/tsconfig.json
  31. 36 0
      query-node/package.json
  32. 256 0
      query-node/schema.graphql
  33. 102 0
      query-node/src/content-directory/content-dir-consts.ts
  34. 145 0
      query-node/src/content-directory/decode.ts
  35. 475 0
      query-node/src/content-directory/entity-helper.ts
  36. 337 0
      query-node/src/content-directory/entity.ts
  37. 7 0
      query-node/src/content-directory/mapping.ts
  38. 286 0
      query-node/src/content-directory/transaction.ts
  39. 2 0
      query-node/src/index.ts
  40. 170 0
      query-node/src/types.ts
  41. 22 0
      query-node/tsconfig.json
  42. 920 0
      query-node/typedefs.json
  43. 11 4
      tests/network-tests/run-tests.sh
  44. 1 0
      tests/query-node/.gitignore
  45. 32 0
      tests/query-node/run-tests.sh
  46. 1 1
      types/augment-codec/all.ts
  47. 0 0
      types/augment-codec/augment-types.ts
  48. 1 0
      types/augment/all/defs.json
  49. 3 0
      types/augment/all/types.ts
  50. 0 0
      types/augment/augment-types.ts
  51. 3 0
      types/src/content-directory/index.ts
  52. 3 1
      types/src/index.ts

+ 11 - 9
.github/workflows/run-network-tests.yml

@@ -78,7 +78,7 @@ jobs:
           name: ${{ steps.compute_shasum.outputs.shasum }}-joystream-node-docker-image.tar.gz
           path: joystream-node-docker-image.tar.gz
   
-  network_tests_1:
+  basic_runtime_with_upgrade:
     name: Integration Tests (Runtime Upgrade)
     needs: build_images
     runs-on: ubuntu-latest
@@ -102,7 +102,7 @@ jobs:
       - name: Execute network tests
         run: RUNTIME=alexandria tests/network-tests/run-tests.sh full
 
-  network_tests_2:
+  basic_runtime:
     name: Integration Tests (New Chain)
     needs: build_images
     runs-on: ubuntu-latest
@@ -126,7 +126,7 @@ jobs:
       - name: Execute network tests
         run: tests/network-tests/run-tests.sh full
 
-  network_tests_3:
+  content_dir_init:
     name: Content Directory Initialization
     needs: build_images
     runs-on: ubuntu-latest
@@ -152,8 +152,8 @@ jobs:
       - name: Initialize the content directory
         run: yarn workspace cd-schemas initialize:dev
 
-  network_tests_4:
-    name: Content Direcotry Integration Tests
+  query_node:
+    name: Query Node Integration Tests
     needs: build_images
     runs-on: ubuntu-latest
     steps:
@@ -173,10 +173,12 @@ jobs:
         run: yarn install --frozen-lockfile
       - name: Ensure tests are runnable
         run: yarn workspace network-tests build
-      - name: Execute network tests
-        run: tests/network-tests/run-tests.sh content-directory
+      # Bring up hydra query-node development instance, then run content directory
+      # integration tests
+      - name: Execute Tests
+        run: tests/query-node/run-tests.sh
   
-  network_tests_5:
+  storage_node:
     name: Storage Node Tests
     needs: build_images
     runs-on: ubuntu-latest
@@ -205,4 +207,4 @@ jobs:
         run: sleep 90
         # Better would be poll `http://localhost:3001/discover/v0/1` until we get 200 Response
       - name: Upload a file
-        run: DEBUG=joystream:* yarn storage-cli upload ./pioneer/packages/apps/public/images/default-thumbnail.png 1 0
+        run: DEBUG=joystream:* yarn storage-cli upload ./pioneer/packages/apps/public/images/default-thumbnail.png 1 0

+ 8 - 0
.gitignore

@@ -32,3 +32,11 @@ yarn*
 
 # Istanbul report output
 **.nyc_output/
+
+# Warthog codegen
+**/generated/
+# Auto generated api preview
+apipreview.graphql
+
+# eslint cache
+**/.eslintcache

+ 66 - 9
cli/src/base/ContentDirectoryCommandBase.ts

@@ -1,28 +1,65 @@
 import ExitCodes from '../ExitCodes'
-import AccountsCommandBase from './AccountsCommandBase'
-import { WorkingGroups, NamedKeyringPair } from '../Types'
+import { WorkingGroups } from '../Types'
 import { ReferenceProperty } from 'cd-schemas/types/extrinsics/AddClassSchema'
 import { FlattenRelations } from 'cd-schemas/types/utility'
 import { BOOL_PROMPT_OPTIONS } from '../helpers/prompting'
-import { Class, ClassId, CuratorGroup, CuratorGroupId, Entity, EntityId } from '@joystream/types/content-directory'
+import {
+  Class,
+  ClassId,
+  CuratorGroup,
+  CuratorGroupId,
+  Entity,
+  EntityId,
+  Actor,
+} from '@joystream/types/content-directory'
 import { Worker } from '@joystream/types/working-group'
 import { CLIError } from '@oclif/errors'
 import { Codec } from '@polkadot/types/types'
 import _ from 'lodash'
+import { RolesCommandBase } from './WorkingGroupsCommandBase'
+import { createType } from '@joystream/types'
 import chalk from 'chalk'
 
 /**
  * Abstract base class for commands related to content directory
  */
-export default abstract class ContentDirectoryCommandBase extends AccountsCommandBase {
+export default abstract class ContentDirectoryCommandBase extends RolesCommandBase {
+  group = WorkingGroups.Curators // override group for RolesCommandBase
+
   // Use when lead access is required in given command
   async requireLead(): Promise<void> {
-    const selectedAccount: NamedKeyringPair = await this.getRequiredSelectedAccount()
-    const lead = await this.getApi().groupLead(WorkingGroups.Curators)
+    await this.getRequiredLead()
+  }
+
+  async getCuratorContext(classNames: string[] = []): Promise<Actor> {
+    const curator = await this.getRequiredWorker()
+    const classes = await Promise.all(classNames.map(async (cName) => (await this.classEntryByNameOrId(cName))[1]))
+    const classMaintainers = classes.map(({ class_permissions: permissions }) => permissions.maintainers.toArray())
 
-    if (!lead || lead.roleAccount.toString() !== selectedAccount.address) {
-      this.error('Content Working Group Lead access required for this command!', { exit: ExitCodes.AccessDenied })
+    const groups = await this.getApi().availableCuratorGroups()
+    const availableGroupIds = groups
+      .filter(
+        ([groupId, group]) =>
+          group.active.valueOf() &&
+          classMaintainers.every((maintainers) => maintainers.some((m) => m.eq(groupId))) &&
+          group.curators.toArray().some((curatorId) => curatorId.eq(curator.workerId))
+      )
+      .map(([id]) => id)
+
+    let groupId: number
+    if (!availableGroupIds.length) {
+      this.error(
+        'You do not have the required maintainer access to at least one of the following classes: ' +
+          classNames.join(', '),
+        { exit: ExitCodes.AccessDenied }
+      )
+    } else if (availableGroupIds.length === 1) {
+      groupId = availableGroupIds[0].toNumber()
+    } else {
+      groupId = await this.promptForCuratorGroup('Select Curator Group context', availableGroupIds)
     }
+
+    return createType('Actor', { Curator: [groupId, curator.workerId.toNumber()] })
   }
 
   async promptForClass(message = 'Select a class'): Promise<Class> {
@@ -144,7 +181,12 @@ export default abstract class ContentDirectoryCommandBase extends AccountsComman
     return group
   }
 
-  async getEntity(id: string | number, requiredClass?: string, ownerMemberId?: number): Promise<Entity> {
+  async getEntity(
+    id: string | number,
+    requiredClass?: string,
+    ownerMemberId?: number,
+    requireSchema = true
+  ): Promise<Entity> {
     if (typeof id === 'string') {
       id = parseInt(id)
     }
@@ -172,6 +214,10 @@ export default abstract class ContentDirectoryCommandBase extends AccountsComman
       })
     }
 
+    if (requireSchema && !entity.supported_schemas.toArray().length) {
+      this.error(`${requiredClass || ''}Entity of id ${id} has no schema support added!`)
+    }
+
     return entity
   }
 
@@ -268,10 +314,21 @@ export default abstract class ContentDirectoryCommandBase extends AccountsComman
     ownerMemberId?: number
   ): Promise<Record<string, string>[]> {
     const [classId, entityClass] = await this.classEntryByNameOrId(className)
+    // Create object of default "[not set]" values (prevents breaking the table if entity has no schema support)
+    const defaultValues = entityClass.properties
+      .map((p) => p.name.toString())
+      .reduce((d, propName) => {
+        if (includedProps?.includes(propName)) {
+          d[propName] = chalk.grey('[not set]')
+        }
+        return d
+      }, {} as Record<string, string>)
+
     const entityEntries = await this.entitiesByClassAndOwner(classId.toNumber(), ownerMemberId)
     const parsedEntities = (await Promise.all(
       entityEntries.map(([id, entity]) => ({
         'ID': id.toString(),
+        ...defaultValues,
         ..._.mapValues(this.parseEntityPropertyValues(entity, entityClass, includedProps), (v) =>
           v.value.toJSON() === false && v.type !== 'Single<Bool>' ? chalk.grey('[not set]') : v.value.toString()
         ),

+ 27 - 16
cli/src/base/WorkingGroupsCommandBase.ts

@@ -24,29 +24,20 @@ const DEFAULT_GROUP = WorkingGroups.StorageProviders
 const DRAFTS_FOLDER = 'opening-drafts'
 
 /**
- * Abstract base class for commands related to working groups
+ * Abstract base class for commands that need to use gates based on user's roles
  */
-export default abstract class WorkingGroupsCommandBase extends AccountsCommandBase {
+export abstract class RolesCommandBase extends AccountsCommandBase {
   group: WorkingGroups = DEFAULT_GROUP
 
-  static flags = {
-    group: flags.string({
-      char: 'g',
-      description:
-        'The working group context in which the command should be executed\n' +
-        `Available values are: ${AvailableGroups.join(', ')}.`,
-      required: true,
-      default: DEFAULT_GROUP,
-    }),
-  }
-
   // Use when lead access is required in given command
   async getRequiredLead(): Promise<GroupMember> {
     const selectedAccount: NamedKeyringPair = await this.getRequiredSelectedAccount()
     const lead = await this.getApi().groupLead(this.group)
 
     if (!lead || lead.roleAccount.toString() !== selectedAccount.address) {
-      this.error('Lead access required for this command!', { exit: ExitCodes.AccessDenied })
+      this.error(`${_.startCase(this.group)} Group Lead access required for this command!`, {
+        exit: ExitCodes.AccessDenied,
+      })
     }
 
     return lead
@@ -59,7 +50,9 @@ export default abstract class WorkingGroupsCommandBase extends AccountsCommandBa
     const groupMembersByAccount = groupMembers.filter((m) => m.roleAccount.toString() === selectedAccount.address)
 
     if (!groupMembersByAccount.length) {
-      this.error('Worker access required for this command!', { exit: ExitCodes.AccessDenied })
+      this.error(`${_.startCase(this.group)} Group Worker access required for this command!`, {
+        exit: ExitCodes.AccessDenied,
+      })
     } else if (groupMembersByAccount.length === 1) {
       return groupMembersByAccount[0]
     } else {
@@ -88,7 +81,7 @@ export default abstract class WorkingGroupsCommandBase extends AccountsCommandBa
 
   async promptForWorker(groupMembers: GroupMember[]): Promise<GroupMember> {
     const chosenWorkerIndex = await this.simplePrompt({
-      message: 'Choose the intended worker context:',
+      message: `Choose the intended ${_.startCase(this.group)} Group Worker context:`,
       type: 'list',
       choices: groupMembers.map((groupMember, index) => ({
         name: `Worker ID ${groupMember.workerId.toString()}`,
@@ -98,6 +91,24 @@ export default abstract class WorkingGroupsCommandBase extends AccountsCommandBa
 
     return groupMembers[chosenWorkerIndex]
   }
+}
+
+/**
+ * Abstract base class for commands directly related to working groups
+ */
+export default abstract class WorkingGroupsCommandBase extends RolesCommandBase {
+  group: WorkingGroups = DEFAULT_GROUP
+
+  static flags = {
+    group: flags.string({
+      char: 'g',
+      description:
+        'The working group context in which the command should be executed\n' +
+        `Available values are: ${AvailableGroups.join(', ')}.`,
+      required: true,
+      default: DEFAULT_GROUP,
+    }),
+  }
 
   async promptForApplicationsToAccept(opening: GroupOpening): Promise<number[]> {
     const acceptableApplications = opening.applications.filter((a) => a.stage === ApplicationStageKeys.Active)

+ 1 - 1
cli/src/commands/content-directory/entity.ts

@@ -15,7 +15,7 @@ export default class EntityCommand extends ContentDirectoryCommandBase {
 
   async run() {
     const { id } = this.parse(EntityCommand).args
-    const entity = await this.getEntity(id)
+    const entity = await this.getEntity(id, undefined, undefined, false)
     const { controller, frozen, referenceable } = entity.entity_permissions
     const [classId, entityClass] = await this.classEntryByNameOrId(entity.class_id.toString())
     const propertyValues = this.parseEntityPropertyValues(entity, entityClass)

+ 1 - 1
cli/src/commands/media/createChannel.ts

@@ -27,7 +27,7 @@ export default class CreateChannelCommand extends ContentDirectoryCommandBase {
     if (!inputJson) {
       const customPrompts: JsonSchemaCustomPrompts = [
         ['language', () => this.promptForEntityId('Choose channel language', 'Language', 'name')],
-        ['curationStatus', async () => undefined],
+        ['isCensored', async () => undefined],
       ]
 
       const prompter = new JsonSchemaPrompter<ChannelEntity>(channelJsonSchema, undefined, customPrompts)

+ 57 - 0
cli/src/commands/media/curateContent.ts

@@ -0,0 +1,57 @@
+import ContentDirectoryCommandBase from '../../base/ContentDirectoryCommandBase'
+import { InputParser } from 'cd-schemas'
+import { flags } from '@oclif/command'
+import { ChannelEntity } from 'cd-schemas/types/entities/ChannelEntity'
+import { VideoEntity } from 'cd-schemas/types/entities/VideoEntity'
+
+const CLASSES = ['Channel', 'Video'] as const
+const STATUSES = ['Accepted', 'Censored'] as const
+
+export default class CurateContentCommand extends ContentDirectoryCommandBase {
+  static description = `Set the curation status of given entity (${CLASSES.join('/')}). Requires Curator access.`
+  static flags = {
+    className: flags.enum({
+      options: [...CLASSES],
+      description: `Name of the class of the entity to curate (${CLASSES.join('/')})`,
+      char: 'c',
+      required: true,
+    }),
+    status: flags.enum({
+      description: `Specifies the curation status (${STATUSES.join('/')})`,
+      char: 's',
+      options: [...STATUSES],
+      required: true,
+    }),
+    id: flags.integer({
+      description: 'ID of the entity to curate',
+      required: true,
+    }),
+  }
+
+  async run() {
+    const { className, status, id } = this.parse(CurateContentCommand).flags
+
+    const account = await this.getRequiredSelectedAccount()
+    // Get curator actor with required maintainer access to $className (Video/Channel) class
+    const actor = await this.getCuratorContext([className])
+
+    await this.requestAccountDecoding(account)
+
+    const inputParser = InputParser.createWithKnownSchemas(this.getOriginalApi())
+
+    await this.getEntity(id, className) // Check if entity exists and is of given class
+
+    const entityUpdateInput: Partial<ChannelEntity & VideoEntity> = {
+      isCensored: status === 'Censored',
+    }
+
+    this.log(`Updating the ${className} with:`)
+    this.jsonPrettyPrint(JSON.stringify(entityUpdateInput))
+    const confirmed = await this.simplePrompt({ type: 'confirm', message: 'Do you confirm the provided input?' })
+
+    if (confirmed) {
+      const operations = await inputParser.getEntityUpdateOperations(entityUpdateInput, className, id)
+      await this.sendAndFollowNamedTx(account, 'contentDirectory', 'transaction', [actor, operations], true)
+    }
+  }
+}

+ 27 - 7
cli/src/commands/media/updateChannel.ts

@@ -5,12 +5,18 @@ import { InputParser } from 'cd-schemas'
 import { IOFlags, getInputJson, saveOutputJson } from '../../helpers/InputOutput'
 import { JSONSchema } from '@apidevtools/json-schema-ref-parser'
 import { JsonSchemaCustomPrompts, JsonSchemaPrompter } from '../../helpers/JsonSchemaPrompt'
-import { Entity } from '@joystream/types/content-directory'
+import { Actor, Entity } from '@joystream/types/content-directory'
+import { flags } from '@oclif/command'
+import { createType } from '@joystream/types'
 
 export default class UpdateChannelCommand extends ContentDirectoryCommandBase {
   static description = 'Update one of the owned channels on Joystream (requires a membership).'
   static flags = {
     ...IOFlags,
+    asCurator: flags.boolean({
+      description: 'Provide this flag in order to use Curator context for the update',
+      required: false,
+    }),
   }
 
   static args = [
@@ -22,13 +28,23 @@ export default class UpdateChannelCommand extends ContentDirectoryCommandBase {
   ]
 
   async run() {
+    const {
+      args: { id },
+      flags: { asCurator },
+    } = this.parse(UpdateChannelCommand)
+
     const account = await this.getRequiredSelectedAccount()
-    const memberId = await this.getRequiredMemberId()
-    const actor = { Member: memberId }
 
-    await this.requestAccountDecoding(account)
+    let memberId: number | undefined, actor: Actor
+
+    if (asCurator) {
+      actor = await this.getCuratorContext(['Channel'])
+    } else {
+      memberId = await this.getRequiredMemberId()
+      actor = createType('Actor', { Member: memberId })
+    }
 
-    const { id } = this.parse(UpdateChannelCommand).args
+    await this.requestAccountDecoding(account)
 
     let channelEntity: Entity, channelId: number
     if (id) {
@@ -49,15 +65,19 @@ export default class UpdateChannelCommand extends ContentDirectoryCommandBase {
 
     let inputJson = await getInputJson<ChannelEntity>(input, channelJsonSchema)
     if (!inputJson) {
-      const customPrompts: JsonSchemaCustomPrompts = [
+      const customPrompts: JsonSchemaCustomPrompts<ChannelEntity> = [
         [
           'language',
           () =>
             this.promptForEntityId('Choose channel language', 'Language', 'name', undefined, currentValues.language),
         ],
-        ['curationStatus', async () => undefined],
       ]
 
+      if (!asCurator) {
+        // Skip isCensored is it's not updated by the curator
+        customPrompts.push(['isCensored', async () => undefined])
+      }
+
       const prompter = new JsonSchemaPrompter<ChannelEntity>(channelJsonSchema, currentValues, customPrompts)
 
       inputJson = await prompter.promptAll()

+ 25 - 5
cli/src/commands/media/updateVideo.ts

@@ -5,12 +5,18 @@ import { LicenseEntity } from 'cd-schemas/types/entities/LicenseEntity'
 import { InputParser } from 'cd-schemas'
 import { JSONSchema } from '@apidevtools/json-schema-ref-parser'
 import { JsonSchemaCustomPrompts, JsonSchemaPrompter } from '../../helpers/JsonSchemaPrompt'
-import { Entity } from '@joystream/types/content-directory'
+import { Actor, Entity } from '@joystream/types/content-directory'
+import { createType } from '@joystream/types'
+import { flags } from '@oclif/command'
 
 export default class UpdateVideoCommand extends ContentDirectoryCommandBase {
   static description = 'Update existing video information (requires a membership).'
   static flags = {
     // TODO: ...IOFlags, - providing input as json
+    asCurator: flags.boolean({
+      description: 'Specify in order to update the video as curator',
+      required: false,
+    }),
   }
 
   static args = [
@@ -22,13 +28,23 @@ export default class UpdateVideoCommand extends ContentDirectoryCommandBase {
   ]
 
   async run() {
+    const {
+      args: { id },
+      flags: { asCurator },
+    } = this.parse(UpdateVideoCommand)
+
     const account = await this.getRequiredSelectedAccount()
-    const memberId = await this.getRequiredMemberId()
-    const actor = { Member: memberId }
 
-    await this.requestAccountDecoding(account)
+    let memberId: number | undefined, actor: Actor
 
-    const { id } = this.parse(UpdateVideoCommand).args
+    if (asCurator) {
+      actor = await this.getCuratorContext(['Video', 'License'])
+    } else {
+      memberId = await this.getRequiredMemberId()
+      actor = createType('Actor', { Member: memberId })
+    }
+
+    await this.requestAccountDecoding(account)
 
     let videoEntity: Entity, videoId: number
     if (id) {
@@ -80,6 +96,10 @@ export default class UpdateVideoCommand extends ContentDirectoryCommandBase {
       'hasMarketing',
     ])
 
+    if (asCurator) {
+      updatedProps.isCensored = await videoPrompter.promptSingleProp('isCensored')
+    }
+
     this.jsonPrettyPrint(JSON.stringify(updatedProps))
 
     // Parse inputs into operations and send final extrinsic

+ 1 - 0
content-directory-schemas/.npmignore

@@ -0,0 +1 @@
+operations.json

+ 1 - 1
content-directory-schemas/examples/createChannel.ts

@@ -3,7 +3,7 @@ import { types as joyTypes } from '@joystream/types'
 import { Keyring } from '@polkadot/keyring'
 // Import input parser and channel entity from cd-schemas (we use it as library here)
 import { InputParser } from 'cd-schemas'
-import { ChannelEntity } from 'cd-schemas/types/entities/ChannelEntity'
+import { ChannelEntity } from 'cd-schemas/types/entities'
 
 async function main() {
   // Initialize the api

+ 68 - 0
content-directory-schemas/examples/createChannelWithoutTransaction.ts

@@ -0,0 +1,68 @@
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { types as joyTypes } from '@joystream/types'
+import { Keyring } from '@polkadot/keyring'
+// Import input parser and channel entity from cd-schemas (we use it as library here)
+import { InputParser } from 'cd-schemas'
+import { ChannelEntity } from 'cd-schemas/types/entities'
+import { FlattenRelations } from 'cd-schemas/types/utility'
+import { EntityId } from '@joystream/types/content-directory'
+
+// Alternative way of creating a channel using separate extrinsics (instead of contentDirectory.transaction)
+async function main() {
+  // Initialize the api
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+  const api = await ApiPromise.create({ provider, types: joyTypes })
+
+  // Get Alice keypair
+  const keyring = new Keyring()
+  keyring.addFromUri('//Alice', undefined, 'sr25519')
+  const [ALICE] = keyring.getPairs()
+
+  const parser = InputParser.createWithKnownSchemas(api)
+
+  // In this case we need to fetch some data first (like classId and language entity id)
+  const classId = await parser.getClassIdByName('Channel')
+  const languageEntityId = await parser.findEntityIdByUniqueQuery({ code: 'EN' }, 'Language')
+
+  // We use FlattenRelations to exlude { new } and { existing } (which are not allowed if we want to parse only a single entity)
+  const channel: FlattenRelations<ChannelEntity> = {
+    title: 'Example channel 2',
+    description: 'This is an example channel',
+    language: languageEntityId,
+    coverPhotoUrl: '',
+    avatarPhotoURL: '',
+    isPublic: true,
+  }
+
+  // In this case we use some basic callback to retrieve entityId from the extrinsc event
+  const entityId = await new Promise<EntityId>((resolve, reject) => {
+    api.tx.contentDirectory.createEntity(classId, { Member: 0 }).signAndSend(ALICE, {}, (res) => {
+      if (res.isError) {
+        reject(new Error(res.status.type))
+      }
+      res.events.forEach(({ event: e }) => {
+        if (e.method === 'EntityCreated') {
+          resolve(e.data[1] as EntityId)
+        }
+        if (e.method === 'ExtrinsicFailed') {
+          reject(new Error('Extrinsic failed'))
+        }
+      })
+    })
+  })
+
+  const inputPropertyValuesMap = await parser.parseToInputEntityValuesMap({ ...channel }, 'Channel')
+  // Having entityId we can create and send addSchemaSupport tx
+  await api.tx.contentDirectory
+    .addSchemaSupportToEntity(
+      { Member: 0 }, // Context (in this case we assume it's Alice's member id)
+      entityId,
+      0, // Schema (currently we have one schema per class, so it can be just 0)
+      inputPropertyValuesMap
+    )
+    .signAndSend(ALICE)
+}
+
+main()
+  .then(() => process.exit())
+  .catch(console.error)

+ 47 - 0
content-directory-schemas/examples/updateChannelTitleWithoutTransaction.ts

@@ -0,0 +1,47 @@
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { types as joyTypes } from '@joystream/types'
+import { Keyring } from '@polkadot/keyring'
+// Import input parser and channel entity from cd-schemas (we use it as library here)
+import { InputParser } from 'cd-schemas'
+import { ChannelEntity } from 'cd-schemas/types/entities'
+import { FlattenRelations } from 'cd-schemas/types/utility'
+
+// Alternative way of update a channel using updateEntityPropertyValues extrinsic
+async function main() {
+  // Initialize the api
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+  const api = await ApiPromise.create({ provider, types: joyTypes })
+
+  // Get Alice keypair
+  const keyring = new Keyring()
+  keyring.addFromUri('//Alice', undefined, 'sr25519')
+  const [ALICE] = keyring.getPairs()
+
+  // Create partial channel entity, only containing the fields we wish to update
+  const channelUpdateInput: Partial<FlattenRelations<ChannelEntity>> = {
+    title: 'Updated channel title 2',
+  }
+
+  // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
+  const parser = InputParser.createWithKnownSchemas(api)
+
+  // We can reuse InputParser's `findEntityIdByUniqueQuery` method to find entityId of the channel we
+  // created in ./createChannelWithoutTransaction.ts example
+  // (normally we would probably use some other way to do it, ie.: query node)
+  const CHANNEL_ID = await parser.findEntityIdByUniqueQuery({ title: 'Example channel 2' }, 'Channel')
+
+  // We use parser to create input property values map
+  const newPropertyValues = await parser.parseToInputEntityValuesMap(channelUpdateInput, 'Channel')
+
+  await api.tx.contentDirectory
+    .updateEntityPropertyValues(
+      { Member: 0 }, // We use member with id 0 as actor (in this case we assume this is Alice)
+      CHANNEL_ID,
+      newPropertyValues
+    )
+    .signAndSend(ALICE)
+}
+
+main()
+  .then(() => process.exit())
+  .catch(console.error)

+ 0 - 7
content-directory-schemas/inputs/classes/CurationStatusClass.json

@@ -1,7 +0,0 @@
-{
-  "name": "CurationStatus",
-  "description": "Curation status of a related entity (ie. Video or Channel)",
-  "maximum_entities_count": 400,
-  "default_entity_creation_voucher_upper_bound": 50,
-  "class_permissions": { "any_member": false }
-}

+ 3 - 3
content-directory-schemas/inputs/schemas/ChannelSchema.json

@@ -33,11 +33,11 @@
       "property_type": { "Single": "Bool" }
     },
     {
-      "name": "curationStatus",
-      "description": "Channel curation status set by the Curator",
+      "name": "isCensored",
+      "description": "Channel censorship status set by the Curator.",
       "required": false,
       "unique": true,
-      "property_type": { "Single": { "Reference": { "className": "CurationStatus" } } },
+      "property_type": { "Single": "Bool" },
       "locking_policy": { "is_locked_from_controller": true }
     },
     {

+ 0 - 27
content-directory-schemas/inputs/schemas/CurationStatusSchema.json

@@ -1,27 +0,0 @@
-{
-  "className": "CurationStatus",
-  "newProperties": [
-    {
-      "name": "approved",
-      "description": "Indicates whether the content was approved by the Curator",
-      "required": false,
-      "property_type": { "Single": "Bool" },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "comment",
-      "description": "Short, optional comment from the Curator",
-      "required": false,
-      "property_type": { "Single": { "Text": 256 } },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "entityId",
-      "description": "ID of the curated entity. It's not a relation to prevent removal lock and allow different types of entities. Used to confirm the validity of Content => CurationStatus relation.",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": "Uint64" },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 3 - 3
content-directory-schemas/inputs/schemas/VideoSchema.json

@@ -90,11 +90,11 @@
       "property_type": { "Single": { "Reference": { "className": "License", "sameOwner": true } } }
     },
     {
-      "name": "curationStatus",
-      "description": "Video curation status set by the Curator",
+      "name": "isCensored",
+      "description": "Video censorship status set by the Curator.",
       "required": false,
       "unique": true,
-      "property_type": { "Single": { "Reference": { "className": "CurationStatus" } } },
+      "property_type": { "Single": "Bool" },
       "locking_policy": { "is_locked_from_controller": true }
     }
   ]

+ 3 - 1
content-directory-schemas/package.json

@@ -19,7 +19,9 @@
     "initialize:dev": "yarn initialize:alice-as-lead && yarn initialize:content-dir",
     "example:createChannel": "ts-node ./examples/createChannel.ts",
     "example:createVideo": "ts-node ./examples/createVideo.ts",
-    "example:updateChannelTitle": "ts-node ./examples/updateChannelTitle.ts"
+    "example:updateChannelTitle": "ts-node ./examples/updateChannelTitle.ts",
+    "example:createChannelWithoutTransaction": "ts-node ./examples/createChannelWithoutTransaction.ts",
+    "example:updateChannelTitlelWithoutTransaction": "ts-node ./examples/updateChannelTitleWithoutTransaction.ts"
   },
   "dependencies": {
     "ajv": "6.12.5",

+ 12 - 1
content-directory-schemas/scripts/schemasToTS.ts

@@ -10,15 +10,19 @@ const OUTPUT_TYPES_LOCATION = path.join(__dirname, '../types')
 const SUBDIRS_INCLUDED = ['extrinsics', 'entities'] as const
 
 async function main() {
+  // Create typescript files
   for (const subdirName of fs.readdirSync(SCHEMAS_LOCATION)) {
     if (!SUBDIRS_INCLUDED.includes(subdirName as any)) {
       console.log(`Subdir/filename not included: ${subdirName} - skipping...`)
       continue
     }
     const schemaSubdir = subdirName as typeof SUBDIRS_INCLUDED[number]
+    const indexExportedTypes: string[] = []
     for (const schemaFilename of fs.readdirSync(path.join(SCHEMAS_LOCATION, schemaSubdir))) {
       const schemaFilePath = path.join(SCHEMAS_LOCATION, schemaSubdir, schemaFilename)
-      const outputFilename = schemaFilename.replace('.schema.json', '.d.ts')
+      const mainTypeName = schemaFilename.replace('.schema.json', '')
+      const outputFilename = mainTypeName + '.d.ts'
+      indexExportedTypes.push(mainTypeName)
       const outputDir = path.join(OUTPUT_TYPES_LOCATION, schemaSubdir)
       if (!fs.existsSync(outputDir)) {
         fs.mkdirSync(outputDir)
@@ -37,6 +41,13 @@ async function main() {
         console.error(e)
       }
     }
+    // Generate main index.d.ts export file for entities
+    const indexFilePath = path.join(OUTPUT_TYPES_LOCATION, schemaSubdir, 'index.d.ts')
+    fs.writeFileSync(
+      indexFilePath,
+      indexExportedTypes.reduce((content, typeName) => (content += `export { ${typeName} } from './${typeName}'\n`), '')
+    )
+    console.log(`${indexFilePath} succesfully generated!`)
   }
 }
 

+ 73 - 43
content-directory-schemas/src/helpers/InputParser.ts

@@ -1,7 +1,7 @@
 import { AddClassSchema, Property } from '../../types/extrinsics/AddClassSchema'
 import { createType } from '@joystream/types'
-import { blake2AsHex } from '@polkadot/util-crypto'
 import {
+  InputEntityValuesMap,
   ClassId,
   OperationType,
   ParametrizedPropertyValue,
@@ -10,14 +10,19 @@ import {
   EntityId,
   Entity,
   ParametrizedClassPropertyValue,
+  InputPropertyValue,
 } from '@joystream/types/content-directory'
+import { blake2AsHex } from '@polkadot/util-crypto'
 import { isSingle, isReference } from './propertyType'
 import { ApiPromise } from '@polkadot/api'
 import { JoyBTreeSet } from '@joystream/types/common'
 import { CreateClass } from '../../types/extrinsics/CreateClass'
 import { EntityBatch } from '../../types/EntityBatch'
 import { getInputs } from './inputs'
-import { SubmittableExtrinsic } from '@polkadot/api/types'
+
+type SimpleEntityValue = string | boolean | number | string[] | boolean[] | number[] | undefined
+// Input without "new" or "extising" keywords
+type SimpleEntityInput = { [K: string]: SimpleEntityValue }
 
 export class InputParser {
   private api: ApiPromise
@@ -31,8 +36,6 @@ export class InputParser {
   private entityIdByUniqueQueryMap = new Map<string, number>()
   private entityByUniqueQueryCurrentIndex = 0
   private classIdByNameMap = new Map<string, number>()
-  private classMapInitialized = false
-  private entityIdByUniqueQueryMapInitialized = false
 
   static createWithInitialInputs(api: ApiPromise): InputParser {
     return new InputParser(
@@ -64,30 +67,28 @@ export class InputParser {
     this.batchInputs = batchInputs || []
   }
 
-  private async initializeClassMap() {
-    if (this.classMapInitialized) {
-      return
-    }
+  private async loadClassMap() {
+    this.classIdByNameMap = new Map<string, number>()
+
     const classEntries = await this.api.query.contentDirectory.classById.entries()
     classEntries.forEach(([key, aClass]) => {
       this.classIdByNameMap.set(aClass.name.toString(), (key.args[0] as ClassId).toNumber())
     })
-    this.classMapInitialized = true
   }
 
-  // Initialize entityIdByUniqueQueryMap with entities fetched from the chain
-  private async initializeEntityIdByUniqueQueryMap() {
-    if (this.entityIdByUniqueQueryMapInitialized) {
-      return
-    }
-
-    await this.initializeClassMap() // Initialize if not yet initialized
+  private async loadEntityIdByUniqueQueryMap() {
+    this.entityIdByUniqueQueryMap = new Map<string, number>()
 
     // Get entity entries
     const entityEntries: [EntityId, Entity][] = (
       await this.api.query.contentDirectory.entityById.entries()
     ).map(([storageKey, entity]) => [storageKey.args[0] as EntityId, entity])
 
+    // Since we use classMap directly we need to make sure it's loaded first
+    if (!this.classIdByNameMap.size) {
+      await this.loadClassMap()
+    }
+
     entityEntries.forEach(([entityId, entity]) => {
       const classId = entity.class_id.toNumber()
       const className = Array.from(this.classIdByNameMap.entries()).find(([, id]) => id === classId)?.[0]
@@ -121,8 +122,6 @@ export class InputParser {
         this.entityIdByUniqueQueryMap.set(hash, entityId.toNumber())
       })
     })
-
-    this.entityIdByUniqueQueryMapInitialized = true
   }
 
   private schemaByClassName(className: string) {
@@ -152,30 +151,40 @@ export class InputParser {
 
   // Seatch for entity by { [uniquePropName]: [uniquePropVal] } on chain
   async findEntityIdByUniqueQuery(uniquePropVal: Record<string, any>, className: string): Promise<number> {
-    await this.initializeEntityIdByUniqueQueryMap()
     const hash = this.getUniqueQueryHash(uniquePropVal, className)
-    const foundId = this.entityIdByUniqueQueryMap.get(hash)
+    let foundId = this.entityIdByUniqueQueryMap.get(hash)
     if (foundId === undefined) {
-      throw new Error(
-        `findEntityIdByUniqueQuery failed for class ${className} and query: ${JSON.stringify(uniquePropVal)}`
-      )
+      // Try to re-load the map and find again
+      await this.loadEntityIdByUniqueQueryMap()
+      foundId = this.entityIdByUniqueQueryMap.get(hash)
+      if (foundId === undefined) {
+        // If still not found - throw
+        throw new Error(
+          `findEntityIdByUniqueQuery failed for class ${className} and query: ${JSON.stringify(uniquePropVal)}`
+        )
+      }
     }
-
     return foundId
   }
 
-  private getClassIdByName(className: string): number {
-    const classId = this.classIdByNameMap.get(className)
+  async getClassIdByName(className: string): Promise<number> {
+    let classId = this.classIdByNameMap.get(className)
     if (classId === undefined) {
-      throw new Error(`Could not find class id by name: "${className}"!`)
+      // Try to re-load the map
+      await this.loadClassMap()
+      classId = this.classIdByNameMap.get(className)
+      if (classId === undefined) {
+        // If still not found - throw
+        throw new Error(`Could not find class id by name: "${className}"!`)
+      }
     }
     return classId
   }
 
-  private parsePropertyType(propertyType: Property['property_type']): PropertyType {
+  private async parsePropertyType(propertyType: Property['property_type']): Promise<PropertyType> {
     if (isSingle(propertyType) && isReference(propertyType.Single)) {
       const { className, sameOwner } = propertyType.Single.Reference
-      const classId = this.getClassIdByName(className)
+      const classId = await this.getClassIdByName(className)
       return createType('PropertyType', { Single: { Reference: [classId, sameOwner] } })
     }
     // Types other than reference are fully compatible
@@ -224,7 +233,9 @@ export class InputParser {
       let value = customHandler && (await customHandler(schemaProperty, propertyValue))
       if (value === undefined) {
         value = createType('ParametrizedPropertyValue', {
-          InputPropertyValue: this.parsePropertyType(schemaProperty.property_type).toInputPropertyValue(propertyValue),
+          InputPropertyValue: (await this.parsePropertyType(schemaProperty.property_type)).toInputPropertyValue(
+            propertyValue
+          ),
         })
       }
 
@@ -299,7 +310,7 @@ export class InputParser {
     } else {
       // Add operations (createEntity, AddSchemaSupportToEntity)
       const createEntityOperationIndex = this.createEntityOperations.length
-      const classId = this.getClassIdByName(schema.className)
+      const classId = await this.getClassIdByName(schema.className)
       this.createEntityOperations.push(createType('OperationType', { CreateEntity: { class_id: classId } }))
       this.addSchemaToEntityOprations.push(
         createType('OperationType', {
@@ -318,10 +329,7 @@ export class InputParser {
 
   private reset() {
     this.entityIndexByUniqueQueryMap = new Map<string, number>()
-    this.entityIdByUniqueQueryMapInitialized = false
-
     this.classIdByNameMap = new Map<string, number>()
-    this.classMapInitialized = false
 
     this.createEntityOperations = []
     this.addSchemaToEntityOprations = []
@@ -331,7 +339,6 @@ export class InputParser {
   }
 
   public async getEntityBatchOperations() {
-    await this.initializeClassMap()
     // First - create entityUniqueQueryMap to allow referencing any entity at any point
     this.batchInputs.forEach((batch) => {
       const entitySchema = this.schemaByClassName(batch.className)
@@ -356,7 +363,6 @@ export class InputParser {
     className: string,
     entityId: number
   ): Promise<OperationType[]> {
-    await this.initializeClassMap()
     const schema = this.schemaByClassName(className)
     await this.parseEntityInput(input, schema, entityId)
     const operations = [
@@ -370,13 +376,14 @@ export class InputParser {
   }
 
   public async parseAddClassSchemaExtrinsic(inputData: AddClassSchema) {
-    await this.initializeClassMap() // Initialize if not yet initialized
-    const classId = this.getClassIdByName(inputData.className)
-    const newProperties = inputData.newProperties.map((p) => ({
-      ...p,
-      // Parse different format for Reference (and potentially other propTypes in the future)
-      property_type: this.parsePropertyType(p.property_type).toJSON(),
-    }))
+    const classId = await this.getClassIdByName(inputData.className)
+    const newProperties = await Promise.all(
+      inputData.newProperties.map(async (p) => ({
+        ...p,
+        // Parse different format for Reference (and potentially other propTypes in the future)
+        property_type: (await this.parsePropertyType(p.property_type)).toJSON(),
+      }))
+    )
     return this.api.tx.contentDirectory.addClassSchema(
       classId,
       new (JoyBTreeSet(PropertyId))(this.api.registry, inputData.existingProperties),
@@ -401,4 +408,27 @@ export class InputParser {
   public getCreateClassExntrinsics() {
     return this.classInputs.map((data) => this.parseCreateClassExtrinsic(data))
   }
+
+  // Helper parser for "standalone" extrinsics like addSchemaSupportToEntity / updateEntityPropertyValues
+  public async parseToInputEntityValuesMap(
+    inputData: SimpleEntityInput,
+    className: string
+  ): Promise<InputEntityValuesMap> {
+    await this.parseEntityInput(inputData, this.schemaByClassName(className))
+    const inputPropValMap = new Map<PropertyId, InputPropertyValue>()
+
+    const [operation] = this.addSchemaToEntityOprations
+    operation
+      .asType('AddSchemaSupportToEntity')
+      .parametrized_property_values /* First we need to sort by propertyId, since otherwise there will be issues
+      when encoding the BTreeMap (similar to BTreeSet) */
+      .sort((a, b) => a.in_class_index.toNumber() - b.in_class_index.toNumber())
+      .map((pcpv) => {
+        inputPropValMap.set(pcpv.in_class_index, pcpv.value.asType('InputPropertyValue'))
+      })
+
+    this.reset()
+
+    return createType('InputEntityValuesMap', inputPropValMap)
+  }
 }

+ 32 - 8
content-directory-schemas/src/helpers/extrinsics.ts

@@ -2,6 +2,10 @@ import { Keyring } from '@polkadot/keyring'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { ApiPromise } from '@polkadot/api'
 import { SubmittableExtrinsic } from '@polkadot/api/types'
+import { DispatchError } from '@polkadot/types/interfaces/system'
+import { TypeRegistry } from '@polkadot/types'
+
+// TODO: Move to @joystream/js soon
 
 export function getAlicePair() {
   const keyring = new Keyring({ type: 'sr25519' })
@@ -34,17 +38,37 @@ export class ExtrinsicsHelper {
       promises.push(
         new Promise((resolve, reject) => {
           tx.signAndSend(sender, { nonce }, (result) => {
+            let txError: string | null = null
             if (result.isError) {
-              reject(new Error(errorMessage))
+              txError = `Transaction failed with status: ${result.status.type}`
+              reject(new Error(`${errorMessage} - ${txError}`))
             }
+
             if (result.status.isInBlock) {
-              if (
-                result.events.some(({ event }) => event.section === 'system' && event.method === 'ExtrinsicSuccess')
-              ) {
-                resolve()
-              } else {
-                reject(new Error(errorMessage))
-              }
+              result.events
+                .filter(({ event }) => event.section === 'system')
+                .forEach(({ event }) => {
+                  if (event.method === 'ExtrinsicFailed') {
+                    const dispatchError = event.data[0] as DispatchError
+                    let errorMsg = dispatchError.toString()
+                    if (dispatchError.isModule) {
+                      try {
+                        // Need to assert that registry is of TypeRegistry type, since Registry intefrace
+                        // seems outdated and doesn't include DispatchErrorModule as possible argument for "findMetaError"
+                        const { name, documentation } = (this.api.registry as TypeRegistry).findMetaError(
+                          dispatchError.asModule
+                        )
+                        errorMsg = `${name} (${documentation})`
+                      } catch (e) {
+                        // This probably means we don't have this error in the metadata
+                        // In this case - continue (we'll just display dispatchError.toString())
+                      }
+                    }
+                    reject(new Error(`${errorMessage} - Extrinsic execution error: ${errorMsg}`))
+                  } else if (event.method === 'ExtrinsicSuccess') {
+                    resolve()
+                  }
+                })
             }
           })
         })

+ 1 - 0
content-directory-schemas/src/index.ts

@@ -3,3 +3,4 @@ export { InputParser } from './helpers/InputParser'
 export { getInputs, getInputsLocation } from './helpers/inputs'
 export { isReference, isSingle } from './helpers/propertyType'
 export { getSchemasLocation } from './helpers/schemas'
+export { default as initializeContentDir } from './helpers/initialize'

+ 1 - 1
content-directory-schemas/tsconfig.json

@@ -23,5 +23,5 @@
       "@polkadot/api/augment": ["../types/augment-codec/augment-api.ts"]
     }
   },
-  "include": [ "src/**/*", "scripts/**/*", "typings/**/*" ]
+  "include": [ "src/**/*", "scripts/**/*", "typings/**/*", "examples/**/*" ]
 }

+ 59 - 0
query-node/.env

@@ -0,0 +1,59 @@
+# Project name
+PROJECT_NAME=query_node
+
+###########################
+#     Common settings     #
+###########################
+
+# The env variables below are by default used by all services and should be 
+# overriden in local env files (e.g. ./generated/indexer) if needed
+# DB config
+DB_NAME=query_node
+DB_USER=postgres
+DB_PASS=postgres
+DB_HOST=localhost
+DB_PORT=5432
+DEBUG=index-builder:*
+TYPEORM_LOGGING=error
+
+###########################
+#    Indexer options      #
+###########################
+
+# Substrate endpoint to source events from
+WS_PROVIDER_ENDPOINT_URI=ws://localhost:9944/
+# Block height to start indexing from.
+# Note, that if there are already some indexed events, this setting is ignored
+BLOCK_HEIGHT=0
+
+# Custom types to register for Substrate API
+# TYPE_REGISTER_PACKAGE_NAME=
+# TYPE_REGISTER_PACKAGE_VERSION=
+# TYPE_REGISTER_FUNCTION=
+
+# Redis cache server
+REDIS_URI=redis://localhost:6379/0
+
+###########################
+#    Processor options    #
+###########################
+
+# Where the mapping scripts are located, relative to ./generated/indexer
+MAPPINGS_LOCATION=../../src
+TYPES_JSON=../../typedefs.json
+
+# Indexer GraphQL API endpoint to fetch indexed events
+INDEXER_ENDPOINT_URL=http://localhost:4100/graphql
+
+# Block height from which the processor starts. Note that if 
+# there are already processed events in the database, this setting is ignored
+BLOCK_HEIGHT=0
+
+###############################
+#    Processor GraphQL API    #
+###############################
+
+GRAPHQL_SERVER_PORT=4002
+GRAPHQL_SERVER_HOST=localhost
+WARTHOG_APP_PORT=4002
+WARTHOG_APP_HOST=localhost

+ 45 - 0
query-node/README.md

@@ -0,0 +1,45 @@
+# query-node
+
+The query-node project contains an input schema (schema.graphql) and mappings for the Joystream `content-directory` runtime module.
+
+## Code generation
+
+We use Hydra-cli to generate a graphql server and a block indexer for joystream chain:
+
+```bash
+$ cd query-node
+$ hydra-cli codegen
+```
+
+After codegen process is done, we must add this lines to the `indexer/tsconfig.json` file. It is required because we are using `joystream/types` for decoding chain data in the mappings:
+
+```json
+{
+  "compilerOptions":
+    ...
+    "baseUrl": ".",
+    "paths": {
+      "@polkadot/types/augment": ["../../node_modules/@joystream/types/augment-codec/augment-types.ts"]
+    }
+}
+```
+
+## Run mapping processor
+
+Before running mappings make sure indexer(`yarn indexer:start`) and indexer-api-server (mappings get the chain data from this graphql server) are both running:
+
+```bash
+yarn processor:start
+```
+
+## Query data
+
+Once processor start to store event data you will be able to query this data from `http://localhost:4002/graphql`.
+
+```graphql
+query {
+  channels {
+    title
+  }
+}
+```

+ 1 - 0
query-node/bootstrap/index.ts

@@ -0,0 +1 @@
+export { bootMembers } from './members';

+ 41 - 0
query-node/bootstrap/members.ts

@@ -0,0 +1,41 @@
+import { Member } from '../generated/graphql-server/src/modules/member/member.model';
+import { DB, getLogger } from '../generated/indexer';
+
+import { ApiPromise } from '@polkadot/api';
+import { Hash } from '@polkadot/types/interfaces';
+import { Option } from '@polkadot/types/codec';
+import type { Profile } from '@joystream/types/lib/members';
+import { Codec } from '@polkadot/types/types';
+
+const logger = getLogger();
+
+export async function bootMembers(api: ApiPromise, db: DB) {
+  let blkHeight: number = process.env.BLOCK_HEIGHT ? parseInt(process.env.BLOCK_HEIGHT) : 0;
+  let blkHash: Hash = await api.rpc.chain.getBlockHash(blkHeight);
+  let ids = await api.query.members.membersCreated.at(blkHash);
+  let num: number = parseInt(ids.toString());
+
+  for (let i = 0; i < num; i++) {
+    let profileOpt = (await api.query.members.memberProfile.at(blkHash, i)) as Option<Profile & Codec>;
+    let profile: Profile | null = profileOpt.unwrapOr(null);
+
+    if (!profile) {
+      continue;
+    }
+
+    let member = new Member();
+    member.memberId = i.toString();
+    member.handle = profile.handle.toString();
+    member.avatarUri = profile.avatar_uri.toString();
+    member.about = profile.about.toString();
+
+    member.rootAccount = Buffer.from(profile.root_account);
+    member.controllerAccount = Buffer.from(profile.controller_account);
+    member.registeredAtBlock = profile.registered_at_block.toString();
+
+    logger.trace(`Saving member: ${JSON.stringify(member, null, 2)}`);
+    await db.save<Member>(member);
+    logger.info(`Saved members: ${i}/${num}`);
+  }
+  logger.info(`Done bootstrapping members!`);
+}

+ 1827 - 0
query-node/bootstrap/package-lock.json

@@ -0,0 +1,1827 @@
+{
+  "name": "mappings",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@babel/runtime": {
+      "version": "7.9.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz",
+      "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==",
+      "requires": {
+        "regenerator-runtime": "^0.13.4"
+      }
+    },
+    "@joystream/types": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/@joystream/types/-/types-0.8.0.tgz",
+      "integrity": "sha512-Gw9Igc7Q9TnpPal1JUBv9bWy6+wpRxK8waGxnI53lYhC5F6PH7KGoRb+VlxnqNdk1lIgcl+kHc4zAYvXJD5TBg==",
+      "requires": {
+        "@polkadot/types": "^0.96.1",
+        "@types/vfile": "^4.0.0",
+        "ajv": "^6.11.0"
+      },
+      "dependencies": {
+        "@polkadot/types": {
+          "version": "0.96.1",
+          "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-0.96.1.tgz",
+          "integrity": "sha512-b8AZBNmMjB0+34Oxue3AYc0gIjDHYCdVGtDpel0omHkLMcEquSvrCniLm+p7g4cfArICiZPFmS9In/OWWdRUVA==",
+          "requires": {
+            "@babel/runtime": "^7.7.1",
+            "@polkadot/util": "^1.7.0-beta.5",
+            "@polkadot/util-crypto": "^1.7.0-beta.5",
+            "@types/memoizee": "^0.4.3",
+            "memoizee": "^0.4.14"
+          }
+        },
+        "@polkadot/util": {
+          "version": "1.8.1",
+          "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-1.8.1.tgz",
+          "integrity": "sha512-sFpr+JLCG9d+epjboXsmJ1qcKa96r8ZYzXmVo8+aPzI/9jKKyez6Unox/dnfnpKppZB2nJuLcsxQm6nocp2Caw==",
+          "requires": {
+            "@babel/runtime": "^7.7.7",
+            "@types/bn.js": "^4.11.6",
+            "bn.js": "^4.11.8",
+            "camelcase": "^5.3.1",
+            "chalk": "^3.0.0",
+            "ip-regex": "^4.1.0",
+            "moment": "^2.24.0"
+          }
+        },
+        "@polkadot/util-crypto": {
+          "version": "1.8.1",
+          "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-1.8.1.tgz",
+          "integrity": "sha512-ypUs10hV1HPvYc0ZsEu+LTGSEh0rkr0as/FUh7+Z9v3Bxibn3aO+EOxJPQuDbZZ59FSMRmc9SeOSa0wn9ddrnw==",
+          "requires": {
+            "@babel/runtime": "^7.7.7",
+            "@polkadot/util": "^1.8.1",
+            "@polkadot/wasm-crypto": "^0.14.1",
+            "@types/bip39": "^2.4.2",
+            "@types/bs58": "^4.0.0",
+            "@types/pbkdf2": "^3.0.0",
+            "@types/secp256k1": "^3.5.0",
+            "@types/xxhashjs": "^0.2.1",
+            "base-x": "3.0.5",
+            "bip39": "^2.5.0",
+            "blakejs": "^1.1.0",
+            "bs58": "^4.0.1",
+            "js-sha3": "^0.8.0",
+            "secp256k1": "^3.8.0",
+            "tweetnacl": "^1.0.1",
+            "xxhashjs": "^0.2.2"
+          }
+        },
+        "@polkadot/wasm-crypto": {
+          "version": "0.14.1",
+          "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-0.14.1.tgz",
+          "integrity": "sha512-Xng7L2Z8TNZa/5g6pot4O06Jf0ohQRZdvfl8eQL+E/L2mcqJYC1IjkMxJBSBuQEV7hisWzh9mHOy5WCcgPk29Q=="
+        },
+        "base-x": {
+          "version": "3.0.5",
+          "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz",
+          "integrity": "sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA==",
+          "requires": {
+            "safe-buffer": "^5.0.1"
+          }
+        },
+        "bip39": {
+          "version": "2.6.0",
+          "resolved": "https://registry.npmjs.org/bip39/-/bip39-2.6.0.tgz",
+          "integrity": "sha512-RrnQRG2EgEoqO24ea+Q/fftuPUZLmrEM3qNhhGsA3PbaXaCW791LTzPuVyx/VprXQcTbPJ3K3UeTna8ZnVl2sg==",
+          "requires": {
+            "create-hash": "^1.1.0",
+            "pbkdf2": "^3.0.9",
+            "randombytes": "^2.0.1",
+            "safe-buffer": "^5.0.1",
+            "unorm": "^1.3.3"
+          }
+        },
+        "bn.js": {
+          "version": "4.11.8",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+          "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+        },
+        "chalk": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        }
+      }
+    },
+    "@polkadot/api": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-1.14.1.tgz",
+      "integrity": "sha512-8HKrDp8khcy41jaEcTbelLircEhT+tCGc0yX9rSij2N4oulpxdY8fWWaF2ipVr2GKpE2t6fjBPiZcVMCDwpu9g==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/api-derive": "1.14.1",
+        "@polkadot/keyring": "^2.10.1",
+        "@polkadot/metadata": "1.14.1",
+        "@polkadot/rpc-core": "1.14.1",
+        "@polkadot/rpc-provider": "1.14.1",
+        "@polkadot/types": "1.14.1",
+        "@polkadot/types-known": "1.14.1",
+        "@polkadot/util": "^2.10.1",
+        "@polkadot/util-crypto": "^2.10.1",
+        "bn.js": "^5.1.1",
+        "eventemitter3": "^4.0.1",
+        "rxjs": "^6.5.5"
+      }
+    },
+    "@polkadot/api-derive": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-1.14.1.tgz",
+      "integrity": "sha512-Qt+ijb+9WhDvBQM1ZJPSJKMchILY4/9pgGAMUcWZf9iC2nR9tOE8OG+OeFdzno43jspuZ8qUrkg5vapfZ/5/gQ==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/api": "1.14.1",
+        "@polkadot/rpc-core": "1.14.1",
+        "@polkadot/rpc-provider": "1.14.1",
+        "@polkadot/types": "1.14.1",
+        "@polkadot/util": "^2.10.1",
+        "@polkadot/util-crypto": "^2.10.1",
+        "bn.js": "^5.1.1",
+        "memoizee": "^0.4.14",
+        "rxjs": "^6.5.5"
+      }
+    },
+    "@polkadot/keyring": {
+      "version": "2.10.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-2.10.1.tgz",
+      "integrity": "sha512-6Wbft7MtxbnWaHZpvg3yT8l4oQNp5xTwbqVkdaRfXmPsmhJ1YJcprFWLuKsWZE4x59cYyK7eKhnKcAvFny4HTQ==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/util": "2.10.1",
+        "@polkadot/util-crypto": "2.10.1"
+      }
+    },
+    "@polkadot/metadata": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/metadata/-/metadata-1.14.1.tgz",
+      "integrity": "sha512-nehxg81vcjSay5ScIwASNzM6Li59M0BR3g57hVkkj4uAbvu4bOEZ92dnQuGKbfzMmEQtvLfDdw/iDPqzCgxyGg==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/types": "1.14.1",
+        "@polkadot/types-known": "1.14.1",
+        "@polkadot/util": "^2.10.1",
+        "@polkadot/util-crypto": "^2.10.1",
+        "bn.js": "^5.1.1"
+      }
+    },
+    "@polkadot/rpc-core": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-1.14.1.tgz",
+      "integrity": "sha512-GxZlnxO4ocwaMKdfHgAc7/fvH5nLqZ1AdR9xKkqyR2t3MVjQozB2NeJi99mmZ093AhYHKvGmaBtu38CVTO8Sxg==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/metadata": "1.14.1",
+        "@polkadot/rpc-provider": "1.14.1",
+        "@polkadot/types": "1.14.1",
+        "@polkadot/util": "^2.10.1",
+        "memoizee": "^0.4.14",
+        "rxjs": "^6.5.5"
+      }
+    },
+    "@polkadot/rpc-provider": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-1.14.1.tgz",
+      "integrity": "sha512-bp1EVLYVculRwcxKGlNxi1CWdsCEal9rtwMryOu5jw637Lpm23N1xq5vrX/pVQB2DzytvmuBJeUsf7DdgHEVAg==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/metadata": "1.14.1",
+        "@polkadot/types": "1.14.1",
+        "@polkadot/util": "^2.10.1",
+        "@polkadot/util-crypto": "^2.10.1",
+        "bn.js": "^5.1.1",
+        "eventemitter3": "^4.0.1",
+        "isomorphic-fetch": "^2.2.1",
+        "websocket": "^1.0.31"
+      }
+    },
+    "@polkadot/types": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-1.14.1.tgz",
+      "integrity": "sha512-Q3JIlAXMVWNGjsdWG0xnhone/1uj7R3vWFjft+cweNs40/tUalY6AbyBZ29XTU8WPEmmUspprQ5YmujhHtZg8Q==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/metadata": "1.14.1",
+        "@polkadot/util": "^2.10.1",
+        "@polkadot/util-crypto": "^2.10.1",
+        "@types/bn.js": "^4.11.6",
+        "bn.js": "^5.1.1",
+        "memoizee": "^0.4.14",
+        "rxjs": "^6.5.5"
+      }
+    },
+    "@polkadot/types-known": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-1.14.1.tgz",
+      "integrity": "sha512-fqde3QavX1z+xIra1D6cObf36ATbK5rCcwG2vQU3YXV3NIHYIqQ6CO79TaybKsxx+Sv7ygy4j13G2rSdLqSuXQ==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/types": "1.14.1",
+        "@polkadot/util": "^2.10.1",
+        "bn.js": "^5.1.1"
+      }
+    },
+    "@polkadot/util": {
+      "version": "2.10.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-2.10.1.tgz",
+      "integrity": "sha512-DaIvvx3zphDlf3ZywLnlrRTngcjGIl7Dn3lbwsgHlMSyENz07TG6YG+ztr0ztUrb9BqFKAeH6XGNtGPBp0LxwA==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@types/bn.js": "^4.11.6",
+        "bn.js": "^5.1.1",
+        "camelcase": "^5.3.1",
+        "chalk": "^4.0.0",
+        "ip-regex": "^4.1.0"
+      }
+    },
+    "@polkadot/util-crypto": {
+      "version": "2.10.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-2.10.1.tgz",
+      "integrity": "sha512-sxJZwi5CWfOrytVGtvMT5gn7+rrdgCECtmiG94AouyzdCIWqr9DC+BbX95q7Rja8+kLwkm08FWAsI5pwN9oizQ==",
+      "requires": {
+        "@babel/runtime": "^7.9.6",
+        "@polkadot/util": "2.10.1",
+        "@polkadot/wasm-crypto": "^1.2.1",
+        "base-x": "^3.0.8",
+        "bip39": "^3.0.2",
+        "blakejs": "^1.1.0",
+        "bn.js": "^5.1.1",
+        "bs58": "^4.0.1",
+        "elliptic": "^6.5.2",
+        "js-sha3": "^0.8.0",
+        "pbkdf2": "^3.0.17",
+        "tweetnacl": "^1.0.3",
+        "xxhashjs": "^0.2.2"
+      }
+    },
+    "@polkadot/wasm-crypto": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-1.2.1.tgz",
+      "integrity": "sha512-nckIoZBV4nBZdeKwFwH5t7skS7L7GO5EFUl5B1F6uCjUfdNpDz3DtqbYQHcLdCZNmG4TDLg6w/1J+rkl2SiUZw=="
+    },
+    "@types/bip39": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/@types/bip39/-/bip39-2.4.2.tgz",
+      "integrity": "sha512-Vo9lqOIRq8uoIzEVrV87ZvcIM0PN9t0K3oYZ/CS61fIYKCBdOIM7mlWzXuRvSXrDtVa1uUO2w1cdfufxTC0bzg==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/bn.js": {
+      "version": "4.11.6",
+      "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
+      "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/bs58": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz",
+      "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==",
+      "requires": {
+        "base-x": "^3.0.6"
+      }
+    },
+    "@types/color-name": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+      "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
+    },
+    "@types/memoizee": {
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/@types/memoizee/-/memoizee-0.4.4.tgz",
+      "integrity": "sha512-c9+1g6+6vEqcw5UuM0RbfQV0mssmZcoG9+hNC5ptDCsv4G+XJW1Z4pE13wV5zbc9e0+YrDydALBTiD3nWG1a3g=="
+    },
+    "@types/node": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.1.tgz",
+      "integrity": "sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA=="
+    },
+    "@types/pbkdf2": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.0.0.tgz",
+      "integrity": "sha512-6J6MHaAlBJC/eVMy9jOwj9oHaprfutukfW/Dyt0NEnpQ/6HN6YQrpvLwzWdWDeWZIdenjGHlbYDzyEODO5Z+2Q==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/secp256k1": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-3.5.3.tgz",
+      "integrity": "sha512-NGcsPDR0P+Q71O63e2ayshmiZGAwCOa/cLJzOIuhOiDvmbvrCIiVtEpqdCJGogG92Bnr6tw/6lqVBsRMEl15OQ==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/unist": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
+      "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ=="
+    },
+    "@types/vfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-4.0.0.tgz",
+      "integrity": "sha512-eleP0/Cz8uVWxARDLi3Axq2+fDdN4ibAXoC6Pv8p6s7znXaUL7XvhgeIhjCiNMnvlLNP+tmCLd+RuCryGgmtEg==",
+      "requires": {
+        "vfile": "*"
+      }
+    },
+    "@types/xxhashjs": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/@types/xxhashjs/-/xxhashjs-0.2.1.tgz",
+      "integrity": "sha512-Akm13wkwsQylVnBokl/aiKLtSxndSjfgTjdvmSxXNehYy4NymwdfdJHwGhpV54wcYfmOByOp3ak8AGdUlvp0sA==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "ajv": {
+      "version": "6.12.2",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+      "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-regex": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+      "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
+    },
+    "ansi-styles": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+      "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+      "requires": {
+        "@types/color-name": "^1.1.1",
+        "color-convert": "^2.0.1"
+      }
+    },
+    "any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
+    },
+    "app-root-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz",
+      "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw=="
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "base-x": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
+      "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "base64-js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
+    },
+    "bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "requires": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
+    "bip39": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz",
+      "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==",
+      "requires": {
+        "@types/node": "11.11.6",
+        "create-hash": "^1.1.0",
+        "pbkdf2": "^3.0.9",
+        "randombytes": "^2.0.1"
+      },
+      "dependencies": {
+        "@types/node": {
+          "version": "11.11.6",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz",
+          "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ=="
+        }
+      }
+    },
+    "bip66": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
+      "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "blakejs": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz",
+      "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U="
+    },
+    "bn.js": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz",
+      "integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA=="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "brorand": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
+    },
+    "browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "requires": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "bs58": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
+      "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=",
+      "requires": {
+        "base-x": "^3.0.2"
+      }
+    },
+    "buffer": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
+      "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4"
+      }
+    },
+    "buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+    },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+    },
+    "chalk": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
+      "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
+      "requires": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      }
+    },
+    "cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "cli-highlight": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.4.tgz",
+      "integrity": "sha512-s7Zofobm20qriqDoU9sXptQx0t2R9PEgac92mENNm7xaEe1hn71IIMsXMK+6encA6WRCWWxIGQbipr3q998tlQ==",
+      "requires": {
+        "chalk": "^3.0.0",
+        "highlight.js": "^9.6.0",
+        "mz": "^2.4.0",
+        "parse5": "^5.1.1",
+        "parse5-htmlparser2-tree-adapter": "^5.1.1",
+        "yargs": "^15.0.0"
+      },
+      "dependencies": {
+        "chalk": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "yargs": {
+          "version": "15.3.1",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
+          "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==",
+          "requires": {
+            "cliui": "^6.0.0",
+            "decamelize": "^1.2.0",
+            "find-up": "^4.1.0",
+            "get-caller-file": "^2.0.1",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^2.0.0",
+            "set-blocking": "^2.0.0",
+            "string-width": "^4.2.0",
+            "which-module": "^2.0.0",
+            "y18n": "^4.0.0",
+            "yargs-parser": "^18.1.1"
+          }
+        }
+      }
+    },
+    "cliui": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "requires": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^6.2.0"
+      }
+    },
+    "color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "requires": {
+        "color-name": "~1.1.4"
+      }
+    },
+    "color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "requires": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "cuint": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
+      "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs="
+    },
+    "d": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+      "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+      "requires": {
+        "es5-ext": "^0.10.50",
+        "type": "^1.0.1"
+      }
+    },
+    "date-format": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz",
+      "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w=="
+    },
+    "debug": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+      "requires": {
+        "ms": "^2.1.1"
+      }
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+    },
+    "dotenv": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz",
+      "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w=="
+    },
+    "drbg.js": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
+      "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=",
+      "requires": {
+        "browserify-aes": "^1.0.6",
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4"
+      }
+    },
+    "elliptic": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
+      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
+      "requires": {
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.8",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+          "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+        }
+      }
+    },
+    "emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+    },
+    "encoding": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+      "requires": {
+        "iconv-lite": "~0.4.13"
+      }
+    },
+    "es5-ext": {
+      "version": "0.10.53",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
+      "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
+      "requires": {
+        "es6-iterator": "~2.0.3",
+        "es6-symbol": "~3.1.3",
+        "next-tick": "~1.0.0"
+      },
+      "dependencies": {
+        "next-tick": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+          "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
+        }
+      }
+    },
+    "es6-iterator": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+      "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
+      "requires": {
+        "d": "1",
+        "es5-ext": "^0.10.35",
+        "es6-symbol": "^3.1.1"
+      }
+    },
+    "es6-symbol": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
+      "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
+      "requires": {
+        "d": "^1.0.1",
+        "ext": "^1.1.2"
+      }
+    },
+    "es6-weak-map": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
+      "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
+      "requires": {
+        "d": "1",
+        "es5-ext": "^0.10.46",
+        "es6-iterator": "^2.0.3",
+        "es6-symbol": "^3.1.1"
+      }
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+    },
+    "event-emitter": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+      "requires": {
+        "d": "1",
+        "es5-ext": "~0.10.14"
+      }
+    },
+    "eventemitter3": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
+      "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ=="
+    },
+    "evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "requires": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "ext": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
+      "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
+      "requires": {
+        "type": "^2.0.0"
+      },
+      "dependencies": {
+        "type": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
+          "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow=="
+        }
+      }
+    },
+    "fast-deep-equal": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+    },
+    "figlet": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.4.0.tgz",
+      "integrity": "sha512-CxxIjEKHlqGosgXaIA+sikGDdV6KZOOlzPJnYuPgQlOSHZP5h9WIghYI30fyXnwEVeSH7Hedy72gC6zJrFC+SQ=="
+    },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
+    },
+    "find-up": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+      "requires": {
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
+      }
+    },
+    "flatted": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+      "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA=="
+    },
+    "fs-extra": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+      "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+      "requires": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
+    },
+    "glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+      "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
+    },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+        }
+      }
+    },
+    "has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+    },
+    "hash-base": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+      "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+      "requires": {
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.6.0",
+        "safe-buffer": "^5.2.0"
+      }
+    },
+    "hash.js": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "highlight.js": {
+      "version": "9.18.1",
+      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz",
+      "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg=="
+    },
+    "hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "requires": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ieee754": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ip-regex": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.1.0.tgz",
+      "integrity": "sha512-pKnZpbgCTfH/1NLIlOduP/V+WRXzC2MOz3Qo8xmxk8C5GudJLgK5QyLVXOSWy3ParAH7Eemurl3xjv/WXYFvMA=="
+    },
+    "is-buffer": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+      "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
+    },
+    "is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+    },
+    "is-promise": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+      "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "isomorphic-fetch": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+      "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+      "requires": {
+        "node-fetch": "^1.0.1",
+        "whatwg-fetch": ">=0.10.0"
+      }
+    },
+    "js-sha3": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+      "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
+    },
+    "js-yaml": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "locate-path": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+      "requires": {
+        "p-locate": "^4.1.0"
+      }
+    },
+    "log4js": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.2.1.tgz",
+      "integrity": "sha512-7n+Oqxxz7VcQJhIlqhcYZBTpbcQ7XsR0MUIfJkx/n3VUjkAS4iUr+4UJlhxf28RvP9PMGQXbgTUhLApnu0XXgA==",
+      "requires": {
+        "date-format": "^3.0.0",
+        "debug": "^4.1.1",
+        "flatted": "^2.0.1",
+        "rfdc": "^1.1.4",
+        "streamroller": "^2.2.4"
+      }
+    },
+    "lru-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
+      "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
+      "requires": {
+        "es5-ext": "~0.10.2"
+      }
+    },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "memoizee": {
+      "version": "0.4.14",
+      "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz",
+      "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==",
+      "requires": {
+        "d": "1",
+        "es5-ext": "^0.10.45",
+        "es6-weak-map": "^2.0.2",
+        "event-emitter": "^0.3.5",
+        "is-promise": "^2.1",
+        "lru-queue": "0.1",
+        "next-tick": "1",
+        "timers-ext": "^0.1.5"
+      }
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+    },
+    "minimalistic-crypto-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "moment": {
+      "version": "2.25.3",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz",
+      "integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg=="
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+    },
+    "mz": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "requires": {
+        "any-promise": "^1.0.0",
+        "object-assign": "^4.0.1",
+        "thenify-all": "^1.0.0"
+      }
+    },
+    "nan": {
+      "version": "2.14.1",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
+      "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
+    },
+    "next-tick": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
+      "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
+    },
+    "node-fetch": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+      "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+      "requires": {
+        "encoding": "^0.1.11",
+        "is-stream": "^1.0.1"
+      }
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "requires": {
+        "p-try": "^2.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+      "requires": {
+        "p-limit": "^2.2.0"
+      }
+    },
+    "p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+    },
+    "parent-require": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz",
+      "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc="
+    },
+    "parse5": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+      "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="
+    },
+    "parse5-htmlparser2-tree-adapter": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-5.1.1.tgz",
+      "integrity": "sha512-CF+TKjXqoqyDwHqBhFQ+3l5t83xYi6fVT1tQNg+Ye0JRLnTxWvIroCjEp1A0k4lneHNBGnICUf0cfYVYGEazqw==",
+      "requires": {
+        "parse5": "^5.1.1"
+      }
+    },
+    "path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "pbkdf2": {
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "requires": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "readable-stream": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+      "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      }
+    },
+    "reflect-metadata": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
+      "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
+    },
+    "regenerator-runtime": {
+      "version": "0.13.5",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
+      "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
+    },
+    "replace-ext": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
+      "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs="
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
+    },
+    "require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+    },
+    "rfdc": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
+      "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug=="
+    },
+    "ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "rxjs": {
+      "version": "6.5.5",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
+      "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
+    "secp256k1": {
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.0.tgz",
+      "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==",
+      "requires": {
+        "bindings": "^1.5.0",
+        "bip66": "^1.1.5",
+        "bn.js": "^4.11.8",
+        "create-hash": "^1.2.0",
+        "drbg.js": "^1.0.1",
+        "elliptic": "^6.5.2",
+        "nan": "^2.14.0",
+        "safe-buffer": "^5.1.2"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.8",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+          "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+        }
+      }
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+    },
+    "sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
+    "streamroller": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz",
+      "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==",
+      "requires": {
+        "date-format": "^2.1.0",
+        "debug": "^4.1.1",
+        "fs-extra": "^8.1.0"
+      },
+      "dependencies": {
+        "date-format": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
+          "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA=="
+        }
+      }
+    },
+    "string-width": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+      "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+      "requires": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.0"
+      }
+    },
+    "string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "requires": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+      "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+      "requires": {
+        "ansi-regex": "^5.0.0"
+      }
+    },
+    "supports-color": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+      "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+      "requires": {
+        "has-flag": "^4.0.0"
+      }
+    },
+    "thenify": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
+      "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
+      "requires": {
+        "any-promise": "^1.0.0"
+      }
+    },
+    "thenify-all": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+      "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
+      "requires": {
+        "thenify": ">= 3.1.0 < 4"
+      }
+    },
+    "timers-ext": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
+      "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
+      "requires": {
+        "es5-ext": "~0.10.46",
+        "next-tick": "1"
+      }
+    },
+    "tslib": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
+      "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
+    },
+    "tweetnacl": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
+      "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
+    },
+    "type": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+      "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
+    },
+    "typedarray-to-buffer": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+      "requires": {
+        "is-typedarray": "^1.0.0"
+      }
+    },
+    "typeorm": {
+      "version": "0.2.24",
+      "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.24.tgz",
+      "integrity": "sha512-L9tQv6nNLRyh+gex/qc8/CyLs8u0kXKqk1OjYGF13k/KOg6N2oibwkuGgv0FuoTGYx2ta2NmqvuMUAMrHIY5ew==",
+      "requires": {
+        "app-root-path": "^3.0.0",
+        "buffer": "^5.1.0",
+        "chalk": "^2.4.2",
+        "cli-highlight": "^2.0.0",
+        "debug": "^4.1.1",
+        "dotenv": "^6.2.0",
+        "glob": "^7.1.2",
+        "js-yaml": "^3.13.1",
+        "mkdirp": "^0.5.1",
+        "reflect-metadata": "^0.1.13",
+        "sha.js": "^2.4.11",
+        "tslib": "^1.9.0",
+        "xml2js": "^0.4.17",
+        "yargonaut": "^1.1.2",
+        "yargs": "^13.2.1"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "chalk": {
+          "version": "2.4.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        },
+        "color-convert": {
+          "version": "1.9.3",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+        },
+        "has-flag": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+        },
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
+    "unist-util-stringify-position": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+      "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+      "requires": {
+        "@types/unist": "^2.0.2"
+      }
+    },
+    "universalify": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+    },
+    "unorm": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz",
+      "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA=="
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "vfile": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.0.tgz",
+      "integrity": "sha512-BaTPalregj++64xbGK6uIlsurN3BCRNM/P2Pg8HezlGzKd1O9PrwIac6bd9Pdx2uTb0QHoioZ+rXKolbVXEgJg==",
+      "requires": {
+        "@types/unist": "^2.0.0",
+        "is-buffer": "^2.0.0",
+        "replace-ext": "1.0.0",
+        "unist-util-stringify-position": "^2.0.0",
+        "vfile-message": "^2.0.0"
+      }
+    },
+    "vfile-message": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
+      "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
+      "requires": {
+        "@types/unist": "^2.0.0",
+        "unist-util-stringify-position": "^2.0.0"
+      }
+    },
+    "websocket": {
+      "version": "1.0.31",
+      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.31.tgz",
+      "integrity": "sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ==",
+      "requires": {
+        "debug": "^2.2.0",
+        "es5-ext": "^0.10.50",
+        "nan": "^2.14.0",
+        "typedarray-to-buffer": "^3.1.5",
+        "yaeti": "^0.0.6"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "whatwg-fetch": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
+      "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
+    },
+    "which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+    },
+    "wrap-ansi": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+      "requires": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "xml2js": {
+      "version": "0.4.23",
+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+      "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+      "requires": {
+        "sax": ">=0.6.0",
+        "xmlbuilder": "~11.0.0"
+      }
+    },
+    "xmlbuilder": {
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
+    },
+    "xxhashjs": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
+      "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
+      "requires": {
+        "cuint": "^0.2.2"
+      }
+    },
+    "y18n": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
+    },
+    "yaeti": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
+      "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
+    },
+    "yargonaut": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz",
+      "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==",
+      "requires": {
+        "chalk": "^1.1.1",
+        "figlet": "^1.1.1",
+        "parent-require": "^1.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+        }
+      }
+    },
+    "yargs": {
+      "version": "13.3.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+      "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+      "requires": {
+        "cliui": "^5.0.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^3.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^13.1.2"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+        },
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "cliui": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+          "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+          "requires": {
+            "string-width": "^3.1.0",
+            "strip-ansi": "^5.2.0",
+            "wrap-ansi": "^5.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "1.9.3",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+        },
+        "emoji-regex": {
+          "version": "7.0.3",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+          "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
+        },
+        "find-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+          "requires": {
+            "locate-path": "^3.0.0"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+        },
+        "locate-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+          "requires": {
+            "p-locate": "^3.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+          "requires": {
+            "p-limit": "^2.0.0"
+          }
+        },
+        "path-exists": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        },
+        "wrap-ansi": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+          "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+          "requires": {
+            "ansi-styles": "^3.2.0",
+            "string-width": "^3.0.0",
+            "strip-ansi": "^5.0.0"
+          }
+        },
+        "yargs-parser": {
+          "version": "13.1.2",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+          "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
+    "yargs-parser": {
+      "version": "18.1.3",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "requires": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    }
+  }
+}

+ 19 - 0
query-node/bootstrap/package.json

@@ -0,0 +1,19 @@
+{
+  "name": "mappings",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.ts",
+  "scripts": {
+    "build": "tsc --build tsconfig.json",
+    "postinstall": "tsc --build tsconfig.json"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "@joystream/types": "^0.8.0",
+    "@polkadot/api": "^1.14.1",
+    "@polkadot/types": "^1.14.1",
+    "log4js": "^6.2.1",
+    "typeorm": "^0.2.24"
+  }
+}

+ 18 - 0
query-node/bootstrap/tsconfig.json

@@ -0,0 +1,18 @@
+{
+	"compilerOptions": {
+		"declaration": true,
+		"importHelpers": true,
+		"module": "commonjs",
+		"outDir": "lib",
+		"rootDir": "./..",
+		"strict": true,
+		"target": "es2017",
+		"experimentalDecorators": true,
+		"emitDecoratorMetadata": true,
+		"skipLibCheck": true,
+		"sourceMap": true,
+		"inlineSources": false
+	},
+	"include": ["./*.ts"],
+	"exclude": ["node_modules"]
+}

+ 36 - 0
query-node/package.json

@@ -0,0 +1,36 @@
+{
+	"name": "joystream_query_node",
+	"version": "1.0.0",
+	"description": "GraphQL server and Substrate indexer. Generated with ♥ by Hydra-CLI",
+	"scripts": {
+		"build": "tsc --build tsconfig.json",
+		"test": "echo \"Error: no test specified\" && exit 1",
+		"clean": "rm -rf ./generated",
+		"processor:start": "(cd ./generated/indexer && yarn && DEBUG=${DEBUG} yarn start:processor)",
+		"indexer:start": "(cd ./generated/indexer && yarn && DEBUG=${DEBUG} yarn start:indexer)",
+		"server:start:dev": "(cd ./generated/graphql-server && yarn start:dev)",
+		"server:start:prod": "(cd ./generated/graphql-server && yarn start:prod)",
+		"configure": "(cd ./generated/graphql-server && yarn config:dev)",
+		"db:up": "docker-compose up -d db",
+		"db:drop": "(cd ./generated/graphql-server && yarn db:drop)",
+		"db:schema:migrate": "(cd ./generated/graphql-server && yarn db:create && yarn db:sync && yarn db:migrate)",
+		"db:indexer:migrate": "(cd ./generated/indexer && yarn db:migrate)",
+		"db:migrate": "yarn db:schema:migrate && yarn db:indexer:migrate",
+		"codegen:all": "hydra-cli codegen",
+		"codegen:indexer": "hydra-cli codegen --no-graphql",
+		"codegen:server": "hydra-cli codegen --no-indexer",
+		"docker:indexer:build": "docker build -t hydra-indexer -f docker/Dockerfile.indexer .",
+		"docker:server:build": "docker build -t hydra-graphql-server -f docker/Dockerfile.server .",
+		"docker:up": "docker-compose up -d"
+	},
+	"author": "",
+	"license": "ISC",
+	"dependencies": {
+		"@joystream/types": "^0.14.0",
+		"@types/bn.js": "^4.11.6",
+		"@types/debug": "^4.1.5",
+		"bn.js": "^5.1.2",
+		"debug": "^4.2.0",
+		"tslib": "^2.0.0"
+	}
+}

+ 256 - 0
query-node/schema.graphql

@@ -0,0 +1,256 @@
+enum Network {
+  BABYLON
+  ALEXANDRIA
+  ROME
+}
+
+type Block @entity {
+  "Block number as a string"
+  id: ID!
+  block: Int!
+  timestamp: Int!
+  nework: Network!
+}
+
+"Stored information about a registered user"
+type Member @entity {
+  "MemberId: runtime identifier for a user"
+  id: ID!
+
+  "The unique handle chosen by member"
+  handle: String @unique @fulltext(query: "handles")
+
+  "A Url to member's Avatar image"
+  avatarUri: String
+
+  "Short text chosen by member to share information about themselves"
+  about: String
+
+  "Blocknumber when member was registered"
+  registeredAtBlock: Int!
+
+  "Member's controller account id"
+  controllerAccount: Bytes!
+
+  "Member's root account id"
+  rootAccount: Bytes!
+
+  happenedIn: Block!
+}
+
+"""
+This type is to keep which entity belongs to which class. This type will be used
+by EntityCreated event. When a new schema support added to an Entity we will get the
+class name from this table.
+We need this because we can't create a database row (Channel, Video etc) without
+with empty fields.
+"""
+type ClassEntity @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "The class id of this entity"
+  classId: Int!
+
+  happenedIn: Block!
+}
+
+#### High Level Derivative Entities ####
+
+type Language @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  name: String!
+  code: String!
+
+  happenedIn: Block!
+}
+
+type Channel @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  # "Owner of the channel" Commenting out this field: 'owner' can be curator_group, lead
+  # or a member. We are not handling events related to curator group so we will not set this field
+  # owner: Member!
+
+  "The title of the Channel"
+  title: String! @fulltext(query: "titles")
+
+  "The description of a Channel"
+  description: String!
+
+  "Url for Channel's cover (background) photo. Recommended ratio: 16:9."
+  coverPhotoURL: String!
+
+  "Channel's avatar photo."
+  avatarPhotoURL: String!
+
+  "Flag signaling whether a channel is public."
+  isPublic: Boolean!
+
+  "Flag signaling whether a channel is curated/verified."
+  isCurated: Boolean!
+
+  "The primary langauge of the channel's content"
+  languageId: Int
+
+  # videos: [Video!] @derivedFrom(field: "channel")
+
+  happenedIn: Block!
+}
+
+type Category @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "The name of the category"
+  name: String! @unique @fulltext(query: "names")
+
+  "The description of the category"
+  description: String
+
+  # videos: [Video!] @derivedFrom(field: "category")
+
+  happenedIn: Block!
+}
+
+"Encoding and containers"
+type VideoMediaEncoding @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  name: String!
+}
+
+type KnownLicense @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "Short, commonly recognized code of the licence (ie. CC_BY_SA)"
+  code: String! @unique
+
+  "Full, descriptive name of the license (ie. Creative Commons - Attribution-NonCommercial-NoDerivs)"
+  name: String
+
+  "Short description of the license conditions"
+  description: String
+
+  "An url pointing to full license content"
+  url: String
+
+  happenedIn: Block!
+}
+
+type UserDefinedLicense @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "Custom license content"
+  content: String!
+
+  happenedIn: Block!
+}
+
+type JoystreamMediaLocation @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "Id of the data object in the Joystream runtime dataDirectory module"
+  dataObjectId: String! @unique
+
+  happenedIn: Block!
+}
+
+type HttpMediaLocation @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "The http url pointing to the media"
+  url: String!
+
+  "The port to use when connecting to the http url (defaults to 80)"
+  port: Int
+
+  happenedIn: Block!
+}
+
+type VideoMedia @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "Encoding of the video media object"
+  encodingId: Int!
+
+  "Video media width in pixels"
+  pixelWidth: Int!
+
+  "Video media height in pixels"
+  pixelHeight: Int!
+
+  "Video media size in bytes"
+  size: Int
+
+  # video: Video! @derivedFrom(field: "media")
+
+  # One of the location field will be non-null
+
+  # httpMediaLocation: HttpMediaLocation
+  # joystreamMediaLocation: JoystreamMediaLocation
+  locationId: Int!
+
+  happenedIn: Block!
+}
+
+type Video @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  "Reference to member's channel"
+  channelId: Int!
+
+  "Reference to a video category"
+  categoryId: Int!
+
+  "The title of the video"
+  title: String! @fulltext(query: "titles")
+
+  "The description of the Video"
+  description: String!
+
+  "Video duration in seconds"
+  duration: Int!
+
+  "Video's skippable intro duration in seconds"
+  skippableIntroDuration: Int
+
+  "Video thumbnail url (recommended ratio: 16:9)"
+  thumbnailURL: String!
+
+  "Video's main langauge"
+  languageId: Int
+
+  "Reference to VideoMedia"
+  videoMediaId: Int!
+
+  "Whether or not Video contains marketing"
+  hasMarketing: Boolean
+
+  "If the Video was published on other platform before beeing published on Joystream - the original publication date"
+  publishedBeforeJoystream: Int
+
+  "Whether the Video is supposed to be publically displayed"
+  isPublic: Boolean!
+
+  "Video curation status set by the Curator"
+  isCurated: Boolean!
+
+  "Whether the Video contains explicit material."
+  isExplicit: Boolean!
+
+  # Lincense
+  licenseId: Int!
+
+  happenedIn: Block!
+}

+ 102 - 0
query-node/src/content-directory/content-dir-consts.ts

@@ -0,0 +1,102 @@
+import { IPropertyIdWithName } from '../types'
+
+// Content directory predefined class names
+export enum ContentDirectoryKnownClasses {
+  CHANNEL = 'Channel',
+  CATEGORY = 'Category',
+  KNOWNLICENSE = 'KnownLicense',
+  USERDEFINEDLICENSE = 'UserDefinedLicense',
+  JOYSTREAMMEDIALOCATION = 'JoystreamMediaLocation',
+  HTTPMEDIALOCATION = 'HttpMediaLocation',
+  VIDEOMEDIA = 'VideoMedia',
+  VIDEO = 'Video',
+  LANGUAGE = 'Language',
+  VIDEOMEDIAENCODING = 'VideoMediaEncoding',
+}
+
+// Predefined content-directory classes, classId may change after the runtime seeding
+export const contentDirectoryClassNamesWithId: { classId: number; name: string }[] = [
+  { name: ContentDirectoryKnownClasses.CHANNEL, classId: 1 },
+  { name: ContentDirectoryKnownClasses.CATEGORY, classId: 2 },
+  { name: ContentDirectoryKnownClasses.KNOWNLICENSE, classId: 6 },
+  { name: ContentDirectoryKnownClasses.USERDEFINEDLICENSE, classId: 0 },
+  { name: ContentDirectoryKnownClasses.LANGUAGE, classId: 7 },
+  { name: ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION, classId: 5 },
+  { name: ContentDirectoryKnownClasses.HTTPMEDIALOCATION, classId: 4 },
+  { name: ContentDirectoryKnownClasses.VIDEOMEDIA, classId: 12 },
+  { name: ContentDirectoryKnownClasses.VIDEO, classId: 11 },
+  { name: ContentDirectoryKnownClasses.VIDEOMEDIAENCODING, classId: 13 },
+]
+
+export const CategoryPropertyNamesWithId: IPropertyIdWithName = {
+  0: 'name',
+  1: 'description',
+}
+
+export const channelPropertyNamesWithId: IPropertyIdWithName = {
+  0: 'title',
+  1: 'description',
+  2: 'coverPhotoURL',
+  3: 'avatarPhotoURL',
+  4: 'isPublic',
+  5: 'isCurated',
+  6: 'language',
+}
+
+export const knownLicensePropertyNamesWIthId: IPropertyIdWithName = {
+  0: 'code',
+  1: 'name',
+  2: 'description',
+  3: 'url',
+}
+
+export const languagePropertyNamesWIthId: IPropertyIdWithName = {
+  0: 'name',
+  1: 'code',
+}
+
+export const userDefinedLicensePropertyNamesWithId: IPropertyIdWithName = {
+  0: 'content',
+}
+
+export const joystreamMediaLocationPropertyNamesWithId: IPropertyIdWithName = {
+  0: 'dataObjectId',
+}
+
+export const httpMediaLocationPropertyNamesWithId: IPropertyIdWithName = {
+  0: 'url',
+  1: 'port',
+}
+
+export const videoMediaEncodingPropertyNamesWithId: IPropertyIdWithName = {
+  0: 'name',
+}
+
+export const videoMediaPropertyNamesWithId: IPropertyIdWithName = {
+  0: 'encoding',
+  1: 'pixelWidth',
+  2: 'pixelHeight',
+  3: 'size',
+  4: 'location',
+}
+
+export const videoPropertyNamesWithId: IPropertyIdWithName = {
+  // referenced entity's id
+  0: 'channel',
+  // referenced entity's id
+  1: 'category',
+  2: 'title',
+  3: 'description',
+  4: 'duration',
+  5: 'skippableIntroDuration',
+  6: 'thumbnailURL',
+  7: 'language',
+  // referenced entity's id
+  8: 'media',
+  9: 'hasMarketing',
+  10: 'publishedBeforeJoystream',
+  11: 'isPublic',
+  12: 'isExplicit',
+  13: 'license',
+  14: 'isCurated',
+}

+ 145 - 0
query-node/src/content-directory/decode.ts

@@ -0,0 +1,145 @@
+import { SubstrateEvent } from '../../generated/indexer'
+import {
+  IPropertyIdWithName,
+  IClassEntity,
+  IProperty,
+  IBatchOperation,
+  ICreateEntityOperation,
+  IEntity,
+} from '../types'
+
+import { ParametrizedClassPropertyValue, UpdatePropertyValuesOperation } from '@joystream/types/content-directory'
+import { createType } from '@joystream/types'
+
+function stringIfyEntityId(event: SubstrateEvent): string {
+  const { 1: entityId } = event.params
+  return entityId.value as string
+}
+
+function setProperties<T>({ extrinsic, blockNumber }: SubstrateEvent, propNamesWithId: IPropertyIdWithName): T {
+  if (extrinsic === undefined) throw Error('Undefined extrinsic')
+
+  const { 3: newPropertyValues } = extrinsic!.args
+  const properties: { [key: string]: any } = {}
+
+  for (const [k, v] of Object.entries(newPropertyValues.value)) {
+    const propertyName = propNamesWithId[k]
+    const propertyValue = createType('InputPropertyValue', v)
+      .asType('Single')
+      .value.toJSON()
+    properties[propertyName] = propertyValue
+  }
+  properties.version = blockNumber
+  return properties as T
+}
+
+function getClassEntity(event: SubstrateEvent): IClassEntity {
+  const { 0: classId } = event.extrinsic!.args
+  const { 1: entityId } = event.params
+  return {
+    entityId: (entityId.value as unknown) as number,
+    classId: (classId.value as unknown) as number,
+  }
+}
+
+/**
+ * When entity is creation through `transaction` extrinsic we use this function to parse
+ * entity properties it looks quite similar to `setProperties` function
+ * @param properties
+ * @param propertyNamesWithId
+ */
+function setEntityPropertyValues<T>(properties: IProperty[], propertyNamesWithId: IPropertyIdWithName): T {
+  const entityProperties: { [key: string]: any } = {}
+
+  for (const [propId, propName] of Object.entries(propertyNamesWithId)) {
+    // get the property value by id
+    const p = properties.find((p) => p.propertyId === propId)
+    const propertyValue = p ? p.value : undefined
+    entityProperties[propName] = propertyValue
+  }
+  // console.log(entityProperties);
+  return entityProperties as T
+}
+
+// Decode entity property values
+function getEntityProperties(propertyValues: ParametrizedClassPropertyValue[]): IProperty[] {
+  const properties: IProperty[] = []
+  const entityPropertyValues = createType('Vec<ParametrizedClassPropertyValue>', propertyValues)
+
+  entityPropertyValues.map((pv: ParametrizedClassPropertyValue) => {
+    const v = createType('ParametrizedPropertyValue', pv.value)
+    const propertyId = pv.in_class_index.toJSON()
+
+    let value
+    if (v.isOfType('InputPropertyValue')) {
+      const inputPropVal = v.asType('InputPropertyValue')
+      value = inputPropVal.isOfType('Single')
+        ? inputPropVal.asType('Single').value.toJSON()
+        : inputPropVal.asType('Vector').value.toJSON()
+    } else if (v.isOfType('InternalEntityJustAdded')) {
+      // const inputPropVal = v.asType('InternalEntityJustAdded');
+      value = v.asType('InternalEntityJustAdded').toJSON()
+    } else {
+      // TODO: Add support for v.asType('InternalEntityVec')
+      throw Error('InternalEntityVec property type is not supported yet!')
+    }
+    properties.push({ propertyId: `${propertyId}`, value })
+  })
+  return properties
+}
+
+function getOperations({ extrinsic }: SubstrateEvent): IBatchOperation {
+  const operations = createType('Vec<OperationType>', extrinsic!.args[1].value as any)
+
+  const updatePropertyValuesOperations: IEntity[] = []
+  const addSchemaSupportToEntityOperations: IEntity[] = []
+  const createEntityOperations: ICreateEntityOperation[] = []
+
+  for (const operation of operations) {
+    if (operation.isOfType('CreateEntity')) {
+      const cep = operation.asType('CreateEntity')
+      createEntityOperations.push({ classId: cep.class_id.toJSON() })
+    } else if (operation.isOfType('AddSchemaSupportToEntity')) {
+      const op = operation.asType('AddSchemaSupportToEntity')
+      const pe = createType('ParameterizedEntity', op.entity_id)
+      const entity: IEntity = {
+        properties: getEntityProperties(op.parametrized_property_values),
+      }
+      if (pe.isOfType('InternalEntityJustAdded')) {
+        entity.indexOf = pe.asType('InternalEntityJustAdded').toJSON()
+      } else {
+        entity.entityId = pe.asType('ExistingEntity').toJSON()
+      }
+      addSchemaSupportToEntityOperations.push(entity)
+    } else {
+      updatePropertyValuesOperations.push(makeEntity(operation.asType('UpdatePropertyValues')))
+    }
+  }
+  return {
+    updatePropertyValuesOperations,
+    addSchemaSupportToEntityOperations,
+    createEntityOperations,
+  }
+}
+
+function makeEntity(upv: UpdatePropertyValuesOperation): IEntity {
+  const entity: IEntity = {
+    properties: getEntityProperties(upv.new_parametrized_property_values),
+  }
+  const pe = createType('ParameterizedEntity', upv.entity_id)
+  if (pe.isOfType('InternalEntityJustAdded')) {
+    entity.indexOf = pe.asType('InternalEntityJustAdded').toJSON()
+  } else {
+    entity.entityId = pe.asType('ExistingEntity').toJSON()
+  }
+  return entity
+}
+
+export const decode = {
+  stringIfyEntityId,
+  getClassEntity,
+  setEntityPropertyValues,
+  getEntityProperties,
+  getOperations,
+  setProperties,
+}

+ 475 - 0
query-node/src/content-directory/entity-helper.ts

@@ -0,0 +1,475 @@
+import { DB, SubstrateEvent } from '../../generated/indexer'
+import { Channel } from '../../generated/graphql-server/src/modules/channel/channel.model'
+import { Category } from '../../generated/graphql-server/src/modules/category/category.model'
+import { KnownLicense } from '../../generated/graphql-server/src/modules/known-license/known-license.model'
+import { UserDefinedLicense } from '../../generated/graphql-server/src/modules/user-defined-license/user-defined-license.model'
+import { JoystreamMediaLocation } from '../../generated/graphql-server/src/modules/joystream-media-location/joystream-media-location.model'
+import { HttpMediaLocation } from '../../generated/graphql-server/src/modules/http-media-location/http-media-location.model'
+import { VideoMedia } from '../../generated/graphql-server/src/modules/video-media/video-media.model'
+import { Video } from '../../generated/graphql-server/src/modules/video/video.model'
+import { Block, Network } from '../../generated/graphql-server/src/modules/block/block.model'
+import { Language } from '../../generated/graphql-server/src/modules/language/language.model'
+import { VideoMediaEncoding } from '../../generated/graphql-server/src/modules/video-media-encoding/video-media-encoding.model'
+import { ClassEntity } from '../../generated/graphql-server/src/modules/class-entity/class-entity.model'
+import { decode } from './decode'
+import {
+  CategoryPropertyNamesWithId,
+  channelPropertyNamesWithId,
+  httpMediaLocationPropertyNamesWithId,
+  joystreamMediaLocationPropertyNamesWithId,
+  knownLicensePropertyNamesWIthId,
+  languagePropertyNamesWIthId,
+  userDefinedLicensePropertyNamesWithId,
+  videoMediaEncodingPropertyNamesWithId,
+  videoPropertyNamesWithId,
+  contentDirectoryClassNamesWithId,
+  ContentDirectoryKnownClasses,
+} from './content-dir-consts'
+import {
+  ICategory,
+  IChannel,
+  ICreateEntityOperation,
+  IDBBlockId,
+  IEntity,
+  IHttpMediaLocation,
+  IJoystreamMediaLocation,
+  IKnownLicense,
+  ILanguage,
+  IUserDefinedLicense,
+  IVideo,
+  IVideoMedia,
+  IVideoMediaEncoding,
+  IWhereCond,
+} from '../types'
+
+async function createBlockOrGetFromDatabase(db: DB, blockNumber: number): Promise<Block> {
+  let b = await db.get(Block, { where: { block: blockNumber } })
+  if (b === undefined) {
+    // TODO: get timestamp from the event or extrinsic
+    b = new Block({ block: blockNumber, nework: Network.BABYLON, timestamp: 123 })
+    await db.save<Block>(b)
+  }
+  return b
+}
+
+async function createChannel({ db, block, id }: IDBBlockId, p: IChannel): Promise<void> {
+  // const { properties: p } = decode.channelEntity(event);
+  const channel = new Channel()
+
+  channel.version = block
+  channel.id = id
+  channel.title = p.title
+  channel.description = p.description
+  channel.isCurated = p.isCurated || false
+  channel.isPublic = p.isPublic
+  channel.coverPhotoUrl = p.coverPhotoURL
+  channel.avatarPhotoUrl = p.avatarPhotoURL
+  channel.languageId = p.language
+  channel.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(channel)
+}
+
+async function createCategory({ db, block, id }: IDBBlockId, p: ICategory): Promise<void> {
+  // const p = decode.categoryEntity(event);
+  const category = new Category()
+
+  category.id = id
+  category.name = p.name
+  category.description = p.description
+  category.version = block
+  category.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(category)
+}
+
+async function createKnownLicense({ db, block, id }: IDBBlockId, p: IKnownLicense): Promise<void> {
+  const knownLicence = new KnownLicense()
+
+  knownLicence.id = id
+  knownLicence.code = p.code
+  knownLicence.name = p.name
+  knownLicence.description = p.description
+  knownLicence.url = p.url
+  knownLicence.version = block
+  knownLicence.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(knownLicence)
+}
+
+async function createUserDefinedLicense({ db, block, id }: IDBBlockId, p: IUserDefinedLicense): Promise<void> {
+  const userDefinedLicense = new UserDefinedLicense()
+
+  userDefinedLicense.id = id
+  userDefinedLicense.content = p.content
+  userDefinedLicense.version = block
+  userDefinedLicense.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(userDefinedLicense)
+}
+
+async function createJoystreamMediaLocation({ db, block, id }: IDBBlockId, p: IJoystreamMediaLocation): Promise<void> {
+  const joyMediaLoc = new JoystreamMediaLocation()
+
+  joyMediaLoc.id = id
+  joyMediaLoc.dataObjectId = p.dataObjectId
+  joyMediaLoc.version = block
+  joyMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(joyMediaLoc)
+}
+
+async function createHttpMediaLocation({ db, block, id }: IDBBlockId, p: IHttpMediaLocation): Promise<void> {
+  const httpMediaLoc = new HttpMediaLocation()
+
+  httpMediaLoc.id = id
+  httpMediaLoc.url = p.url
+  httpMediaLoc.port = p.port
+  httpMediaLoc.version = block
+  httpMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(httpMediaLoc)
+}
+
+async function createVideoMedia({ db, block, id }: IDBBlockId, p: IVideoMedia): Promise<void> {
+  const videoMedia = new VideoMedia()
+
+  videoMedia.id = id
+  videoMedia.encodingId = p.encoding
+  videoMedia.locationId = p.location
+  videoMedia.pixelHeight = p.pixelHeight
+  videoMedia.pixelWidth = p.pixelWidth
+  videoMedia.size = p.size
+  videoMedia.version = block
+  videoMedia.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(videoMedia)
+}
+
+async function createVideo({ db, block, id }: IDBBlockId, p: IVideo): Promise<void> {
+  const video = new Video()
+
+  video.id = id
+  video.title = p.title
+  video.description = p.description
+  video.categoryId = p.category
+  video.channelId = p.channel
+  video.duration = p.duration
+  video.hasMarketing = p.hasMarketing
+  // TODO: needs to be handled correctly, from runtime CurationStatus is coming
+  video.isCurated = p.isCurated || true
+  video.isExplicit = p.isExplicit
+  video.isPublic = p.isPublic
+  video.languageId = p.language
+  video.licenseId = p.license
+  video.videoMediaId = p.media
+  video.publishedBeforeJoystream = p.publishedBeforeJoystream
+  video.skippableIntroDuration = p.skippableIntroDuration
+  video.thumbnailUrl = p.thumbnailURL
+  video.version = block
+  video.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save<Video>(video)
+}
+
+async function createLanguage({ db, block, id }: IDBBlockId, p: ILanguage): Promise<void> {
+  const language = new Language()
+  language.id = id
+  language.name = p.name
+  language.code = p.code
+  language.version = block
+  language.happenedIn = await createBlockOrGetFromDatabase(db, block)
+
+  await db.save<Language>(language)
+}
+
+async function createVideoMediaEncoding({ db, block, id }: IDBBlockId, p: IVideoMediaEncoding): Promise<void> {
+  const encoding = new VideoMediaEncoding()
+
+  encoding.id = id
+  encoding.name = p.name
+  encoding.version = block
+  encoding.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save<VideoMediaEncoding>(encoding)
+}
+
+async function batchCreateClassEntities(db: DB, block: number, operations: ICreateEntityOperation[]): Promise<void> {
+  // Create entities before adding schema support
+  operations.map(async ({ classId }, index) => {
+    const c = new ClassEntity()
+    c.id = index.toString()
+    c.classId = classId
+    c.version = block
+    c.happenedIn = await createBlockOrGetFromDatabase(db, block)
+    await db.save<ClassEntity>(c)
+  })
+}
+
+async function getClassName(
+  db: DB,
+  entity: IEntity,
+  createEntityOperations: ICreateEntityOperation[]
+): Promise<string | undefined> {
+  const { entityId, indexOf } = entity
+  if (entityId === undefined && indexOf === undefined) {
+    throw Error(`Can not determine class of the entity`)
+  }
+
+  let classId: number | undefined
+  // Is newly created entity in the same transaction
+  if (indexOf !== undefined) {
+    classId = createEntityOperations[indexOf].classId
+  } else {
+    const ce = await db.get(ClassEntity, { where: { id: entityId } })
+    if (ce === undefined) console.log(`Class not found for the entity: ${entityId}`)
+    classId = ce ? ce.classId : undefined
+  }
+
+  const c = contentDirectoryClassNamesWithId.find((c) => c.classId === classId)
+  // TODO: stop execution, class should be created before entity creation
+  if (c === undefined) console.log(`Not recognized class id: ${classId}`)
+  return c ? c.name : undefined
+}
+
+async function removeChannel(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Channel, where)
+  if (record === undefined) throw Error(`Channel not found`)
+  await db.remove<Channel>(record)
+}
+async function removeCategory(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Category, where)
+  if (record === undefined) throw Error(`Category not found`)
+  await db.remove<Category>(record)
+}
+async function removeVideoMedia(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(VideoMedia, where)
+  if (record === undefined) throw Error(`VideoMedia not found`)
+  await db.remove<VideoMedia>(record)
+}
+async function removeVideo(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Video, where)
+  if (record === undefined) throw Error(`Video not found`)
+  await db.remove<Video>(record)
+}
+async function removeUserDefinedLicense(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(UserDefinedLicense, where)
+  if (record === undefined) throw Error(`UserDefinedLicense not found`)
+  await db.remove<UserDefinedLicense>(record)
+}
+async function removeKnownLicense(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(KnownLicense, where)
+  if (record === undefined) throw Error(`KnownLicense not found`)
+  await db.remove<KnownLicense>(record)
+}
+async function removeHttpMediaLocation(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(HttpMediaLocation, where)
+  if (record === undefined) throw Error(`HttpMediaLocation not found`)
+  await db.remove<HttpMediaLocation>(record)
+}
+async function removeJoystreamMediaLocation(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(JoystreamMediaLocation, where)
+  if (record === undefined) throw Error(`JoystreamMediaLocation not found`)
+  await db.remove<JoystreamMediaLocation>(record)
+}
+async function removeLanguage(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Language, where)
+  if (record === undefined) throw Error(`Language not found`)
+  await db.remove<Language>(record)
+}
+async function removeVideoMediaEncoding(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(VideoMediaEncoding, where)
+  if (record === undefined) throw Error(`Language not found`)
+  await db.remove<VideoMediaEncoding>(record)
+}
+
+// ========Entity property value updates========
+
+async function updateCategoryEntityPropertyValues(db: DB, where: IWhereCond, props: ICategory): Promise<void> {
+  const record = await db.get(Category, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<Category>(record)
+}
+async function updateChannelEntityPropertyValues(db: DB, where: IWhereCond, props: IChannel): Promise<void> {
+  const record = await db.get(Channel, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<Channel>(record)
+}
+async function updateVideoMediaEntityPropertyValues(db: DB, where: IWhereCond, props: IVideoMedia): Promise<void> {
+  const record = await db.get(VideoMedia, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<VideoMedia>(record)
+}
+async function updateVideoEntityPropertyValues(db: DB, where: IWhereCond, props: IVideo): Promise<void> {
+  const record = await db.get(Video, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<Video>(record)
+}
+async function updateUserDefinedLicenseEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IUserDefinedLicense
+): Promise<void> {
+  const record = await db.get(UserDefinedLicense, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<UserDefinedLicense>(record)
+}
+async function updateKnownLicenseEntityPropertyValues(db: DB, where: IWhereCond, props: IKnownLicense): Promise<void> {
+  const record = await db.get(KnownLicense, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<KnownLicense>(record)
+}
+async function updateHttpMediaLocationEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IHttpMediaLocation
+): Promise<void> {
+  const record = await db.get(HttpMediaLocation, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<HttpMediaLocation>(record)
+}
+async function updateJoystreamMediaLocationEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IJoystreamMediaLocation
+): Promise<void> {
+  const record = await db.get(JoystreamMediaLocation, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<JoystreamMediaLocation>(record)
+}
+async function updateLanguageEntityPropertyValues(db: DB, where: IWhereCond, props: ILanguage): Promise<void> {
+  const record = await db.get(Language, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<Language>(record)
+}
+async function updateVideoMediaEncodingEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IVideoMediaEncoding
+): Promise<void> {
+  const record = await db.get(VideoMediaEncoding, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<VideoMediaEncoding>(record)
+}
+
+async function updateEntityPropertyValues(
+  db: DB,
+  event: SubstrateEvent,
+  where: IWhereCond,
+  className: string
+): Promise<void> {
+  switch (className) {
+    case ContentDirectoryKnownClasses.CHANNEL:
+      updateChannelEntityPropertyValues(db, where, decode.setProperties<IChannel>(event, channelPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.CATEGORY:
+      await updateCategoryEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<ICategory>(event, CategoryPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.KNOWNLICENSE:
+      await updateKnownLicenseEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IKnownLicense>(event, knownLicensePropertyNamesWIthId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
+      await updateUserDefinedLicenseEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IUserDefinedLicense>(event, userDefinedLicensePropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
+      await updateJoystreamMediaLocationEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IJoystreamMediaLocation>(event, joystreamMediaLocationPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
+      await updateHttpMediaLocationEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IHttpMediaLocation>(event, httpMediaLocationPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIA:
+      await updateVideoMediaEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IVideoMedia>(event, videoPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.VIDEO:
+      await updateVideoEntityPropertyValues(db, where, decode.setProperties<IVideo>(event, videoPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.LANGUAGE:
+      await updateLanguageEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<ILanguage>(event, languagePropertyNamesWIthId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
+      await updateVideoMediaEncodingEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IVideoMediaEncoding>(event, videoMediaEncodingPropertyNamesWithId)
+      )
+      break
+
+    default:
+      throw new Error(`Unknown class name: ${className}`)
+  }
+}
+
+export {
+  createCategory,
+  createChannel,
+  createVideoMedia,
+  createVideo,
+  createUserDefinedLicense,
+  createKnownLicense,
+  createHttpMediaLocation,
+  createJoystreamMediaLocation,
+  createLanguage,
+  createVideoMediaEncoding,
+  removeCategory,
+  removeChannel,
+  removeVideoMedia,
+  removeVideo,
+  removeUserDefinedLicense,
+  removeKnownLicense,
+  removeHttpMediaLocation,
+  removeJoystreamMediaLocation,
+  removeLanguage,
+  removeVideoMediaEncoding,
+  createBlockOrGetFromDatabase,
+  batchCreateClassEntities,
+  getClassName,
+  updateCategoryEntityPropertyValues,
+  updateChannelEntityPropertyValues,
+  updateVideoMediaEntityPropertyValues,
+  updateVideoEntityPropertyValues,
+  updateUserDefinedLicenseEntityPropertyValues,
+  updateHttpMediaLocationEntityPropertyValues,
+  updateJoystreamMediaLocationEntityPropertyValues,
+  updateKnownLicenseEntityPropertyValues,
+  updateLanguageEntityPropertyValues,
+  updateVideoMediaEncodingEntityPropertyValues,
+  updateEntityPropertyValues,
+}

+ 337 - 0
query-node/src/content-directory/entity.ts

@@ -0,0 +1,337 @@
+import Debug from 'debug'
+import { DB, SubstrateEvent } from '../../generated/indexer'
+import { ClassEntity } from '../../generated/graphql-server/src/modules/class-entity/class-entity.model'
+
+import { decode } from './decode'
+import {
+  createCategory,
+  createChannel,
+  createVideoMedia,
+  createVideo,
+  createUserDefinedLicense,
+  createKnownLicense,
+  createHttpMediaLocation,
+  createJoystreamMediaLocation,
+  removeCategory,
+  removeChannel,
+  removeVideoMedia,
+  removeVideo,
+  removeUserDefinedLicense,
+  removeKnownLicense,
+  removeHttpMediaLocation,
+  removeJoystreamMediaLocation,
+  removeLanguage,
+  removeVideoMediaEncoding,
+  createLanguage,
+  createVideoMediaEncoding,
+  updateCategoryEntityPropertyValues,
+  updateChannelEntityPropertyValues,
+  updateVideoMediaEntityPropertyValues,
+  updateVideoEntityPropertyValues,
+  updateUserDefinedLicenseEntityPropertyValues,
+  updateHttpMediaLocationEntityPropertyValues,
+  updateJoystreamMediaLocationEntityPropertyValues,
+  updateKnownLicenseEntityPropertyValues,
+  updateLanguageEntityPropertyValues,
+  updateVideoMediaEncodingEntityPropertyValues,
+  createBlockOrGetFromDatabase,
+} from './entity-helper'
+import {
+  CategoryPropertyNamesWithId,
+  channelPropertyNamesWithId,
+  httpMediaLocationPropertyNamesWithId,
+  joystreamMediaLocationPropertyNamesWithId,
+  knownLicensePropertyNamesWIthId,
+  languagePropertyNamesWIthId,
+  userDefinedLicensePropertyNamesWithId,
+  videoMediaEncodingPropertyNamesWithId,
+  videoPropertyNamesWithId,
+  contentDirectoryClassNamesWithId,
+  ContentDirectoryKnownClasses,
+} from './content-dir-consts'
+
+import {
+  IChannel,
+  ICategory,
+  IKnownLicense,
+  IUserDefinedLicense,
+  IJoystreamMediaLocation,
+  IHttpMediaLocation,
+  IVideoMedia,
+  IVideo,
+  ILanguage,
+  IVideoMediaEncoding,
+  IDBBlockId,
+  IWhereCond,
+} from '../types'
+
+const debug = Debug('mappings:content-directory')
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+async function contentDirectory_EntitySchemaSupportAdded(db: DB, event: SubstrateEvent): Promise<void> {
+  if (event.extrinsic && event.extrinsic.method === 'transaction') return
+  debug(`EntitySchemaSupportAdded event: ${JSON.stringify(event)}`)
+
+  const { blockNumber: block } = event
+  const entityId = decode.stringIfyEntityId(event)
+  const classEntity = await db.get(ClassEntity, { where: { id: entityId } })
+
+  if (classEntity === undefined) {
+    console.log(`Class not found for the EntityId: ${entityId}`)
+    return
+  }
+
+  const cls = contentDirectoryClassNamesWithId.find((c) => c.classId === classEntity.classId)
+  if (cls === undefined) {
+    console.log('Not recognized class')
+    return
+  }
+
+  const arg: IDBBlockId = { db, block, id: entityId }
+
+  switch (cls.name) {
+    case ContentDirectoryKnownClasses.CHANNEL:
+      await createChannel(arg, decode.setProperties<IChannel>(event, channelPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.CATEGORY:
+      await createCategory(arg, decode.setProperties<ICategory>(event, CategoryPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.KNOWNLICENSE:
+      await createKnownLicense(arg, decode.setProperties<IKnownLicense>(event, knownLicensePropertyNamesWIthId))
+      break
+
+    case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
+      await createUserDefinedLicense(
+        arg,
+        decode.setProperties<IUserDefinedLicense>(event, userDefinedLicensePropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
+      await createJoystreamMediaLocation(
+        arg,
+        decode.setProperties<IJoystreamMediaLocation>(event, joystreamMediaLocationPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
+      await createHttpMediaLocation(
+        arg,
+        decode.setProperties<IHttpMediaLocation>(event, httpMediaLocationPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIA:
+      await createVideoMedia(arg, decode.setProperties<IVideoMedia>(event, videoPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.VIDEO:
+      await createVideo(arg, decode.setProperties<IVideo>(event, videoPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.LANGUAGE:
+      await createLanguage(arg, decode.setProperties<ILanguage>(event, languagePropertyNamesWIthId))
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
+      await createVideoMediaEncoding(
+        arg,
+        decode.setProperties<IVideoMediaEncoding>(event, videoMediaEncodingPropertyNamesWithId)
+      )
+      break
+
+    default:
+      throw new Error(`Unknown class name: ${cls.name}`)
+  }
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+async function contentDirectory_EntityRemoved(db: DB, event: SubstrateEvent): Promise<void> {
+  debug(`EntityRemoved event: ${JSON.stringify(event)}`)
+
+  const entityId = decode.stringIfyEntityId(event)
+  const where: IWhereCond = { where: { id: entityId } }
+
+  const classEntity = await db.get(ClassEntity, where)
+  if (classEntity === undefined) {
+    console.log(`Class not found for the EntityId: ${entityId}`)
+    return
+  }
+
+  const cls = contentDirectoryClassNamesWithId.find((c) => c.classId === classEntity.classId)
+  if (cls === undefined) {
+    console.log('Undefined class')
+    return
+  }
+
+  switch (cls.name) {
+    case ContentDirectoryKnownClasses.CHANNEL:
+      await removeChannel(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.CATEGORY:
+      await removeCategory(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.KNOWNLICENSE:
+      await removeKnownLicense(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
+      await removeUserDefinedLicense(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
+      await removeJoystreamMediaLocation(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
+      await removeHttpMediaLocation(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIA:
+      await removeVideoMedia(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.VIDEO:
+      await removeVideo(db, where)
+      break
+    case ContentDirectoryKnownClasses.LANGUAGE:
+      await removeLanguage(db, where)
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
+      await removeVideoMediaEncoding(db, where)
+      break
+
+    default:
+      throw new Error(`Unknown class name: ${cls.name}`)
+  }
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+async function contentDirectory_EntityCreated(db: DB, event: SubstrateEvent): Promise<void> {
+  if (event.extrinsic && event.extrinsic.method === 'transaction') return
+  debug(`EntityCreated event: ${JSON.stringify(event)}`)
+
+  const c = decode.getClassEntity(event)
+  const classEntity = new ClassEntity()
+
+  classEntity.classId = c.classId
+  classEntity.id = c.entityId.toString()
+  classEntity.version = event.blockNumber
+  classEntity.happenedIn = await createBlockOrGetFromDatabase(db, event.blockNumber)
+  await db.save<ClassEntity>(classEntity)
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+async function contentDirectory_EntityPropertyValuesUpdated(db: DB, event: SubstrateEvent): Promise<void> {
+  debug(`EntityPropertyValuesUpdated event: ${JSON.stringify(event)}`)
+
+  const { extrinsic } = event
+
+  if (extrinsic && extrinsic.method === 'transaction') return
+  if (extrinsic === undefined) throw Error(`Extrinsic data not found for event: ${event.id}`)
+
+  const { 2: newPropertyValues } = extrinsic.args
+  const entityId = decode.stringIfyEntityId(event)
+
+  const ce = await db.get(ClassEntity, { where: { id: entityId } })
+  if (ce === undefined) throw Error(`Class not found for the entity id: ${entityId}`)
+
+  const cls = contentDirectoryClassNamesWithId.find((c) => c.classId === ce.classId)
+  if (cls === undefined) throw Error(`Not known class id: ${ce.classId}`)
+
+  const where: IWhereCond = { where: { id: entityId } }
+
+  // TODO: change setProperties method signature to accecpt SubstrateExtrinsic, then remove the following
+  // line. The reason we push the same arg is beacuse of the setProperties method check the 3rd indices
+  // to get properties values
+  extrinsic.args.push(newPropertyValues)
+
+  switch (cls.name) {
+    case ContentDirectoryKnownClasses.CHANNEL:
+      updateChannelEntityPropertyValues(db, where, decode.setProperties<IChannel>(event, channelPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.CATEGORY:
+      await updateCategoryEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<ICategory>(event, CategoryPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.KNOWNLICENSE:
+      await updateKnownLicenseEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IKnownLicense>(event, knownLicensePropertyNamesWIthId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
+      await updateUserDefinedLicenseEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IUserDefinedLicense>(event, userDefinedLicensePropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
+      await updateJoystreamMediaLocationEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IJoystreamMediaLocation>(event, joystreamMediaLocationPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
+      await updateHttpMediaLocationEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IHttpMediaLocation>(event, httpMediaLocationPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIA:
+      await updateVideoMediaEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IVideoMedia>(event, videoPropertyNamesWithId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.VIDEO:
+      await updateVideoEntityPropertyValues(db, where, decode.setProperties<IVideo>(event, videoPropertyNamesWithId))
+      break
+
+    case ContentDirectoryKnownClasses.LANGUAGE:
+      await updateLanguageEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<ILanguage>(event, languagePropertyNamesWIthId)
+      )
+      break
+
+    case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
+      await updateVideoMediaEncodingEntityPropertyValues(
+        db,
+        where,
+        decode.setProperties<IVideoMediaEncoding>(event, videoMediaEncodingPropertyNamesWithId)
+      )
+      break
+
+    default:
+      throw new Error(`Unknown class name: ${cls.name}`)
+  }
+}
+
+export {
+  contentDirectory_EntityCreated,
+  contentDirectory_EntityRemoved,
+  contentDirectory_EntitySchemaSupportAdded,
+  contentDirectory_EntityPropertyValuesUpdated,
+}

+ 7 - 0
query-node/src/content-directory/mapping.ts

@@ -0,0 +1,7 @@
+export {
+  contentDirectory_EntitySchemaSupportAdded,
+  contentDirectory_EntityRemoved,
+  contentDirectory_EntityCreated,
+  contentDirectory_EntityPropertyValuesUpdated,
+} from './entity'
+export { contentDirectory_TransactionCompleted } from './transaction'

+ 286 - 0
query-node/src/content-directory/transaction.ts

@@ -0,0 +1,286 @@
+import Debug from 'debug'
+
+import { DB, SubstrateEvent } from '../../generated/indexer'
+import { decode } from './decode'
+import {
+  ICategory,
+  IChannel,
+  ICreateEntityOperation,
+  IDBBlockId,
+  IEntity,
+  IHttpMediaLocation,
+  IJoystreamMediaLocation,
+  IKnownLicense,
+  ILanguage,
+  IUserDefinedLicense,
+  IVideo,
+  IVideoMedia,
+  IVideoMediaEncoding,
+  IWhereCond,
+} from '../types'
+import {
+  CategoryPropertyNamesWithId,
+  channelPropertyNamesWithId,
+  knownLicensePropertyNamesWIthId,
+  userDefinedLicensePropertyNamesWithId,
+  joystreamMediaLocationPropertyNamesWithId,
+  httpMediaLocationPropertyNamesWithId,
+  videoMediaPropertyNamesWithId,
+  videoMediaEncodingPropertyNamesWithId,
+  videoPropertyNamesWithId,
+  languagePropertyNamesWIthId,
+  ContentDirectoryKnownClasses,
+} from './content-dir-consts'
+import {
+  createCategory,
+  createChannel,
+  createVideoMedia,
+  createVideo,
+  createUserDefinedLicense,
+  createKnownLicense,
+  createHttpMediaLocation,
+  createJoystreamMediaLocation,
+  createLanguage,
+  createVideoMediaEncoding,
+  getClassName,
+  updateCategoryEntityPropertyValues,
+  updateChannelEntityPropertyValues,
+  updateVideoMediaEntityPropertyValues,
+  updateVideoEntityPropertyValues,
+  updateUserDefinedLicenseEntityPropertyValues,
+  updateHttpMediaLocationEntityPropertyValues,
+  updateJoystreamMediaLocationEntityPropertyValues,
+  updateKnownLicenseEntityPropertyValues,
+  updateLanguageEntityPropertyValues,
+  updateVideoMediaEncodingEntityPropertyValues,
+  batchCreateClassEntities,
+} from './entity-helper'
+
+const debug = Debug('mappings:content-directory')
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export async function contentDirectory_TransactionCompleted(db: DB, event: SubstrateEvent): Promise<void> {
+  debug(`TransactionCompleted event: ${JSON.stringify(event)}`)
+
+  const { extrinsic, blockNumber: block } = event
+  if (!extrinsic) {
+    throw Error(`No extrinsic found for the event: ${event.id}`)
+  }
+
+  const { 1: operations } = extrinsic.args
+  if (operations.name.toString() !== 'operations') {
+    throw Error(`Could not found 'operations' in the extrinsic.args[1]`)
+  }
+
+  const {
+    addSchemaSupportToEntityOperations,
+    createEntityOperations,
+    updatePropertyValuesOperations,
+  } = decode.getOperations(event)
+
+  // Create entities before adding schema support
+  // We need this to know which entity belongs to which class(we will need to know to update/create
+  // Channel, Video etc.). For example if there is
+  // a property update operation there is no class id
+  await batchCreateClassEntities(db, block, createEntityOperations)
+  await batchUpdatePropertyValue(db, createEntityOperations, updatePropertyValuesOperations)
+  await batchAddSchemaSupportToEntity(db, createEntityOperations, addSchemaSupportToEntityOperations, block)
+}
+
+/**
+ *
+ * @param db database connection
+ * @param createEntityOperations: Entity creations with in the same transaction
+ * @param entities List of entities that schema support is added for
+ * @param block block number
+ */
+async function batchAddSchemaSupportToEntity(
+  db: DB,
+  createEntityOperations: ICreateEntityOperation[],
+  entities: IEntity[],
+  block: number
+) {
+  // find the related entity ie. Channel, Video etc
+  for (const entity of entities) {
+    const { entityId, indexOf, properties } = entity
+
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const id = entityId ? entityId.toString() : indexOf!.toString()
+
+    const className = await getClassName(db, entity, createEntityOperations)
+    if (className === undefined) continue
+
+    const arg: IDBBlockId = { db, block, id }
+
+    switch (className) {
+      case ContentDirectoryKnownClasses.CATEGORY:
+        await createCategory(arg, decode.setEntityPropertyValues<ICategory>(properties, CategoryPropertyNamesWithId))
+        break
+
+      case ContentDirectoryKnownClasses.CHANNEL:
+        await createChannel(arg, decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId))
+        break
+
+      case ContentDirectoryKnownClasses.KNOWNLICENSE:
+        await createKnownLicense(
+          arg,
+          decode.setEntityPropertyValues<IKnownLicense>(properties, knownLicensePropertyNamesWIthId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
+        await createUserDefinedLicense(
+          arg,
+          decode.setEntityPropertyValues<IUserDefinedLicense>(properties, userDefinedLicensePropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
+        await createJoystreamMediaLocation(
+          arg,
+          decode.setEntityPropertyValues<IJoystreamMediaLocation>(properties, joystreamMediaLocationPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
+        await createHttpMediaLocation(
+          arg,
+          decode.setEntityPropertyValues<IHttpMediaLocation>(properties, httpMediaLocationPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.VIDEOMEDIA:
+        await createVideoMedia(
+          arg,
+          decode.setEntityPropertyValues<IVideoMedia>(properties, videoMediaPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.VIDEO:
+        await createVideo(arg, decode.setEntityPropertyValues<IVideo>(properties, videoPropertyNamesWithId))
+        break
+
+      case ContentDirectoryKnownClasses.LANGUAGE:
+        await createLanguage(arg, decode.setEntityPropertyValues<ILanguage>(properties, languagePropertyNamesWIthId))
+        break
+
+      case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
+        await createVideoMediaEncoding(
+          arg,
+          decode.setEntityPropertyValues<IVideoMediaEncoding>(properties, videoMediaEncodingPropertyNamesWithId)
+        )
+        break
+
+      default:
+        console.log(`Unknown class name: ${className}`)
+        break
+    }
+  }
+}
+
+/**
+ * Batch update operations for entity properties values update
+ * @param db database connection
+ * @param createEntityOperations Entity creations with in the same transaction
+ * @param entities list of entities those properties values updated
+ */
+async function batchUpdatePropertyValue(db: DB, createEntityOperations: ICreateEntityOperation[], entities: IEntity[]) {
+  for (const entity of entities) {
+    const { entityId, indexOf, properties } = entity
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const id = entityId ? entityId.toString() : indexOf!.toString()
+
+    const where: IWhereCond = { where: { id } }
+    const className = await getClassName(db, entity, createEntityOperations)
+    if (className === undefined) {
+      console.log(`Can not update entity properties values. Unknown class name`)
+      return
+    }
+
+    switch (className) {
+      case ContentDirectoryKnownClasses.CHANNEL:
+        updateChannelEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IChannel>(properties, CategoryPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.CATEGORY:
+        await updateCategoryEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<ICategory>(properties, CategoryPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.KNOWNLICENSE:
+        await updateKnownLicenseEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IKnownLicense>(properties, knownLicensePropertyNamesWIthId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
+        await updateUserDefinedLicenseEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IUserDefinedLicense>(properties, userDefinedLicensePropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
+        await updateJoystreamMediaLocationEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IJoystreamMediaLocation>(properties, joystreamMediaLocationPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
+        await updateHttpMediaLocationEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IHttpMediaLocation>(properties, httpMediaLocationPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.VIDEOMEDIA:
+        await updateVideoMediaEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IVideoMedia>(properties, videoPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.VIDEO:
+        await updateVideoEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IVideo>(properties, videoPropertyNamesWithId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.LANGUAGE:
+        await updateLanguageEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<ILanguage>(properties, languagePropertyNamesWIthId)
+        )
+        break
+
+      case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
+        await updateVideoMediaEncodingEntityPropertyValues(
+          db,
+          where,
+          decode.setEntityPropertyValues<IVideoMediaEncoding>(properties, videoMediaEncodingPropertyNamesWithId)
+        )
+        break
+
+      default:
+        console.log(`Unknown class name: ${className}`)
+        break
+    }
+  }
+}

+ 2 - 0
query-node/src/index.ts

@@ -0,0 +1,2 @@
+export * from './content-directory/mapping'
+// export * from "./membership/mapping"

+ 170 - 0
query-node/src/types.ts

@@ -0,0 +1,170 @@
+import * as BN from 'bn.js'
+import { EntityId, SchemaId, ParametrizedClassPropertyValue, ClassId } from '@joystream/types/content-directory'
+import { DB } from '../generated/indexer'
+
+export interface BaseJoystreamMember {
+  memberId: BN
+}
+
+export interface JoystreamMember extends BaseJoystreamMember {
+  handle: string
+  avatarUri: string
+  about: string
+  registeredAtBlock: number
+  rootAccount: Buffer
+  controllerAccount: Buffer
+}
+
+export interface MemberAboutText extends BaseJoystreamMember {
+  about: string
+}
+
+export interface MemberAvatarURI extends BaseJoystreamMember {
+  avatarUri: string
+}
+
+export interface MemberHandle extends BaseJoystreamMember {
+  handle: string
+}
+
+export interface MemberRootAccount extends BaseJoystreamMember {
+  rootAccount: Buffer
+}
+export interface MemberControllerAccount extends BaseJoystreamMember {
+  controllerAccount: Buffer
+}
+
+export interface IChannel {
+  title: string
+  description: string
+  coverPhotoURL: string
+  avatarPhotoURL: string
+  isPublic: boolean
+  isCurated: boolean
+  language: number
+}
+
+export interface ICategory {
+  name: string
+  description: string
+}
+
+export interface IKnownLicense {
+  code: string
+  name?: string
+  description?: string
+  url?: string
+}
+
+export interface IUserDefinedLicense {
+  content: string
+}
+
+export interface IJoystreamMediaLocation {
+  dataObjectId: string
+}
+
+export interface IHttpMediaLocation {
+  url: string
+  port?: number
+}
+
+export interface ILanguage {
+  name: string
+  code: string
+}
+
+export interface IVideoMediaEncoding {
+  name: string
+}
+
+export interface IVideoMedia {
+  encoding: number
+  pixelWidth: number
+  pixelHeight: number
+  size: number
+  location: number
+}
+
+export interface IVideo {
+  // referenced entity's id
+  channel: number
+  // referenced entity's id
+  category: number
+  title: string
+  description: string
+  duration: number
+  skippableIntroDuration?: number
+  thumbnailURL: string
+  language: number
+  // referenced entity's id
+  media: number
+  hasMarketing?: boolean
+  publishedBeforeJoystream?: number
+  isPublic: boolean
+  isCurated: boolean
+  isExplicit: boolean
+  license: number
+}
+
+export enum OperationType {
+  CreateEntity = 'CreateEntity',
+  AddSchemaSupportToEntity = 'AddSchemaSupportToEntity',
+  UpdatePropertyValues = 'UpdatePropertyValues',
+}
+
+export interface IAddSchemaSupportToEntity {
+  entity_id: EntityId
+  schema_id: SchemaId
+  parametrized_property_values: ParametrizedClassPropertyValue[]
+}
+
+export interface ICreateEntity {
+  class_id: ClassId
+}
+
+export interface IClassEntity {
+  entityId: number
+  classId: number
+}
+
+export interface IBatchOperation {
+  createEntityOperations: ICreateEntityOperation[]
+  addSchemaSupportToEntityOperations: IEntity[]
+  updatePropertyValuesOperations: IEntity[]
+}
+
+export interface IProperty {
+  [propertyId: string]: any
+  // propertyId: string;
+  // value: any;
+}
+
+export interface IEntity {
+  classId?: number
+  entityId?: number
+  // if entity is created in the same transaction, this is the entity id which is the index of the create
+  // entity operation
+  indexOf?: number
+  properties: IProperty[]
+}
+
+export interface IPropertyIdWithName {
+  // propertyId - property name
+  [propertyId: string]: string
+}
+
+export interface IWhereCond {
+  where: { id: string }
+}
+
+export interface ICreateEntityOperation {
+  classId: number
+}
+
+// An interface to use in function signature to simplify function parameters
+export interface IDBBlockId {
+  db: DB
+  block: number
+  id: string
+}

+ 22 - 0
query-node/tsconfig.json

@@ -0,0 +1,22 @@
+{
+  "compilerOptions": {
+    "declaration": true,
+    "importHelpers": true,
+    "module": "commonjs",
+    "outDir": "lib",
+    "rootDir": "src",
+    "strict": true,
+    "target": "es2017",
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true,
+    "skipLibCheck": true,
+    "sourceMap": true,
+    "inlineSources": false,
+    "baseUrl": ".",
+    "paths": {
+      "@polkadot/types/augment": ["./node_modules/@joystream/types/augment-codec/augment-types.ts"]
+    }
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules"]
+}

+ 920 - 0
query-node/typedefs.json

@@ -0,0 +1,920 @@
+{
+  "Credential": "u64",
+  "CredentialSet": "BTreeSet<Credential>",
+  "BlockAndTime": {
+    "block": "u32",
+    "time": "u64"
+  },
+  "ThreadId": "u64",
+  "PostId": "u64",
+  "InputValidationLengthConstraint": {
+    "min": "u16",
+    "max_min_diff": "u16"
+  },
+  "WorkingGroup": {
+    "_enum": ["Storage", "Content"]
+  },
+  "SlashingTerms": {
+    "_enum": {
+      "Unslashable": "Null",
+      "Slashable": "SlashableTerms"
+    }
+  },
+  "SlashableTerms": {
+    "max_count": "u16",
+    "max_percent_pts_per_time": "u16"
+  },
+  "MemoText": "Text",
+  "Address": "AccountId",
+  "LookupSource": "AccountId",
+  "EntryMethod": {
+    "_enum": {
+      "Paid": "u64",
+      "Screening": "AccountId",
+      "Genesis": "Null"
+    }
+  },
+  "MemberId": "u64",
+  "PaidTermId": "u64",
+  "SubscriptionId": "u64",
+  "Membership": {
+    "handle": "Text",
+    "avatar_uri": "Text",
+    "about": "Text",
+    "registered_at_block": "u32",
+    "registered_at_time": "u64",
+    "entry": "EntryMethod",
+    "suspended": "bool",
+    "subscription": "Option<SubscriptionId>",
+    "root_account": "GenericAccountId",
+    "controller_account": "GenericAccountId"
+  },
+  "PaidMembershipTerms": {
+    "fee": "u128",
+    "text": "Text"
+  },
+  "ActorId": "u64",
+  "ElectionStage": {
+    "_enum": {
+      "Announcing": "u32",
+      "Voting": "u32",
+      "Revealing": "u32"
+    }
+  },
+  "ElectionStake": {
+    "new": "u128",
+    "transferred": "u128"
+  },
+  "SealedVote": {
+    "voter": "GenericAccountId",
+    "commitment": "Hash",
+    "stake": "ElectionStake",
+    "vote": "Option<GenericAccountId>"
+  },
+  "TransferableStake": {
+    "seat": "u128",
+    "backing": "u128"
+  },
+  "ElectionParameters": {
+    "announcing_period": "u32",
+    "voting_period": "u32",
+    "revealing_period": "u32",
+    "council_size": "u32",
+    "candidacy_limit": "u32",
+    "new_term_duration": "u32",
+    "min_council_stake": "u128",
+    "min_voting_stake": "u128"
+  },
+  "Seat": {
+    "member": "GenericAccountId",
+    "stake": "u128",
+    "backers": "Backers"
+  },
+  "Seats": "Vec<Seat>",
+  "Backer": {
+    "member": "GenericAccountId",
+    "stake": "u128"
+  },
+  "Backers": "Vec<Backer>",
+  "RoleParameters": {
+    "min_stake": "u128",
+    "min_actors": "u32",
+    "max_actors": "u32",
+    "reward": "u128",
+    "reward_period": "u32",
+    "bonding_period": "u32",
+    "unbonding_period": "u32",
+    "min_service_period": "u32",
+    "startup_grace_period": "u32",
+    "entry_request_fee": "u128"
+  },
+  "PostTextChange": {
+    "expired_at": "BlockAndTime",
+    "text": "Text"
+  },
+  "ModerationAction": {
+    "moderated_at": "BlockAndTime",
+    "moderator_id": "GenericAccountId",
+    "rationale": "Text"
+  },
+  "ChildPositionInParentCategory": {
+    "parent_id": "CategoryId",
+    "child_nr_in_parent_category": "u32"
+  },
+  "CategoryId": "u64",
+  "Category": {
+    "id": "CategoryId",
+    "title": "Text",
+    "description": "Text",
+    "created_at": "BlockAndTime",
+    "deleted": "bool",
+    "archived": "bool",
+    "num_direct_subcategories": "u32",
+    "num_direct_unmoderated_threads": "u32",
+    "num_direct_moderated_threads": "u32",
+    "position_in_parent_category": "Option<ChildPositionInParentCategory>",
+    "moderator_id": "GenericAccountId"
+  },
+  "Thread": {
+    "id": "ThreadId",
+    "title": "Text",
+    "category_id": "CategoryId",
+    "nr_in_category": "u32",
+    "moderation": "Option<ModerationAction>",
+    "num_unmoderated_posts": "u32",
+    "num_moderated_posts": "u32",
+    "created_at": "BlockAndTime",
+    "author_id": "GenericAccountId"
+  },
+  "Post": {
+    "id": "PostId",
+    "thread_id": "ThreadId",
+    "nr_in_thread": "u32",
+    "current_text": "Text",
+    "moderation": "Option<ModerationAction>",
+    "text_change_history": "Vec<PostTextChange>",
+    "created_at": "BlockAndTime",
+    "author_id": "GenericAccountId"
+  },
+  "ReplyId": "u64",
+  "Reply": {
+    "owner": "GenericAccountId",
+    "thread_id": "ThreadId",
+    "text": "Text",
+    "moderation": "Option<ModerationAction>"
+  },
+  "StakeId": "u64",
+  "Stake": {
+    "created": "u32",
+    "staking_status": "StakingStatus"
+  },
+  "StakingStatus": {
+    "_enum": {
+      "NotStaked": "Null",
+      "Staked": "Staked"
+    }
+  },
+  "Staked": {
+    "staked_amount": "u128",
+    "staked_status": "StakedStatus",
+    "next_slash_id": "u64",
+    "ongoing_slashes": "BTreeMap<u64,Slash>"
+  },
+  "StakedStatus": {
+    "_enum": {
+      "Normal": "Null",
+      "Unstaking": "Unstaking"
+    }
+  },
+  "Unstaking": {
+    "started_at_block": "u32",
+    "is_active": "bool",
+    "blocks_remaining_in_active_period_for_unstaking": "u32"
+  },
+  "Slash": {
+    "started_at_block": "u32",
+    "is_active": "bool",
+    "blocks_remaining_in_active_period_for_slashing": "u32",
+    "slash_amount": "u128"
+  },
+  "MintId": "u64",
+  "Mint": {
+    "capacity": "u128",
+    "next_adjustment": "Option<NextAdjustment>",
+    "created_at": "u32",
+    "total_minted": "u128"
+  },
+  "MintBalanceOf": "u128",
+  "BalanceOfMint": "u128",
+  "NextAdjustment": {
+    "adjustment": "AdjustOnInterval",
+    "at_block": "u32"
+  },
+  "AdjustOnInterval": {
+    "block_interval": "u32",
+    "adjustment_type": "AdjustCapacityBy"
+  },
+  "AdjustCapacityBy": {
+    "_enum": {
+      "Setting": "u128",
+      "Adding": "u128",
+      "Reducing": "u128"
+    }
+  },
+  "RecipientId": "u64",
+  "RewardRelationshipId": "u64",
+  "Recipient": {
+    "total_reward_received": "u128",
+    "total_reward_missed": "u128"
+  },
+  "RewardRelationship": {
+    "recipient": "RecipientId",
+    "mint_id": "MintId",
+    "account": "GenericAccountId",
+    "amount_per_payout": "u128",
+    "next_payment_at_block": "Option<u32>",
+    "payout_interval": "Option<u32>",
+    "total_reward_received": "u128",
+    "total_reward_missed": "u128"
+  },
+  "ApplicationId": "u64",
+  "OpeningId": "u64",
+  "Application": {
+    "opening_id": "OpeningId",
+    "application_index_in_opening": "u32",
+    "add_to_opening_in_block": "u32",
+    "active_role_staking_id": "Option<StakeId>",
+    "active_application_staking_id": "Option<StakeId>",
+    "stage": "ApplicationStage",
+    "human_readable_text": "Text"
+  },
+  "ApplicationStage": {
+    "_enum": {
+      "Active": "Null",
+      "Unstaking": "UnstakingApplicationStage",
+      "Inactive": "InactiveApplicationStage"
+    }
+  },
+  "ActivateOpeningAt": {
+    "_enum": {
+      "CurrentBlock": "Null",
+      "ExactBlock": "u32"
+    }
+  },
+  "ApplicationRationingPolicy": {
+    "max_active_applicants": "u32"
+  },
+  "OpeningStage": {
+    "_enum": {
+      "WaitingToBegin": "WaitingToBeingOpeningStageVariant",
+      "Active": "ActiveOpeningStageVariant"
+    }
+  },
+  "StakingPolicy": {
+    "amount": "u128",
+    "amount_mode": "StakingAmountLimitMode",
+    "crowded_out_unstaking_period_length": "Option<u32>",
+    "review_period_expired_unstaking_period_length": "Option<u32>"
+  },
+  "Opening": {
+    "created": "u32",
+    "stage": "OpeningStage",
+    "max_review_period_length": "u32",
+    "application_rationing_policy": "Option<ApplicationRationingPolicy>",
+    "application_staking_policy": "Option<StakingPolicy>",
+    "role_staking_policy": "Option<StakingPolicy>",
+    "human_readable_text": "Text"
+  },
+  "WaitingToBeingOpeningStageVariant": {
+    "begins_at_block": "u32"
+  },
+  "ActiveOpeningStageVariant": {
+    "stage": "ActiveOpeningStage",
+    "applications_added": "Vec<ApplicationId>",
+    "active_application_count": "u32",
+    "unstaking_application_count": "u32",
+    "deactivated_application_count": "u32"
+  },
+  "ActiveOpeningStage": {
+    "_enum": {
+      "AcceptingApplications": "AcceptingApplications",
+      "ReviewPeriod": "ReviewPeriod",
+      "Deactivated": "Deactivated"
+    }
+  },
+  "AcceptingApplications": {
+    "started_accepting_applicants_at_block": "u32"
+  },
+  "ReviewPeriod": {
+    "started_accepting_applicants_at_block": "u32",
+    "started_review_period_at_block": "u32"
+  },
+  "Deactivated": {
+    "cause": "OpeningDeactivationCause",
+    "deactivated_at_block": "u32",
+    "started_accepting_applicants_at_block": "u32",
+    "started_review_period_at_block": "Option<u32>"
+  },
+  "OpeningDeactivationCause": {
+    "_enum": [
+      "CancelledBeforeActivation",
+      "CancelledAcceptingApplications",
+      "CancelledInReviewPeriod",
+      "ReviewPeriodExpired",
+      "Filled"
+    ]
+  },
+  "InactiveApplicationStage": {
+    "deactivation_initiated": "u32",
+    "deactivated": "u32",
+    "cause": "ApplicationDeactivationCause"
+  },
+  "UnstakingApplicationStage": {
+    "deactivation_initiated": "u32",
+    "cause": "ApplicationDeactivationCause"
+  },
+  "ApplicationDeactivationCause": {
+    "_enum": ["External", "Hired", "NotHired", "CrowdedOut", "OpeningCancelled", "ReviewPeriodExpired", "OpeningFilled"]
+  },
+  "StakingAmountLimitMode": {
+    "_enum": ["AtLeast", "Exact"]
+  },
+  "ChannelId": "u64",
+  "CuratorId": "u64",
+  "CuratorOpeningId": "u64",
+  "CuratorApplicationId": "u64",
+  "LeadId": "u64",
+  "PrincipalId": "u64",
+  "OptionalText": "Option<Text>",
+  "Channel": {
+    "verified": "bool",
+    "handle": "Text",
+    "title": "OptionalText",
+    "description": "OptionalText",
+    "avatar": "OptionalText",
+    "banner": "OptionalText",
+    "content": "ChannelContentType",
+    "owner": "MemberId",
+    "role_account": "GenericAccountId",
+    "publication_status": "ChannelPublicationStatus",
+    "curation_status": "ChannelCurationStatus",
+    "created": "u32",
+    "principal_id": "PrincipalId"
+  },
+  "ChannelContentType": {
+    "_enum": ["Video", "Music", "Ebook"]
+  },
+  "ChannelCurationStatus": {
+    "_enum": ["Normal", "Censored"]
+  },
+  "ChannelPublicationStatus": {
+    "_enum": ["Public", "Unlisted"]
+  },
+  "CurationActor": {
+    "_enum": {
+      "Lead": "Null",
+      "Curator": "CuratorId"
+    }
+  },
+  "Curator": {
+    "role_account": "GenericAccountId",
+    "reward_relationship": "Option<RewardRelationshipId>",
+    "role_stake_profile": "Option<CuratorRoleStakeProfile>",
+    "stage": "CuratorRoleStage",
+    "induction": "CuratorInduction",
+    "principal_id": "PrincipalId"
+  },
+  "CuratorApplication": {
+    "role_account": "GenericAccountId",
+    "curator_opening_id": "CuratorOpeningId",
+    "member_id": "MemberId",
+    "application_id": "ApplicationId"
+  },
+  "CuratorOpening": {
+    "opening_id": "OpeningId",
+    "curator_applications": "Vec<CuratorApplicationId>",
+    "policy_commitment": "OpeningPolicyCommitment"
+  },
+  "Lead": {
+    "member_id": "MemberId",
+    "role_account": "GenericAccountId",
+    "reward_relationship": "Option<RewardRelationshipId>",
+    "inducted": "u32",
+    "stage": "LeadRoleState"
+  },
+  "OpeningPolicyCommitment": {
+    "application_rationing_policy": "Option<ApplicationRationingPolicy>",
+    "max_review_period_length": "u32",
+    "application_staking_policy": "Option<StakingPolicy>",
+    "role_staking_policy": "Option<StakingPolicy>",
+    "role_slashing_terms": "SlashingTerms",
+    "fill_opening_successful_applicant_application_stake_unstaking_period": "Option<u32>",
+    "fill_opening_failed_applicant_application_stake_unstaking_period": "Option<u32>",
+    "fill_opening_failed_applicant_role_stake_unstaking_period": "Option<u32>",
+    "terminate_curator_application_stake_unstaking_period": "Option<u32>",
+    "terminate_curator_role_stake_unstaking_period": "Option<u32>",
+    "exit_curator_role_application_stake_unstaking_period": "Option<u32>",
+    "exit_curator_role_stake_unstaking_period": "Option<u32>"
+  },
+  "Principal": {
+    "_enum": {
+      "Lead": "Null",
+      "Curator": "CuratorId",
+      "ChannelOwner": "ChannelId"
+    }
+  },
+  "WorkingGroupUnstaker": {
+    "_enum": {
+      "Lead": "LeadId",
+      "Curator": "CuratorId"
+    }
+  },
+  "CuratorApplicationIdToCuratorIdMap": "BTreeMap<ApplicationId,CuratorId>",
+  "CuratorApplicationIdSet": "BTreeSet<CuratorApplicationId>",
+  "CuratorRoleStakeProfile": {
+    "stake_id": "StakeId",
+    "termination_unstaking_period": "Option<u32>",
+    "exit_unstaking_period": "Option<u32>"
+  },
+  "CuratorRoleStage": {
+    "_enum": {
+      "Active": "Null",
+      "Unstaking": "CuratorExitSummary",
+      "Exited": "CuratorExitSummary"
+    }
+  },
+  "CuratorExitSummary": {
+    "origin": "CuratorExitInitiationOrigin",
+    "initiated_at_block_number": "u32",
+    "rationale_text": "Text"
+  },
+  "CuratorExitInitiationOrigin": {
+    "_enum": ["Lead", "Curator"]
+  },
+  "LeadRoleState": {
+    "_enum": {
+      "Active": "Null",
+      "Exited": "ExitedLeadRole"
+    }
+  },
+  "ExitedLeadRole": {
+    "initiated_at_block_number": "u32"
+  },
+  "CuratorInduction": {
+    "lead": "LeadId",
+    "curator_application_id": "CuratorApplicationId",
+    "at_block": "u32"
+  },
+  "RationaleText": "Bytes",
+  "ApplicationOf": {
+    "role_account_id": "GenericAccountId",
+    "opening_id": "OpeningId",
+    "member_id": "MemberId",
+    "application_id": "ApplicationId"
+  },
+  "ApplicationIdSet": "BTreeSet<ApplicationId>",
+  "ApplicationIdToWorkerIdMap": "BTreeMap<ApplicationId,WorkerId>",
+  "WorkerId": "u64",
+  "WorkerOf": {
+    "member_id": "MemberId",
+    "role_account_id": "GenericAccountId",
+    "reward_relationship": "Option<RewardRelationshipId>",
+    "role_stake_profile": "Option<RoleStakeProfile>"
+  },
+  "OpeningOf": {
+    "hiring_opening_id": "OpeningId",
+    "applications": "Vec<ApplicationId>",
+    "policy_commitment": "WorkingGroupOpeningPolicyCommitment",
+    "opening_type": "OpeningType"
+  },
+  "StorageProviderId": "u64",
+  "OpeningType": {
+    "_enum": {
+      "Leader": "Null",
+      "Worker": "Null"
+    }
+  },
+  "HiringApplicationId": "u64",
+  "RewardPolicy": {
+    "amount_per_payout": "u128",
+    "next_payment_at_block": "u32",
+    "payout_interval": "Option<u32>"
+  },
+  "WorkingGroupOpeningPolicyCommitment": {
+    "application_rationing_policy": "Option<ApplicationRationingPolicy>",
+    "max_review_period_length": "u32",
+    "application_staking_policy": "Option<StakingPolicy>",
+    "role_staking_policy": "Option<StakingPolicy>",
+    "role_slashing_terms": "SlashingTerms",
+    "fill_opening_successful_applicant_application_stake_unstaking_period": "Option<u32>",
+    "fill_opening_failed_applicant_application_stake_unstaking_period": "Option<u32>",
+    "fill_opening_failed_applicant_role_stake_unstaking_period": "Option<u32>",
+    "terminate_application_stake_unstaking_period": "Option<u32>",
+    "terminate_role_stake_unstaking_period": "Option<u32>",
+    "exit_role_application_stake_unstaking_period": "Option<u32>",
+    "exit_role_stake_unstaking_period": "Option<u32>"
+  },
+  "RoleStakeProfile": {
+    "stake_id": "StakeId",
+    "termination_unstaking_period": "Option<u32>",
+    "exit_unstaking_period": "Option<u32>"
+  },
+  "Url": "Text",
+  "IPNSIdentity": "Text",
+  "ServiceProviderRecord": {
+    "identity": "IPNSIdentity",
+    "expires_at": "u32"
+  },
+  "ContentId": "[u8;32]",
+  "LiaisonJudgement": {
+    "_enum": ["Pending", "Accepted", "Rejected"]
+  },
+  "DataObject": {
+    "owner": "MemberId",
+    "added_at": "BlockAndTime",
+    "type_id": "DataObjectTypeId",
+    "size": "u64",
+    "liaison": "StorageProviderId",
+    "liaison_judgement": "LiaisonJudgement",
+    "ipfs_content_id": "Text"
+  },
+  "DataObjectStorageRelationshipId": "u64",
+  "DataObjectStorageRelationship": {
+    "content_id": "ContentId",
+    "storage_provider": "StorageProviderId",
+    "ready": "bool"
+  },
+  "DataObjectTypeId": "u64",
+  "DataObjectType": {
+    "description": "Text",
+    "active": "bool"
+  },
+  "DataObjectsMap": "BTreeMap<ContentId,DataObject>",
+  "ProposalId": "u32",
+  "ProposalStatus": {
+    "_enum": {
+      "Active": "Option<ActiveStake>",
+      "Finalized": "Finalized"
+    }
+  },
+  "ProposalOf": {
+    "parameters": "ProposalParameters",
+    "proposerId": "MemberId",
+    "title": "Text",
+    "description": "Text",
+    "createdAt": "u32",
+    "status": "ProposalStatus",
+    "votingResults": "VotingResults"
+  },
+  "ProposalDetails": {
+    "_enum": {
+      "Text": "Text",
+      "RuntimeUpgrade": "Bytes",
+      "SetElectionParameters": "ElectionParameters",
+      "Spending": "(Balance,AccountId)",
+      "SetLead": "Option<SetLeadParams>",
+      "SetContentWorkingGroupMintCapacity": "u128",
+      "EvictStorageProvider": "GenericAccountId",
+      "SetValidatorCount": "u32",
+      "SetStorageRoleParameters": "RoleParameters",
+      "AddWorkingGroupLeaderOpening": "AddOpeningParameters",
+      "BeginReviewWorkingGroupLeaderApplication": "(OpeningId,WorkingGroup)",
+      "FillWorkingGroupLeaderOpening": "FillOpeningParameters",
+      "SetWorkingGroupMintCapacity": "(Balance,WorkingGroup)",
+      "DecreaseWorkingGroupLeaderStake": "(WorkerId,Balance,WorkingGroup)",
+      "SlashWorkingGroupLeaderStake": "(WorkerId,Balance,WorkingGroup)",
+      "SetWorkingGroupLeaderReward": "(WorkerId,Balance,WorkingGroup)",
+      "TerminateWorkingGroupLeaderRole": "TerminateRoleParameters"
+    }
+  },
+  "ProposalDetailsOf": {
+    "_enum": {
+      "Text": "Text",
+      "RuntimeUpgrade": "Bytes",
+      "SetElectionParameters": "ElectionParameters",
+      "Spending": "(Balance,AccountId)",
+      "SetLead": "Option<SetLeadParams>",
+      "SetContentWorkingGroupMintCapacity": "u128",
+      "EvictStorageProvider": "GenericAccountId",
+      "SetValidatorCount": "u32",
+      "SetStorageRoleParameters": "RoleParameters",
+      "AddWorkingGroupLeaderOpening": "AddOpeningParameters",
+      "BeginReviewWorkingGroupLeaderApplication": "(OpeningId,WorkingGroup)",
+      "FillWorkingGroupLeaderOpening": "FillOpeningParameters",
+      "SetWorkingGroupMintCapacity": "(Balance,WorkingGroup)",
+      "DecreaseWorkingGroupLeaderStake": "(WorkerId,Balance,WorkingGroup)",
+      "SlashWorkingGroupLeaderStake": "(WorkerId,Balance,WorkingGroup)",
+      "SetWorkingGroupLeaderReward": "(WorkerId,Balance,WorkingGroup)",
+      "TerminateWorkingGroupLeaderRole": "TerminateRoleParameters"
+    }
+  },
+  "VotingResults": {
+    "abstensions": "u32",
+    "approvals": "u32",
+    "rejections": "u32",
+    "slashes": "u32"
+  },
+  "ProposalParameters": {
+    "votingPeriod": "u32",
+    "gracePeriod": "u32",
+    "approvalQuorumPercentage": "u32",
+    "approvalThresholdPercentage": "u32",
+    "slashingQuorumPercentage": "u32",
+    "slashingThresholdPercentage": "u32",
+    "requiredStake": "Option<u128>"
+  },
+  "VoteKind": {
+    "_enum": ["Approve", "Reject", "Slash", "Abstain"]
+  },
+  "ThreadCounter": {
+    "author_id": "MemberId",
+    "counter": "u32"
+  },
+  "DiscussionThread": {
+    "title": "Bytes",
+    "created_at": "u32",
+    "author_id": "MemberId"
+  },
+  "DiscussionPost": {
+    "text": "Bytes",
+    "created_at": "u32",
+    "updated_at": "u32",
+    "author_id": "MemberId",
+    "thread_id": "ThreadId",
+    "edition_number": "u32"
+  },
+  "AddOpeningParameters": {
+    "activate_at": "ActivateOpeningAt",
+    "commitment": "WorkingGroupOpeningPolicyCommitment",
+    "human_readable_text": "Bytes",
+    "working_group": "WorkingGroup"
+  },
+  "FillOpeningParameters": {
+    "opening_id": "OpeningId",
+    "successful_application_id": "ApplicationId",
+    "reward_policy": "Option<RewardPolicy>",
+    "working_group": "WorkingGroup"
+  },
+  "TerminateRoleParameters": {
+    "worker_id": "WorkerId",
+    "rationale": "Bytes",
+    "slash": "bool",
+    "working_group": "WorkingGroup"
+  },
+  "ActiveStake": {
+    "stake_id": "StakeId",
+    "source_account_id": "GenericAccountId"
+  },
+  "Finalized": {
+    "proposalStatus": "ProposalDecisionStatus",
+    "finalizedAt": "u32",
+    "encodedUnstakingErrorDueToBrokenRuntime": "Option<Vec<u8>>",
+    "stakeDataAfterUnstakingError": "Option<ActiveStake>"
+  },
+  "ProposalDecisionStatus": {
+    "_enum": {
+      "Canceled": "Null",
+      "Vetoed": "Null",
+      "Rejected": "Null",
+      "Slashed": "Null",
+      "Expired": "Null",
+      "Approved": "Approved"
+    }
+  },
+  "ExecutionFailed": {
+    "error": "Text"
+  },
+  "Approved": {
+    "_enum": {
+      "PendingExecution": "Null",
+      "Executed": "Null",
+      "ExecutionFailed": "ExecutionFailed"
+    }
+  },
+  "SetLeadParams": "(MemberId,GenericAccountId)",
+  "Nonce": "u64",
+  "EntityId": "u64",
+  "ClassId": "u64",
+  "CuratorGroupId": "u64",
+  "VecMaxLength": "u16",
+  "TextMaxLength": "u16",
+  "HashedTextMaxLength": "Option<u16>",
+  "PropertyId": "u16",
+  "SchemaId": "u16",
+  "SameController": "bool",
+  "ClassPermissions": {
+    "any_member": "bool",
+    "entity_creation_blocked": "bool",
+    "all_entity_property_values_locked": "bool",
+    "maintainers": "Vec<CuratorGroupId>"
+  },
+  "PropertyTypeSingle": {
+    "_enum": {
+      "Bool": "Null",
+      "Uint16": "Null",
+      "Uint32": "Null",
+      "Uint64": "Null",
+      "Int16": "Null",
+      "Int32": "Null",
+      "Int64": "Null",
+      "Text": "TextMaxLength",
+      "Hash": "HashedTextMaxLength",
+      "Reference": "(ClassId,SameController)"
+    }
+  },
+  "PropertyTypeVector": {
+    "vec_type": "PropertyTypeSingle",
+    "max_length": "VecMaxLength"
+  },
+  "PropertyType": {
+    "_enum": {
+      "Single": "PropertyTypeSingle",
+      "Vector": "PropertyTypeVector"
+    }
+  },
+  "PropertyLockingPolicy": {
+    "is_locked_from_maintainer": "bool",
+    "is_locked_from_controller": "bool"
+  },
+  "Property": {
+    "property_type": "PropertyType",
+    "required": "bool",
+    "unique": "bool",
+    "name": "Text",
+    "description": "Text",
+    "locking_policy": "PropertyLockingPolicy"
+  },
+  "Schema": {
+    "properties": "Vec<PropertyId>",
+    "is_active": "bool"
+  },
+  "Class": {
+    "class_permissions": "ClassPermissions",
+    "properties": "Vec<Property>",
+    "schemas": "Vec<Schema>",
+    "name": "Text",
+    "description": "Text",
+    "maximum_entities_count": "EntityId",
+    "current_number_of_entities": "EntityId",
+    "default_entity_creation_voucher_upper_bound": "EntityId"
+  },
+  "EntityController": {
+    "_enum": {
+      "Maintainers": "Null",
+      "Member": "MemberId",
+      "Lead": "Null"
+    }
+  },
+  "EntityPermissions": {
+    "controller": "EntityController",
+    "frozen": "bool",
+    "referenceable": "bool"
+  },
+  "StoredValue": {
+    "_enum": {
+      "Bool": "bool",
+      "Uint16": "u16",
+      "Uint32": "u32",
+      "Uint64": "u64",
+      "Int16": "i16",
+      "Int32": "i32",
+      "Int64": "i64",
+      "Text": "Text",
+      "Hash": "Hash",
+      "Reference": "EntityId"
+    }
+  },
+  "VecStoredValue": {
+    "_enum": {
+      "Bool": "Vec<bool>",
+      "Uint16": "Vec<u16>",
+      "Uint32": "Vec<u32>",
+      "Uint64": "Vec<u64>",
+      "Int16": "Vec<i16>",
+      "Int32": "Vec<i32>",
+      "Int64": "Vec<i64>",
+      "Hash": "Vec<Hash>",
+      "Text": "Vec<Text>",
+      "Reference": "Vec<EntityId>"
+    }
+  },
+  "VecStoredPropertyValue": {
+    "vec_value": "VecStoredValue",
+    "nonce": "Nonce"
+  },
+  "StoredPropertyValue": {
+    "_enum": {
+      "Single": "StoredValue",
+      "Vector": "VecStoredPropertyValue"
+    }
+  },
+  "InboundReferenceCounter": {
+    "total": "u32",
+    "same_owner": "u32"
+  },
+  "Entity": {
+    "entity_permissions": "EntityPermissions",
+    "class_id": "ClassId",
+    "supported_schemas": "Vec<SchemaId>",
+    "values": "BTreeMap<PropertyId,StoredPropertyValue>",
+    "reference_counter": "InboundReferenceCounter"
+  },
+  "CuratorGroup": {
+    "curators": "Vec<u64>",
+    "active": "bool",
+    "number_of_classes_maintained": "u32"
+  },
+  "EntityCreationVoucher": {
+    "maximum_entities_count": "EntityId",
+    "entities_created": "EntityId"
+  },
+  "Actor": {
+    "_enum": {
+      "Curator": "(CuratorGroupId,u64)",
+      "Member": "MemberId",
+      "Lead": "Null"
+    }
+  },
+  "EntityReferenceCounterSideEffect": {
+    "total": "i32",
+    "same_owner": "i32"
+  },
+  "ReferenceCounterSideEffects": "BTreeMap<EntityId,EntityReferenceCounterSideEffect>",
+  "SideEffects": "Option<ReferenceCounterSideEffects>",
+  "SideEffect": "Option<(EntityId,EntityReferenceCounterSideEffect)>",
+  "Status": "bool",
+  "InputValue": {
+    "_enum": {
+      "Bool": "bool",
+      "Uint16": "u16",
+      "Uint32": "u32",
+      "Uint64": "u64",
+      "Int16": "i16",
+      "Int32": "i32",
+      "Int64": "i64",
+      "Text": "Text",
+      "TextToHash": "Text",
+      "Reference": "EntityId"
+    }
+  },
+  "VecInputValue": {
+    "_enum": {
+      "Bool": "Vec<bool>",
+      "Uint16": "Vec<u16>",
+      "Uint32": "Vec<u32>",
+      "Uint64": "Vec<u64>",
+      "Int16": "Vec<i16>",
+      "Int32": "Vec<i32>",
+      "Int64": "Vec<i64>",
+      "TextToHash": "Vec<Text>",
+      "Text": "Vec<Text>",
+      "Reference": "Vec<EntityId>"
+    }
+  },
+  "InputPropertyValue": {
+    "_enum": {
+      "Single": "InputValue",
+      "Vector": "VecInputValue"
+    }
+  },
+  "ParameterizedEntity": {
+    "_enum": {
+      "InternalEntityJustAdded": "u32",
+      "ExistingEntity": "EntityId"
+    }
+  },
+  "ParametrizedPropertyValue": {
+    "_enum": {
+      "InputPropertyValue": "InputPropertyValue",
+      "InternalEntityJustAdded": "u32",
+      "InternalEntityVec": "Vec<ParameterizedEntity>"
+    }
+  },
+  "ParametrizedClassPropertyValue": {
+    "in_class_index": "PropertyId",
+    "value": "ParametrizedPropertyValue"
+  },
+  "CreateEntityOperation": {
+    "class_id": "ClassId"
+  },
+  "UpdatePropertyValuesOperation": {
+    "entity_id": "ParameterizedEntity",
+    "new_parametrized_property_values": "Vec<ParametrizedClassPropertyValue>"
+  },
+  "AddSchemaSupportToEntityOperation": {
+    "entity_id": "ParameterizedEntity",
+    "schema_id": "SchemaId",
+    "parametrized_property_values": "Vec<ParametrizedClassPropertyValue>"
+  },
+  "OperationType": {
+    "_enum": {
+      "CreateEntity": "CreateEntityOperation",
+      "UpdatePropertyValues": "UpdatePropertyValuesOperation",
+      "AddSchemaSupportToEntity": "AddSchemaSupportToEntityOperation"
+    }
+  },
+  "ClassPermissionsType": "Null",
+  "ClassPropertyValue": "Null",
+  "Operation": "Null",
+  "ReferenceConstraint": "Null"
+}

+ 11 - 4
tests/network-tests/run-tests.sh

@@ -1,6 +1,9 @@
 #!/usr/bin/env bash
 set -e
 
+SCRIPT_PATH="$(dirname "${BASH_SOURCE[0]}")"
+cd $SCRIPT_PATH
+
 # Location that will be mounted as the /data volume in containers
 # This is how we access the initial members and balances files from
 # the containers and where generated chainspec files will be located.
@@ -51,9 +54,14 @@ docker run --rm -v ${DATA_PATH}:/data joystream/node:${RUNTIME} build-spec \
   --raw --disable-default-bootnode \
   --chain /data/chain-spec.json > ~/tmp/chain-spec-raw.json
 
+NETWORK_ARG=
+if [ "$ATTACH_TO_NETWORK" != "" ]; then
+  NETWORK_ARG="--network ${ATTACH_TO_NETWORK}"
+fi
+
 # Start a chain with generated chain spec
 # Add "-l ws=trace,ws::handler=info" to get websocket trace logs
-CONTAINER_ID=`docker run -d -v ${DATA_PATH}:/data -p 9944:9944 joystream/node:${RUNTIME} \
+CONTAINER_ID=`docker run -d -v ${DATA_PATH}:/data -p 9944:9944 ${NETWORK_ARG} --name joystream-node joystream/node:${RUNTIME} \
   --validator --alice --unsafe-ws-external --rpc-cors=all -l runtime \
   --chain /data/chain-spec-raw.json`
 
@@ -77,9 +85,8 @@ if [ "$TARGET_RUNTIME" == "$RUNTIME" ]; then
 else
   # Copy new runtime wasm file from target joystream/node image
   echo "Extracting wasm blob from target joystream/node image."
-  mkdir -p .tmp/
   id=`docker create joystream/node:${TARGET_RUNTIME}`
-  docker cp $id:/joystream/runtime.compact.wasm .tmp/
+  docker cp $id:/joystream/runtime.compact.wasm ${DATA_PATH}
   docker rm $id
 
   # Display runtime version before runtime upgrade
@@ -87,7 +94,7 @@ else
 
   echo "Performing runtime upgrade."
   DEBUG=* yarn workspace api-scripts tsnode-strict \
-    src/dev-set-runtime-code.ts -- `pwd`/.tmp/runtime.compact.wasm
+    src/dev-set-runtime-code.ts -- ${DATA_PATH}/runtime.compact.wasm
 
   echo "Runtime upgraded."
 fi

+ 1 - 0
tests/query-node/.gitignore

@@ -0,0 +1 @@
+hydra-test

+ 32 - 0
tests/query-node/run-tests.sh

@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+set -e
+
+SCRIPT_PATH="$(dirname "${BASH_SOURCE[0]}")"
+cd $SCRIPT_PATH
+mkdir -p hydra-test && cd hydra-test
+
+npx @dzlzv/hydra-cli@v0.0.16 scaffold --wsProviderUrl=ws://joystream-node:9944/ --projectName=Test
+
+cp ../../../types/augment/all/defs.json ./typedefs.json
+export TYPES_JSON=../../typedefs.json
+
+# Joystream mappings and graphsql schema
+# cp ../../../query-node/mappings/src/* ./mappings
+# cp ../../../query-node/schema.graphql ./
+
+function cleanup() {
+    docker logs hydra-test_processor_1 --tail 15
+    docker-compose down -v
+}
+
+trap cleanup EXIT
+
+yarn docker:db:up
+yarn docker:build
+yarn docker:db:migrate
+yarn docker:up
+
+# Run tests
+ATTACH_TO_NETWORK=hydra-test_default ../../network-tests/run-tests.sh content-directory
+
+

File diff suppressed because it is too large
+ 1 - 1
types/augment-codec/all.ts


File diff suppressed because it is too large
+ 0 - 0
types/augment-codec/augment-types.ts


+ 1 - 0
types/augment/all/defs.json

@@ -966,6 +966,7 @@
             "AddSchemaSupportToEntity": "AddSchemaSupportToEntityOperation"
         }
     },
+    "InputEntityValuesMap": "BTreeMap<PropertyId,InputPropertyValue>",
     "ClassPermissionsType": "Null",
     "ClassPropertyValue": "Null",
     "Operation": "Null",

+ 3 - 0
types/augment/all/types.ts

@@ -565,6 +565,9 @@ export interface InboundReferenceCounter extends Struct {
   readonly same_owner: u32;
 }
 
+/** @name InputEntityValuesMap */
+export interface InputEntityValuesMap extends BTreeMap<PropertyId, InputPropertyValue> {}
+
 /** @name InputPropertyValue */
 export interface InputPropertyValue extends Enum {
   readonly isSingle: boolean;

File diff suppressed because it is too large
+ 0 - 0
types/augment/augment-types.ts


+ 3 - 0
types/src/content-directory/index.ts

@@ -269,6 +269,8 @@ export class ClassPropertyValue extends Null {}
 export class Operation extends Null {}
 export class ReferenceConstraint extends Null {}
 
+export class InputEntityValuesMap extends BTreeMap.with(PropertyId, InputPropertyValue) {}
+
 export const contentDirectoryTypes = {
   Nonce,
   EntityId,
@@ -316,6 +318,7 @@ export const contentDirectoryTypes = {
   UpdatePropertyValuesOperation,
   AddSchemaSupportToEntityOperation,
   OperationType,
+  InputEntityValuesMap,
   // Versioned store relicts - to be removed:
   ClassPermissionsType,
   ClassPropertyValue,

+ 3 - 1
types/src/index.ts

@@ -15,7 +15,7 @@ import media from './media'
 import proposals from './proposals'
 import contentDirectory from './content-directory'
 import { InterfaceTypes } from '@polkadot/types/types/registry'
-import { TypeRegistry, Text, UInt, Null, bool, Option, Vec, BTreeSet } from '@polkadot/types'
+import { TypeRegistry, Text, UInt, Null, bool, Option, Vec, BTreeSet, BTreeMap } from '@polkadot/types'
 import { ExtendedEnum } from './JoyEnum'
 import { ExtendedStruct } from './JoyStruct'
 import BN from 'bn.js'
@@ -87,6 +87,8 @@ type CreateInterface_NoOption<T extends Codec> =
       ? boolean
       : T extends Vec<infer S> | BTreeSet<infer S>
       ? CreateInterface<S>[]
+      : T extends BTreeMap<infer K, infer V>
+      ? Map<K, V>
       : any)
 
 // Wrapper for CreateInterface_NoOption that includes resolving an Option

Some files were not shown because too many files changed in this diff