Browse Source

Merge remote-tracking branch 'upstream/sumer' into qnode-indexer-processor

metmirr 4 years ago
parent
commit
6c048484c6
100 changed files with 2296 additions and 3379 deletions
  1. 7 3
      .github/workflows/run-network-tests.yml
  2. 4 2
      Cargo.lock
  3. 2 2
      apps.Dockerfile
  4. 1 1
      cli/README.md
  5. 4 4
      cli/package.json
  6. 3 3
      cli/src/Api.ts
  7. 16 15
      cli/src/base/ApiCommandBase.ts
  8. 2 2
      cli/src/commands/api/setUri.ts
  9. 3 2
      cli/src/commands/media/uploadVideo.ts
  10. 0 10
      content-directory-schemas/.gitignore
  11. 0 1
      content-directory-schemas/.npmignore
  12. 0 279
      content-directory-schemas/README.md
  13. 0 52
      content-directory-schemas/examples/createChannel.ts
  14. 0 68
      content-directory-schemas/examples/createChannelWithoutTransaction.ts
  15. 0 76
      content-directory-schemas/examples/createVideo.ts
  16. 0 47
      content-directory-schemas/examples/updateChannelTitle.ts
  17. 0 47
      content-directory-schemas/examples/updateChannelTitleWithoutTransaction.ts
  18. 0 7
      content-directory-schemas/inputs/classes/ChannelClass.json
  19. 0 6
      content-directory-schemas/inputs/classes/ContentCategoryClass.json
  20. 0 6
      content-directory-schemas/inputs/classes/FeaturedVideoClass.json
  21. 0 7
      content-directory-schemas/inputs/classes/HttpMediaLocationClass.json
  22. 0 7
      content-directory-schemas/inputs/classes/JoystreamMediaLocationClass.json
  23. 0 6
      content-directory-schemas/inputs/classes/KnownLicenseClass.json
  24. 0 6
      content-directory-schemas/inputs/classes/LanguageClass.json
  25. 0 7
      content-directory-schemas/inputs/classes/LicenseClass.json
  26. 0 7
      content-directory-schemas/inputs/classes/MediaLocationClass.json
  27. 0 7
      content-directory-schemas/inputs/classes/UserDefinedLicenseClass.json
  28. 0 7
      content-directory-schemas/inputs/classes/VideoClass.json
  29. 0 7
      content-directory-schemas/inputs/classes/VideoMediaClass.json
  30. 0 6
      content-directory-schemas/inputs/classes/VideoMediaEncodingClass.json
  31. 0 18
      content-directory-schemas/inputs/classes/index.js
  32. 0 20
      content-directory-schemas/inputs/entityBatches/ContentCategoryBatch.json
  33. 0 61
      content-directory-schemas/inputs/entityBatches/KnownLicenseBatch.json
  34. 0 43
      content-directory-schemas/inputs/entityBatches/LanguageBatch.json
  35. 0 27
      content-directory-schemas/inputs/entityBatches/VideoMediaEncodingBatch.json
  36. 0 49
      content-directory-schemas/inputs/schemas/ChannelSchema.json
  37. 0 20
      content-directory-schemas/inputs/schemas/ContentCategorySchema.json
  38. 0 12
      content-directory-schemas/inputs/schemas/FeaturedVideoSchema.json
  39. 0 20
      content-directory-schemas/inputs/schemas/HttpMediaLocationSchema.json
  40. 0 13
      content-directory-schemas/inputs/schemas/JoystreamMediaLocationSchema.json
  41. 0 52
      content-directory-schemas/inputs/schemas/KnownLicenseSchema.json
  42. 0 20
      content-directory-schemas/inputs/schemas/LanguageSchema.json
  43. 0 23
      content-directory-schemas/inputs/schemas/LicenseSchema.json
  44. 0 19
      content-directory-schemas/inputs/schemas/MediaLocationSchema.json
  45. 0 13
      content-directory-schemas/inputs/schemas/UserDefinedLicenseSchema.json
  46. 0 13
      content-directory-schemas/inputs/schemas/VideoMediaEncodingSchema.json
  47. 0 40
      content-directory-schemas/inputs/schemas/VideoMediaSchema.json
  48. 0 99
      content-directory-schemas/inputs/schemas/VideoSchema.json
  49. 0 56
      content-directory-schemas/package.json
  50. 0 155
      content-directory-schemas/schemas/extrinsics/AddClassSchema.schema.json
  51. 0 48
      content-directory-schemas/schemas/extrinsics/CreateClass.schema.json
  52. 0 38
      content-directory-schemas/schemas/propertyValidationDefs.schema.json
  53. 0 65
      content-directory-schemas/scripts/initializeContentDir.ts
  54. 0 163
      content-directory-schemas/scripts/inputSchemasToEntitySchemas.ts
  55. 0 56
      content-directory-schemas/scripts/schemasToTS.ts
  56. 0 85
      content-directory-schemas/scripts/validate.ts
  57. 0 435
      content-directory-schemas/src/helpers/InputParser.ts
  58. 0 19
      content-directory-schemas/src/helpers/initialize.ts
  59. 0 40
      content-directory-schemas/src/helpers/inputs.ts
  60. 0 16
      content-directory-schemas/src/helpers/propertyType.ts
  61. 0 8
      content-directory-schemas/src/helpers/schemas.ts
  62. 0 6
      content-directory-schemas/src/index.ts
  63. 0 27
      content-directory-schemas/tsconfig.json
  64. 0 7
      content-directory-schemas/tsconfig.lib.json
  65. 0 4
      content-directory-schemas/types/EntityBatch.d.ts
  66. 0 67
      content-directory-schemas/types/extrinsics/AddClassSchema.d.ts
  67. 0 30
      content-directory-schemas/types/extrinsics/CreateClass.d.ts
  68. 0 2
      content-directory-schemas/types/extrinsics/index.d.ts
  69. 0 1
      content-directory-schemas/types/utility.d.ts
  70. 0 2
      content-directory-schemas/typings/@joystream/prettier-config/index.d.ts
  71. 0 60
      content-directory-schemas/vscode-recommended.settings.json
  72. 1 1
      node/Cargo.toml
  73. 48 38
      node/src/chain_spec/content_config.rs
  74. 7 0
      node/src/chain_spec/mod.rs
  75. 0 1
      package.json
  76. 1 0
      pioneer/packages/apps/public/locales/en/joy-media.json
  77. 3 1
      pioneer/packages/joy-utils/src/consts/workingGroups.ts
  78. 196 175
      query-node/schema.graphql
  79. 4 2
      runtime-modules/common/src/lib.rs
  80. 6 9
      runtime-modules/common/src/storage.rs
  81. 2 0
      runtime-modules/content/Cargo.toml
  82. 26 3
      runtime-modules/content/src/errors.rs
  83. 547 215
      runtime-modules/content/src/lib.rs
  84. 7 37
      runtime-modules/content/src/permissions/curator_group.rs
  85. 197 6
      runtime-modules/content/src/permissions/mod.rs
  86. 181 0
      runtime-modules/content/src/tests/channels.rs
  87. 296 0
      runtime-modules/content/src/tests/mock.rs
  88. 4 0
      runtime-modules/content/src/tests/mod.rs
  89. 6 0
      runtime-modules/governance/src/mock.rs
  90. 129 34
      runtime-modules/membership/src/lib.rs
  91. 5 0
      runtime-modules/membership/src/mock.rs
  92. 30 13
      runtime-modules/membership/src/tests.rs
  93. 5 0
      runtime-modules/proposals/codex/src/tests/mock.rs
  94. 5 0
      runtime-modules/proposals/discussion/src/tests/mock.rs
  95. 5 0
      runtime-modules/proposals/engine/src/tests/mock/mod.rs
  96. 5 0
      runtime-modules/service-discovery/src/mock.rs
  97. 189 163
      runtime-modules/storage/src/data_directory.rs
  98. 5 0
      runtime-modules/storage/src/lib.rs
  99. 344 1
      runtime-modules/storage/src/tests/data_directory.rs
  100. 0 28
      runtime-modules/storage/src/tests/data_object_type_registry.rs

+ 7 - 3
.github/workflows/run-network-tests.yml

@@ -145,12 +145,10 @@ jobs:
           docker images
       - name: Install packages and dependencies
         run: yarn install --frozen-lockfile
-      - name: Ensure tests are runnable
-        run: yarn workspace @joystream/cd-schemas checks --quiet
       - name: Start chain
         run: docker-compose up -d joystream-node
       - name: Initialize the content directory
-        run: yarn workspace @joystream/cd-schemas initialize:dev
+        run: yarn workspace api-scripts initialize-content-lead
 
   query_node:
     name: Query Node Integration Tests
@@ -223,3 +221,9 @@ jobs:
               echo "Upload test failed, will retry"
             fi
           done
+          # Wait for storage-node to set status Accepted on uploaded content
+          sleep 6
+          cd utils/api-scripts/
+          # Assume only one accepted data object was created
+          CONTENT_ID=`yarn --silent script get-first-content-id | tail -n2 | head -n1`
+          yarn storage-cli download http://localhost:3001 ${CONTENT_ID} ./download.png

+ 4 - 2
Cargo.lock

@@ -1993,7 +1993,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node"
-version = "3.6.0"
+version = "3.7.1"
 dependencies = [
  "frame-benchmarking",
  "frame-benchmarking-cli",
@@ -2053,7 +2053,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime"
-version = "7.10.0"
+version = "7.13.0"
 dependencies = [
  "frame-benchmarking",
  "frame-executive",
@@ -3311,7 +3311,9 @@ version = "3.0.0"
 dependencies = [
  "frame-support",
  "frame-system",
+ "pallet-balances",
  "pallet-common",
+ "pallet-timestamp",
  "parity-scale-codec",
  "serde",
  "sp-arithmetic 2.0.0-rc4",

+ 2 - 2
apps.Dockerfile

@@ -6,9 +6,9 @@ COPY . /joystream
 # Do not set NODE_ENV=production until after running yarn install
 # to ensure dev dependencies are installed.
 RUN yarn install --frozen-lockfile
-
+# Uncomment query-node build when mappings are fixed
+# RUN yarn workspace query-node-root build
 RUN yarn workspace pioneer build
 RUN yarn workspace storage-node build
-RUN yarn workspace query-node-root build
 
 ENTRYPOINT [ "yarn" ]

+ 1 - 1
cli/README.md

@@ -44,7 +44,7 @@ $ npm install -g @joystream/cli
 $ joystream-cli COMMAND
 running command...
 $ joystream-cli (-v|--version|version)
-@joystream/cli/0.3.0 linux-x64 node-v12.18.2
+@joystream/cli/0.3.2 linux-x64 node-v13.12.0
 $ joystream-cli --help [COMMAND]
 USAGE
   $ joystream-cli COMMAND

+ 4 - 4
cli/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@joystream/cli",
   "description": "Command Line Interface for Joystream community and governance activities",
-  "version": "0.3.1",
+  "version": "0.3.2",
   "author": "Leszek Wiesner",
   "bin": {
     "joystream-cli": "./bin/run"
@@ -10,8 +10,7 @@
   "dependencies": {
     "@apidevtools/json-schema-ref-parser": "^9.0.6",
     "@ffprobe-installer/ffprobe": "^1.1.0",
-    "@joystream/types": "^0.14.0",
-    "@joystream/cd-schemas": "^0.1.0",
+    "@joystream/types": "^0.16.0",
     "@oclif/command": "^1.5.19",
     "@oclif/config": "^1.14.0",
     "@oclif/plugin-autocomplete": "^0.2.0",
@@ -38,7 +37,8 @@
     "moment": "^2.24.0",
     "proper-lockfile": "^4.1.1",
     "slug": "^2.1.1",
-    "tslib": "^1.11.1"
+    "tslib": "^1.11.1",
+    "axios": "^0.21.1"
   },
   "devDependencies": {
     "@oclif/dev-cli": "^1.22.2",

+ 3 - 3
cli/src/Api.ts

@@ -49,7 +49,7 @@ import { Stake, StakeId } from '@joystream/types/stake'
 
 import { InputValidationLengthConstraint } from '@joystream/types/common'
 import { Class, ClassId, CuratorGroup, CuratorGroupId, Entity, EntityId } from '@joystream/types/content-directory'
-import { ContentId, DataObject } from '@joystream/types/media'
+import { ContentId, DataObject } from '@joystream/types/storage'
 import { ServiceProviderRecord, Url } from '@joystream/types/discovery'
 import _ from 'lodash'
 
@@ -527,8 +527,8 @@ export default class Api {
     return exists ? await this._api.query.contentDirectory.entityById<Entity>(id) : null
   }
 
-  async dataObjectByContentId(contentId: ContentId): Promise<DataObject | null> {
-    const dataObject = await this._api.query.dataDirectory.dataObjectByContentId<Option<DataObject>>(contentId)
+  async dataByContentId(contentId: ContentId): Promise<DataObject | null> {
+    const dataObject = await this._api.query.dataDirectory.dataByContentId<Option<DataObject>>(contentId)
     return dataObject.unwrapOr(null)
   }
 

+ 16 - 15
cli/src/base/ApiCommandBase.ts

@@ -24,7 +24,6 @@ export class ExtrinsicFailedError extends Error {}
  */
 export default abstract class ApiCommandBase extends StateAwareCommandBase {
   private api: Api | null = null
-  forceSkipApiUriPrompt = false
 
   getApi(): Api {
     if (!this.api) throw new CLIError('Tried to get API before initialization.', { exit: ExitCodes.ApiError })
@@ -44,23 +43,25 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     return this.getOriginalApi().createType(typeName, value)
   }
 
-  async init() {
+  async init(skipConnection = false): Promise<void> {
     await super.init()
-    let apiUri: string = this.getPreservedState().apiUri
-    if (!apiUri) {
-      this.warn("You haven't provided a node/endpoint for the CLI to connect to yet!")
-      apiUri = await this.promptForApiUri()
-    }
+    if (!skipConnection) {
+      let apiUri: string = this.getPreservedState().apiUri
+      if (!apiUri) {
+        this.warn("You haven't provided a node/endpoint for the CLI to connect to yet!")
+        apiUri = await this.promptForApiUri()
+      }
 
-    const { metadataCache } = this.getPreservedState()
-    this.api = await Api.create(apiUri, metadataCache)
+      const { metadataCache } = this.getPreservedState()
+      this.api = await Api.create(apiUri, metadataCache)
 
-    const { genesisHash, runtimeVersion } = this.getOriginalApi()
-    const metadataKey = `${genesisHash}-${runtimeVersion.specVersion}`
-    if (!metadataCache[metadataKey]) {
-      // Add new entry to metadata cache
-      metadataCache[metadataKey] = await this.getOriginalApi().runtimeMetadata.toJSON()
-      await this.setPreservedState({ metadataCache })
+      const { genesisHash, runtimeVersion } = this.getOriginalApi()
+      const metadataKey = `${genesisHash}-${runtimeVersion.specVersion}`
+      if (!metadataCache[metadataKey]) {
+        // Add new entry to metadata cache
+        metadataCache[metadataKey] = await this.getOriginalApi().runtimeMetadata.toJSON()
+        await this.setPreservedState({ metadataCache })
+      }
     }
   }
 

+ 2 - 2
cli/src/commands/api/setUri.ts

@@ -15,8 +15,8 @@ export default class ApiSetUri extends ApiCommandBase {
   ]
 
   async init() {
-    this.forceSkipApiUriPrompt = true
-    await super.init()
+    // Pass "skipConnection" arg to prevent command from exiting if current api uri is invalid
+    await super.init(true)
   }
 
   async run() {

+ 3 - 2
cli/src/commands/media/uploadVideo.ts

@@ -8,7 +8,7 @@ import { JsonSchemaPrompter } from '../../helpers/JsonSchemaPrompt'
 import { flags } from '@oclif/command'
 import fs from 'fs'
 import ExitCodes from '../../ExitCodes'
-import { ContentId } from '@joystream/types/media'
+import { ContentId } from '@joystream/types/storage'
 import ipfsHash from 'ipfs-only-hash'
 import { cli } from 'cli-ux'
 import axios, { AxiosRequestConfig } from 'axios'
@@ -204,6 +204,7 @@ export default class UploadVideoCommand extends MediaCommandBase {
           'Content-Length': fileSize.toString(),
         },
         maxContentLength: MAX_FILE_SIZE,
+        maxBodyLength: MAX_FILE_SIZE,
       }
       await axios.put(uploadUrl, fileStream, config)
       cli.action.stop()
@@ -395,7 +396,7 @@ export default class UploadVideoCommand extends MediaCommandBase {
       ipfsCid,
     ])
 
-    const dataObject = await this.getApi().dataObjectByContentId(contentId)
+    const dataObject = await this.getApi().dataByContentId(contentId)
     if (!dataObject) {
       this.error('Data object could not be retrieved from chain', { exit: ExitCodes.ApiError })
     }

+ 0 - 10
content-directory-schemas/.gitignore

@@ -1,10 +0,0 @@
-operations.json
-
-# Auto-generated
-schemas/entities
-schemas/entityBatches
-schemas/entityReferences
-types/entities
-
-# Build
-lib

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

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

+ 0 - 279
content-directory-schemas/README.md

@@ -1,279 +0,0 @@
-# Content directory tooling
-
-## Definitions
-
-In order to make this documentation as clear as possible it is important to make a strict distinction between two types of schemas:
-
-- `json-schemas` mean files with `.schema.json` extension. This is a common standard for describing how to validate other `json` files or objects (ie. a `package.json` file may be an example of a file that can be supported by a `json-schema`). A documentation of this standard can be found here: https://json-schema.org/
-- `runtime-scheams` means schemas as they are "understood" by the `content-directory` runtime module, so schemas that can be added to classes via `api.tx.contentDirectory.addClassSchema` and linked to entities via `api.tx.contentDirectory.addSchemaSupportToEntity`
-
-## Content directory input
-
-### Initializing content directory
-
-In order to intialize the content directory on a development chain based on data that is provided in form of json files inside `/inputs` directory (`classes`, `schemas` and example entities - `entityBatches`), we can run:
-
-```
-yarn workspace @joystream/cd-schemas initialize:dev
-```
-
-This will handle:
-
-- Creating a membership for `ALICE` (if not already created)
-- Setting (hiring) `ALICE` as content curators lead (if not already set)
-- Creating classes in the runtime based on `inputs/classes` json inputs (if the content directory is currently empty)
-- Creating schemas in the runtime based on `inputs/schemas` and adding them to the related classes
-- Creating entities based on `inputs/entityBatches`. Those json inputs allow describing entities and relationships between them in a simplified way and are then converted into one huge `api.tx.contentDirectory.transaction` call (this is further described in _**Entity batches**_ section).
-
-### Input files naming
-
-In order to get the full benefit of the tooling, in some cases you may need to respect a specific pattern of file naming:
-
-Each input file name should end with `Class`, `Schema` or `Batch` (depending on the input type, ie. `LanguageBatch`).
-It is also recommended that each of those file names starts with a class name (currently in `entityBatches` there's no distinction between schemas and classes, as it is assumed there will be a one-to-one relationship between them)
-
-### `json-schemas` support for json inputs in `VSCode`
-
-In order to link json files inside `inputs` directory to `json-schemas` inside `schemas` and have them validated in real-time by the IDE, follow the steps below:
-
-**If you don't have `.vscode/settings.json` in the root monorepo workspace yet:**
-
-1. Create `.vscode` directory inside your monorepo workspace
-1. Copy `vscode-recommended.settings.json` into this `.vscode` directory and rename it to `settings.json`.
-
-**If you already have the `.vscode/settings.json` file in the root monorepo workspace:**
-
-1. Copy the settings from `vscode-recommended.settings.json` and merge them with the existing `.vscode/settings.json`
-
-Now all the json files matching `*Class.json`, `*Schema.json`, `*{EntityName}Batch.json` patters will be linked to the correct `json schemas`. If you edit any file inside `inputs` or add a new one that follows the naming pattern (described in _Input files naming_), you should get the benefit of autocompleted properties, validated input, on-hover tooltips with property descriptions etc.
-
-For more context, see: https://code.visualstudio.com/docs/languages/json
-
-### Validate inputs and `json-schemas` via a command
-
-All inputs inside `inputs` directory and `json-schemas` used to validate those inputs can also be validated using `yarn workspace @joystream/cd-schemas validate` command. This is mainly to facilitate checking the validity of `.json` and `.schema.json` files inside `content-directory-schemas` through CI.
-
-### Entity batches
-
-The concept of entity batches (`inputs/entityBatches`) basically provides an easy way of describing complex input to content directory (ie. many entities related to each other in many ways) without the need to deal with lower-level, hard-to-validate runtime operations like `CreateEntity` and `AddSchemaSupportEntity` and trying to glue them together into a huge `api.tx.contentDirectory.transaction` call.
-
-Instead, the script that initializes the content directory (`scripts/initializeContentDir.ts`) is able to generate the complex `api.tx.contentDirectory.transaction` call based on a more human-readable input provided in `inputs/entityBatches`.
-
-This input can be provided as a simple json array of objects matching `{ [propertyName]: propertyValue}` structure.
-
-For example, in order to describe creating entities as simple as `Language`, which only has `Code` and `Name` properties, we can just create an array of objects like:
-
-```
-[
-  { "Code": "EN", "Name": "English" },
-  { "Code": "RU", "Name": "Russian" },
-  { "Code": "DE", "Name": "German" }
-]
-```
-
-_(This is the actual content of `inputs/entityBatches/LanguageBatch.json`)_
-
-#### Related entities
-
-There also exists a specific syntax for defining relations between entities in batches.
-We can do it by either using `"new"` or `"existing"` keyword.
-
-- The `"new"` keyword allows describing a scenario where related entity should be created **along with** the main entity and then referenced by it. An example of this could be `Video` and `VideoMedia` which have a one-to-one relationship and it doesn't make much sense to specify them in separate batches. Instead, we can use a syntax like:
-
-```
-{
-  "title": "Awesome video",
-  /* other Video properties... */
-  "media": { "new": {
-    "pixelWidth": 1024,
-    "pixelHeight": 764,
-    /* other VideoMedia object properties... */
-  }
-}
-```
-
-- The `"existing"` keyword allows referencing an entity created as part of any other batch inside `inputs/entityBatches`. We can do it by specifying the value of **any unique property of the referenced entity**. So, for example to reference a `Language` entity from `VideoBatch.json` file, we use this syntax:
-
-```
-{
-  "title": "Awesome video",
-  /* other Video properties... */
-  "language": { "existing": { "Code": "EN" } }
-}
-```
-
-## `json-schemas` and tooling
-
-### Entity `json-schemas`
-
-There is a script that provides an easy way of converting `runtime-schemas` (based on inputs from `inputs/schemas`) to `json-schemas` (`.schema.json` files) which allow validating the input (ie. json files) describing some specific entities. It can be run with:
-
-```
-yarn workspace @joystream/cd-schemas generate:entity-schemas
-```
-
-Those `json-schemas` are currently mainly used for validating the inputs inside `inputs/entityBatches`.
-
-The generated `json-schemas` include:
-
-- `schemas/entities` - `json-schemas` that provide validation for given entity (ie. `Video`) input. They can, for example, check if the `title` property in a json object is a string that is no longer than `64` characters. They are used to validate a single entity in `inputs/entityBatches`, but can also be re-used to provide "frontend" validation of any entity input to the content directory (ie. input provided to/via `joystream-cli`).
-- `schemas/entityReferences` - `json-schemas` that describe how an entity of given class can be referenced. Currently they are used for providing an easy way of referencing entites between batches in `inputs/entityBatches`. For more details on how entities can be referenced in batches, read the _**Entity batches**_ section.
-- `schemas/entityBatches` - very simple `json-schemas` that basically just provide `array` wrappers over `schemas/entities`. Those are the actual `json-schemas` that can be linked to json input files inside `inputs/entityBatches` (ie. via `.vscode/settings.json`)
-
-### Typescript support
-
-Thanks to the `json-schema-to-typescript` library, we can very simply generate Typescript interfaces based on existing `json-schemas`. This can be done via:
-
-```
-yarn workspace @joystream/cd-schemas generate:types
-```
-
-This command will generate:
-
-- `types/entities` based on `schemas/entities`, providing typescript interfaces for entities like `Video` etc. (note that this interface will include a peculiar way of describing entity relationships, further described in _**Entity batches**_ section)
-- `types/extrinsics` based on `schemas/extrinsics`, providing typescript interfaces for input to extrinsics like `AddClassSchema` and `CreateClass`
-
-The most obvious use-case of those interfaces currently is that when we're parsing any json files inside `inputs` using a Typescript code, we can assert that the resulting object will be of given type, ie.:
-
-```
-const createClassInput = JSON.parse(fs.readFileSync('/path/to/inputs/LanguageClass.json')) as CreateClass
-```
-
-Besides that, a Typescript code can be written to generate some inputs (ie. using a loop) that can then can be used to create classes/schemas or insert entities into the content directory.
-
-There are a lot of other potential use-cases, but for the purpose of this documentation it should be enough to mention there exists this very easy way of converting `.schema.json` files into Typescript interfaces.
-
-## Using as library
-
-The `content-directory-schemas` directory of the monorepo is constructed in such a way, that it should be possible to use it as library and import from it json schemas, types (mentioned in `Typescript support` section) and tools to, for example, convert entity input like this described in the `Entity batches` section into `CreateEntity`, `AddSchemaSupportToEntity` and/or `UpdateEntityPropertyValues` operations.
-
-### Examples
-
-The best way to ilustrate this would be by providing some examples:
-
-#### Creating a channel
-```
-  import { InputParser } from '@joystream/cd-schemas'
-  import { ChannelEntity } from '@joystream/cd-schemas/types/entities/ChannelEntity'
-  // Other imports...
-
-  async main() {
-    // Initialize the api, SENDER_KEYPAIR and SENDER_MEMBER_ID...
-
-    const channel: ChannelEntity = {
-      handle: 'Example channel',
-      description: 'This is an example channel',
-      language: { existing: { code: 'EN' } },
-      coverPhotoUrl: '',
-      avatarPhotoUrl: '',
-      isPublic: true,
-    }
-
-    const parser = InputParser.createWithKnownSchemas(api, [
-      {
-        className: 'Channel',
-        entries: [channel],
-      },
-    ])
-
-    const operations = await parser.getEntityBatchOperations()
-    await api.tx.contentDirectory
-      .transaction({ Member: SENDER_MEMBER_ID }, operations)
-      .signAndSend(SENDER_KEYPAIR)
-  }
-```
-_Full example with comments can be found in `content-directory-schemas/examples/createChannel.ts` and ran with `yarn workspace @joystream/cd-schemas example:createChannel`_
-
-#### Creating a video
-```
-import { InputParser } from '@joystream/cd-schemas'
-import { VideoEntity } from '@joystream/cd-schemas/types/entities/VideoEntity'
-// ...
-
-async main() {
-  // ...
-
-  const video: VideoEntity = {
-    title: 'Example video',
-    description: 'This is an example video',
-    language: { existing: { code: 'EN' } },
-    category: { existing: { name: 'Education' } },
-    channel: { existing: { handle: 'Example channel' } },
-    media: {
-      new: {
-        encoding: { existing: { name: 'H.263_MP4' } },
-        pixelHeight: 600,
-        pixelWidth: 800,
-        location: {
-          new: {
-            httpMediaLocation: {
-              new: { url: 'https://testnet.joystream.org/' },
-            },
-          },
-        },
-      },
-    },
-    license: {
-      new: {
-        knownLicense: {
-          existing: { code: 'CC_BY' },
-        },
-      },
-    },
-    duration: 3600,
-    thumbnailUrl: '',
-    isExplicit: false,
-    isPublic: true,
-  }
-
-  const parser = InputParser.createWithKnownSchemas(api, [
-    {
-      className: 'Video',
-      entries: [video],
-    },
-  ])
-
-  const operations = await parser.getEntityBatchOperations()
-  await api.tx.contentDirectory
-    .transaction({ Member: SENDER_MEMBER_ID }, operations)
-    .signAndSend(SENDER_KEYPAIR)
-}
-```
-_Full example with comments can be found in `content-directory-schemas/examples/createVideo.ts` and ran with `yarn workspace @joystream/cd-schemas example:createChannel`_
-
-#### Update channel handle
-
-```
-import { InputParser } from '@joystream/cd-schemas'
-import { ChannelEntity } from '@joystream/cd-schemas/types/entities/ChannelEntity'
-// ...
-
-async function main() {
-  // ...
-
-  const channelUpdateInput: Partial<ChannelEntity> = {
-    handle: 'Updated channel handle',
-  }
-
-  const parser = InputParser.createWithKnownSchemas(api)
-
-  const CHANNEL_ID = await parser.findEntityIdByUniqueQuery({ handle: 'Example channel' }, 'Channel')
-
-  const updateOperations = await parser.getEntityUpdateOperations(channelUpdateInput, 'Channel', CHANNEL_ID)
-
-  await api.tx.contentDirectory
-    .transaction({ Member: SENDER_MEMBER_ID }, [updateOperation])
-    .signAndSend(SENDER_KEYPAIR)
-}
-```
-_Full example with comments can be found in `content-directory-schemas/examples/updateChannelHandle.ts` and ran with `yarn workspace @joystream/cd-schemas example:updateChannelHandle`_
-
-Note: Updates can also inlucde `new` and `existing` keywords. In case `new` is specified inside the update - `CreateEntity` and `AddSchemaSupportToEntity` operations will be included as part of the operations returned by `InputParser.getEntityUpdateOperations`.
-
-## Current limitations
-
-Some limitations that should be dealt with in the nearest future:
-
-- Filename restrictions described in **_Input files naming_** section
-- Some code runs on the assumption that there is only one schema for each class, which is very limiting
-- `Vector<Reference>` property type is not yet supported when parsing entity batches

+ 0 - 52
content-directory-schemas/examples/createChannel.ts

@@ -1,52 +0,0 @@
-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 @joystream/cd-schemas (we use it as library here)
-import { InputParser } from '@joystream/cd-schemas'
-import { ChannelEntity } from '@joystream/cd-schemas/types/entities'
-
-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 channel: ChannelEntity = {
-    handle: 'Example channel',
-    description: 'This is an example channel',
-    // We can use "existing" syntax to reference either an on-chain entity or other entity that's part of the same batch.
-    // Here we reference language that we assume was added by initialization script (initialize:dev), as it is part of
-    // input/entityBatches/LanguageBatch.json
-    language: { existing: { code: 'EN' } },
-    coverPhotoUrl: '',
-    avatarPhotoUrl: '',
-    isPublic: true,
-  }
-  // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
-  const parser = InputParser.createWithKnownSchemas(
-    api,
-    // The second argument is an array of entity batches, following standard entity batch syntax ({ className, entries }):
-    [
-      {
-        className: 'Channel',
-        entries: [channel], // We could specify multiple entries here, but in this case we only need one
-      },
-    ]
-  )
-  // We parse the input into CreateEntity and AddSchemaSupportToEntity operations
-  const operations = await parser.getEntityBatchOperations()
-  await api.tx.contentDirectory
-    .transaction(
-      { Member: 0 }, // We use member with id 0 as actor (in this case we assume this is Alice)
-      operations // We provide parsed operations as second argument
-    )
-    .signAndSend(ALICE)
-}
-
-main()
-  .then(() => process.exit())
-  .catch(console.error)

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

@@ -1,68 +0,0 @@
-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 @joystream/cd-schemas (we use it as library here)
-import { InputParser } from '@joystream/cd-schemas'
-import { ChannelEntity } from '@joystream/cd-schemas/types/entities'
-import { FlattenRelations } from '@joystream/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> = {
-    handle: '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)

+ 0 - 76
content-directory-schemas/examples/createVideo.ts

@@ -1,76 +0,0 @@
-import { ApiPromise, WsProvider } from '@polkadot/api'
-import { types as joyTypes } from '@joystream/types'
-import { Keyring } from '@polkadot/keyring'
-// Import input parser and video entity from @joystream/cd-schemas (we use it as library here)
-import { InputParser } from '@joystream/cd-schemas'
-import { VideoEntity } from '@joystream/cd-schemas/types/entities/VideoEntity'
-
-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 video: VideoEntity = {
-    title: 'Example video',
-    description: 'This is an example video',
-    // We reference existing language and category by their unique properties with "existing" syntax
-    // (those referenced here are part of inputs/entityBatches)
-    language: { existing: { code: 'EN' } },
-    category: { existing: { name: 'Education' } },
-    // We use the same "existing" syntax to reference a channel by unique property (handle)
-    // In this case it's a channel that we created in createChannel example
-    channel: { existing: { handle: 'Example channel' } },
-    media: {
-      // We use "new" syntax to sygnalize we want to create a new VideoMedia entity that will be related to this Video entity
-      new: {
-        // We use "exisiting" enconding from inputs/entityBatches/VideoMediaEncodingBatch.json
-        encoding: { existing: { name: 'H.263_MP4' } },
-        pixelHeight: 600,
-        pixelWidth: 800,
-        // We create nested VideoMedia->MediaLocation->HttpMediaLocation relations using the "new" syntax
-        location: { new: { httpMediaLocation: { new: { url: 'https://testnet.joystream.org/' } } } },
-      },
-    },
-    // Here we use combined "new" and "existing" syntaxes to create Video->License->KnownLicense relations
-    license: {
-      new: {
-        knownLicense: {
-          // This license can be found in inputs/entityBatches/KnownLicenseBatch.json
-          existing: { code: 'CC_BY' },
-        },
-      },
-    },
-    duration: 3600,
-    thumbnailUrl: '',
-    isExplicit: false,
-    isPublic: true,
-  }
-  // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
-  const parser = InputParser.createWithKnownSchemas(
-    api,
-    // The second argument is an array of entity batches, following standard entity batch syntax ({ className, entries }):
-    [
-      {
-        className: 'Video',
-        entries: [video], // We could specify multiple entries here, but in this case we only need one
-      },
-    ]
-  )
-  // We parse the input into CreateEntity and AddSchemaSupportToEntity operations
-  const operations = await parser.getEntityBatchOperations()
-  await api.tx.contentDirectory
-    .transaction(
-      { Member: 0 }, // We use member with id 0 as actor (in this case we assume this is Alice)
-      operations // We provide parsed operations as second argument
-    )
-    .signAndSend(ALICE)
-}
-
-main()
-  .then(() => process.exit())
-  .catch(console.error)

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

@@ -1,47 +0,0 @@
-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 @joystream/cd-schemas (we use it as library here)
-import { InputParser } from '@joystream/cd-schemas'
-import { ChannelEntity } from '@joystream/cd-schemas/types/entities/ChannelEntity'
-
-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<ChannelEntity> = {
-    handle: 'Updated channel handle',
-  }
-
-  // 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 ./createChannel.ts example (normally we would probably use some other way to do it, ie.: query node)
-  const CHANNEL_ID = await parser.findEntityIdByUniqueQuery({ handle: 'Example channel' }, 'Channel')
-
-  // Use getEntityUpdateOperations to parse the update input
-  const updateOperations = await parser.getEntityUpdateOperations(
-    channelUpdateInput,
-    'Channel', // Class name
-    CHANNEL_ID // Id of the entity we want to update
-  )
-
-  await api.tx.contentDirectory
-    .transaction(
-      { Member: 0 }, // We use member with id 0 as actor (in this case we assume this is Alice)
-      updateOperations // In this case this will be just a single UpdateEntityPropertyValues operation
-    )
-    .signAndSend(ALICE)
-}
-
-main()
-  .then(() => process.exit())
-  .catch(console.error)

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

@@ -1,47 +0,0 @@
-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 @joystream/cd-schemas (we use it as library here)
-import { InputParser } from '@joystream/cd-schemas'
-import { ChannelEntity } from '@joystream/cd-schemas/types/entities'
-import { FlattenRelations } from '@joystream/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>> = {
-    handle: 'Updated channel handle 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({ handle: '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/ChannelClass.json

@@ -1,7 +0,0 @@
-{
-  "name": "Channel",
-  "description": "A channel belonging to certain member. Members can publish certain type of content (ie. videos) through channels.",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 25,
-  "class_permissions": { "any_member": true }
-}

+ 0 - 6
content-directory-schemas/inputs/classes/ContentCategoryClass.json

@@ -1,6 +0,0 @@
-{
-  "name": "ContentCategory",
-  "description": "A category the content may be published under",
-  "maximum_entities_count": 500,
-  "default_entity_creation_voucher_upper_bound": 500
-}

+ 0 - 6
content-directory-schemas/inputs/classes/FeaturedVideoClass.json

@@ -1,6 +0,0 @@
-{
-  "name": "FeaturedVideo",
-  "description": "Featured video references",
-  "maximum_entities_count": 10,
-  "default_entity_creation_voucher_upper_bound": 10
-}

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

@@ -1,7 +0,0 @@
-{
-  "name": "HttpMediaLocation",
-  "description": "An object describing http location of media object",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 100,
-  "class_permissions": { "any_member": true }
-}

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

@@ -1,7 +0,0 @@
-{
-  "name": "JoystreamMediaLocation",
-  "description": "An object describing location of media object in a format specific to Joystream platform",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 100,
-  "class_permissions": { "any_member": true }
-}

+ 0 - 6
content-directory-schemas/inputs/classes/KnownLicenseClass.json

@@ -1,6 +0,0 @@
-{
-  "name": "KnownLicense",
-  "description": "A commonly recognized license (ie. CC_BY_SA)",
-  "maximum_entities_count": 500,
-  "default_entity_creation_voucher_upper_bound": 500
-}

+ 0 - 6
content-directory-schemas/inputs/classes/LanguageClass.json

@@ -1,6 +0,0 @@
-{
-  "name": "Language",
-  "description": "A language in which the content on the platform may be published",
-  "maximum_entities_count": 500,
-  "default_entity_creation_voucher_upper_bound": 500
-}

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

@@ -1,7 +0,0 @@
-{
-  "name": "License",
-  "description": "Describes a license the media can be published under",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 100,
-  "class_permissions": { "any_member": true }
-}

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

@@ -1,7 +0,0 @@
-{
-  "name": "MediaLocation",
-  "description": "An object describing how the related media object can be accessed",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 100,
-  "class_permissions": { "any_member": true }
-}

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

@@ -1,7 +0,0 @@
-{
-  "name": "UserDefinedLicense",
-  "description": "Custom license defined by the user",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 100,
-  "class_permissions": { "any_member": true }
-}

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

@@ -1,7 +0,0 @@
-{
-  "name": "Video",
-  "description": "Describes a Video",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 100,
-  "class_permissions": { "any_member": true }
-}

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

@@ -1,7 +0,0 @@
-{
-  "name": "VideoMedia",
-  "description": "Describes a video media object",
-  "maximum_entities_count": 5000,
-  "default_entity_creation_voucher_upper_bound": 100,
-  "class_permissions": { "any_member": true }
-}

+ 0 - 6
content-directory-schemas/inputs/classes/VideoMediaEncodingClass.json

@@ -1,6 +0,0 @@
-{
-  "name": "VideoMediaEncoding",
-  "description": "Available encoding format for the video media",
-  "maximum_entities_count": 100,
-  "default_entity_creation_voucher_upper_bound": 50
-}

+ 0 - 18
content-directory-schemas/inputs/classes/index.js

@@ -1,18 +0,0 @@
-const EXPECTED_CLASS_ORDER = [
-  'Channel',
-  'ContentCategory',
-  'HttpMediaLocation',
-  'JoystreamMediaLocation',
-  'KnownLicense',
-  'Language',
-  'License',
-  'MediaLocation',
-  'UserDefinedLicense',
-  'Video',
-  'VideoMedia',
-  'VideoMediaEncoding',
-  'FeaturedVideo',
-]
-
-// Exports class input jsons in a predictable order
-module.exports = EXPECTED_CLASS_ORDER.map((className) => require(`./${className}Class.json`))

+ 0 - 20
content-directory-schemas/inputs/entityBatches/ContentCategoryBatch.json

@@ -1,20 +0,0 @@
-{
-  "className": "ContentCategory",
-  "entries": [
-    { "name": "Film & Animation" },
-    { "name": "Autos & Vehicles" },
-    { "name": "Music" },
-    { "name": "Pets & Animals" },
-    { "name": "Sports" },
-    { "name": "Travel & Events" },
-    { "name": "Gaming" },
-    { "name": "People & Blogs" },
-    { "name": "Comedy" },
-    { "name": "Entertainment" },
-    { "name": "News & Politics" },
-    { "name": "Howto & Style" },
-    { "name": "Education" },
-    { "name": "Science & Technology" },
-    { "name": "Nonprofits & Activism" }
-  ]
-}

+ 0 - 61
content-directory-schemas/inputs/entityBatches/KnownLicenseBatch.json

@@ -1,61 +0,0 @@
-{
-  "className": "KnownLicense",
-  "entries": [
-    {
-      "code": "PDM",
-      "name": "Public Domain",
-      "description": "For items which are not protected by copyright. This is not a license, but rather a copyright status. Some government-produced works, items with expired copyrights, and those which are ineligible for copyright protection may be included in this category.",
-      "url": "https://creativecommons.org/share-your-work/public-domain/pdm",
-      "attributionRequired": false
-    },
-    {
-      "code": "CC0",
-      "name": "Public Domain Dedication",
-      "description": "The CC0 (Public Domain Dedication) License allows creators to waive all rights to their creations and release them into the Public Domain.",
-      "url": "https://creativecommons.org/share-your-work/public-domain/cc0",
-      "attributionRequired": true
-    },
-    {
-      "code": "CC_BY",
-      "name": "Creative Commons Attribution License",
-      "description": "Sharing and adapting this content is permitted, but attribution must be provided. Read the License Deed for more information.",
-      "url": "https://creativecommons.org/licenses/by/4.0",
-      "attributionRequired": true
-    },
-    {
-      "code": "CC_BY_SA",
-      "name": "Creative Commons Attribution-ShareAlike License",
-      "description": "Sharing and adapting this content is permitted, but attribution must be provided. Any derivative works must be distributed under the same license. Read the License Deed for more information.",
-      "url": "https://creativecommons.org/licenses/by-sa/4.0",
-      "attributionRequired": true
-    },
-    {
-      "code": "CC_BY_ND",
-      "name": "Creative Commons Attribution-NoDerivs License",
-      "description": "Sharing this content is permitted, but attribution must be provided. You may not remix, transform, or build upon the material. Read the License Deed for more information.",
-      "url": "https://creativecommons.org/licenses/by-nd/4.0",
-      "attributionRequired": true
-    },
-    {
-      "code": "CC_BY_NC",
-      "name": "Creative Commons Attribution-NonCommercial License",
-      "description": "Sharing and adapting this content is permitted, but attribution must be provided. Commercial use is not permitted. Read the License Deed for more information.",
-      "url": "https://creativecommons.org/licenses/by-nc/4.0",
-      "attributionRequired": true
-    },
-    {
-      "code": "CC_BY_NC_SA",
-      "name": "Creative Commons Attribution-NonCommercial-ShareAlike License",
-      "description": "Sharing and adapting this content is permitted, but attribution must be provided. Any derivative works must be distributed under the same license. Commercial use is not permitted. Read the License Deed for more information.",
-      "url": "https://creativecommons.org/licenses/by-nc-sa/4.0",
-      "attributionRequired": true
-    },
-    {
-      "code": "CC_BY_NC_ND",
-      "name": "Creative Commons Attribution-NonCommercial-NoDerivs License",
-      "description": "Sharing this content is permitted, but attribution must be provided. You may not remix, transform, or build upon the material. Commercial use is not permitted. Read the License Deed for more information.",
-      "url": "https://creativecommons.org/licenses/by-nc-nd/4.0",
-      "attributionRequired": true
-    }
-  ]
-}

+ 0 - 43
content-directory-schemas/inputs/entityBatches/LanguageBatch.json

@@ -1,43 +0,0 @@
-{
-  "className": "Language",
-  "entries": [
-    { "code": "EN", "name": "English" },
-    { "code": "RU", "name": "Russian" },
-    { "code": "DE", "name": "German" },
-    { "code": "IT", "name": "Italian" },
-    { "code": "ES", "name": "Spanish" },
-    { "code": "UK", "name": "Ukrainian" },
-    { "code": "CZ", "name": "Czech" },
-    { "code": "PL", "name": "Polish" },
-    { "code": "RO", "name": "Romanian" },
-    { "code": "NO", "name": "Norwegian" },
-    { "code": "AR", "name": "Arabic" },
-    { "code": "BG", "name": "Bulgarian" },
-    { "code": "ZH", "name": "Chinese" },
-    { "code": "HR", "name": "Croatian" },
-    { "code": "DA", "name": "Danish" },
-    { "code": "NL", "name": "Dutch" },
-    { "code": "FI", "name": "Finnish" },
-    { "code": "FR", "name": "French" },
-    { "code": "EL", "name": "Greek" },
-    { "code": "HI", "name": "Hindi" },
-    { "code": "HU", "name": "Hungarian" },
-    { "code": "ID", "name": "Indonesian" },
-    { "code": "GA", "name": "Irish" },
-    { "code": "IS", "name": "Icelandic" },
-    { "code": "JA", "name": "Japanese" },
-    { "code": "KO", "name": "Korean" },
-    { "code": "LT", "name": "Lithuanian" },
-    { "code": "MK", "name": "Macedonian" },
-    { "code": "PT", "name": "Portuguese" },
-    { "code": "SR", "name": "Serbian" },
-    { "code": "SK", "name": "Slovak" },
-    { "code": "SL", "name": "Slovenian" },
-    { "code": "SV", "name": "Swedish" },
-    { "code": "TH", "name": "Thai" },
-    { "code": "BO", "name": "Tibetan" },
-    { "code": "TR", "name": "Turkish" },
-    { "code": "VI", "name": "Vietnamese" },
-    { "code": "CY", "name": "Welsh" }
-  ]
-}

+ 0 - 27
content-directory-schemas/inputs/entityBatches/VideoMediaEncodingBatch.json

@@ -1,27 +0,0 @@
-{
-  "className": "VideoMediaEncoding",
-  "entries": [
-    { "name": "H.263_MP4" },
-    { "name": "H.263_3GP" },
-    { "name": "H.263_AVI" },
-    { "name": "H.264_MP4" },
-    { "name": "H.264_3GP" },
-    { "name": "H.264_AVI" },
-    { "name": "H.264_MKV" },
-    { "name": "H.265_MP4" },
-    { "name": "H.265_3GP" },
-    { "name": "H.265_AVI" },
-    { "name": "VP8_WEBM" },
-    { "name": "VP8_MP4" },
-    { "name": "VP8_AVI" },
-    { "name": "VP8_MKV" },
-    { "name": "VP9_WEBM" },
-    { "name": "VP9_MP4" },
-    { "name": "VP9_AVI" },
-    { "name": "VP9_MKV" },
-    { "name": "AV1_MP4" },
-    { "name": "MVC_MP4" },
-    { "name": "MVC_3GP" },
-    { "name": "MVC_MKV" }
-  ]
-}

+ 0 - 49
content-directory-schemas/inputs/schemas/ChannelSchema.json

@@ -1,49 +0,0 @@
-{
-  "className": "Channel",
-  "newProperties": [
-    {
-      "name": "handle",
-      "description": "The handle of the Channel",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": { "Text": 64 } }
-    },
-    {
-      "name": "description",
-      "description": "The description of a Channel",
-      "required": true,
-      "property_type": { "Single": { "Text": 1024 } }
-    },
-    {
-      "name": "coverPhotoUrl",
-      "description": "Url for Channel's cover (background) photo. Recommended ratio: 16:9.",
-      "required": false,
-      "property_type": { "Single": { "Text": 256 } }
-    },
-    {
-      "name": "avatarPhotoUrl",
-      "description": "Channel's avatar photo.",
-      "required": false,
-      "property_type": { "Single": { "Text": 256 } }
-    },
-    {
-      "name": "isPublic",
-      "description": "Flag signaling whether a channel is public.",
-      "required": true,
-      "property_type": { "Single": "Bool" }
-    },
-    {
-      "name": "isCensored",
-      "description": "Channel censorship status set by the Curator.",
-      "required": false,
-      "property_type": { "Single": "Bool" },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "language",
-      "description": "The primary langauge of the channel's content",
-      "required": false,
-      "property_type": { "Single": { "Reference": { "className": "Language" } } }
-    }
-  ]
-}

+ 0 - 20
content-directory-schemas/inputs/schemas/ContentCategorySchema.json

@@ -1,20 +0,0 @@
-{
-  "className": "ContentCategory",
-  "newProperties": [
-    {
-      "name": "name",
-      "description": "The name of the category",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": { "Text": 64 } },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "description",
-      "description": "The description of the category",
-      "required": false,
-      "property_type": { "Single": { "Text": 1024 } },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 12
content-directory-schemas/inputs/schemas/FeaturedVideoSchema.json

@@ -1,12 +0,0 @@
-{
-  "className": "FeaturedVideo",
-  "newProperties": [
-    {
-      "name": "video",
-      "description": "Reference to a video",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": { "Reference": { "className": "Video" } } }
-    }
-  ]
-}

+ 0 - 20
content-directory-schemas/inputs/schemas/HttpMediaLocationSchema.json

@@ -1,20 +0,0 @@
-{
-  "className": "HttpMediaLocation",
-  "newProperties": [
-    {
-      "name": "url",
-      "description": "The http url pointing to the media",
-      "required": true,
-      "unique": false,
-      "property_type": { "Single": { "Text": 256 } },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "port",
-      "description": "The port to use when connecting to the http url (defaults to 80)",
-      "required": false,
-      "property_type": { "Single": "Uint16" },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 13
content-directory-schemas/inputs/schemas/JoystreamMediaLocationSchema.json

@@ -1,13 +0,0 @@
-{
-  "className": "JoystreamMediaLocation",
-  "newProperties": [
-    {
-      "name": "dataObjectId",
-      "description": "Id of the data object in the Joystream runtime dataDirectory module",
-      "property_type": { "Single": { "Text": 48 } },
-      "required": true,
-      "unique": true,
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 52
content-directory-schemas/inputs/schemas/KnownLicenseSchema.json

@@ -1,52 +0,0 @@
-{
-  "className": "KnownLicense",
-  "newProperties": [
-    {
-      "name": "code",
-      "description": "Short, commonly recognized code of the licence (ie. CC_BY_SA)",
-      "required": true,
-      "unique": true,
-      "property_type": {
-        "Single": { "Text": 16 }
-      },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "name",
-      "description": "Full, descriptive name of the license (ie. Creative Commons - Attribution-NonCommercial-NoDerivs)",
-      "required": false,
-      "unique": true,
-      "property_type": {
-        "Single": { "Text": 64 }
-      },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "description",
-      "description": "Short description of the license conditions",
-      "required": false,
-      "property_type": {
-        "Single": { "Text": 1024 }
-      },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "url",
-      "description": "An url pointing to full license content",
-      "required": false,
-      "property_type": {
-        "Single": { "Text": 256 }
-      },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "attributionRequired",
-      "description": "Whether this license requires an attribution",
-      "required": false,
-      "property_type": {
-        "Single": "Bool"
-      },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 20
content-directory-schemas/inputs/schemas/LanguageSchema.json

@@ -1,20 +0,0 @@
-{
-  "className": "Language",
-  "newProperties": [
-    {
-      "name": "name",
-      "description": "The name of the language (ie. English)",
-      "required": true,
-      "property_type": { "Single": { "Text": 64 } },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "code",
-      "description": "ISO 639-1 code of the language (ie. en)",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": { "Text": 2 } },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 23
content-directory-schemas/inputs/schemas/LicenseSchema.json

@@ -1,23 +0,0 @@
-{
-  "className": "License",
-  "newProperties": [
-    {
-      "name": "knownLicense",
-      "description": "Reference to a known license",
-      "required": false,
-      "property_type": { "Single": { "Reference": { "className": "KnownLicense" } } }
-    },
-    {
-      "name": "userDefinedLicense",
-      "description": "Reference to user-defined license",
-      "required": false,
-      "property_type": { "Single": { "Reference": { "className": "UserDefinedLicense", "sameOwner": true } } }
-    },
-    {
-      "name": "attribution",
-      "description": "Attribution (if required by the license)",
-      "required": false,
-      "property_type": { "Single": { "Text": 512 } }
-    }
-  ]
-}

+ 0 - 19
content-directory-schemas/inputs/schemas/MediaLocationSchema.json

@@ -1,19 +0,0 @@
-{
-  "className": "MediaLocation",
-  "newProperties": [
-    {
-      "name": "httpMediaLocation",
-      "description": "A reference to HttpMediaLocation",
-      "required": false,
-      "property_type": { "Single": { "Reference": { "className": "HttpMediaLocation", "sameOwner": true } } },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "joystreamMediaLocation",
-      "description": "A reference to JoystreamMediaLocation",
-      "required": false,
-      "property_type": { "Single": { "Reference": { "className": "JoystreamMediaLocation", "sameOwner": true } } },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 13
content-directory-schemas/inputs/schemas/UserDefinedLicenseSchema.json

@@ -1,13 +0,0 @@
-{
-  "className": "UserDefinedLicense",
-  "newProperties": [
-    {
-      "name": "content",
-      "description": "Custom license content",
-      "required": true,
-      "property_type": {
-        "Single": { "Text": 4096 }
-      }
-    }
-  ]
-}

+ 0 - 13
content-directory-schemas/inputs/schemas/VideoMediaEncodingSchema.json

@@ -1,13 +0,0 @@
-{
-  "className": "VideoMediaEncoding",
-  "newProperties": [
-    {
-      "name": "name",
-      "description": "The name of the encoding format (ie. H264_mpeg4)",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": { "Text": 32 } },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 40
content-directory-schemas/inputs/schemas/VideoMediaSchema.json

@@ -1,40 +0,0 @@
-{
-  "className": "VideoMedia",
-  "newProperties": [
-    {
-      "name": "encoding",
-      "description": "Encoding of the video media object",
-      "required": true,
-      "property_type": { "Single": { "Reference": { "className": "VideoMediaEncoding" } } },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "pixelWidth",
-      "description": "Video media width in pixels",
-      "required": true,
-      "property_type": { "Single": "Uint16" },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "pixelHeight",
-      "description": "Video media height in pixels",
-      "required": true,
-      "property_type": { "Single": "Uint16" },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "size",
-      "description": "Video media size in bytes",
-      "required": false,
-      "property_type": { "Single": "Uint64" },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "location",
-      "description": "Location of the video media object",
-      "required": true,
-      "property_type": { "Single": { "Reference": { "className": "MediaLocation", "sameOwner": true } } },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 99
content-directory-schemas/inputs/schemas/VideoSchema.json

@@ -1,99 +0,0 @@
-{
-  "className": "Video",
-  "newProperties": [
-    {
-      "name": "channel",
-      "description": "Reference to member's channel",
-      "required": true,
-      "property_type": { "Single": { "Reference": { "className": "Channel", "sameOwner": true } } }
-    },
-    {
-      "name": "category",
-      "description": "Reference to a video category",
-      "required": true,
-      "property_type": { "Single": { "Reference": { "className": "ContentCategory" } } }
-    },
-    {
-      "name": "title",
-      "description": "The title of the video",
-      "required": true,
-      "property_type": { "Single": { "Text": 64 } }
-    },
-    {
-      "name": "description",
-      "description": "The description of the Video",
-      "required": true,
-      "property_type": { "Single": { "Text": 1024 } }
-    },
-    {
-      "name": "duration",
-      "description": "Video duration in seconds",
-      "required": true,
-      "property_type": { "Single": "Uint32" }
-    },
-    {
-      "name": "skippableIntroDuration",
-      "description": "Video's kippable intro duration in seconds",
-      "required": false,
-      "property_type": { "Single": "Uint16" }
-    },
-    {
-      "name": "thumbnailUrl",
-      "description": "Video thumbnail url (recommended ratio: 16:9)",
-      "required": true,
-      "property_type": { "Single": { "Text": 256 } }
-    },
-    {
-      "name": "language",
-      "description": "Video's main langauge",
-      "required": false,
-      "property_type": { "Single": { "Reference": { "className": "Language" } } }
-    },
-    {
-      "name": "media",
-      "description": "Reference to VideoMedia",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": { "Reference": { "className": "VideoMedia", "sameOwner": true } } },
-      "locking_policy": { "is_locked_from_controller": true }
-    },
-    {
-      "name": "hasMarketing",
-      "description": "Whether or not Video contains marketing",
-      "required": false,
-      "property_type": { "Single": "Bool" }
-    },
-    {
-      "name": "publishedBeforeJoystream",
-      "description": "If the Video was published on other platform before beeing published on Joystream - the original publication date",
-      "required": false,
-      "property_type": { "Single": "Int32" }
-    },
-    {
-      "name": "isPublic",
-      "description": "Whether the Video is supposed to be publically displayed",
-      "required": true,
-      "property_type": { "Single": "Bool" }
-    },
-    {
-      "name": "isExplicit",
-      "description": "Whether the Video contains explicit material.",
-      "required": true,
-      "property_type": { "Single": "Bool" }
-    },
-    {
-      "name": "license",
-      "description": "A License the Video is published under",
-      "required": true,
-      "unique": true,
-      "property_type": { "Single": { "Reference": { "className": "License", "sameOwner": true } } }
-    },
-    {
-      "name": "isCensored",
-      "description": "Video censorship status set by the Curator.",
-      "required": false,
-      "property_type": { "Single": "Bool" },
-      "locking_policy": { "is_locked_from_controller": true }
-    }
-  ]
-}

+ 0 - 56
content-directory-schemas/package.json

@@ -1,56 +0,0 @@
-{
-  "name": "@joystream/cd-schemas",
-  "version": "0.1.0",
-  "description": "JSON schemas, inputs and related tooling for Joystream content directory 2.0",
-  "author": "Joystream contributors",
-  "main": "lib/index.js",
-  "scripts": {
-    "build": "tsc --build tsconfig.lib.json",
-    "lint": "eslint ./ --ext .ts --ignore-path .gitignore",
-    "ts-check": "tsc --noEmit --pretty",
-    "pretty": "prettier ./ --write --ignore-path .gitignore",
-    "validate": "ts-node ./scripts/validate.ts",
-    "checks": "yarn ts-check && prettier ./ --check --ignore-path .gitignore && yarn validate && yarn lint",
-    "generate:types": "ts-node --files ./scripts/schemasToTS.ts",
-    "generate:entity-schemas": "ts-node ./scripts/inputSchemasToEntitySchemas.ts",
-    "generate:all": "yarn generate:entity-schemas && yarn generate:types",
-    "initialize:lead": "ts-node ./scripts/devInitContentLead.ts",
-    "initialize:content-dir": "ts-node ./scripts/initializeContentDir.ts",
-    "initialize:dev": "yarn initialize:lead && yarn initialize:content-dir",
-    "example:createChannel": "ts-node ./examples/createChannel.ts",
-    "example:createVideo": "ts-node ./examples/createVideo.ts",
-    "example:updateChannelHandle": "ts-node ./examples/updateChannelHandle.ts",
-    "example:createChannelWithoutTransaction": "ts-node ./examples/createChannelWithoutTransaction.ts",
-    "example:updateChannelHandlelWithoutTransaction": "ts-node ./examples/updateChannelHandleWithoutTransaction.ts"
-  },
-  "dependencies": {
-    "ajv": "6.12.5",
-    "@joystream/prettier-config": "*",
-    "@polkadot/api": "1.26.1",
-    "@polkadot/keyring": "^3.0.1",
-    "@joystream/types": "^0.14.0",
-    "@apidevtools/json-schema-ref-parser": "^9.0.6"
-  },
-  "devDependencies": {
-    "ts-node": "^8.8.2",
-    "typescript": "^3.9.7",
-    "json-schema-to-typescript": "^9.1.1"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/Joystream/joystream.git"
-  },
-  "license": "GPL-3.0-only",
-  "bugs": {
-    "url": "https://github.com/Joystream/joystream/issues"
-  },
-  "homepage": "https://github.com/Joystream/joystream",
-  "volta": {
-    "node": "12.18.2",
-    "yarn": "1.22.4"
-  },
-  "publishConfig": {
-    "access": "public",
-    "registry": "https://registry.npmjs.org"
-  }
-}

+ 0 - 155
content-directory-schemas/schemas/extrinsics/AddClassSchema.schema.json

@@ -1,155 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema",
-  "$id": "https://joystream.org/AddClassSchema.schema.json",
-  "title": "AddClassSchema",
-  "description": "JSON schema to describe a new schema for a certain class in Joystream network",
-  "type": "object",
-  "additionalProperties": false,
-  "required": ["className", "newProperties"],
-  "properties": {
-    "className": { "type": "string" },
-    "existingProperties": {
-      "type": "array",
-      "uniqueItems": true,
-      "items": { "$ref": "#/definitions/PropertyInSchemIndex" }
-    },
-    "newProperties": {
-      "type": "array",
-      "uniqueItems": true,
-      "items": { "$ref": "#/definitions/Property" }
-    }
-  },
-  "definitions": {
-    "ClassId": {
-      "type": "integer",
-      "minimum": 1
-    },
-    "PropertyInSchemIndex": {
-      "type": "integer",
-      "minimum": 0
-    },
-    "DefaultBoolean": {
-      "type": "boolean",
-      "default": false
-    },
-    "Property": {
-      "type": "object",
-      "additionalProperties": false,
-      "required": ["name", "property_type"],
-      "properties": {
-        "property_type": {
-          "oneOf": [{ "$ref": "#/definitions/SinglePropertyVariant" }, { "$ref": "#/definitions/VecPropertyVariant" }]
-        },
-        "name": { "$ref": "#/definitions/PropertyName" },
-        "description": { "$ref": "#/definitions/PropertyDescription" },
-        "required": { "$ref": "#/definitions/DefaultBoolean" },
-        "unique": { "$ref": "#/definitions/DefaultBoolean" },
-        "locking_policy": { "$ref": "#/definitions/LockingPolicy" }
-      }
-    },
-    "PropertyName": {
-      "type": "string",
-      "minLength": 1,
-      "maxLength": 49
-    },
-    "PropertyDescription": {
-      "type": "string",
-      "minLength": 1,
-      "maxLength": 500,
-      "default": ""
-    },
-    "SinglePropertyType": {
-      "oneOf": [
-        { "$ref": "#/definitions/PrimitiveProperty", "description": "Primitive property (bool/integer)" },
-        { "$ref": "#/definitions/TextProperty" },
-        { "$ref": "#/definitions/HashProperty" },
-        { "$ref": "#/definitions/ReferenceProperty" }
-      ]
-    },
-    "SinglePropertyVariant": {
-      "type": "object",
-      "additionalProperties": false,
-      "required": ["Single"],
-      "properties": {
-        "Single": { "$ref": "#/definitions/SinglePropertyType" }
-      }
-    },
-    "VecPropertyType": {
-      "type": "object",
-      "additionalProperties": false,
-      "required": ["vec_type", "max_length"],
-      "properties": {
-        "vec_type": { "$ref": "#/definitions/SinglePropertyType" },
-        "max_length": { "$ref": "#/definitions/MaxVecItems" }
-      }
-    },
-    "VecPropertyVariant": {
-      "type": "object",
-      "additionalProperties": false,
-      "required": ["Vector"],
-      "properties": {
-        "Vector": { "$ref": "#/definitions/VecPropertyType" }
-      }
-    },
-    "PrimitiveProperty": {
-      "type": "string",
-      "enum": ["Bool", "Uint16", "Uint32", "Uint64", "Int16", "Int32", "Int64"]
-    },
-    "TextProperty": {
-      "type": "object",
-      "additionalProperties": false,
-      "required": ["Text"],
-      "properties": {
-        "Text": { "$ref": "#/definitions/MaxTextLength" }
-      }
-    },
-    "HashProperty": {
-      "type": "object",
-      "additionalProperties": false,
-      "required": ["Hash"],
-      "properties": {
-        "Hash": { "$ref": "#/definitions/MaxTextLength" }
-      }
-    },
-    "MaxTextLength": {
-      "type": "integer",
-      "minimum": 1,
-      "maximum": 65535
-    },
-    "MaxVecItems": {
-      "type": "integer",
-      "minimum": 1,
-      "maximum": 65535
-    },
-    "ReferenceProperty": {
-      "type": "object",
-      "additionalProperties": false,
-      "required": ["Reference"],
-      "properties": {
-        "Reference": {
-          "type": "object",
-          "additionalProperties": false,
-          "required": ["className"],
-          "properties": {
-            "className": {
-              "type": "string",
-              "description": "Referenced class name"
-            },
-            "sameOwner": {
-              "$ref": "#/definitions/DefaultBoolean",
-              "description": "Whether same owner (controller) is required"
-            }
-          }
-        }
-      }
-    },
-    "LockingPolicy": {
-      "type": "object",
-      "additionalProperties": false,
-      "properties": {
-        "is_locked_from_maintainer": { "$ref": "#/definitions/DefaultBoolean" },
-        "is_locked_from_controller": { "$ref": "#/definitions/DefaultBoolean" }
-      }
-    }
-  }
-}

+ 0 - 48
content-directory-schemas/schemas/extrinsics/CreateClass.schema.json

@@ -1,48 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema",
-  "$id": "https://joystream.org/CreateClass.schema.json",
-  "title": "CreateClass",
-  "description": "JSON schema to describe a new class for Joystream network",
-  "type": "object",
-  "additionalProperties": false,
-  "required": ["name", "description", "maximum_entities_count", "default_entity_creation_voucher_upper_bound"],
-  "properties": {
-    "name": {
-      "type": "string",
-      "description": "Name of this class. Required property.",
-      "minLength": 1,
-      "maxLength": 49
-    },
-    "description": {
-      "type": "string",
-      "description": "Description of this class.",
-      "minLength": 1,
-      "maxLength": 500
-    },
-    "class_permissions": {
-      "type": "object",
-      "additionalProperties": false,
-      "properties": {
-        "any_member": { "$ref": "#/definitions/DefaultBoolean" },
-        "entity_creation_blocked": { "$ref": "#/definitions/DefaultBoolean" },
-        "all_entity_property_values_locked": { "$ref": "#/definitions/DefaultBoolean" },
-        "maintainers": {
-          "type": "array",
-          "uniqueItems": true,
-          "items": {
-            "type": "integer"
-          },
-          "default": []
-        }
-      }
-    },
-    "maximum_entities_count": { "type": "integer" },
-    "default_entity_creation_voucher_upper_bound": { "type": "integer" }
-  },
-  "definitions": {
-    "DefaultBoolean": {
-      "type": "boolean",
-      "default": false
-    }
-  }
-}

+ 0 - 38
content-directory-schemas/schemas/propertyValidationDefs.schema.json

@@ -1,38 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema",
-  "$id": "https://joystream.org/propertyValidationDefs.schema.json",
-  "title": "propertyValidationDefs",
-  "description": "JSON schema containing definitions for different property types input",
-  "definitions": {
-    "Bool": {
-      "type": "boolean"
-    },
-    "Uint16": {
-      "type": "integer",
-      "minimum": 0,
-      "maximum": 65535
-    },
-    "Uint32": {
-      "type": "integer",
-      "minimum": 0,
-      "maximum": 4294967295
-    },
-    "Uint64": {
-      "type": "integer",
-      "minimum": 0
-    },
-    "Int16": {
-      "type": "integer",
-      "minimum": -32768,
-      "maximum": 32767
-    },
-    "Int32": {
-      "type": "integer",
-      "minimum": -2147483648,
-      "maximum": 2147483647
-    },
-    "Int64": {
-      "type": "integer"
-    }
-  }
-}

+ 0 - 65
content-directory-schemas/scripts/initializeContentDir.ts

@@ -1,65 +0,0 @@
-import { types } from '@joystream/types'
-import { ApiPromise, WsProvider } from '@polkadot/api'
-import { getInitializationInputs } from '../src/helpers/inputs'
-import fs from 'fs'
-import path from 'path'
-import { InputParser } from '../src/helpers/InputParser'
-import { ExtrinsicsHelper, getAlicePair, getKeyFromSuri } from '../src/helpers/extrinsics'
-
-// Save entity operations output here for easier debugging
-const ENTITY_OPERATIONS_OUTPUT_PATH = path.join(__dirname, '../operations.json')
-
-const { classInputs, schemaInputs, entityBatchInputs } = getInitializationInputs()
-
-async function main() {
-  // Init api
-  const WS_URI = process.env.WS_URI || 'ws://127.0.0.1:9944'
-  console.log(`Initializing the api (${WS_URI})...`)
-  const provider = new WsProvider(WS_URI)
-  const api = await ApiPromise.create({ provider, types })
-
-  const LeadKeyPair = process.env.LEAD_URI ? getKeyFromSuri(process.env.LEAD_URI) : getAlicePair()
-
-  // Emptiness check
-  if ((await api.query.contentDirectory.classById.keys()).length > 0) {
-    console.log('Content directory is not empty! Skipping...')
-    process.exit()
-  }
-
-  const txHelper = new ExtrinsicsHelper(api)
-  const parser = new InputParser(api, classInputs, schemaInputs, entityBatchInputs)
-
-  console.log(`Initializing classes (${classInputs.length} input files found)...\n`)
-  const classExtrinsics = parser.getCreateClassExntrinsics()
-  await txHelper.sendAndCheck(LeadKeyPair, classExtrinsics, 'Class initialization failed!')
-
-  console.log(`Initializing schemas (${schemaInputs.length} input files found)...\n`)
-  const schemaExtrinsics = await parser.getAddSchemaExtrinsics()
-  await txHelper.sendAndCheck(LeadKeyPair, schemaExtrinsics, 'Schemas initialization failed!')
-
-  console.log(`Initializing entities (${entityBatchInputs.length} input files found)`)
-  const entityOperations = await parser.getEntityBatchOperations()
-  // Save operations in operations.json (for reference in case of errors)
-  console.log(`Saving entity batch operations in ${ENTITY_OPERATIONS_OUTPUT_PATH}...`)
-  fs.writeFileSync(
-    ENTITY_OPERATIONS_OUTPUT_PATH,
-    JSON.stringify(
-      entityOperations.map((o) => o.toJSON()),
-      null,
-      4
-    )
-  )
-  console.log(`Sending Transaction extrinsic (${entityOperations.length} operations)...`)
-  await txHelper.sendAndCheck(
-    LeadKeyPair,
-    [api.tx.contentDirectory.transaction({ Lead: null }, entityOperations)],
-    'Entity initialization failed!'
-  )
-}
-
-main()
-  .then(() => process.exit())
-  .catch((e) => {
-    console.error(e)
-    process.exit(-1)
-  })

+ 0 - 163
content-directory-schemas/scripts/inputSchemasToEntitySchemas.ts

@@ -1,163 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-import {
-  AddClassSchema,
-  HashProperty,
-  Property,
-  ReferenceProperty,
-  SinglePropertyVariant,
-  TextProperty,
-  VecPropertyVariant,
-} from '../types/extrinsics/AddClassSchema'
-import PRIMITIVE_PROPERTY_DEFS from '../schemas/propertyValidationDefs.schema.json'
-import { getInputs } from '../src/helpers/inputs'
-import { getSchemasLocation, SCHEMA_TYPES } from '../src/helpers/schemas'
-import { JSONSchema7, JSONSchema7TypeName } from 'json-schema'
-
-const schemaInputs = getInputs<AddClassSchema>('schemas')
-
-const strictObjectDef = (def: Record<string, any>): JSONSchema7 => ({
-  type: 'object',
-  additionalProperties: false,
-  ...def,
-})
-
-const onePropertyObjectDef = (propertyName: string, propertyDef: Record<string, any>): JSONSchema7 =>
-  strictObjectDef({
-    required: [propertyName],
-    properties: {
-      [propertyName]: propertyDef,
-    },
-  })
-
-const TextPropertyDef = ({ Text: maxLength }: TextProperty): JSONSchema7 => ({
-  type: 'string',
-  maxLength,
-})
-
-const HashPropertyDef = ({ Hash: maxLength }: HashProperty): JSONSchema7 => ({
-  type: 'string',
-  maxLength,
-})
-
-const ReferencePropertyDef = ({ Reference: ref }: ReferenceProperty): JSONSchema7 => ({
-  'oneOf': [
-    onePropertyObjectDef('new', { '$ref': `../entities/${ref.className}Entity.schema.json` }),
-    onePropertyObjectDef('existing', { '$ref': `../entityReferences/${ref.className}Ref.schema.json` }),
-    PRIMITIVE_PROPERTY_DEFS.definitions.Uint64 as JSONSchema7,
-  ],
-})
-
-const SinglePropertyDef = ({ Single: singlePropType }: SinglePropertyVariant): JSONSchema7 => {
-  if (typeof singlePropType === 'string') {
-    return PRIMITIVE_PROPERTY_DEFS.definitions[singlePropType] as JSONSchema7
-  } else if ((singlePropType as TextProperty).Text) {
-    return TextPropertyDef(singlePropType as TextProperty)
-  } else if ((singlePropType as HashProperty).Hash) {
-    return HashPropertyDef(singlePropType as HashProperty)
-  } else if ((singlePropType as ReferenceProperty).Reference) {
-    return ReferencePropertyDef(singlePropType as ReferenceProperty)
-  }
-
-  throw new Error(`Unknown single proprty type: ${JSON.stringify(singlePropType)}`)
-}
-
-const VecPropertyDef = ({ Vector: vec }: VecPropertyVariant): JSONSchema7 => ({
-  type: 'array',
-  maxItems: vec.max_length,
-  'items': SinglePropertyDef({ Single: vec.vec_type }),
-})
-
-const PropertyDef = ({ property_type: propertyType, description, required }: Property): JSONSchema7 => {
-  const def = {
-    ...((propertyType as SinglePropertyVariant).Single
-      ? SinglePropertyDef(propertyType as SinglePropertyVariant)
-      : VecPropertyDef(propertyType as VecPropertyVariant)),
-    description,
-  }
-  // Non-required fields:
-  // Simple fields:
-  if (!required && def.type) {
-    def.type = [def.type as JSONSchema7TypeName, 'null']
-  }
-  // Relationships:
-  else if (!required && def.oneOf) {
-    def.oneOf = [...def.oneOf, { type: 'null' }]
-  }
-
-  return def
-}
-
-// Mkdir entity schemas directories if they do not exist
-SCHEMA_TYPES.forEach((type) => {
-  if (!fs.existsSync(getSchemasLocation(type))) {
-    fs.mkdirSync(getSchemasLocation(type))
-  }
-})
-
-// Run schema conversion:
-schemaInputs.forEach(({ fileName, data: inputData }) => {
-  const schemaName = fileName.replace('Schema.json', '')
-
-  if (inputData.newProperties && !inputData.existingProperties) {
-    const properites = inputData.newProperties
-    const propertiesObj = properites.reduce((pObj, p) => {
-      pObj[p.name] = PropertyDef(p)
-      return pObj
-    }, {} as Record<string, ReturnType<typeof PropertyDef>>)
-
-    const EntitySchema: JSONSchema7 = {
-      $schema: 'http://json-schema.org/draft-07/schema',
-      $id: `https://joystream.org/entities/${schemaName}Entity.schema.json`,
-      title: `${schemaName}Entity`,
-      description: `JSON schema for entities based on ${schemaName} runtime schema`,
-      ...strictObjectDef({
-        required: properites.filter((p) => p.required).map((p) => p.name),
-        properties: propertiesObj,
-      }),
-    }
-
-    const ReferenceSchema: JSONSchema7 = {
-      $schema: 'http://json-schema.org/draft-07/schema',
-      $id: `https://joystream.org/entityReferences/${schemaName}Ref.schema.json`,
-      title: `${schemaName}Reference`,
-      description: `JSON schema for reference to ${schemaName} entity based on runtime schema`,
-      anyOf: [
-        ...properites.filter((p) => p.required && p.unique).map((p) => onePropertyObjectDef(p.name, PropertyDef(p))),
-        PRIMITIVE_PROPERTY_DEFS.definitions.Uint64 as JSONSchema7,
-      ],
-    }
-
-    const BatchSchema: JSONSchema7 = {
-      $schema: 'http://json-schema.org/draft-07/schema',
-      $id: `https://joystream.org/entityBatches/${schemaName}Batch.schema.json`,
-      title: `${schemaName}Batch`,
-      description: `JSON schema for batch of entities based on ${schemaName} runtime schema`,
-      ...strictObjectDef({
-        required: ['className', 'entries'],
-        properties: {
-          className: { type: 'string' },
-          entries: {
-            type: 'array',
-            items: { '$ref': `../entities/${schemaName}Entity.schema.json` },
-          },
-        },
-      }),
-    }
-
-    const entitySchemaPath = path.join(getSchemasLocation('entities'), `${schemaName}Entity.schema.json`)
-    fs.writeFileSync(entitySchemaPath, JSON.stringify(EntitySchema, undefined, 4))
-    console.log(`${entitySchemaPath} succesfully generated!`)
-
-    const entityReferenceSchemaPath = path.join(getSchemasLocation('entityReferences'), `${schemaName}Ref.schema.json`)
-    fs.writeFileSync(entityReferenceSchemaPath, JSON.stringify(ReferenceSchema, undefined, 4))
-    console.log(`${entityReferenceSchemaPath} succesfully generated!`)
-
-    const batchOfEntitiesSchemaPath = path.join(getSchemasLocation('entityBatches'), `${schemaName}Batch.schema.json`)
-    fs.writeFileSync(batchOfEntitiesSchemaPath, JSON.stringify(BatchSchema, undefined, 4))
-    console.log(`${batchOfEntitiesSchemaPath} succesfully generated!`)
-  } else {
-    console.log('WARNING: Schemas with "existingProperties" not supported yet!')
-    console.log('Skipping...')
-  }
-})

+ 0 - 56
content-directory-schemas/scripts/schemasToTS.ts

@@ -1,56 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-import { compileFromFile } from 'json-schema-to-typescript'
-// TODO: This will require publishing @joystream/prettier-config if we want to include it in joystream-js
-import prettierConfig from '@joystream/prettier-config'
-
-const SCHEMAS_LOCATION = path.join(__dirname, '../schemas')
-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 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)
-      }
-      const outputFilePath = path.join(outputDir, outputFilename)
-      try {
-        await compileFromFile(schemaFilePath, {
-          cwd: path.join(SCHEMAS_LOCATION, schemaSubdir),
-          style: prettierConfig,
-        }).then((ts) => {
-          fs.writeFileSync(outputFilePath, ts)
-          console.log(`${outputFilePath} succesfully generated!`)
-        })
-      } catch (e) {
-        console.log(`${outputFilePath} compilation FAILED!`)
-        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!`)
-  }
-}
-
-main()
-  .then(() => process.exit())
-  .catch((e) => console.error(e))

+ 0 - 85
content-directory-schemas/scripts/validate.ts

@@ -1,85 +0,0 @@
-// TODO: Add entity batches validation
-import Ajv from 'ajv'
-import { FetchedInput, getInputs, InputType, INPUT_TYPES } from '../src/helpers/inputs'
-import path from 'path'
-import fs from 'fs'
-import $RefParser from '@apidevtools/json-schema-ref-parser'
-
-const SCHEMAS_LOCATION = path.join(__dirname, '../schemas')
-
-const ajv = new Ajv({ allErrors: true })
-
-const validateJsonSchema = (jsonSchemaShortPath: string, jsonSchema: Record<string, unknown>) => {
-  if (!ajv.validateSchema(jsonSchema)) {
-    console.log(`\nERROR! ${jsonSchemaShortPath} - schema validation failed!`)
-    console.log(ajv.errorsText(undefined, { separator: '\n' }))
-    console.log('\n')
-    process.exitCode = 100
-
-    return false
-  }
-
-  return true
-}
-
-const validateInputAgainstSchema = (input: FetchedInput, jsonSchema: Record<string, unknown>) => {
-  if (!ajv.validate(jsonSchema, input.data)) {
-    console.log(`\nERROR! ${input.fileName} - validation failed!`)
-    console.log(ajv.errorsText(undefined, { separator: '\n' }))
-    console.log('\n')
-    process.exitCode = 100
-
-    return false
-  }
-
-  return true
-}
-
-const getJsonSchemaForInput = (inputType: InputType, input: FetchedInput) => {
-  let schemaLocation = ''
-  if (inputType === 'classes') {
-    schemaLocation = path.join(SCHEMAS_LOCATION, 'extrinsics', 'CreateClass.schema.json')
-  }
-  if (inputType === 'schemas') {
-    schemaLocation = path.join(SCHEMAS_LOCATION, 'extrinsics', 'AddClassSchema.schema.json')
-  }
-  if (inputType === 'entityBatches') {
-    const jsonSchemaFilename = input.fileName.replace('.json', '.schema.json')
-    schemaLocation = path.join(SCHEMAS_LOCATION, 'entityBatches', jsonSchemaFilename)
-  }
-
-  return {
-    jsonSchemaPath: schemaLocation,
-    jsonSchema: JSON.parse(fs.readFileSync(schemaLocation).toString()),
-  }
-}
-
-async function main() {
-  const alreadyValidatedJsonSchemas = new Map<string, boolean>()
-  for (const inputType of INPUT_TYPES) {
-    console.log(`Validating inputs/${inputType} and related json-schemas...\n`)
-    for (const input of getInputs(inputType)) {
-      let { jsonSchemaPath, jsonSchema } = getJsonSchemaForInput(inputType, input)
-      jsonSchema = await $RefParser.dereference(jsonSchemaPath, jsonSchema)
-      const jsonSchemaShortPath = path.relative(path.join(SCHEMAS_LOCATION, '..'), jsonSchemaPath)
-      // Validate the schema itself
-      let isJsonSchemaValid = alreadyValidatedJsonSchemas.get(jsonSchemaShortPath)
-      if (isJsonSchemaValid === undefined) {
-        console.log(`Validating ${jsonSchemaShortPath}...`)
-        isJsonSchemaValid = validateJsonSchema(jsonSchemaShortPath, jsonSchema)
-        alreadyValidatedJsonSchemas.set(jsonSchemaShortPath, isJsonSchemaValid)
-      }
-      if (!isJsonSchemaValid) {
-        return
-      }
-      console.log(`Validating inputs/${inputType}/${input.fileName}...`)
-      validateInputAgainstSchema(input, jsonSchema)
-    }
-
-    console.log('\n\n')
-  }
-}
-
-main()
-  .then(() => process.exit())
-  .catch((e) => console.error(e))

+ 0 - 435
content-directory-schemas/src/helpers/InputParser.ts

@@ -1,435 +0,0 @@
-import { AddClassSchema, Property } from '../../types/extrinsics/AddClassSchema'
-import { createType } from '@joystream/types'
-import {
-  InputEntityValuesMap,
-  ClassId,
-  OperationType,
-  ParametrizedPropertyValue,
-  PropertyId,
-  PropertyType,
-  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 { getInitializationInputs, getInputs } from './inputs'
-
-type SimpleEntityValue = string | boolean | number | string[] | boolean[] | number[] | undefined | null
-// Input without "new" or "extising" keywords
-type SimpleEntityInput = { [K: string]: SimpleEntityValue }
-
-export class InputParser {
-  private api: ApiPromise
-  private classInputs: CreateClass[]
-  private schemaInputs: AddClassSchema[]
-  private batchInputs: EntityBatch[]
-  private createEntityOperations: OperationType[] = []
-  private addSchemaToEntityOprations: OperationType[] = []
-  private updateEntityPropertyValuesOperations: OperationType[] = []
-  private entityIndexByUniqueQueryMap = new Map<string, number>()
-  private entityIdByUniqueQueryMap = new Map<string, number>()
-  private entityByUniqueQueryCurrentIndex = 0
-  private classIdByNameMap = new Map<string, number>()
-
-  static createWithInitialInputs(api: ApiPromise): InputParser {
-    const { classInputs, schemaInputs, entityBatchInputs } = getInitializationInputs()
-    return new InputParser(api, classInputs, schemaInputs, entityBatchInputs)
-  }
-
-  static createWithKnownSchemas(api: ApiPromise, entityBatches?: EntityBatch[]): InputParser {
-    return new InputParser(
-      api,
-      [],
-      getInputs<AddClassSchema>('schemas').map(({ data }) => data),
-      entityBatches
-    )
-  }
-
-  constructor(
-    api: ApiPromise,
-    classInputs?: CreateClass[],
-    schemaInputs?: AddClassSchema[],
-    batchInputs?: EntityBatch[]
-  ) {
-    this.api = api
-    this.classInputs = classInputs || []
-    this.schemaInputs = schemaInputs || []
-    this.batchInputs = batchInputs || []
-  }
-
-  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())
-    })
-  }
-
-  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]
-      if (!className) {
-        // Class not found - skip
-        return
-      }
-      let schema: AddClassSchema
-      try {
-        schema = this.schemaByClassName(className)
-      } catch (e) {
-        // Input schema not found - skip
-        return
-      }
-      const valuesEntries = Array.from(entity.getField('values').entries())
-      schema.newProperties.forEach(({ name, unique }, index) => {
-        if (!unique) {
-          return // Skip non-unique properties
-        }
-        const storedValue = valuesEntries.find(([propertyId]) => propertyId.toNumber() === index)?.[1]
-        if (
-          storedValue === undefined ||
-          // If unique value is Bool, it's almost definitely empty, so we skip it
-          (storedValue.isOfType('Single') && storedValue.asType('Single').isOfType('Bool'))
-        ) {
-          // Skip empty values (not all unique properties are required)
-          return
-        }
-        const simpleValue = storedValue.getValue().toJSON()
-        const hash = this.getUniqueQueryHash({ [name]: simpleValue }, schema.className)
-        this.entityIdByUniqueQueryMap.set(hash, entityId.toNumber())
-      })
-    })
-  }
-
-  private schemaByClassName(className: string) {
-    const foundSchema = this.schemaInputs.find((data) => data.className === className)
-    if (!foundSchema) {
-      throw new Error(`Schema not found by class name: ${className}`)
-    }
-
-    return foundSchema
-  }
-
-  private getUniqueQueryHash(uniquePropVal: Record<string, any>, className: string) {
-    return blake2AsHex(JSON.stringify([className, uniquePropVal]))
-  }
-
-  private findEntityIndexByUniqueQuery(uniquePropVal: Record<string, any>, className: string) {
-    const hash = this.getUniqueQueryHash(uniquePropVal, className)
-    const foundIndex = this.entityIndexByUniqueQueryMap.get(hash)
-    if (foundIndex === undefined) {
-      throw new Error(
-        `findEntityIndexByUniqueQuery failed for class ${className} and query: ${JSON.stringify(uniquePropVal)}`
-      )
-    }
-
-    return foundIndex
-  }
-
-  // Seatch for entity by { [uniquePropName]: [uniquePropVal] } on chain
-  async findEntityIdByUniqueQuery(uniquePropVal: Record<string, any>, className: string): Promise<number> {
-    const hash = this.getUniqueQueryHash(uniquePropVal, className)
-    let foundId = this.entityIdByUniqueQueryMap.get(hash)
-    if (foundId === undefined) {
-      // 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
-  }
-
-  async getClassIdByName(className: string): Promise<number> {
-    let classId = this.classIdByNameMap.get(className)
-    if (classId === undefined) {
-      // 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 async parsePropertyType(propertyType: Property['property_type']): Promise<PropertyType> {
-    if (isSingle(propertyType) && isReference(propertyType.Single)) {
-      const { className, sameOwner } = propertyType.Single.Reference
-      const classId = await this.getClassIdByName(className)
-      return createType('PropertyType', { Single: { Reference: [classId, sameOwner] } })
-    }
-    // Types other than reference are fully compatible
-    return createType('PropertyType', propertyType)
-  }
-
-  private includeEntityInputInUniqueQueryMap(entityInput: Record<string, any>, schema: AddClassSchema) {
-    Object.entries(entityInput)
-      .filter(([, pValue]) => pValue !== undefined)
-      .forEach(([propertyName, propertyValue]) => {
-        const schemaPropertyType = schema.newProperties.find((p) => p.name === propertyName)!.property_type
-        // Handle entities "nested" via "new"
-        if (isSingle(schemaPropertyType) && isReference(schemaPropertyType.Single)) {
-          if (propertyValue !== null && Object.keys(propertyValue).includes('new')) {
-            const refEntitySchema = this.schemaByClassName(schemaPropertyType.Single.Reference.className)
-            this.includeEntityInputInUniqueQueryMap(propertyValue.new, refEntitySchema)
-          }
-        }
-      })
-    // Add entries to entityIndexByUniqueQueryMap
-    schema.newProperties
-      .filter((p) => p.unique)
-      .forEach(({ name }) => {
-        if (entityInput[name] === undefined) {
-          // Skip empty values (not all unique properties are required)
-          return
-        }
-        const hash = this.getUniqueQueryHash({ [name]: entityInput[name] }, schema.className)
-        this.entityIndexByUniqueQueryMap.set(hash, this.entityByUniqueQueryCurrentIndex)
-      })
-    ++this.entityByUniqueQueryCurrentIndex
-  }
-
-  private async createParametrizedPropertyValues(
-    entityInput: Record<string, any>,
-    schema: AddClassSchema,
-    customHandler?: (property: Property, value: any) => Promise<ParametrizedPropertyValue | undefined>
-  ): Promise<ParametrizedClassPropertyValue[]> {
-    const filteredInput = Object.entries(entityInput).filter(([, pValue]) => pValue !== undefined)
-    const parametrizedClassPropValues: ParametrizedClassPropertyValue[] = []
-
-    for (const [propertyName, propertyValue] of filteredInput) {
-      const schemaPropertyIndex = schema.newProperties.findIndex((p) => p.name === propertyName)
-      const schemaProperty = schema.newProperties[schemaPropertyIndex]
-
-      let value = customHandler && (await customHandler(schemaProperty, propertyValue))
-      if (value === undefined) {
-        if (propertyValue === null) {
-          // Optional values: (can be cleared by setting them to Bool(false)):
-          value = createType('ParametrizedPropertyValue', { InputPropertyValue: { Single: { Bool: false } } })
-        } else {
-          value = createType('ParametrizedPropertyValue', {
-            InputPropertyValue: (await this.parsePropertyType(schemaProperty.property_type)).toInputPropertyValue(
-              propertyValue
-            ),
-          })
-        }
-      }
-
-      parametrizedClassPropValues.push(
-        createType('ParametrizedClassPropertyValue', {
-          in_class_index: schemaPropertyIndex,
-          value,
-        })
-      )
-    }
-
-    return parametrizedClassPropValues
-  }
-
-  private async existingEntityQueryToParametrizedPropertyValue(className: string, uniquePropVal: Record<string, any>) {
-    try {
-      // First - try to find in existing batches
-      const entityIndex = this.findEntityIndexByUniqueQuery(uniquePropVal, className)
-      return createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
-    } catch (e) {
-      // If not found - fallback to chain search
-      const entityId = await this.findEntityIdByUniqueQuery(uniquePropVal, className)
-      return createType('ParametrizedPropertyValue', {
-        InputPropertyValue: { Single: { Reference: entityId } },
-      })
-    }
-  }
-
-  // parseEntityInput Overloads
-  private parseEntityInput(entityInput: Record<string, any>, schema: AddClassSchema): Promise<number>
-  private parseEntityInput(
-    entityInput: Record<string, any>,
-    schema: AddClassSchema,
-    updatedEntityId: number
-  ): Promise<void>
-
-  // Parse entity input. Speficy "updatedEntityId" only if want to parse into update operation!
-  private async parseEntityInput(
-    entityInput: Record<string, any>,
-    schema: AddClassSchema,
-    updatedEntityId?: number
-  ): Promise<void | number> {
-    const parametrizedPropertyValues = await this.createParametrizedPropertyValues(
-      entityInput,
-      schema,
-      async (property, value) => {
-        // Custom handler for references
-        const { property_type: propertyType } = property
-        if (isSingle(propertyType) && isReference(propertyType.Single)) {
-          const refEntitySchema = this.schemaByClassName(propertyType.Single.Reference.className)
-          if (value !== null && Object.keys(value).includes('new')) {
-            const entityIndex = await this.parseEntityInput(value.new, refEntitySchema)
-            return createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
-          } else if (value !== null && Object.keys(value).includes('existing')) {
-            return this.existingEntityQueryToParametrizedPropertyValue(refEntitySchema.className, value.existing)
-          }
-        }
-        return undefined
-      }
-    )
-
-    if (updatedEntityId) {
-      // Update operation
-      this.updateEntityPropertyValuesOperations.push(
-        createType('OperationType', {
-          UpdatePropertyValues: {
-            entity_id: { ExistingEntity: updatedEntityId },
-            new_parametrized_property_values: parametrizedPropertyValues,
-          },
-        })
-      )
-    } else {
-      // Add operations (createEntity, AddSchemaSupportToEntity)
-      const createEntityOperationIndex = this.createEntityOperations.length
-      const classId = await this.getClassIdByName(schema.className)
-      this.createEntityOperations.push(createType('OperationType', { CreateEntity: { class_id: classId } }))
-      this.addSchemaToEntityOprations.push(
-        createType('OperationType', {
-          AddSchemaSupportToEntity: {
-            schema_id: 0,
-            entity_id: { InternalEntityJustAdded: createEntityOperationIndex },
-            parametrized_property_values: parametrizedPropertyValues,
-          },
-        })
-      )
-
-      // Return CreateEntity operation index
-      return createEntityOperationIndex
-    }
-  }
-
-  private reset() {
-    this.entityIndexByUniqueQueryMap = new Map<string, number>()
-    this.classIdByNameMap = new Map<string, number>()
-
-    this.createEntityOperations = []
-    this.addSchemaToEntityOprations = []
-    this.updateEntityPropertyValuesOperations = []
-
-    this.entityByUniqueQueryCurrentIndex = 0
-  }
-
-  public async getEntityBatchOperations() {
-    // First - create entityUniqueQueryMap to allow referencing any entity at any point
-    this.batchInputs.forEach((batch) => {
-      const entitySchema = this.schemaByClassName(batch.className)
-      batch.entries.forEach((entityInput) => this.includeEntityInputInUniqueQueryMap(entityInput, entitySchema))
-    })
-    // Then - parse into actual operations
-    for (const batch of this.batchInputs) {
-      const entitySchema = this.schemaByClassName(batch.className)
-      for (const entityInput of batch.entries) {
-        await this.parseEntityInput(entityInput, entitySchema)
-      }
-    }
-
-    const operations = [...this.createEntityOperations, ...this.addSchemaToEntityOprations]
-    this.reset()
-
-    return operations
-  }
-
-  public async getEntityUpdateOperations(
-    input: Record<string, any>,
-    className: string,
-    entityId: number
-  ): Promise<OperationType[]> {
-    const schema = this.schemaByClassName(className)
-    await this.parseEntityInput(input, schema, entityId)
-    const operations = [
-      ...this.createEntityOperations,
-      ...this.addSchemaToEntityOprations,
-      ...this.updateEntityPropertyValuesOperations,
-    ]
-    this.reset()
-
-    return operations
-  }
-
-  public async parseAddClassSchemaExtrinsic(inputData: AddClassSchema) {
-    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),
-      newProperties
-    )
-  }
-
-  public parseCreateClassExtrinsic(inputData: CreateClass) {
-    return this.api.tx.contentDirectory.createClass(
-      inputData.name,
-      inputData.description,
-      inputData.class_permissions || {},
-      inputData.maximum_entities_count,
-      inputData.default_entity_creation_voucher_upper_bound
-    )
-  }
-
-  public async getAddSchemaExtrinsics() {
-    return await Promise.all(this.schemaInputs.map((data) => this.parseAddClassSchemaExtrinsic(data)))
-  }
-
-  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)
-  }
-}

+ 0 - 19
content-directory-schemas/src/helpers/initialize.ts

@@ -1,19 +0,0 @@
-import { ApiPromise } from '@polkadot/api'
-import { InputParser } from './InputParser'
-import { ExtrinsicsHelper } from './extrinsics'
-import { KeyringPair } from '@polkadot/keyring/types'
-
-export default async function initializeContentDir(api: ApiPromise, leadKey: KeyringPair): Promise<void> {
-  const txHelper = new ExtrinsicsHelper(api)
-  const parser = InputParser.createWithInitialInputs(api)
-
-  // Initialize classes first in order to later be able to get classIdByNameMap
-  const createClassTxs = await parser.getCreateClassExntrinsics()
-  await txHelper.sendAndCheck(leadKey, createClassTxs, 'Classes initialization failed!')
-
-  // Initialize schemas and entities
-  const addSchemaTxs = await parser.getAddSchemaExtrinsics()
-  const entitiesTx = api.tx.contentDirectory.transaction({ Lead: null }, await parser.getEntityBatchOperations())
-  await txHelper.sendAndCheck(leadKey, addSchemaTxs, 'Schemas initialization failed!')
-  await txHelper.sendAndCheck(leadKey, [entitiesTx], 'Entities initialization failed!')
-}

+ 0 - 40
content-directory-schemas/src/helpers/inputs.ts

@@ -1,40 +0,0 @@
-import path from 'path'
-import fs from 'fs'
-import { CreateClass, AddClassSchema } from '../../types/extrinsics'
-import { EntityBatch } from '../../types/EntityBatch'
-
-export const INPUTS_LOCATION = path.join(__dirname, '../../inputs')
-export const INPUT_TYPES = ['classes', 'schemas', 'entityBatches'] as const
-
-export type InputType = typeof INPUT_TYPES[number]
-export type FetchedInput<Schema = any> = { fileName: string; data: Schema }
-
-export const getInputsLocation = (inputType: InputType) => path.join(INPUTS_LOCATION, inputType)
-
-export function getInputs<Schema = any>(
-  inputType: InputType,
-  rootInputsLocation = INPUTS_LOCATION
-): FetchedInput<Schema>[] {
-  const inputs: FetchedInput<Schema>[] = []
-  fs.readdirSync(path.join(rootInputsLocation, inputType)).forEach((fileName) => {
-    const inputFilePath = path.join(rootInputsLocation, inputType, fileName)
-    if (path.extname(inputFilePath) !== '.json') {
-      return
-    }
-    const inputJson = fs.readFileSync(inputFilePath).toString()
-    inputs.push({
-      fileName,
-      data: JSON.parse(inputJson) as Schema,
-    })
-  })
-  return inputs
-}
-
-export function getInitializationInputs(rootInputsLocation = INPUTS_LOCATION) {
-  return {
-    // eslint-disable-next-line @typescript-eslint/no-var-requires
-    classInputs: require('../../inputs/classes/index.js') as CreateClass[],
-    schemaInputs: getInputs<AddClassSchema>('schemas').map(({ data }) => data),
-    entityBatchInputs: getInputs<EntityBatch>('entityBatches').map(({ data }) => data),
-  }
-}

+ 0 - 16
content-directory-schemas/src/helpers/propertyType.ts

@@ -1,16 +0,0 @@
-import {
-  Property,
-  ReferenceProperty,
-  SinglePropertyType,
-  SinglePropertyVariant,
-} from '../../types/extrinsics/AddClassSchema'
-
-type PropertyType = Property['property_type']
-
-export function isSingle(propertyType: PropertyType): propertyType is SinglePropertyVariant {
-  return (propertyType as SinglePropertyVariant).Single !== undefined
-}
-
-export function isReference(propertySubtype: SinglePropertyType): propertySubtype is ReferenceProperty {
-  return (propertySubtype as ReferenceProperty).Reference !== undefined
-}

+ 0 - 8
content-directory-schemas/src/helpers/schemas.ts

@@ -1,8 +0,0 @@
-import path from 'path'
-
-export const SCHEMAS_LOCATION = path.join(__dirname, '../../schemas')
-export const SCHEMA_TYPES = ['entities', 'entityBatches', 'entityReferences', 'extrinsics'] as const
-
-export type SchemaType = typeof SCHEMA_TYPES[number]
-
-export const getSchemasLocation = (schemaType: SchemaType) => path.join(SCHEMAS_LOCATION, schemaType)

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

@@ -1,6 +0,0 @@
-export { ExtrinsicsHelper, getAlicePair } from './helpers/extrinsics'
-export { InputParser } from './helpers/InputParser'
-export { getInputs, getInitializationInputs, getInputsLocation } from './helpers/inputs'
-export { isReference, isSingle } from './helpers/propertyType'
-export { getSchemasLocation } from './helpers/schemas'
-export { default as initializeContentDir } from './helpers/initialize'

+ 0 - 27
content-directory-schemas/tsconfig.json

@@ -1,27 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "ES2017",
-    "module": "commonjs",
-    "strict": true,
-    "noImplicitAny": true,
-    "noImplicitReturns": true,
-    "moduleResolution": "node",
-    "allowSyntheticDefaultImports": true,     /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
-    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
-    "declaration": true,
-    "resolveJsonModule": true,
-    "types" : [
-      "node"
-    ],
-    "forceConsistentCasingInFileNames": true,
-    "baseUrl": ".",
-    "typeRoots": [
-      "typings"
-    ],
-    "paths": {
-      "@polkadot/types/augment": ["../types/augment-codec/augment-types.ts"],
-      "@polkadot/api/augment": ["../types/augment-codec/augment-api.ts"]
-    }
-  },
-  "include": [ "src/**/*", "scripts/**/*", "typings/**/*", "examples/**/*" ]
-}

+ 0 - 7
content-directory-schemas/tsconfig.lib.json

@@ -1,7 +0,0 @@
-{
-  "extends": "./tsconfig.json",
-  "include": ["src/**/*"],
-  "compilerOptions": {
-    "outDir": "lib"
-  }
-}

+ 0 - 4
content-directory-schemas/types/EntityBatch.d.ts

@@ -1,4 +0,0 @@
-export interface EntityBatch {
-  className: string
-  entries: Record<string, any>[]
-}

+ 0 - 67
content-directory-schemas/types/extrinsics/AddClassSchema.d.ts

@@ -1,67 +0,0 @@
-/* tslint:disable */
-/**
- * This file was automatically generated by json-schema-to-typescript.
- * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
- * and run json-schema-to-typescript to regenerate this file.
- */
-
-export type PropertyInSchemIndex = number
-export type SinglePropertyType =
-  | ('Bool' | 'Uint16' | 'Uint32' | 'Uint64' | 'Int16' | 'Int32' | 'Int64')
-  | TextProperty
-  | HashProperty
-  | ReferenceProperty
-export type MaxTextLength = number
-export type MaxVecItems = number
-export type PropertyName = string
-export type PropertyDescription = string
-export type DefaultBoolean = boolean
-
-/**
- * JSON schema to describe a new schema for a certain class in Joystream network
- */
-export interface AddClassSchema {
-  className: string
-  existingProperties?: PropertyInSchemIndex[]
-  newProperties: Property[]
-}
-export interface Property {
-  property_type: SinglePropertyVariant | VecPropertyVariant
-  name: PropertyName
-  description?: PropertyDescription
-  required?: DefaultBoolean
-  unique?: DefaultBoolean
-  locking_policy?: LockingPolicy
-}
-export interface SinglePropertyVariant {
-  Single: SinglePropertyType
-}
-export interface TextProperty {
-  Text: MaxTextLength
-}
-export interface HashProperty {
-  Hash: MaxTextLength
-}
-export interface ReferenceProperty {
-  Reference: {
-    /**
-     * Referenced class name
-     */
-    className: string
-    /**
-     * Whether same owner (controller) is required
-     */
-    sameOwner?: boolean
-  }
-}
-export interface VecPropertyVariant {
-  Vector: VecPropertyType
-}
-export interface VecPropertyType {
-  vec_type: SinglePropertyType
-  max_length: MaxVecItems
-}
-export interface LockingPolicy {
-  is_locked_from_maintainer?: DefaultBoolean
-  is_locked_from_controller?: DefaultBoolean
-}

+ 0 - 30
content-directory-schemas/types/extrinsics/CreateClass.d.ts

@@ -1,30 +0,0 @@
-/* tslint:disable */
-/**
- * This file was automatically generated by json-schema-to-typescript.
- * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
- * and run json-schema-to-typescript to regenerate this file.
- */
-
-export type DefaultBoolean = boolean
-
-/**
- * JSON schema to describe a new class for Joystream network
- */
-export interface CreateClass {
-  /**
-   * Name of this class. Required property.
-   */
-  name: string
-  /**
-   * Description of this class.
-   */
-  description: string
-  class_permissions?: {
-    any_member?: DefaultBoolean
-    entity_creation_blocked?: DefaultBoolean
-    all_entity_property_values_locked?: DefaultBoolean
-    maintainers?: number[]
-  }
-  maximum_entities_count: number
-  default_entity_creation_voucher_upper_bound: number
-}

+ 0 - 2
content-directory-schemas/types/extrinsics/index.d.ts

@@ -1,2 +0,0 @@
-export { AddClassSchema } from './AddClassSchema'
-export { CreateClass } from './CreateClass'

+ 0 - 1
content-directory-schemas/types/utility.d.ts

@@ -1 +0,0 @@
-export type FlattenRelations<T> = { [K in keyof T]: Exclude<T[K], { new: any } | { existing: any }> }

+ 0 - 2
content-directory-schemas/typings/@joystream/prettier-config/index.d.ts

@@ -1,2 +0,0 @@
-// Prevent declaration file not found error
-declare module '@joystream/prettier-config'

+ 0 - 60
content-directory-schemas/vscode-recommended.settings.json

@@ -1,60 +0,0 @@
-{
-  "json.schemas": [
-    {
-      "fileMatch": ["*Schema.json"],
-      "url": "/content-directory-schemas/schemas/extrinsics/AddClassSchema.schema.json"
-    },
-    {
-      "fileMatch": ["*Class.json"],
-      "url": "/content-directory-schemas/schemas/extrinsics/CreateClass.schema.json"
-    },
-    {
-      "fileMatch": ["*/LanguageBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/LanguageBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/MediaLocationBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/MediaLocationBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/HttpMediaLocationBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/HttpMediaLocationBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/JoystreamMediaLocationBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/JoystreamMediaLocationBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/ContentCategoryBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/ContentCategoryBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/ChannelBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/ChannelBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/VideoMediaEncodingBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/VideoMediaEncodingBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/KnownLicenseBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/KnownLicenseBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/UserDefinedLicenseBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/UserDefinedLicenseBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/LicenseBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/LicenseBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/VideoMediaBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/VideoMediaBatch.schema.json"
-    },
-    {
-      "fileMatch": ["*/VideoBatch.json"],
-      "url": "/content-directory-schemas/schemas/entityBatches/VideoBatch.schema.json"
-    }
-  ]
-}

+ 1 - 1
node/Cargo.toml

@@ -3,7 +3,7 @@ authors = ['Joystream contributors']
 build = 'build.rs'
 edition = '2018'
 name = 'joystream-node'
-version = '3.6.0'
+version = '3.7.1'
 default-run = "joystream-node"
 
 [[bin]]

+ 48 - 38
node/src/chain_spec/content_config.rs

@@ -1,8 +1,7 @@
 use codec::Decode;
 use node_runtime::{
-    common::storage::StorageObjectOwner,
-    data_directory::{DataObject, Quota},
-    ChannelId, ContentId, DAOId, DataDirectoryConfig, MemberId, Runtime,
+    common::storage::StorageObjectOwner, data_directory::*, ChannelId, ContentId, DAOId,
+    DataDirectoryConfig, MemberId, Runtime,
 };
 use serde::Deserialize;
 use std::{fs, path::Path};
@@ -16,16 +15,17 @@ struct Content {
     content_id: ContentId,
     data_object: DataObject<Runtime>,
     storage_object_owner: StorageObjectOwner<MemberId, ChannelId, DAOId>,
-    quota: Quota,
+    voucher: Voucher,
 }
 
 #[derive(Decode)]
 struct ContentData {
     /// DataObject(s) and ContentId
     data_objects: Vec<Content>,
-    quota_size_limit_upper_bound: u64,
-    quota_objects_limit_upper_bound: u64,
-    global_quota: Quota,
+    voucher_size_limit_upper_bound: u64,
+    voucher_objects_limit_upper_bound: u64,
+    global_voucher: Voucher,
+    default_voucher: Voucher,
     uploading_blocked: bool,
 }
 
@@ -37,8 +37,8 @@ struct EncodedContent {
     data_object: String,
     /// hex encoded StorageObjectOwner
     storage_object_owner: String,
-    /// hex encoded Quota
-    quota: String,
+    /// hex encoded Voucher
+    voucher: String,
 }
 
 impl EncodedContent {
@@ -50,14 +50,14 @@ impl EncodedContent {
             .expect("failed to parse data_object hex string");
         let encoded_storage_object_owner = hex::decode(&self.storage_object_owner[2..].as_bytes())
             .expect("failed to parse content_id hex string");
-        let encoded_quota = hex::decode(&self.quota[2..].as_bytes())
+        let encoded_voucher = hex::decode(&self.voucher[2..].as_bytes())
             .expect("failed to parse data_object hex string");
         Content {
             content_id: Decode::decode(&mut encoded_content_id.as_slice()).unwrap(),
             data_object: Decode::decode(&mut encoded_data_object.as_slice()).unwrap(),
             storage_object_owner: Decode::decode(&mut encoded_storage_object_owner.as_slice())
                 .unwrap(),
-            quota: Decode::decode(&mut encoded_quota.as_slice()).unwrap(),
+            voucher: Decode::decode(&mut encoded_voucher.as_slice()).unwrap(),
         }
     }
 }
@@ -66,12 +66,14 @@ impl EncodedContent {
 struct EncodedContentData {
     /// EncodedContent
     data_objects: Vec<EncodedContent>,
-    /// hex encoded QuotaSizeLimitUpperBound
-    quota_size_limit_upper_bound: String,
-    /// hex encoded QuotaObjectsLimitUpperBound
-    quota_objects_limit_upper_bound: String,
-    /// hex encoded GlobalQuota
-    global_quota: String,
+    /// hex encoded VoucherSizeLimitUpperBound
+    voucher_size_limit_upper_bound: String,
+    /// hex encoded VoucherObjectsLimitUpperBound
+    voucher_objects_limit_upper_bound: String,
+    /// hex encoded GlobalVoucher
+    global_voucher: String,
+    /// hex encoded DefaultVoucher
+    default_voucher: String,
     /// hex encoded UploadingBlocked flag
     uploading_blocked: String,
 }
@@ -89,25 +91,31 @@ impl EncodedContentData {
                 .iter()
                 .map(|data_objects| data_objects.decode())
                 .collect(),
-            quota_size_limit_upper_bound: {
-                let encoded_quota_size_limit_upper_bound =
-                    hex::decode(&self.quota_size_limit_upper_bound[2..].as_bytes())
+            voucher_size_limit_upper_bound: {
+                let encoded_voucher_size_limit_upper_bound =
+                    hex::decode(&self.voucher_size_limit_upper_bound[2..].as_bytes())
                         .expect("failed to parse data_object hex string");
 
-                Decode::decode(&mut encoded_quota_size_limit_upper_bound.as_slice()).unwrap()
+                Decode::decode(&mut encoded_voucher_size_limit_upper_bound.as_slice()).unwrap()
             },
-            quota_objects_limit_upper_bound: {
-                let encoded_quota_objects_limit_upper_bound =
-                    hex::decode(&self.quota_objects_limit_upper_bound[2..].as_bytes())
+            voucher_objects_limit_upper_bound: {
+                let encoded_voucher_objects_limit_upper_bound =
+                    hex::decode(&self.voucher_objects_limit_upper_bound[2..].as_bytes())
                         .expect("failed to parse data_object hex string");
 
-                Decode::decode(&mut encoded_quota_objects_limit_upper_bound.as_slice()).unwrap()
+                Decode::decode(&mut encoded_voucher_objects_limit_upper_bound.as_slice()).unwrap()
             },
-            global_quota: {
-                let encoded_global_quota = hex::decode(&self.global_quota[2..].as_bytes())
+            global_voucher: {
+                let encoded_global_voucher = hex::decode(&self.global_voucher[2..].as_bytes())
                     .expect("failed to parse data_object hex string");
 
-                Decode::decode(&mut encoded_global_quota.as_slice()).unwrap()
+                Decode::decode(&mut encoded_global_voucher.as_slice()).unwrap()
+            },
+            default_voucher: {
+                let encoded_default_voucher = hex::decode(&self.default_voucher[2..].as_bytes())
+                    .expect("failed to parse data_object hex string");
+
+                Decode::decode(&mut encoded_default_voucher.as_slice()).unwrap()
             },
             uploading_blocked: {
                 let encoded_uploading_blocked =
@@ -124,11 +132,12 @@ impl EncodedContentData {
 pub fn empty_data_directory_config() -> DataDirectoryConfig {
     DataDirectoryConfig {
         data_object_by_content_id: vec![],
-        quotas: vec![],
-        quota_size_limit_upper_bound: 20000,
-        quota_objects_limit_upper_bound: 200,
-        global_quota: Quota::new(2000000, 2000),
-        uploading_blocked: false,
+        vouchers: vec![],
+        voucher_size_limit_upper_bound: DEFAULT_VOUCHER_SIZE_LIMIT_UPPER_BOUND,
+        voucher_objects_limit_upper_bound: DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND,
+        global_voucher: DEFAULT_GLOBAL_VOUCHER,
+        default_voucher: DEFAULT_VOUCHER,
+        uploading_blocked: DEFAULT_UPLOADING_BLOCKED_STATUS,
     }
 }
 
@@ -144,14 +153,15 @@ pub fn data_directory_config_from_json(data_file: &Path) -> DataDirectoryConfig
             .iter()
             .map(|object| (object.content_id, object.data_object.clone()))
             .collect(),
-        quotas: content
+        vouchers: content
             .data_objects
             .iter()
-            .map(|object| (object.storage_object_owner.clone(), object.quota))
+            .map(|object| (object.storage_object_owner.clone(), object.voucher))
             .collect(),
-        quota_size_limit_upper_bound: content.quota_size_limit_upper_bound,
-        quota_objects_limit_upper_bound: content.quota_objects_limit_upper_bound,
-        global_quota: content.global_quota,
+        voucher_size_limit_upper_bound: content.voucher_size_limit_upper_bound,
+        voucher_objects_limit_upper_bound: content.voucher_objects_limit_upper_bound,
+        global_voucher: content.global_voucher,
+        default_voucher: content.default_voucher,
         uploading_blocked: content.uploading_blocked,
     }
 }

+ 7 - 0
node/src/chain_spec/mod.rs

@@ -225,6 +225,9 @@ pub fn testnet_genesis(
 
     let default_text_constraint = node_runtime::working_group::default_text_constraint();
 
+    let default_storage_size_constraint =
+        node_runtime::working_group::default_storage_size_constraint();
+
     GenesisConfig {
         system: Some(SystemConfig {
             code: WASM_BINARY.to_vec(),
@@ -311,6 +314,7 @@ pub fn testnet_genesis(
             opening_human_readable_text_constraint: default_text_constraint,
             worker_application_human_readable_text_constraint: default_text_constraint,
             worker_exit_rationale_text_constraint: default_text_constraint,
+            worker_storage_size_constraint: default_storage_size_constraint,
         }),
         working_group_Instance3: Some(ContentDirectoryWorkingGroupConfig {
             phantom: Default::default(),
@@ -318,6 +322,7 @@ pub fn testnet_genesis(
             opening_human_readable_text_constraint: default_text_constraint,
             worker_application_human_readable_text_constraint: default_text_constraint,
             worker_exit_rationale_text_constraint: default_text_constraint,
+            worker_storage_size_constraint: default_storage_size_constraint,
         }),
         working_group_Instance4: Some(BuilderWorkingGroupConfig {
             phantom: Default::default(),
@@ -325,6 +330,7 @@ pub fn testnet_genesis(
             opening_human_readable_text_constraint: default_text_constraint,
             worker_application_human_readable_text_constraint: default_text_constraint,
             worker_exit_rationale_text_constraint: default_text_constraint,
+            worker_storage_size_constraint: default_storage_size_constraint,
         }),
         working_group_Instance5: Some(GatewayWorkingGroupConfig {
             phantom: Default::default(),
@@ -332,6 +338,7 @@ pub fn testnet_genesis(
             opening_human_readable_text_constraint: default_text_constraint,
             worker_application_human_readable_text_constraint: default_text_constraint,
             worker_exit_rationale_text_constraint: default_text_constraint,
+            worker_storage_size_constraint: default_storage_size_constraint,
         }),
         content: Some({
             ContentConfig {

+ 0 - 1
package.json

@@ -21,7 +21,6 @@
     "pioneer",
     "pioneer/packages/*",
     "utils/api-scripts",
-    "content-directory-schemas",
     "query-node",
     "query-node/mappings",
     "query-node/generated/*",

+ 1 - 0
pioneer/packages/apps/public/locales/en/joy-media.json

@@ -0,0 +1 @@
+{}

+ 3 - 1
pioneer/packages/joy-utils/src/consts/workingGroups.ts

@@ -1,5 +1,7 @@
 import { WorkingGroupKey } from '@joystream/types/common';
 export const apiModuleByGroup: { [k in WorkingGroupKey]: string } = {
   Storage: 'storageWorkingGroup',
-  Content: 'contentDirectoryWorkingGroup'
+  Content: 'contentDirectoryWorkingGroup',
+  Builder: 'builderWorkingGroup',
+  Gateway: 'gatewayWorkingGroup'
 };

+ 196 - 175
query-node/schema.graphql

@@ -1,3 +1,8 @@
+# temporary type used before `DateTime` type is working
+type DateTime @entity {
+  timestamp: BigInt!
+}
+
 enum Network {
   BABYLON
   ALEXANDRIA
@@ -8,17 +13,23 @@ type Block @entity {
   "Block number as a string"
   id: ID!
   block: Int!
-  timestamp: BigInt!
+  executedAt: DateTime!
   network: Network!
 }
 
+enum MembershipEntryMethod {
+  PAID
+  SCREENING
+  GENESIS
+}
+
 "Stored information about a registered user"
-type Member @entity {
+type Membership @entity {
   "MemberId: runtime identifier for a user"
   id: ID!
 
   "The unique handle chosen by member"
-  handle: String @unique @fulltext(query: "membersByHandle")
+  handle: String! @unique @fulltext(query: "membersByHandle")
 
   "A Url to member's Avatar image"
   avatarUri: String
@@ -26,205 +37,216 @@ type Member @entity {
   "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!
+  controllerAccount: String!
 
   "Member's root account id"
-  rootAccount: Bytes!
+  rootAccount: String!
 
-  happenedIn: Block!
-}
+  "Blocknumber when member was registered"
+  registeredAtBlock: 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!
+  "Timestamp when member was registered"
+  registeredAtTime: DateTime!
 
-  "The class id of this entity"
-  classId: Int!
+  "How the member was registered"
+  entry: MembershipEntryMethod!
 
-  happenedIn: Block!
+  "The type of subscription the member has purchased if any."
+  subscription: BigInt
 }
 
-"Keep track of the next entity id"
-type NextEntityId @entity {
-  "Constant field is set to '1'"
+"Category of media channel"
+type ChannelCategory @entity {
   id: ID!
 
-  nextId: Int!
+  "The name of the category"
+  name: String @fulltext(query: "channelCategoriesByName")
+
+  channels: [Channel!] @derivedFrom(field: "category")
+
+  happenedIn: Block!
 }
 
-#### High Level Derivative Entities ####
+"Storage asset"
+union Asset = AssetUrl | AssetStorage
 
-type Language @entity {
-  "Runtime entity identifier (EntityId)"
+"Asset stored at an external source"
+type AssetUrl @variant {
   id: ID!
 
-  name: String!
-  code: String!
+  "The http url pointing to the media"
+  url: String!
+}
 
+"Asset was never fully uploaded."
+type AssetNeverProvided @variant {
   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"
-  handle: String! @fulltext(query: "search")
+"Asset was deleted and is no longer available."
+type AssetDeleted @variant {
+  happenedIn: Block!
+}
 
-  "The description of a Channel"
-  description: String!
+"Status of an asset upload"
+type AssetUploadStatus @variant {
+  """
+  Data object in upload life-cycle.
+  If this is deleted, then set oldDataObject in its place if it is set and not rejected, otherwise union goes to Deleted.
+  """
+  dataObject: AssetDataObject!
+
+  """
+  Possible prior data object which was in some stage of upload life-cycle when new one was initiated.
+  If accepted, then apps may chose to use old in place of new before it is accepted.
+  If this is deleted, then set to null.
+  """
+  oldDataObject: AssetDataObject
 
-  "Url for Channel's cover (background) photo. Recommended ratio: 16:9."
-  coverPhotoUrl: String
+  happenedIn: Block!
+}
 
-  "Channel's avatar photo."
-  avatarPhotoUrl: String
+union AssetStorageUploadStatus = AssetNeverProvided | AssetDeleted | AssetUploadStatus
 
-  "Flag signaling whether a channel is public."
-  isPublic: Boolean!
+type AssetStorage @variant {
+  id: ID!
 
-  "Flag signaling whether a channel is curated/verified."
-  isCurated: Boolean!
+  "Upload to content directory status"
+  uploadStatus: AssetStorageUploadStatus!
+}
 
-  "The primary langauge of the channel's content"
-  language: Language
+"The decision of the storage provider when it acts as liaison"
+enum LiaisonJudgement {
+  "Content awaits for a judgment"
+  PENDING,
 
-  videos: [Video!] @derivedFrom(field: "channel")
+  "Content accepted"
+  ACCEPTED,
 
-  happenedIn: Block!
+  "Content rejected"
+  REJECTED,
 }
 
-type Category @entity {
-  "Runtime entity identifier (EntityId)"
-  id: ID!
+"Manages content ids, type and storage provider decision about it"
+type AssetDataObject @entity {
+  "Content owner"
+  owner: AssetOwner!
 
-  "The name of the category"
-  name: String! @unique @fulltext(query: "categoriesByName")
+  "Content added at"
+  addedAt: Block!
 
-  "The description of the category"
-  description: String
+  "Content type id"
+  typeId: Int!
 
-  videos: [Video!] @derivedFrom(field: "category")
+  "Content size in bytes"
+  size: BigInt!
 
-  happenedIn: Block!
-}
+  "Storage provider id of the liaison"
+  liaisonId: BigInt!
 
-"Encoding and containers"
-type VideoMediaEncoding @entity {
-  "Runtime entity identifier (EntityId)"
-  id: ID!
+  "Storage provider as liaison judgment"
+  liaisonJudgement: LiaisonJudgement!
 
-  name: String!
+  "IPFS content id"
+  ipfsContentId: String!
 
-  happenedIn: Block!
+  "Joystream runtime content"
+  joystreamContentId: String!
 }
 
-type KnownLicenseEntity @entity {
-  "Runtime entity identifier (EntityId)"
-  id: ID!
+"Owner type for storage object"
+union AssetOwner = AssetOwnerMember | AssetOwnerChannel | AssetOwnerDao | AssetOwnerCouncil | AssetOwnerWorkingGroup
 
-  "Short, commonly recognized code of the licence (ie. CC_BY_SA)"
-  code: String! @unique
+"Asset owned by a member"
+type AssetOwnerMember @variant {
+  "Member identifier"
+  memberId: BigInt!
+}
 
-  "Full, descriptive name of the license (ie. Creative Commons - Attribution-NonCommercial-NoDerivs)"
-  name: String
+"Asset owned by a channel"
+type AssetOwnerChannel @variant {
+  "Channel identifier"
+  channel: Channel!
+}
 
-  "Short description of the license conditions"
-  description: String
+"Asset owned by a DAO"
+type AssetOwnerDao @variant {
+  "DAO identifier"
+  daoId: BigInt!
+}
 
-  "An url pointing to full license content"
-  url: String
+"Asset owned by the Council"
+type AssetOwnerCouncil @variant {
+  "Variant needs to have at least one property. This value is not used."
+  dummy: Int!
+}
 
-  happenedIn: Block!
+"Asset owned by a WorkingGroup"
+type AssetOwnerWorkingGroup @variant {
+  "Working group identifier"
+  workingGroupId: BigInt!
 }
 
-type UserDefinedLicenseEntity @entity {
+#### High Level Derivative Entities ####
+
+type Language @entity {
   "Runtime entity identifier (EntityId)"
   id: ID!
 
-  "Custom license content"
-  content: String!
+  "Language identifier ISO 639-1"
+  iso: String!
 
   happenedIn: Block!
 }
 
-type MediaLocationEntity @entity {
+type Channel @entity {
   "Runtime entity identifier (EntityId)"
   id: ID!
 
-  # One of the following field will be non-null
+  # "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!
 
-  "A reference to HttpMediaLocation"
-  httpMediaLocation: HttpMediaLocationEntity
+  category: ChannelCategory
 
-  "A reference to JoystreamMediaLocation"
-  joystreamMediaLocation: JoystreamMediaLocationEntity
+  "Reward account where revenue is sent if set."
+  rewardAccount: String
 
-  videoMedia: VideoMedia @derivedFrom(field: "locationEntity")
+  "The title of the Channel"
+  title: String @fulltext(query: "search")
 
-  happenedIn: Block!
-}
+  "The description of a Channel"
+  description: String
 
-type JoystreamMediaLocationEntity @entity {
-  "Runtime entity identifier (EntityId)"
-  id: ID!
+  "Channel's cover (background) photo. Recommended ratio: 16:9."
+  coverPhoto: Asset
 
-  "Id of the data object in the Joystream runtime dataDirectory module"
-  dataObjectId: String! @unique
+  "Channel's avatar photo."
+  avatarPhoto: Asset
 
-  happenedIn: Block!
-}
+  "Flag signaling whether a channel is public."
+  isPublic: Boolean
 
-type HttpMediaLocationEntity @entity {
-  "Runtime entity identifier (EntityId)"
-  id: ID!
+  "Flag signaling whether a channel is censored."
+  isCensored: Boolean!
 
-  "The http url pointing to the media"
-  url: String!
+  "The primary langauge of the channel's content"
+  language: Language
 
-  "The port to use when connecting to the http url (defaults to 80)"
-  port: Int
+  videos: [Video!] @derivedFrom(field: "channel")
 
   happenedIn: Block!
 }
 
-type VideoMedia @entity {
+type VideoCategory @entity {
   "Runtime entity identifier (EntityId)"
   id: ID!
 
-  "Encoding of the video media object"
-  encoding: VideoMediaEncoding!
-
-  "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")
-
-  "Location of the video media object"
-  location: MediaLocation!
+  "The name of the category"
+  name: String @fulltext(query: "videoCategoriesByName")
 
-  locationEntity: MediaLocationEntity
+  videos: [Video!] @derivedFrom(field: "category")
 
   happenedIn: Block!
 }
@@ -237,45 +259,46 @@ type Video @entity {
   channel: Channel!
 
   "Reference to a video category"
-  category: Category!
+  category: VideoCategory
 
   "The title of the video"
-  title: String! @fulltext(query: "search")
+  title: String @fulltext(query: "search")
 
   "The description of the Video"
-  description: String!
+  description: String
 
   "Video duration in seconds"
-  duration: Int!
+  duration: Int
 
-  "Video's skippable intro duration in seconds"
-  skippableIntroDuration: Int
-
-  "Video thumbnail url (recommended ratio: 16:9)"
-  thumbnailUrl: String!
+  "Video thumbnail (recommended ratio: 16:9)"
+  thumbnail: Asset
 
   "Video's main langauge"
   language: Language
 
-  "Reference to VideoMedia"
-  media: VideoMedia!
-
   "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
+  publishedBeforeJoystream: DateTime
 
   "Whether the Video is supposed to be publically displayed"
-  isPublic: Boolean!
+  isPublic: Boolean
 
-  "Video curation status set by the Curator"
-  isCurated: Boolean!
+  "Flag signaling whether a video is censored."
+  isCensored: Boolean!
 
   "Whether the Video contains explicit material."
-  isExplicit: Boolean!
+  isExplicit: Boolean
 
-  license: LicenseEntity!
+  "License under the video is published"
+  license: License
+
+  "Reference to video asset"
+  media: Asset
+
+  "Video file metadata"
+  mediaMetadata: VideoMediaMetadata
 
   happenedIn: Block!
 
@@ -285,52 +308,50 @@ type Video @entity {
   featured: FeaturedVideo @derivedFrom(field: "video")
 }
 
-type JoystreamMediaLocation @variant {
-  "Id of the data object in the Joystream runtime dataDirectory module"
-  dataObjectId: String!
-}
-
-type HttpMediaLocation @variant {
-  "The http url pointing to the media"
-  url: String!
+type VideoMediaMetadata @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
 
-  "The port to use when connecting to the http url (defaults to 80)"
-  port: Int
-}
+  "Encoding of the video media object"
+  encoding: VideoMediaEncoding
 
-union MediaLocation = HttpMediaLocation | JoystreamMediaLocation
+  "Video media width in pixels"
+  pixelWidth: Int
 
-type KnownLicense @variant {
-  "Short, commonly recognized code of the licence (ie. CC_BY_SA)"
-  code: String!
+  "Video media height in pixels"
+  pixelHeight: Int
 
-  "Full, descriptive name of the license (ie. Creative Commons - Attribution-NonCommercial-NoDerivs)"
-  name: String
+  "Video media size in bytes"
+  size: Int
 
-  "Short description of the license conditions"
-  description: String
+  video: Video @derivedFrom(field: "mediaMetadata")
 
-  "An url pointing to full license content"
-  url: String
+  happenedIn: Block!
 }
 
-type UserDefinedLicense @variant {
-  "Custom license content"
-  content: String!
-}
+type VideoMediaEncoding @entity {
+  "Encoding of the video media object"
+  codecName: String
 
-union License = KnownLicense | UserDefinedLicense
+  "Media container format"
+  container: String
 
-type LicenseEntity @entity {
+  "Content MIME type"
+  mimeMediaType: String
+}
+
+type License @entity {
   "Runtime entity identifier (EntityId)"
   id: ID!
 
-  type: License!
+  "License code defined by Joystream"
+  code: Int
 
   "Attribution (if required by the license)"
   attribution: String
 
-  happenedIn: Block!
+  "Custom license content"
+  custom_text: String
 }
 
 type FeaturedVideo @entity {

+ 4 - 2
runtime-modules/common/src/lib.rs

@@ -36,7 +36,8 @@ pub trait MembershipTypes: system::Trait {
         + Copy
         + MaybeSerialize
         + Ord
-        + PartialEq;
+        + PartialEq
+        + Eq;
 
     /// Describes the common type for the working group members (workers).
     type ActorId: Parameter
@@ -47,7 +48,8 @@ pub trait MembershipTypes: system::Trait {
         + Copy
         + MaybeSerialize
         + Ord
-        + PartialEq;
+        + PartialEq
+        + Eq;
 }
 
 /// Generic trait for strorage ownership dependent pallets.

+ 6 - 9
runtime-modules/common/src/storage.rs

@@ -24,13 +24,14 @@ pub enum StorageObjectOwner<MemberId, ChannelId, DAOId> {
     WorkingGroup(WorkingGroup), // acts through new extrinsic in working group
 }
 
+impl<MemberId, ChannelId, DAOId> Default for StorageObjectOwner<MemberId, ChannelId, DAOId> {
+    fn default() -> Self {
+        Self::Council
+    }
+}
 // To be implemented by current storage data_directory runtime module.
 // Defined in 'common' package
 pub trait StorageSystem<T: crate::StorageOwnership + crate::MembershipTypes> {
-    // Should hook into call on storage system,
-    // but requires rich error (with reasons)  types.
-    // caller already knows the `ContentId`s as they are part of
-    // the ContentUploadParameters
     fn atomically_add_content(
         owner: StorageObjectOwner<T::MemberId, T::ChannelId, T::DAOId>,
         content_parameters: Vec<ContentParameters<T::ContentId, T::DataObjectTypeId>>,
@@ -42,16 +43,12 @@ pub trait StorageSystem<T: crate::StorageOwnership + crate::MembershipTypes> {
         content_parameters: Vec<ContentParameters<T::ContentId, T::DataObjectTypeId>>,
     ) -> DispatchResult;
 
-    // Should hook into call on storage system,
-    // but requires rich error (with reasons)  types.
-    // caller already knows the `ContentId`s as they are part of
-    // the ContentUploadParameters
     fn atomically_remove_content(
         owner: &StorageObjectOwner<T::MemberId, T::ChannelId, T::DAOId>,
         content_ids: &[T::ContentId],
     ) -> DispatchResult;
 
-    // Checks if given owner can remove content under givencontent ids from the storage system
+    // Checks if given owner can remove content under given content ids from the storage system
     fn can_remove_content(
         owner: &StorageObjectOwner<T::MemberId, T::ChannelId, T::DAOId>,
         content_ids: &[T::ContentId],

+ 2 - 0
runtime-modules/content/Cargo.toml

@@ -17,6 +17,8 @@ common = { package = 'pallet-common', default-features = false, path = '../commo
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
+pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4'}
 
 [features]
 default = ['std']

+ 26 - 3
runtime-modules/content/src/errors.rs

@@ -4,6 +4,8 @@ use frame_support::decl_error;
 decl_error! {
     /// Content directory errors
     pub enum Error for Module<T: Trait> {
+        /// Feature Not Implemented
+        FeatureNotImplemented,
 
         // Curator Management Errors
         // -------------------------
@@ -26,12 +28,12 @@ decl_error! {
         /// Curator group is not active
         CuratorGroupIsNotActive,
 
+        /// Curator id is not a worker id in content working group
+        CuratorIdInvalid,
+
         // Authentication Errors
         // ---------------------
 
-        /// Origin cannot be made into raw origin
-        OriginCanNotBeMadeIntoRawOrigin,
-
         /// Lead authentication failed
         LeadAuthFailed,
 
@@ -43,5 +45,26 @@ decl_error! {
 
         /// Expected root or signed origin
         BadOrigin,
+
+        /// Operation cannot be perfomed with this Actor
+        ActorNotAuthorized,
+
+        /// This content actor cannot own a channel
+        ActorCannotOwnChannel,
+
+        /// A Channel or Video Category does not exist.
+        CategoryDoesNotExist,
+
+        /// Channel does not exist
+        ChannelDoesNotExist,
+
+        /// Video does not exist
+        VideoDoesNotExist,
+
+        /// Video in season can`t be removed (because order is important)
+        VideoInSeason,
+
+        /// Curators can only censor non-curator group owned channels
+        CannotCensoreCuratorGroupOwnedChannels
     }
 }

File diff suppressed because it is too large
+ 547 - 215
runtime-modules/content/src/lib.rs


+ 7 - 37
runtime-modules/content/src/permissions/curator_group.rs

@@ -9,9 +9,6 @@ pub struct CuratorGroup<T: Trait> {
 
     /// When `false`, curator in a given group is forbidden to act
     active: bool,
-
-    /// Used to count the number of Channels, given curator group owns
-    number_of_channels_owned: u32,
 }
 
 impl<T: Trait> Default for CuratorGroup<T> {
@@ -20,14 +17,13 @@ impl<T: Trait> Default for CuratorGroup<T> {
             curators: BTreeSet::new(),
             // default curator group status right after creation
             active: false,
-            number_of_channels_owned: 0,
         }
     }
 }
 
 impl<T: Trait> CuratorGroup<T> {
     /// Check if `CuratorGroup` contains curator under given `curator_id`
-    pub fn is_curator(&self, curator_id: &T::CuratorId) -> bool {
+    pub fn has_curator(&self, curator_id: &T::CuratorId) -> bool {
         self.curators.contains(curator_id)
     }
 
@@ -36,10 +32,6 @@ impl<T: Trait> CuratorGroup<T> {
         self.active
     }
 
-    pub fn get_number_of_channels_owned(&self) -> u32 {
-        self.number_of_channels_owned
-    }
-
     /// Set `CuratorGroup` status as provided
     pub fn set_status(&mut self, is_active: bool) {
         self.active = is_active
@@ -55,27 +47,8 @@ impl<T: Trait> CuratorGroup<T> {
         &mut self.curators
     }
 
-    /// Increment number of channels `CuratorGroup` owns
-    pub fn increment_number_of_channels_owned_count(&mut self) {
-        self.number_of_channels_owned += 1;
-    }
-
-    /// Decrement number of channels `CuratorGroup` owns
-    pub fn decrement_number_of_channels_owned_count(&mut self) {
-        self.number_of_channels_owned -= 1;
-    }
-
-    /// Ensure curator group does not maintain any `Channel`
-    pub fn ensure_curator_group_owns_no_channels(&self) -> Result<(), Error<T>> {
-        ensure!(
-            self.number_of_channels_owned == 0,
-            Error::<T>::CuratorGroupRemovalForbidden
-        );
-        Ok(())
-    }
-
     /// Ensure `MaxNumberOfCuratorsPerGroup` constraint satisfied
-    pub fn ensure_max_number_of_curators_limit_not_reached(&self) -> Result<(), Error<T>> {
+    pub fn ensure_max_number_of_curators_limit_not_reached(&self) -> DispatchResult {
         ensure!(
             self.curators.len() < T::MaxNumberOfCuratorsPerGroup::get() as usize,
             Error::<T>::CuratorsPerGroupLimitReached
@@ -84,12 +57,9 @@ impl<T: Trait> CuratorGroup<T> {
     }
 
     /// Ensure curator under given `curator_id` exists in `CuratorGroup`
-    pub fn ensure_curator_in_group_exists(
-        &self,
-        curator_id: &T::CuratorId,
-    ) -> Result<(), Error<T>> {
+    pub fn ensure_curator_in_group_exists(&self, curator_id: &T::CuratorId) -> DispatchResult {
         ensure!(
-            self.get_curators().contains(curator_id),
+            self.has_curator(curator_id),
             Error::<T>::CuratorIsNotAMemberOfGivenCuratorGroup
         );
         Ok(())
@@ -99,9 +69,9 @@ impl<T: Trait> CuratorGroup<T> {
     pub fn ensure_curator_in_group_does_not_exist(
         &self,
         curator_id: &T::CuratorId,
-    ) -> Result<(), Error<T>> {
+    ) -> DispatchResult {
         ensure!(
-            !self.get_curators().contains(curator_id),
+            !self.has_curator(curator_id),
             Error::<T>::CuratorIsAlreadyAMemberOfGivenCuratorGroup
         );
         Ok(())
@@ -112,7 +82,7 @@ impl<T: Trait> CuratorGroup<T> {
         curator_id: &T::CuratorId,
         curator_group_id: &T::CuratorGroupId,
         account_id: &T::AccountId,
-    ) -> Result<(), Error<T>> {
+    ) -> DispatchResult {
         // Ensure curator authorization performed succesfully
         ensure_curator_auth_success::<T>(curator_id, account_id)?;
 

+ 197 - 6
runtime-modules/content/src/permissions/mod.rs

@@ -12,6 +12,7 @@ use frame_support::{ensure, Parameter};
 pub use serde::{Deserialize, Serialize};
 use sp_arithmetic::traits::BaseArithmetic;
 use sp_runtime::traits::{MaybeSerializeDeserialize, Member};
+use system::ensure_root;
 
 /// Model of authentication manager.
 pub trait ContentActorAuthenticator: system::Trait + MembershipTypes {
@@ -44,6 +45,9 @@ pub trait ContentActorAuthenticator: system::Trait + MembershipTypes {
     /// Authorize actor as lead
     fn is_lead(account_id: &Self::AccountId) -> bool;
 
+    /// Checks if Id represents a worker id in the working group
+    fn is_valid_curator_id(curator_id: &Self::CuratorId) -> bool;
+
     /// Authorize actor as curator
     fn is_curator(curator_id: &Self::CuratorId, account_id: &Self::AccountId) -> bool;
 
@@ -51,11 +55,19 @@ pub trait ContentActorAuthenticator: system::Trait + MembershipTypes {
     fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool;
 }
 
+pub fn ensure_is_valid_curator_id<T: Trait>(curator_id: &T::CuratorId) -> DispatchResult {
+    ensure!(
+        T::is_valid_curator_id(curator_id),
+        Error::<T>::CuratorIdInvalid
+    );
+    Ok(())
+}
+
 /// Ensure curator authorization performed succesfully
 pub fn ensure_curator_auth_success<T: Trait>(
     curator_id: &T::CuratorId,
     account_id: &T::AccountId,
-) -> Result<(), Error<T>> {
+) -> DispatchResult {
     ensure!(
         T::is_curator(curator_id, account_id),
         Error::<T>::CuratorAuthFailed
@@ -67,7 +79,7 @@ pub fn ensure_curator_auth_success<T: Trait>(
 pub fn ensure_member_auth_success<T: Trait>(
     member_id: &T::MemberId,
     account_id: &T::AccountId,
-) -> Result<(), Error<T>> {
+) -> DispatchResult {
     ensure!(
         T::is_member(member_id, account_id),
         Error::<T>::MemberAuthFailed
@@ -76,7 +88,7 @@ pub fn ensure_member_auth_success<T: Trait>(
 }
 
 /// Ensure lead authorization performed succesfully
-pub fn ensure_lead_auth_success<T: Trait>(account_id: &T::AccountId) -> Result<(), Error<T>> {
+pub fn ensure_lead_auth_success<T: Trait>(account_id: &T::AccountId) -> DispatchResult {
     ensure!(T::is_lead(account_id), Error::<T>::LeadAuthFailed);
     Ok(())
 }
@@ -84,13 +96,191 @@ pub fn ensure_lead_auth_success<T: Trait>(account_id: &T::AccountId) -> Result<(
 /// Ensure given `Origin` is lead
 pub fn ensure_is_lead<T: Trait>(origin: T::Origin) -> DispatchResult {
     let account_id = ensure_signed(origin)?;
-    Ok(ensure_lead_auth_success::<T>(&account_id)?)
+    ensure_lead_auth_success::<T>(&account_id)
+}
+
+pub fn ensure_actor_authorized_to_create_channels_and_videos_assets<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+) -> DispatchResult {
+    match actor {
+        // Lead should use their member or curator role to create or update channel assets.
+        ContentActor::Lead => {
+            Err(Error::<T>::ActorCannotOwnChannel.into())
+        }
+        ContentActor::Curator(curator_group_id, curator_id) => {
+            let sender = ensure_signed(origin)?;
+
+            // Authorize curator, performing all checks to ensure curator can act
+            CuratorGroup::<T>::perform_curator_in_group_auth(
+                curator_id,
+                curator_group_id,
+                &sender,
+            )
+        }
+        ContentActor::Member(member_id) => {
+            let sender = ensure_signed(origin)?;
+
+            ensure_member_auth_success::<T>(member_id, &sender)
+        }
+        // TODO:
+        // ContentActor::Dao(_daoId) => ...,
+    }
+}
+
+// Enure actor can update or delete channels and videos
+pub fn ensure_actor_authorized_update_channel_and_videos<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+    owner: &ChannelOwner<T::MemberId, T::CuratorGroupId, T::DAOId>,
+) -> DispatchResult {
+    // Only owner of a channel can update and delete channel assets.
+    // Lead can update and delete curator group owned channel assets.
+    match actor {
+        ContentActor::Lead => {
+            let sender = ensure_signed(origin)?;
+            ensure_lead_auth_success::<T>(&sender)?;
+            if let ChannelOwner::CuratorGroup(_) = owner {
+                Ok(())
+            } else {
+                Err(Error::<T>::ActorNotAuthorized.into())
+            }
+        }
+        ContentActor::Curator(curator_group_id, curator_id) => {
+            let sender = ensure_signed(origin)?;
+
+            // Authorize curator, performing all checks to ensure curator can act
+            CuratorGroup::<T>::perform_curator_in_group_auth(
+                curator_id,
+                curator_group_id,
+                &sender,
+            )?;
+
+            // Ensure curator group is the channel owner.
+            ensure!(
+                *owner == ChannelOwner::CuratorGroup(*curator_group_id),
+                Error::<T>::ActorNotAuthorized
+            );
+
+            Ok(())
+        }
+        ContentActor::Member(member_id) => {
+            let sender = ensure_signed(origin)?;
+
+            ensure_member_auth_success::<T>(member_id, &sender)?;
+
+            // Ensure the member is the channel owner.
+            ensure!(
+                *owner == ChannelOwner::Member(*member_id),
+                Error::<T>::ActorNotAuthorized
+            );
+
+            Ok(())
+        }
+        // TODO:
+        // ContentActor::Dao(_daoId) => ...,
+    }
+}
+
+// Enure actor can update or delete channels and videos
+pub fn ensure_actor_authorized_to_set_featured_videos<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+) -> DispatchResult {
+    // Only Lead authorized to set featured videos
+    if let ContentActor::Lead = actor {
+        let sender = ensure_signed(origin)?;
+        ensure_lead_auth_success::<T>(&sender)
+    } else {
+        Err(Error::<T>::ActorNotAuthorized.into())
+    }
+}
+
+pub fn ensure_actor_authorized_to_censor<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+    owner: &ChannelOwner<T::MemberId, T::CuratorGroupId, T::DAOId>,
+) -> DispatchResult {
+    // Only lead and curators can censor channels and videos
+    // Only lead can censor curator group owned channels and videos
+    match actor {
+        ContentActor::Lead => {
+            let sender = ensure_signed(origin)?;
+            ensure_lead_auth_success::<T>(&sender)
+        },
+        ContentActor::Curator(curator_group_id, curator_id) => {
+            let sender = ensure_signed(origin)?;
+
+            // Authorize curator, performing all checks to ensure curator can act
+            CuratorGroup::<T>::perform_curator_in_group_auth(
+                curator_id,
+                curator_group_id,
+                &sender,
+            )?;
+
+            // Curators cannot censor curator group channels
+            if let ChannelOwner::CuratorGroup(_) = owner {
+                Err(Error::<T>::CannotCensoreCuratorGroupOwnedChannels.into())
+            } else {
+                Ok(())
+            }
+        },
+        ContentActor::Member(_) => {
+            // Members cannot censore channels!
+            Err(Error::<T>::ActorNotAuthorized.into())
+        }
+        // TODO:
+        // ContentActor::Dao(_daoId) => ...,
+    }
+}
+
+pub fn ensure_actor_authorized_to_manage_categories<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+) -> DispatchResult {
+    // Only lead and curators can manage categories
+    match actor {
+        ContentActor::Lead => {
+            let sender = ensure_signed(origin)?;
+            ensure_lead_auth_success::<T>(&sender)
+        },
+        ContentActor::Curator(curator_group_id, curator_id) => {
+            let sender = ensure_signed(origin)?;
+
+            // Authorize curator, performing all checks to ensure curator can act
+            CuratorGroup::<T>::perform_curator_in_group_auth(
+                curator_id,
+                curator_group_id,
+                &sender,
+            )
+        },
+        ContentActor::Member(_) => {
+            // Members cannot censore channels!
+            Err(Error::<T>::ActorNotAuthorized.into())
+        }
+        // TODO:
+        // ContentActor::Dao(_daoId) => ...,
+    }
+}
+
+pub fn ensure_actor_authorized_to_delete_stale_assets<T: Trait>(
+    origin: T::Origin,
+    actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
+) -> DispatchResult {
+    // Only Lead and (sudo) can delete assets no longer associated with a channel or person.
+    if let ContentActor::Lead = actor {
+        let sender = ensure_signed(origin)?;
+        ensure_lead_auth_success::<T>(&sender)
+    } else {
+        ensure_root(origin)?;
+        Ok(())
+    }
 }
 
 /// Enum, representing all possible `Actor`s
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Eq, PartialEq, Clone, Copy, Debug)]
-pub enum Actor<
+pub enum ContentActor<
     CuratorGroupId: Default + Clone + Copy,
     CuratorId: Default + Clone + Copy,
     MemberId: Default + Clone + Copy,
@@ -98,13 +288,14 @@ pub enum Actor<
     Curator(CuratorGroupId, CuratorId),
     Member(MemberId),
     Lead,
+    // Dao,
 }
 
 impl<
         CuratorGroupId: Default + Clone + Copy,
         CuratorId: Default + Clone + Copy,
         MemberId: Default + Clone + Copy,
-    > Default for Actor<CuratorGroupId, CuratorId, MemberId>
+    > Default for ContentActor<CuratorGroupId, CuratorId, MemberId>
 {
     fn default() -> Self {
         Self::Lead

+ 181 - 0
runtime-modules/content/src/tests/channels.rs

@@ -0,0 +1,181 @@
+#![cfg(test)]
+
+use super::mock::*;
+use crate::*;
+use frame_support::{assert_err, assert_ok};
+
+fn add_curator_to_new_group(curator_id: CuratorId) -> CuratorGroupId {
+    let curator_group_id = Content::next_curator_group_id();
+    // create new group and add curator id to it
+    assert_ok!(Content::create_curator_group(Origin::signed(LEAD_ORIGIN)));
+    assert_ok!(Content::add_curator_to_group(
+        Origin::signed(LEAD_ORIGIN),
+        curator_group_id,
+        curator_id
+    ));
+    // make group active
+    assert_ok!(Content::set_curator_group_status(
+        Origin::signed(LEAD_ORIGIN),
+        curator_group_id,
+        true
+    ));
+    curator_group_id
+}
+
+#[test]
+fn lead_cannot_create_channel() {
+    with_default_mock_builder(|| {
+        assert_err!(
+            Content::create_channel(
+                Origin::signed(FIRST_MEMBER_ORIGIN),
+                ContentActor::Lead,
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ),
+            Error::<Test>::ActorCannotOwnChannel
+        );
+    })
+}
+
+#[test]
+fn curators_can_create_channel() {
+    with_default_mock_builder(|| {
+        // Curator group doesn't exist yet
+        assert_err!(
+            Content::create_channel(
+                Origin::signed(FIRST_CURATOR_ORIGIN),
+                ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ),
+            Error::<Test>::CuratorGroupIsNotActive
+        );
+
+        let group_id = add_curator_to_new_group(FIRST_CURATOR_ID);
+        assert_eq!(FIRST_CURATOR_GROUP_ID, group_id);
+
+        // Curator from wrong group
+        assert_err!(
+            Content::create_channel(
+                Origin::signed(SECOND_CURATOR_ORIGIN),
+                ContentActor::Curator(FIRST_CURATOR_GROUP_ID, SECOND_CURATOR_ID),
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ),
+            Error::<Test>::CuratorIsNotAMemberOfGivenCuratorGroup
+        );
+
+        // Curator in correct active group, but wrong origin
+        assert_err!(
+            Content::create_channel(
+                Origin::signed(SECOND_CURATOR_ORIGIN),
+                ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ),
+            Error::<Test>::CuratorAuthFailed
+        );
+
+        // Curator in correct active group, with correct origin
+        assert_ok!(Content::create_channel(
+            Origin::signed(FIRST_CURATOR_ORIGIN),
+            ContentActor::Curator(FIRST_CURATOR_GROUP_ID, FIRST_CURATOR_ID),
+            ChannelCreationParameters {
+                assets: vec![],
+                meta: vec![],
+                reward_account: None,
+            }
+        ));
+    })
+}
+#[test]
+fn members_can_manage_channels() {
+    with_default_mock_builder(|| {
+        // Not a member
+        assert_err!(
+            Content::create_channel(
+                Origin::signed(UNKNOWN_ORIGIN),
+                ContentActor::Member(MEMBERS_COUNT + 1),
+                ChannelCreationParameters {
+                    assets: vec![],
+                    meta: vec![],
+                    reward_account: None,
+                }
+            ),
+            Error::<Test>::MemberAuthFailed
+        );
+
+        let channel_id_1 = Content::next_channel_id();
+
+        // Member can create the channel
+        assert_ok!(Content::create_channel(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParameters {
+                assets: vec![],
+                meta: vec![],
+                reward_account: None,
+            }
+        ));
+
+        // TODO: assert emitted events...
+
+        let channel_id_2 = Content::next_channel_id();
+
+        // Member can create the channel
+        assert_ok!(Content::create_channel(
+            Origin::signed(SECOND_MEMBER_ORIGIN),
+            ContentActor::Member(SECOND_MEMBER_ID),
+            ChannelCreationParameters {
+                assets: vec![],
+                meta: vec![],
+                reward_account: None,
+            }
+        ));
+
+        // TODO: assert emitted events...
+
+        // Update channel
+        assert_ok!(Content::update_channel(
+            Origin::signed(FIRST_MEMBER_ORIGIN),
+            ContentActor::Member(FIRST_MEMBER_ID),
+            channel_id_1,
+            ChannelUpdateParameters {
+                assets: None,
+                new_meta: None,
+                reward_account: None,
+            }
+        ));
+
+        // TODO: assert emitted events...
+
+        // Member cannot update a channel they do not own
+        assert_err!(
+            Content::update_channel(
+                Origin::signed(FIRST_MEMBER_ORIGIN),
+                ContentActor::Member(FIRST_MEMBER_ID),
+                channel_id_2,
+                ChannelUpdateParameters {
+                    assets: None,
+                    new_meta: None,
+                    reward_account: None,
+                }
+            ),
+            Error::<Test>::ActorNotAuthorized
+        );
+
+        // TODO: assert emitted events...
+    })
+}

+ 296 - 0
runtime-modules/content/src/tests/mock.rs

@@ -0,0 +1,296 @@
+#![cfg(test)]
+
+use crate::*;
+
+// use frame_support::storage::StorageMap;
+// use frame_support::traits::{OnFinalize, OnInitialize};
+use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
+use sp_core::H256;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    Perbill,
+};
+
+use crate::ContentActorAuthenticator;
+use crate::Trait;
+use common::currency::GovernanceCurrency;
+use common::storage::StorageSystem;
+
+pub type CuratorId = <Test as ContentActorAuthenticator>::CuratorId;
+pub type CuratorGroupId = <Test as ContentActorAuthenticator>::CuratorGroupId;
+pub type MemberId = <Test as MembershipTypes>::MemberId;
+// pub type ChannelId = <Test as StorageOwnership>::ChannelId;
+// pub type DAOId = <Test as StorageOwnership>::DAOId;
+
+/// Origins
+
+pub const LEAD_ORIGIN: u64 = 1;
+
+pub const FIRST_CURATOR_ORIGIN: u64 = 2;
+pub const SECOND_CURATOR_ORIGIN: u64 = 3;
+
+pub const FIRST_MEMBER_ORIGIN: u64 = 4;
+pub const SECOND_MEMBER_ORIGIN: u64 = 5;
+pub const UNKNOWN_ORIGIN: u64 = 7777;
+
+// Members range from MemberId 1 to 10
+pub const MEMBERS_COUNT: MemberId = 10;
+
+/// Runtime Id's
+
+pub const FIRST_CURATOR_ID: CuratorId = 1;
+pub const SECOND_CURATOR_ID: CuratorId = 2;
+
+pub const FIRST_CURATOR_GROUP_ID: CuratorGroupId = 1;
+// pub const SECOND_CURATOR_GROUP_ID: CuratorGroupId = 2;
+
+pub const FIRST_MEMBER_ID: MemberId = 1;
+pub const SECOND_MEMBER_ID: MemberId = 2;
+
+impl_outer_origin! {
+    pub enum Origin for Test {}
+}
+
+mod content {
+    pub use crate::Event;
+}
+
+impl_outer_event! {
+    pub enum MetaEvent for Test {
+        content<T>,
+        system<T>,
+        balances<T>,
+    }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Test;
+
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    pub const MaximumBlockWeight: u32 = 1024;
+    pub const MaximumBlockLength: u32 = 2 * 1024;
+    pub const AvailableBlockRatio: Perbill = Perbill::one();
+    pub const MinimumPeriod: u64 = 5;
+}
+
+impl system::Trait for Test {
+    type BaseCallFilter = ();
+    type Origin = Origin;
+    type Call = ();
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = u64;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = MetaEvent;
+    type BlockHashCount = BlockHashCount;
+    type MaximumBlockWeight = MaximumBlockWeight;
+    type DbWeight = ();
+    type BlockExecutionWeight = ();
+    type ExtrinsicBaseWeight = ();
+    type MaximumExtrinsicWeight = ();
+    type MaximumBlockLength = MaximumBlockLength;
+    type AvailableBlockRatio = AvailableBlockRatio;
+    type Version = ();
+    type ModuleToIndex = ();
+    type AccountData = balances::AccountData<u64>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+}
+
+impl pallet_timestamp::Trait for Test {
+    type Moment = u64;
+    type OnTimestampSet = ();
+    type MinimumPeriod = MinimumPeriod;
+}
+
+impl common::MembershipTypes for Test {
+    type MemberId = u64;
+    type ActorId = u64;
+}
+
+impl common::StorageOwnership for Test {
+    type ChannelId = u64;
+    type DAOId = u64;
+    type ContentId = u64;
+    type DataObjectTypeId = u64;
+}
+
+parameter_types! {
+    pub const ExistentialDeposit: u32 = 0;
+}
+
+impl balances::Trait for Test {
+    type Balance = u64;
+    type DustRemoval = ();
+    type Event = MetaEvent;
+    type ExistentialDeposit = ExistentialDeposit;
+    type AccountStore = System;
+}
+
+impl GovernanceCurrency for Test {
+    type Currency = balances::Module<Self>;
+}
+
+impl ContentActorAuthenticator for Test {
+    type CuratorId = u64;
+    type CuratorGroupId = u64;
+
+    fn is_lead(account_id: &Self::AccountId) -> bool {
+        let lead_account_id = ensure_signed(Origin::signed(LEAD_ORIGIN)).unwrap();
+        *account_id == lead_account_id
+    }
+
+    fn is_curator(curator_id: &Self::CuratorId, account_id: &Self::AccountId) -> bool {
+        let first_curator_account_id = ensure_signed(Origin::signed(FIRST_CURATOR_ORIGIN)).unwrap();
+        let second_curator_account_id =
+            ensure_signed(Origin::signed(SECOND_CURATOR_ORIGIN)).unwrap();
+        (first_curator_account_id == *account_id && FIRST_CURATOR_ID == *curator_id)
+            || (second_curator_account_id == *account_id && SECOND_CURATOR_ID == *curator_id)
+    }
+
+    fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool {
+        let unknown_member_account_id = ensure_signed(Origin::signed(UNKNOWN_ORIGIN)).unwrap();
+        *member_id < MEMBERS_COUNT && unknown_member_account_id != *account_id
+    }
+
+    fn is_valid_curator_id(curator_id: &Self::CuratorId) -> bool {
+        *curator_id == FIRST_CURATOR_ID || *curator_id == SECOND_CURATOR_ID
+    }
+}
+
+pub struct MockStorageSystem {}
+
+// Anyone can upload and delete without restriction
+impl StorageSystem<Test> for MockStorageSystem {
+    fn atomically_add_content(
+        _owner: StorageObjectOwner<Test>,
+        _content_parameters: Vec<ContentParameters<Test>>,
+    ) -> DispatchResult {
+        Ok(())
+    }
+
+    fn can_add_content(
+        _owner: StorageObjectOwner<Test>,
+        _content_parameters: Vec<ContentParameters<Test>>,
+    ) -> DispatchResult {
+        Ok(())
+    }
+
+    fn atomically_remove_content(
+        _owner: &StorageObjectOwner<Test>,
+        _content_ids: &[u64],
+    ) -> DispatchResult {
+        Ok(())
+    }
+
+    fn can_remove_content(
+        _owner: &StorageObjectOwner<Test>,
+        _content_ids: &[u64],
+    ) -> DispatchResult {
+        Ok(())
+    }
+}
+
+parameter_types! {
+    pub const MaxNumberOfCuratorsPerGroup: u32 = 10;
+    pub const ChannelOwnershipPaymentEscrowId: [u8; 8] = *b"12345678";
+}
+
+impl Trait for Test {
+    /// The overarching event type.
+    type Event = MetaEvent;
+
+    /// Channel Transfer Payments Escrow Account seed for ModuleId to compute deterministic AccountId
+    type ChannelOwnershipPaymentEscrowId = ChannelOwnershipPaymentEscrowId;
+
+    /// Type of identifier for Videos
+    type VideoId = u64;
+
+    /// Type of identifier for Video Categories
+    type VideoCategoryId = u64;
+
+    /// Type of identifier for Channel Categories
+    type ChannelCategoryId = u64;
+
+    /// Type of identifier for Playlists
+    type PlaylistId = u64;
+
+    /// Type of identifier for Persons
+    type PersonId = u64;
+
+    /// Type of identifier for Channels
+    type SeriesId = u64;
+
+    /// Type of identifier for Channel transfer requests
+    type ChannelOwnershipTransferRequestId = u64;
+
+    /// The maximum number of curators per group constraint
+    type MaxNumberOfCuratorsPerGroup = MaxNumberOfCuratorsPerGroup;
+
+    // Type that handles asset uploads to storage system
+    type StorageSystem = MockStorageSystem;
+}
+
+pub type System = system::Module<Test>;
+pub type Content = Module<Test>;
+// #[derive (Default)]
+pub struct ExtBuilder {
+    next_channel_category_id: u64,
+    next_channel_id: u64,
+    next_video_category_id: u64,
+    next_video_id: u64,
+    next_playlist_id: u64,
+    next_person_id: u64,
+    next_series_id: u64,
+    next_channel_transfer_request_id: u64,
+    next_curator_group_id: u64,
+}
+
+impl Default for ExtBuilder {
+    fn default() -> Self {
+        Self {
+            next_channel_category_id: 1,
+            next_channel_id: 1,
+            next_video_category_id: 1,
+            next_video_id: 1,
+            next_playlist_id: 1,
+            next_person_id: 1,
+            next_series_id: 1,
+            next_channel_transfer_request_id: 1,
+            next_curator_group_id: 1,
+        }
+    }
+}
+
+impl ExtBuilder {
+    pub fn build(self) -> sp_io::TestExternalities {
+        let mut t = system::GenesisConfig::default()
+            .build_storage::<Test>()
+            .unwrap();
+
+        GenesisConfig::<Test> {
+            next_channel_category_id: self.next_channel_category_id,
+            next_channel_id: self.next_channel_id,
+            next_video_category_id: self.next_video_category_id,
+            next_video_id: self.next_video_id,
+            next_playlist_id: self.next_playlist_id,
+            next_person_id: self.next_person_id,
+            next_series_id: self.next_series_id,
+            next_channel_transfer_request_id: self.next_channel_transfer_request_id,
+            next_curator_group_id: self.next_curator_group_id,
+        }
+        .assimilate_storage(&mut t)
+        .unwrap();
+
+        t.into()
+    }
+}
+
+pub fn with_default_mock_builder<R, F: FnOnce() -> R>(f: F) -> R {
+    ExtBuilder::default().build().execute_with(|| f())
+}

+ 4 - 0
runtime-modules/content/src/tests/mod.rs

@@ -0,0 +1,4 @@
+#![cfg(test)]
+
+mod channels;
+mod mock;

+ 6 - 0
runtime-modules/governance/src/mock.rs

@@ -69,12 +69,18 @@ impl election::Trait for Test {
 
     type CouncilElected = (Council,);
 }
+
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = ();
     type MemberId = u64;
     type SubscriptionId = u32;
     type PaidTermId = u32;
     type ActorId = u32;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 impl minting::Trait for Test {
     type Currency = Balances;

+ 129 - 34
runtime-modules/membership/src/lib.rs

@@ -10,21 +10,17 @@ pub(crate) mod mock;
 mod tests;
 
 use codec::{Codec, Decode, Encode};
-use frame_support::traits::Currency;
-use frame_support::{decl_event, decl_module, decl_storage, ensure, Parameter};
+use frame_support::dispatch::DispatchResult;
+use frame_support::traits::{Currency, Get, LockableCurrency, WithdrawReason};
+use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, Parameter};
 use sp_arithmetic::traits::{BaseArithmetic, One};
-use sp_runtime::traits::{MaybeSerialize, Member};
+use sp_runtime::traits::{MaybeSerialize, Member, Zero};
 use sp_std::borrow::ToOwned;
 use sp_std::vec;
 use sp_std::vec::Vec;
 use system::{ensure_root, ensure_signed};
 
 use common::currency::{BalanceOf, GovernanceCurrency};
-
-//TODO: Convert errors to the Substrate decl_error! macro.
-/// Result with string error message. This exists for backward compatibility purpose.
-pub type DispatchResult = Result<(), &'static str>;
-
 pub trait Trait: system::Trait + GovernanceCurrency + pallet_timestamp::Trait {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
@@ -65,6 +61,10 @@ pub trait Trait: system::Trait + GovernanceCurrency + pallet_timestamp::Trait {
         + MaybeSerialize
         + PartialEq
         + Ord;
+
+    /// The maximum amount of initial funds that may be endowed to new members added by
+    /// screening authority. If set to zero, no initial balance can be given.
+    type ScreenedMemberMaxInitialBalance: Get<BalanceOf<Self>>;
 }
 
 const FIRST_PAID_TERMS_ID: u8 = 1;
@@ -259,6 +259,8 @@ decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event() = default;
 
+        const ScreenedMemberMaxInitialBalance: BalanceOf<T> = T::ScreenedMemberMaxInitialBalance::get();
+
         /// Non-members can buy membership
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn buy_membership(
@@ -271,13 +273,13 @@ decl_module! {
             let who = ensure_signed(origin)?;
 
             // make sure we are accepting new memberships
-            ensure!(Self::new_memberships_allowed(), "new members not allowed");
+            ensure!(Self::new_memberships_allowed(), Error::<T>::NewMembershipsNotAllowed);
 
             // ensure paid_terms_id is active
             let terms = Self::ensure_active_terms_id(paid_terms_id)?;
 
             // ensure enough free balance to cover terms fees
-            ensure!(T::Currency::can_slash(&who, terms.fee), "not enough balance to buy membership");
+            ensure!(T::Currency::can_slash(&who, terms.fee), Error::<T>::NotEnoughBalanceToBuyMembership);
 
             let user_info = Self::check_user_registration_info(handle, avatar_uri, about)?;
 
@@ -302,7 +304,7 @@ decl_module! {
 
             let membership = Self::ensure_membership(member_id)?;
 
-            ensure!(membership.controller_account == sender, "only controller account can update member about text");
+            ensure!(membership.controller_account == sender, Error::<T>::ControllerAccountRequired);
 
             Self::_change_member_about_text(member_id, &text)?;
         }
@@ -314,7 +316,7 @@ decl_module! {
 
             let membership = Self::ensure_membership(member_id)?;
 
-            ensure!(membership.controller_account == sender, "only controller account can update member avatar");
+            ensure!(membership.controller_account == sender, Error::<T>::ControllerAccountRequired);
 
             Self::_change_member_avatar(member_id, &uri)?;
         }
@@ -327,7 +329,7 @@ decl_module! {
 
             let membership = Self::ensure_membership(member_id)?;
 
-            ensure!(membership.controller_account == sender, "only controller account can update member handle");
+            ensure!(membership.controller_account == sender, Error::<T>::ControllerAccountRequired);
 
             Self::_change_member_handle(member_id, handle)?;
         }
@@ -345,7 +347,7 @@ decl_module! {
 
             let membership = Self::ensure_membership(member_id)?;
 
-            ensure!(membership.controller_account == sender, "only controller account can update member info");
+            ensure!(membership.controller_account == sender, Error::<T>::ControllerAccountRequired);
 
             if let Some(uri) = avatar_uri {
                 Self::_change_member_avatar(member_id, &uri)?;
@@ -364,7 +366,7 @@ decl_module! {
 
             let mut membership = Self::ensure_membership(member_id)?;
 
-            ensure!(membership.root_account == sender, "only root account can set new controller account");
+            ensure!(membership.root_account == sender, Error::<T>::RootAccountRequired);
 
             // only update if new_controller_account is different than current one
             if membership.controller_account != new_controller_account {
@@ -388,7 +390,7 @@ decl_module! {
 
             let mut membership = Self::ensure_membership(member_id)?;
 
-            ensure!(membership.root_account == sender, "only root account can set new root account");
+            ensure!(membership.root_account == sender, Error::<T>::RootAccountRequired);
 
             // only update if new root account is different than current one
             if membership.root_account != new_root_account {
@@ -406,22 +408,27 @@ decl_module! {
             }
         }
 
+        /// Screened members are awarded a initial locked balance that can only be slashed or used
+        /// for fees, and is not transferable. The screening authority must ensure that the provided
+        /// new_member_account was verified to avoid applying locks arbitrarily to accounts not controlled
+        /// by the member.
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn add_screened_member(
             origin,
             new_member_account: T::AccountId,
             handle: Option<Vec<u8>>,
             avatar_uri: Option<Vec<u8>>,
-            about: Option<Vec<u8>>
+            about: Option<Vec<u8>>,
+            initial_balance: Option<BalanceOf<T>>,
         ) {
             // ensure sender is screening authority
             let sender = ensure_signed(origin)?;
 
             if <ScreeningAuthority<T>>::exists() {
-                ensure!(sender == Self::screening_authority(), "not screener");
+                ensure!(sender == Self::screening_authority(), Error::<T>::NotScreeningAuthority);
             } else {
                 // no screening authority defined. Cannot accept this request
-                return Err("no screening authority defined".into());
+                return Err(Error::<T>::NoScreeningAuthorityDefined.into());
             }
 
             // make sure we are accepting new memberships
@@ -429,6 +436,38 @@ decl_module! {
 
             let user_info = Self::check_user_registration_info(handle, avatar_uri, about)?;
 
+            if let Some(initial_balance) = initial_balance {
+                ensure!(
+                    T::ScreenedMemberMaxInitialBalance::get() >= initial_balance,
+                    Error::<T>::InitialBalanceExceedsMaxInitialBalance
+                );
+
+                // Only allow "new" accounts with 0 balance
+                ensure!(
+                    T::Currency::free_balance(&new_member_account).is_zero(),
+                    Error::<T>::OnlyNewAccountsCanBeUsedForScreenedMembers
+                );
+
+                ensure!(
+                    system::Module::<T>::account_nonce(&new_member_account).is_zero(),
+                    Error::<T>::OnlyNewAccountsCanBeUsedForScreenedMembers
+                );
+
+                // Check account nonce
+
+                // Set a lock to prevent transfers of the amount that will be endowed
+                T::Currency::set_lock(
+                    *b"faucet00",
+                    &new_member_account,
+                    initial_balance,
+                    WithdrawReason::Transfer.into(),
+                );
+
+                // Endow the new member account with an amount to get started
+                T::Currency::deposit_creating(&new_member_account, initial_balance);
+            };
+
+            // cannot fail because of prior check_user_registration_info
             let member_id = Self::insert_member(
                 &new_member_account,
                 &new_member_account,
@@ -472,11 +511,11 @@ pub enum MemberRootAccountMismatch {
 
 impl<T: Trait> Module<T> {
     /// Provided that the member_id exists return its membership. Returns error otherwise.
-    pub fn ensure_membership(id: T::MemberId) -> Result<Membership<T>, &'static str> {
+    pub fn ensure_membership(id: T::MemberId) -> Result<Membership<T>, Error<T>> {
         if <MembershipById<T>>::contains_key(&id) {
             Ok(Self::membership(&id))
         } else {
-            Err("member profile not found")
+            Err(Error::<T>::MemberProfileNotFound)
         }
     }
 
@@ -506,37 +545,37 @@ impl<T: Trait> Module<T> {
 
     fn ensure_active_terms_id(
         terms_id: T::PaidTermId,
-    ) -> Result<PaidMembershipTerms<BalanceOf<T>>, &'static str> {
+    ) -> Result<PaidMembershipTerms<BalanceOf<T>>, Error<T>> {
         let active_terms = Self::active_paid_membership_terms();
         ensure!(
             active_terms.iter().any(|&id| id == terms_id),
-            "paid terms id not active"
+            Error::<T>::PaidTermIdNotActive
         );
 
         if <PaidMembershipTermsById<T>>::contains_key(terms_id) {
             Ok(Self::paid_membership_terms_by_id(terms_id))
         } else {
-            Err("paid membership term id does not exist")
+            Err(Error::<T>::PaidTermIdNotFound)
         }
     }
 
     #[allow(clippy::ptr_arg)] // cannot change to the "&[u8]" suggested by clippy
-    fn ensure_unique_handle(handle: &Vec<u8>) -> DispatchResult {
+    fn ensure_unique_handle(handle: &Vec<u8>) -> Result<(), Error<T>> {
         ensure!(
             !<MemberIdByHandle<T>>::contains_key(handle),
-            "handle already registered"
+            Error::<T>::HandleAlreadyRegistered
         );
         Ok(())
     }
 
-    fn validate_handle(handle: &[u8]) -> DispatchResult {
+    fn validate_handle(handle: &[u8]) -> Result<(), Error<T>> {
         ensure!(
             handle.len() >= Self::min_handle_length() as usize,
-            "handle too short"
+            Error::<T>::HandleTooShort
         );
         ensure!(
             handle.len() <= Self::max_handle_length() as usize,
-            "handle too long"
+            Error::<T>::HandleTooLong
         );
         Ok(())
     }
@@ -547,10 +586,10 @@ impl<T: Trait> Module<T> {
         text
     }
 
-    fn validate_avatar(uri: &[u8]) -> DispatchResult {
+    fn validate_avatar(uri: &[u8]) -> Result<(), Error<T>> {
         ensure!(
             uri.len() <= Self::max_avatar_uri_length() as usize,
-            "avatar uri too long"
+            Error::<T>::AvatarUriTooLong
         );
         Ok(())
     }
@@ -560,9 +599,9 @@ impl<T: Trait> Module<T> {
         handle: Option<Vec<u8>>,
         avatar_uri: Option<Vec<u8>>,
         about: Option<Vec<u8>>,
-    ) -> Result<ValidatedUserInfo, &'static str> {
+    ) -> Result<ValidatedUserInfo, Error<T>> {
         // Handle is required during registration
-        let handle = handle.ok_or("handle must be provided during registration")?;
+        let handle = handle.ok_or(Error::<T>::HandleMustBeProvidedDuringRegistration)?;
         Self::validate_handle(&handle)?;
 
         let about = Self::validate_text(&about.unwrap_or_default());
@@ -583,7 +622,7 @@ impl<T: Trait> Module<T> {
         entry_method: EntryMethod<T::PaidTermId, T::AccountId>,
         registered_at_block: T::BlockNumber,
         registered_at_time: T::Moment,
-    ) -> Result<T::MemberId, &'static str> {
+    ) -> Result<T::MemberId, Error<T>> {
         Self::ensure_unique_handle(&user_info.handle)?;
 
         let new_member_id = Self::members_created();
@@ -697,3 +736,59 @@ impl<T: Trait> Module<T> {
         Ok(())
     }
 }
+
+decl_error! {
+    /// Membership module predefined errors
+    pub enum Error for Module<T: Trait> {
+        /// New memberships not allowed.
+        NewMembershipsNotAllowed,
+
+        /// A screening authority is not defined.
+        NoScreeningAuthorityDefined,
+
+        /// Origin is not the screeing authority.
+        NotScreeningAuthority,
+
+        /// Not enough balance to buy membership.
+        NotEnoughBalanceToBuyMembership,
+
+        /// Screening authority attempting to endow more that maximum allowed.
+        InitialBalanceExceedsMaxInitialBalance,
+
+        /// Only new accounts can be used for screened members.
+        OnlyNewAccountsCanBeUsedForScreenedMembers,
+
+        /// Controller account required.
+        ControllerAccountRequired,
+
+        /// Root account required.
+        RootAccountRequired,
+
+        /// Invalid origin.
+        UnsignedOrigin,
+
+        /// Member profile not found (invalid member id).
+        MemberProfileNotFound,
+
+        /// Handle already registered.
+        HandleAlreadyRegistered,
+
+        /// Handle must be provided during registration.
+        HandleMustBeProvidedDuringRegistration,
+
+        /// Handle too short.
+        HandleTooShort,
+
+        /// Handle too long.
+        HandleTooLong,
+
+        /// Avatar url is too long.
+        AvatarUriTooLong,
+
+        /// Paid term id not found.
+        PaidTermIdNotFound,
+
+        /// Paid term id not active.
+        PaidTermIdNotActive,
+    }
+}

+ 5 - 0
runtime-modules/membership/src/mock.rs

@@ -78,12 +78,17 @@ impl GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl Trait for Test {
     type Event = ();
     type MemberId = u64;
     type PaidTermId = u32;
     type SubscriptionId = u32;
     type ActorId = u32;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 pub struct TestExternalitiesBuilder<T: Trait> {

+ 30 - 13
runtime-modules/membership/src/tests.rs

@@ -2,6 +2,7 @@
 
 use super::genesis;
 use super::mock::*;
+use crate::*;
 
 use frame_support::*;
 
@@ -13,12 +14,6 @@ fn get_membership_by_id(member_id: u64) -> crate::Membership<Test> {
     }
 }
 
-fn assert_dispatch_error_message(result: Result<(), &'static str>, expected_message: &'static str) {
-    assert!(result.is_err());
-    let message = result.err().unwrap();
-    assert_eq!(message, expected_message);
-}
-
 #[derive(Clone, Debug, PartialEq)]
 pub struct TestUserInfo {
     pub handle: Option<Vec<u8>>,
@@ -149,9 +144,9 @@ fn buy_membership_fails_without_enough_balance() {
             let initial_balance = DEFAULT_FEE - 1;
             set_alice_free_balance(initial_balance);
 
-            assert_dispatch_error_message(
+            assert_err!(
                 buy_default_membership_as_alice(),
-                "not enough balance to buy membership",
+                Error::<Test>::NotEnoughBalanceToBuyMembership,
             );
         });
 }
@@ -173,9 +168,9 @@ fn new_memberships_allowed_flag() {
 
             crate::NewMembershipsAllowed::put(false);
 
-            assert_dispatch_error_message(
+            assert_err!(
                 buy_default_membership_as_alice(),
-                "new members not allowed",
+                Error::<Test>::NewMembershipsNotAllowed,
             );
         });
 }
@@ -200,9 +195,9 @@ fn unique_handles() {
             <crate::MemberIdByHandle<Test>>::insert(get_alice_info().handle.unwrap(), 1);
 
             // should not be allowed to buy membership with that handle
-            assert_dispatch_error_message(
+            assert_err!(
                 buy_default_membership_as_alice(),
-                "handle already registered",
+                Error::<Test>::HandleAlreadyRegistered,
             );
         });
 }
@@ -253,6 +248,7 @@ fn add_screened_member() {
             <crate::ScreeningAuthority<Test>>::put(&screening_authority);
 
             let next_member_id = Members::members_created();
+            let endownment = ScreenedMemberMaxInitialBalance::get() - 1;
 
             let info = get_alice_info();
             assert_ok!(Members::add_screened_member(
@@ -260,7 +256,8 @@ fn add_screened_member() {
                 ALICE_ACCOUNT_ID,
                 info.handle,
                 info.avatar_uri,
-                info.about
+                info.about,
+                Some(endownment),
             ));
 
             let profile = get_membership_by_id(next_member_id);
@@ -272,6 +269,26 @@ fn add_screened_member() {
                 crate::EntryMethod::Screening(screening_authority),
                 profile.entry
             );
+            assert_eq!(Balances::free_balance(ALICE_ACCOUNT_ID), endownment);
+
+            // Transfer should fail because of balance lock
+            assert_err!(
+                Balances::transfer(Origin::signed(ALICE_ACCOUNT_ID), screening_authority, 1),
+                balances::Error::<Test, _>::LiquidityRestrictions
+            );
+
+            // .. but we should be able to slash
+            assert!(Balances::can_slash(&ALICE_ACCOUNT_ID, 1));
+
+            // Deposit more funds to have a surplus above lock limit
+            let _ = Balances::deposit_creating(&ALICE_ACCOUNT_ID, 10);
+
+            // If free balance above lock limit, transfers should be possible
+            assert_ok!(Balances::transfer(
+                Origin::signed(ALICE_ACCOUNT_ID),
+                screening_authority,
+                1
+            ));
         });
 }
 

+ 5 - 0
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -44,12 +44,17 @@ impl common::currency::GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = ();
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 parameter_types! {

+ 5 - 0
runtime-modules/proposals/discussion/src/tests/mock.rs

@@ -71,12 +71,17 @@ impl common::currency::GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = TestEvent;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl crate::Trait for Test {

+ 5 - 0
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -84,12 +84,17 @@ parameter_types! {
     pub const MaxActiveProposalLimit: u32 = 100;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = TestEvent;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl crate::Trait for Test {

+ 5 - 0
runtime-modules/service-discovery/src/mock.rs

@@ -105,12 +105,17 @@ impl stake::Trait for Test {
     type SlashId = u64;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = MetaEvent;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl common::currency::GovernanceCurrency for Test {

+ 189 - 163
runtime-modules/storage/src/data_directory.rs

@@ -23,7 +23,6 @@
 
 use codec::{Decode, Encode};
 use frame_support::dispatch::DispatchResult;
-use frame_support::traits::Get;
 use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure};
 use sp_std::collections::btree_map::BTreeMap;
 use sp_std::vec::Vec;
@@ -40,6 +39,12 @@ use crate::data_object_type_registry;
 use crate::data_object_type_registry::IsActiveDataObjectType;
 use crate::*;
 
+pub const DEFAULT_VOUCHER_SIZE_LIMIT_UPPER_BOUND: u64 = 100000000;
+pub const DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND: u64 = 200;
+pub const DEFAULT_GLOBAL_VOUCHER: Voucher = Voucher::new(200000000, 2000);
+pub const DEFAULT_VOUCHER: Voucher = Voucher::new(5000000, 100);
+pub const DEFAULT_UPLOADING_BLOCKED_STATUS: bool = false;
+
 /// The _Data directory_ main _Trait_.
 pub trait Trait:
     pallet_timestamp::Trait
@@ -61,9 +66,6 @@ pub trait Trait:
 
     /// Validates member id and origin combination.
     type MemberOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
-
-    /// Default content quota for all actors.
-    type DefaultQuota: Get<Quota>;
 }
 
 decl_error! {
@@ -87,29 +89,32 @@ decl_error! {
         /// DataObject Injection Failed. Too Many DataObjects.
         DataObjectsInjectionExceededLimit,
 
-        /// Contant uploading failed. Actor quota objects limit exceeded.
-        QuotaObjectsLimitExceeded,
+        /// Contant uploading failed. Actor voucher objects limit exceeded.
+        VoucherObjectsLimitExceeded,
 
-        /// Contant uploading failed. Actor quota size limit exceeded.
-        QuotaSizeLimitExceeded,
+        /// Contant uploading failed. Actor voucher size limit exceeded.
+        VoucherSizeLimitExceeded,
 
-        /// Quota size limit upper bound exceeded
-        QuotaSizeLimitUpperBoundExceeded,
+        /// Voucher size limit upper bound exceeded
+        VoucherSizeLimitUpperBoundExceeded,
 
-        /// Quota objects limit upper bound exceeded
-        QuotaObjectsLimitUpperBoundExceeded,
+        /// Voucher objects limit upper bound exceeded
+        VoucherObjectsLimitUpperBoundExceeded,
 
-        /// Contant uploading failed. Actor quota size limit exceeded.
-        GlobalQuotaSizeLimitExceeded,
+        /// Contant uploading failed. Actor voucher size limit exceeded.
+        GlobalVoucherSizeLimitExceeded,
 
-        /// Contant uploading failed. Actor quota objects limit exceeded.
-        GlobalQuotaObjectsLimitExceeded,
+        /// Contant uploading failed. Actor voucher objects limit exceeded.
+        GlobalVoucherObjectsLimitExceeded,
 
         /// Content uploading blocked.
         ContentUploadingBlocked,
 
         /// Provided owner should be equal o the data object owner under given content id
-        OwnersAreNotEqual
+        OwnersAreNotEqual,
+
+        /// No storage provider available to service the request
+        NoProviderAvailable
     }
 }
 
@@ -146,7 +151,7 @@ pub type DataObject<T> = DataObjectInternal<
 
 /// Manages content ids, type and storage provider decision about it.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Clone, Encode, Decode, PartialEq, Debug)]
+#[derive(Clone, Encode, Decode, PartialEq, Debug, Default)]
 pub struct DataObjectInternal<
     MemberId,
     ChannelId,
@@ -179,15 +184,15 @@ pub struct DataObjectInternal<
 }
 
 #[derive(Clone, Copy)]
-pub struct Voucher {
+pub struct Delta {
     pub size: u64,
     pub objects: u64,
 }
 
-/// Uploading quota for StorageObjectOwner
+/// Uploading voucher for StorageObjectOwner
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, Default)]
-pub struct Quota {
+pub struct Voucher {
     // Total objects size limit per StorageObjectOwner
     pub size_limit: u64,
     // Total objects number limit per StorageObjectOwner
@@ -196,8 +201,8 @@ pub struct Quota {
     pub objects_used: u64,
 }
 
-impl Quota {
-    /// Create new quota with provided size & objects limits
+impl Voucher {
+    /// Create new voucher with provided size & objects limits
     pub const fn new(size_limit: u64, objects_limit: u64) -> Self {
         Self {
             size_limit,
@@ -207,26 +212,26 @@ impl Quota {
         }
     }
 
-    /// Calculate free quota
-    pub fn calculate_voucher(&self) -> Voucher {
-        Voucher {
+    /// Calculate voucher delta
+    pub fn calculate_delta(&self) -> Delta {
+        Delta {
             size: self.size_limit - self.size_used,
             objects: self.objects_limit - self.objects_used,
         }
     }
 
-    pub fn fill_quota(self, voucher: Voucher) -> Self {
+    pub fn fill_voucher(self, voucher_delta: Delta) -> Self {
         Self {
-            size_used: self.size_used + voucher.size,
-            objects_used: self.objects_used + voucher.objects,
+            size_used: self.size_used + voucher_delta.size,
+            objects_used: self.objects_used + voucher_delta.objects,
             ..self
         }
     }
 
-    pub fn release_quota(self, voucher: Voucher) -> Self {
+    pub fn release_voucher(self, voucher_delta: Delta) -> Self {
         Self {
-            size_used: self.size_used - voucher.size,
-            objects_used: self.objects_used - voucher.objects,
+            size_used: self.size_used - voucher_delta.size,
+            objects_used: self.objects_used - voucher_delta.objects,
             ..self
         }
     }
@@ -247,49 +252,52 @@ decl_storage! {
     trait Store for Module<T: Trait> as DataDirectory {
 
         /// Maps data objects by their content id.
-        pub DataObjectByContentId get(fn data_object_by_content_id) config():
-            map hasher(blake2_128_concat) T::ContentId => Option<DataObject<T>>;
+        pub DataByContentId get(fn data_object_by_content_id) config():
+            map hasher(blake2_128_concat) T::ContentId => DataObject<T>;
 
-        /// Maps storage owner to it`s quota. Created when the first upload by the new actor occured.
-        pub Quotas get(fn quotas) config():
-            map hasher(blake2_128_concat) StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>> => Quota;
+        /// Maps storage owner to it`s voucher. Created when the first upload by the new actor occured.
+        pub Vouchers get(fn vouchers) config():
+            map hasher(blake2_128_concat) ObjectOwner<T> => Voucher;
 
-        /// Upper bound for the Quota size limit.
-        pub QuotaSizeLimitUpperBound get(fn quota_size_limit_upper_bound) config(): u64;
+        /// Upper bound for the Voucher size limit.
+        pub VoucherSizeLimitUpperBound get(fn voucher_size_limit_upper_bound) config(): u64 = DEFAULT_VOUCHER_SIZE_LIMIT_UPPER_BOUND;
 
-        /// Upper bound for the Quota objects number limit.
-        pub QuotaObjectsLimitUpperBound get(fn quota_objects_limit_upper_bound) config(): u64;
+        /// Upper bound for the Voucher objects number limit.
+        pub VoucherObjectsLimitUpperBound get(fn voucher_objects_limit_upper_bound) config(): u64 = DEFAULT_VOUCHER_OBJECTS_LIMIT_UPPER_BOUND;
 
-        /// Global quota.
-        pub GlobalQuota get(fn global_quota) config(): Quota;
+        /// Default content voucher for all actors.
+        pub DefaultVoucher get(fn default_voucher) config(): Voucher;
+
+        /// Global voucher.
+        pub GlobalVoucher get(fn global_voucher) config(): Voucher = DEFAULT_GLOBAL_VOUCHER;
 
         /// If all new uploads blocked
-        pub UploadingBlocked get(fn uploading_blocked) config(): bool;
+        pub UploadingBlocked get(fn uploading_blocked) config(): bool = DEFAULT_UPLOADING_BLOCKED_STATUS;
+
     }
 }
 
 decl_event! {
     /// _Data directory_ events
     pub enum Event<T> where
-        StorageObjectOwner = StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        StorageObjectOwner = ObjectOwner<T>,
         StorageProviderId = StorageProviderId<T>,
-        Content = Vec<ContentParameters<ContentId<T>, DataObjectTypeId<T>>>,
         ContentId = ContentId<T>,
-        ContentIds = Vec<ContentId<T>>,
-        QuotaLimit = u64,
+        ContentParameters = ContentParameters<ContentId<T>, DataObjectTypeId<T>>,
+        VoucherLimit = u64,
         UploadingStatus = bool
     {
         /// Emits on adding of the content.
         /// Params:
         /// - Content parameters representation.
         /// - StorageObjectOwner enum.
-        ContentAdded(Content, StorageObjectOwner),
+        ContentAdded(Vec<ContentParameters>, StorageObjectOwner),
 
         /// Emits on content removal.
         /// Params:
         /// - Content parameters representation.
         /// - StorageObjectOwner enum.
-        ContentRemoved(ContentIds, StorageObjectOwner),
+        ContentRemoved(Vec<ContentId>, StorageObjectOwner),
 
         /// Emits when the storage provider accepts a content.
         /// Params:
@@ -303,17 +311,17 @@ decl_event! {
         /// - Id of the storage provider.
         ContentRejected(ContentId, StorageProviderId),
 
-        /// Emits when the storage object owner quota size limit update performed.
+        /// Emits when the storage object owner voucher size limit update performed.
         /// Params:
         /// - StorageObjectOwner enum.
-        /// - quota size limit.
-        StorageObjectOwnerQuotaSizeLimitUpdated(StorageObjectOwner, QuotaLimit),
+        /// - voucher size limit.
+        StorageObjectOwnerVoucherSizeLimitUpdated(StorageObjectOwner, VoucherLimit),
 
-        /// Emits when the storage object owner quota objects limit update performed.
+        /// Emits when the storage object owner voucher objects limit update performed.
         /// Params:
         /// - StorageObjectOwner enum.
-        /// - quota objects limit.
-        StorageObjectOwnerQuotaObjectsLimitUpdated(StorageObjectOwner, QuotaLimit),
+        /// - voucher objects limit.
+        StorageObjectOwnerVoucherObjectsLimitUpdated(StorageObjectOwner, VoucherLimit),
 
         /// Emits when the content uploading status update performed.
         /// Params:
@@ -336,7 +344,7 @@ decl_module! {
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn add_content(
             origin,
-            owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+            owner: ObjectOwner<T>,
             content: Vec<ContentParameters<ContentId<T>, DataObjectTypeId<T>>>
         ) {
 
@@ -347,14 +355,14 @@ decl_module! {
 
             Self::ensure_content_is_valid(&content)?;
 
-            let owner_quota = Self::get_quota(&owner);
+            let owner_voucher = Self::get_voucher(&owner);
 
-            // Ensure owner quota constraints satisfied.
-            // Calculate upload voucher
-            let upload_voucher = Self::ensure_owner_quota_constraints_satisfied(owner_quota, &content)?;
+            // Ensure owner voucher constraints satisfied.
+            // Calculate upload voucher delta
+            let upload_voucher_delta = Self::ensure_owner_voucher_constraints_satisfied(owner_voucher, &content)?;
 
-            // Ensure global quota constraints satisfied.
-            Self::ensure_global_quota_constraints_satisfied(upload_voucher)?;
+            // Ensure global voucher constraints satisfied.
+            Self::ensure_global_voucher_constraints_satisfied(upload_voucher_delta)?;
 
             let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
 
@@ -363,7 +371,7 @@ decl_module! {
             //
 
             // Let's create the entry then
-            Self::upload_content(owner_quota, upload_voucher, liaison, content.clone(), owner.clone());
+            Self::upload_content(owner_voucher, upload_voucher_delta, liaison, content.clone(), owner.clone());
 
             Self::deposit_event(RawEvent::ContentAdded(content, owner));
         }
@@ -372,7 +380,7 @@ decl_module! {
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn remove_content(
             origin,
-            owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+            owner: ObjectOwner<T>,
             content_ids: Vec<ContentId<T>>
         ) {
 
@@ -392,58 +400,58 @@ decl_module! {
             Self::deposit_event(RawEvent::ContentRemoved(content_ids, owner));
         }
 
-        /// Updates storage object owner quota objects limit. Requires leader privileges.
+        /// Updates storage object owner voucher objects limit. Requires leader privileges.
         #[weight = 10_000_000] // TODO: adjust weight
-        pub fn update_storage_object_owner_quota_objects_limit(
+        pub fn update_storage_object_owner_voucher_objects_limit(
             origin,
-            abstract_owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
-            new_quota_objects_limit: u64
+            abstract_owner: ObjectOwner<T>,
+            new_voucher_objects_limit: u64
         ) {
             <StorageWorkingGroup<T>>::ensure_origin_is_active_leader(origin)?;
-            ensure!(new_quota_objects_limit <= Self::quota_objects_limit_upper_bound(), Error::<T>::QuotaSizeLimitUpperBoundExceeded);
+            ensure!(new_voucher_objects_limit <= Self::voucher_objects_limit_upper_bound(), Error::<T>::VoucherObjectsLimitUpperBoundExceeded);
 
             //
             // == MUTATION SAFE ==
             //
 
-            if <Quotas<T>>::contains_key(&abstract_owner) {
-                <Quotas<T>>::mutate(&abstract_owner, |quota| {
-                    quota.set_new_objects_limit(new_quota_objects_limit);
+            if <Vouchers<T>>::contains_key(&abstract_owner) {
+                <Vouchers<T>>::mutate(&abstract_owner, |voucher| {
+                    voucher.set_new_objects_limit(new_voucher_objects_limit);
                 });
             } else {
-                let mut quota = T::DefaultQuota::get();
-                quota.set_new_objects_limit(new_quota_objects_limit);
-                <Quotas<T>>::insert(&abstract_owner, quota);
+                let mut voucher = Self::default_voucher();
+                voucher.set_new_objects_limit(new_voucher_objects_limit);
+                <Vouchers<T>>::insert(&abstract_owner, voucher);
             };
 
-            Self::deposit_event(RawEvent::StorageObjectOwnerQuotaObjectsLimitUpdated(abstract_owner, new_quota_objects_limit));
+            Self::deposit_event(RawEvent::StorageObjectOwnerVoucherObjectsLimitUpdated(abstract_owner, new_voucher_objects_limit));
         }
 
-        /// Updates storage object owner quota size limit. Requires leader privileges.
+        /// Updates storage object owner voucher size limit. Requires leader privileges.
         #[weight = 10_000_000] // TODO: adjust weight
-        pub fn update_storage_object_owner_quota_size_limit(
+        pub fn update_storage_object_owner_voucher_size_limit(
             origin,
-            abstract_owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
-            new_quota_size_limit: u64
+            abstract_owner: ObjectOwner<T>,
+            new_voucher_size_limit: u64
         ) {
             <StorageWorkingGroup<T>>::ensure_origin_is_active_leader(origin)?;
-            ensure!(new_quota_size_limit <= Self::quota_size_limit_upper_bound(), Error::<T>::QuotaObjectsLimitUpperBoundExceeded);
+            ensure!(new_voucher_size_limit <= Self::voucher_size_limit_upper_bound(), Error::<T>::VoucherSizeLimitUpperBoundExceeded);
 
             //
             // == MUTATION SAFE ==
             //
 
-            if <Quotas<T>>::contains_key(&abstract_owner) {
-                <Quotas<T>>::mutate(&abstract_owner, |quota| {
-                    quota.set_new_size_limit(new_quota_size_limit);
+            if <Vouchers<T>>::contains_key(&abstract_owner) {
+                <Vouchers<T>>::mutate(&abstract_owner, |voucher| {
+                    voucher.set_new_size_limit(new_voucher_size_limit);
                 });
             } else {
-                let mut quota = T::DefaultQuota::get();
-                quota.set_new_size_limit(new_quota_size_limit);
-                <Quotas<T>>::insert(&abstract_owner, quota);
+                let mut voucher = Self::default_voucher();
+                voucher.set_new_size_limit(new_voucher_size_limit);
+                <Vouchers<T>>::insert(&abstract_owner, voucher);
             };
 
-            Self::deposit_event(RawEvent::StorageObjectOwnerQuotaSizeLimitUpdated(abstract_owner, new_quota_size_limit));
+            Self::deposit_event(RawEvent::StorageObjectOwnerVoucherSizeLimitUpdated(abstract_owner, new_voucher_size_limit));
         }
 
         /// Storage provider accepts a content. Requires signed storage provider account and its id.
@@ -481,7 +489,7 @@ decl_module! {
 
         /// Locks / unlocks content uploading
         #[weight = 10_000_000] // TODO: adjust weight
-        fn update_content_uploading_status(origin, is_blocked: bool) {
+        pub fn update_content_uploading_status(origin, is_blocked: bool) {
             <StorageWorkingGroup<T>>::ensure_origin_is_active_leader(origin)?;
 
             // == MUTATION SAFE ==
@@ -493,10 +501,30 @@ decl_module! {
 }
 
 impl<T: Trait> Module<T> {
+    // Used to initialize data_directory runtime storage on runtime upgrade
+    pub fn initialize_data_directory(
+        vouchers: Vec<(ObjectOwner<T>, Voucher)>,
+        voucher_size_limit_upper_bound: u64,
+        voucher_objects_limit_upper_bound: u64,
+        global_voucher: Voucher,
+        default_voucher: Voucher,
+        uploading_blocked: bool,
+    ) {
+        for (storage_object_owner, voucher) in vouchers {
+            <Vouchers<T>>::insert(storage_object_owner, voucher);
+        }
+
+        <VoucherSizeLimitUpperBound>::put(voucher_size_limit_upper_bound);
+        <VoucherObjectsLimitUpperBound>::put(voucher_objects_limit_upper_bound);
+        <GlobalVoucher>::put(global_voucher);
+        <DefaultVoucher>::put(default_voucher);
+        <UploadingBlocked>::put(uploading_blocked);
+    }
+
     // Ensure given origin can perform operation under specific storage object owner
     fn ensure_storage_object_owner_origin(
         origin: T::Origin,
-        owner: &StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: &ObjectOwner<T>,
     ) -> DispatchResult {
         if let StorageObjectOwner::Member(member_id) = owner {
             T::MemberOriginValidator::ensure_actor_origin(origin, *member_id)?;
@@ -506,12 +534,12 @@ impl<T: Trait> Module<T> {
         Ok(())
     }
 
-    // Get owner quota if exists, otherwise return default one.
-    fn get_quota(owner: &StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>) -> Quota {
-        if <Quotas<T>>::contains_key(owner) {
-            Self::quotas(owner)
+    // Get owner voucher if exists, otherwise return default one.
+    fn get_voucher(owner: &ObjectOwner<T>) -> Voucher {
+        if <Vouchers<T>>::contains_key(owner) {
+            Self::vouchers(owner)
         } else {
-            T::DefaultQuota::get()
+            Self::default_voucher()
         }
     }
 
@@ -524,32 +552,32 @@ impl<T: Trait> Module<T> {
         Ok(())
     }
 
-    // Ensure owner quota constraints satisfied, returns total object length and total size voucher for this upload.
-    fn ensure_owner_quota_constraints_satisfied(
-        owner_quota: Quota,
+    // Ensure owner voucher constraints satisfied, returns total object length and total size voucher delta for this upload.
+    fn ensure_owner_voucher_constraints_satisfied(
+        owner_voucher: Voucher,
         content: &[ContentParameters<T::ContentId, DataObjectTypeId<T>>],
-    ) -> Result<Voucher, Error<T>> {
-        let owner_quota_voucher = owner_quota.calculate_voucher();
+    ) -> Result<Delta, Error<T>> {
+        let owner_voucher_delta = owner_voucher.calculate_delta();
 
-        // Ensure total content length is less or equal then available per given owner quota
+        // Ensure total content length is less or equal then available per given owner voucher
         let content_length = content.len() as u64;
 
         ensure!(
-            owner_quota_voucher.objects >= content_length,
-            Error::<T>::QuotaObjectsLimitExceeded
+            owner_voucher_delta.objects >= content_length,
+            Error::<T>::VoucherObjectsLimitExceeded
         );
 
-        // Ensure total content size is less or equal then available per given owner quota
+        // Ensure total content size is less or equal then available per given owner voucher
         let content_size = content
             .iter()
             .fold(0, |total_size, content| total_size + content.size);
 
         ensure!(
-            owner_quota_voucher.size >= content_size,
-            Error::<T>::QuotaSizeLimitExceeded
+            owner_voucher_delta.size >= content_size,
+            Error::<T>::VoucherSizeLimitExceeded
         );
 
-        Ok(Voucher {
+        Ok(Delta {
             size: content_size,
             objects: content_length,
         })
@@ -558,12 +586,11 @@ impl<T: Trait> Module<T> {
     // Ensure content under given content ids can be successfully removed
     fn ensure_content_can_be_removed(
         content_ids: &[T::ContentId],
-        owner: &StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: &ObjectOwner<T>,
     ) -> Result<Vec<DataObject<T>>, Error<T>> {
         let mut content = Vec::new();
         for content_id in content_ids {
-            let data_object =
-                Self::data_object_by_content_id(content_id).ok_or(Error::<T>::CidNotFound)?;
+            let data_object = Self::get_data_object(content_id)?;
             ensure!(data_object.owner == *owner, Error::<T>::OwnersAreNotEqual);
             content.push(data_object);
         }
@@ -571,43 +598,44 @@ impl<T: Trait> Module<T> {
         Ok(content)
     }
 
-    fn calculate_content_voucher(content: Vec<DataObject<T>>) -> Voucher {
+    // Calculates content voucher delta
+    fn calculate_content_voucher(content: Vec<DataObject<T>>) -> Delta {
         let content_length = content.len() as u64;
 
         let content_size = content
             .into_iter()
             .fold(0, |total_size, content| total_size + content.size);
 
-        Voucher {
+        Delta {
             size: content_size,
             objects: content_length,
         }
     }
 
-    // Ensures global quota constraints satisfied.
-    fn ensure_global_quota_constraints_satisfied(upload_voucher: Voucher) -> DispatchResult {
-        let global_quota_voucher = Self::global_quota().calculate_voucher();
+    // Ensures global voucher constraints satisfied.
+    fn ensure_global_voucher_constraints_satisfied(upload_voucher_delta: Delta) -> DispatchResult {
+        let global_voucher_voucher = Self::global_voucher().calculate_delta();
 
         ensure!(
-            global_quota_voucher.objects >= upload_voucher.objects,
-            Error::<T>::GlobalQuotaObjectsLimitExceeded
+            global_voucher_voucher.objects >= upload_voucher_delta.objects,
+            Error::<T>::GlobalVoucherObjectsLimitExceeded
         );
 
         ensure!(
-            global_quota_voucher.size >= upload_voucher.size,
-            Error::<T>::GlobalQuotaSizeLimitExceeded
+            global_voucher_voucher.size >= upload_voucher_delta.size,
+            Error::<T>::GlobalVoucherSizeLimitExceeded
         );
 
         Ok(())
     }
 
-    // Complete content upload, update quotas
+    // Complete content upload, update vouchers
     fn upload_content(
-        owner_quota: Quota,
-        upload_voucher: Voucher,
+        owner_voucher: Voucher,
+        upload_voucher_delta: Delta,
         liaison: StorageProviderId<T>,
         multi_content: Vec<ContentParameters<T::ContentId, DataObjectTypeId<T>>>,
-        owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: ObjectOwner<T>,
     ) {
         for content in multi_content {
             let data: DataObject<T> = DataObjectInternal {
@@ -620,35 +648,35 @@ impl<T: Trait> Module<T> {
                 ipfs_content_id: content.ipfs_content_id,
             };
 
-            <DataObjectByContentId<T>>::insert(content.content_id, data);
+            <DataByContentId<T>>::insert(content.content_id, data);
         }
 
-        // Updade or create owner quota.
-        <Quotas<T>>::insert(owner, owner_quota.fill_quota(upload_voucher));
+        // Updade or create owner voucher.
+        <Vouchers<T>>::insert(owner, owner_voucher.fill_voucher(upload_voucher_delta));
 
-        // Update global quota
-        <GlobalQuota>::mutate(|global_quota| global_quota.fill_quota(upload_voucher));
+        // Update global voucher
+        <GlobalVoucher>::put(Self::global_voucher().fill_voucher(upload_voucher_delta));
     }
 
     // Complete content removal
     fn delete_content(
-        owner: &StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: &ObjectOwner<T>,
         content_ids: &[T::ContentId],
         content: Vec<DataObject<T>>,
     ) {
         let removal_voucher = Self::calculate_content_voucher(content);
 
         for content_id in content_ids {
-            <DataObjectByContentId<T>>::remove(content_id);
+            <DataByContentId<T>>::remove(content_id);
         }
 
-        // Updade owner quota.
-        <Quotas<T>>::mutate(owner, |owner_quota| {
-            owner_quota.release_quota(removal_voucher)
+        // Updade owner voucher.
+        <Vouchers<T>>::mutate(owner, |owner_voucher| {
+            owner_voucher.release_voucher(removal_voucher)
         });
 
-        // Update global quota
-        <GlobalQuota>::mutate(|global_quota| global_quota.release_quota(removal_voucher));
+        // Update global voucher
+        <GlobalVoucher>::put(Self::global_voucher().release_voucher(removal_voucher));
     }
 
     fn ensure_content_is_valid(
@@ -661,7 +689,7 @@ impl<T: Trait> Module<T> {
             );
 
             ensure!(
-                !<DataObjectByContentId<T>>::contains_key(&content.content_id),
+                !<DataByContentId<T>>::contains_key(&content.content_id),
                 Error::<T>::DataObjectAlreadyAdded
             );
         }
@@ -673,8 +701,7 @@ impl<T: Trait> Module<T> {
         content_id: T::ContentId,
         judgement: LiaisonJudgement,
     ) -> DispatchResult {
-        let mut data =
-            Self::data_object_by_content_id(&content_id).ok_or(Error::<T>::CidNotFound)?;
+        let mut data = Self::get_data_object(&content_id)?;
 
         // Make sure the liaison matches
         ensure!(
@@ -683,7 +710,7 @@ impl<T: Trait> Module<T> {
         );
 
         data.liaison_judgement = judgement;
-        <DataObjectByContentId<T>>::insert(content_id, data);
+        <DataByContentId<T>>::insert(content_id, data);
 
         Ok(())
     }
@@ -692,7 +719,7 @@ impl<T: Trait> Module<T> {
 /// Provides random storage provider id. We use it when assign the content to the storage provider.
 pub trait StorageProviderHelper<T: Trait> {
     /// Provides random storage provider id.
-    fn get_random_storage_provider() -> Result<StorageProviderId<T>, &'static str>;
+    fn get_random_storage_provider() -> Result<StorageProviderId<T>, Error<T>>;
 }
 
 /// Content access helper.
@@ -701,39 +728,41 @@ pub trait ContentIdExists<T: Trait> {
     fn has_content(id: &T::ContentId) -> bool;
 
     /// Returns the data object for the provided content id.
-    fn get_data_object(id: &T::ContentId) -> Result<DataObject<T>, &'static str>;
+    fn get_data_object(id: &T::ContentId) -> Result<DataObject<T>, Error<T>>;
 }
 
 impl<T: Trait> ContentIdExists<T> for Module<T> {
     fn has_content(content_id: &T::ContentId) -> bool {
-        Self::data_object_by_content_id(*content_id).is_some()
+        <DataByContentId<T>>::contains_key(content_id)
     }
 
-    fn get_data_object(content_id: &T::ContentId) -> Result<DataObject<T>, &'static str> {
-        match Self::data_object_by_content_id(*content_id) {
-            Some(data) => Ok(data),
-            None => Err(Error::<T>::LiaisonRequired.into()),
+    fn get_data_object(content_id: &T::ContentId) -> Result<DataObject<T>, Error<T>> {
+        if Self::has_content(content_id) {
+            Ok(Self::data_object_by_content_id(*content_id))
+        } else {
+            Err(Error::<T>::CidNotFound)
         }
     }
 }
 
 impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
     fn atomically_add_content(
-        owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: ObjectOwner<T>,
         content: Vec<ContentParameters<T::ContentId, DataObjectTypeId<T>>>,
     ) -> DispatchResult {
         Self::ensure_content_is_valid(&content)?;
 
         Self::ensure_uploading_is_not_blocked()?;
 
-        let owner_quota = Self::get_quota(&owner);
+        let owner_voucher = Self::get_voucher(&owner);
 
-        // Ensure owner quota constraints satisfied.
+        // Ensure owner voucher constraints satisfied.
         // Calculate upload voucher
-        let upload_voucher = Self::ensure_owner_quota_constraints_satisfied(owner_quota, &content)?;
+        let upload_voucher =
+            Self::ensure_owner_voucher_constraints_satisfied(owner_voucher, &content)?;
 
-        // Ensure global quota constraints satisfied.
-        Self::ensure_global_quota_constraints_satisfied(upload_voucher)?;
+        // Ensure global voucher constraints satisfied.
+        Self::ensure_global_voucher_constraints_satisfied(upload_voucher)?;
 
         let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
 
@@ -743,12 +772,12 @@ impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
 
         // Let's create the entry then
 
-        Self::upload_content(owner_quota, upload_voucher, liaison, content, owner);
+        Self::upload_content(owner_voucher, upload_voucher, liaison, content, owner);
         Ok(())
     }
 
     fn atomically_remove_content(
-        owner: &StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: &ObjectOwner<T>,
         content_ids: &[T::ContentId],
     ) -> DispatchResult {
         // Ensure content under given content ids can be successfully removed
@@ -764,23 +793,20 @@ impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
     }
 
     fn can_add_content(
-        owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: ObjectOwner<T>,
         content: Vec<ContentParameters<T::ContentId, DataObjectTypeId<T>>>,
     ) -> DispatchResult {
         Self::ensure_uploading_is_not_blocked()?;
 
         T::StorageProviderHelper::get_random_storage_provider()?;
-        let owner_quota = Self::get_quota(&owner);
+        let owner_voucher = Self::get_voucher(&owner);
 
-        // Ensure owner quota constraints satisfied.
-        Self::ensure_owner_quota_constraints_satisfied(owner_quota, &content)?;
+        // Ensure owner voucher constraints satisfied.
+        Self::ensure_owner_voucher_constraints_satisfied(owner_voucher, &content)?;
         Self::ensure_content_is_valid(&content)
     }
 
-    fn can_remove_content(
-        owner: &StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
-        content_ids: &[ContentId<T>],
-    ) -> DispatchResult {
+    fn can_remove_content(owner: &ObjectOwner<T>, content_ids: &[ContentId<T>]) -> DispatchResult {
         // Ensure content under given content ids can be successfully removed
         Self::ensure_content_can_be_removed(content_ids, &owner)?;
 

+ 5 - 0
runtime-modules/storage/src/lib.rs

@@ -7,6 +7,8 @@ pub mod data_object_type_registry;
 
 mod tests;
 
+pub use common::storage::StorageObjectOwner;
+
 // The storage working group instance alias.
 pub type StorageWorkingGroupInstance = working_group::Instance2;
 
@@ -30,3 +32,6 @@ pub(crate) type DataObjectTypeId<T> = <T as common::StorageOwnership>::DataObjec
 
 /// Storage provider is a worker from the working group module.
 pub type StorageProviderId<T> = working_group::WorkerId<T>;
+
+/// Alias for StorageObjectOwner
+pub type ObjectOwner<T> = StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>;

+ 344 - 1
runtime-modules/storage/src/tests/data_directory.rs

@@ -1,5 +1,6 @@
 #![cfg(test)]
 
+use crate::data_directory::Error;
 use common::storage::StorageObjectOwner;
 use frame_support::dispatch::DispatchError;
 use system::RawOrigin;
@@ -46,13 +47,355 @@ fn add_content_fails_with_invalid_origin() {
             ipfs_content_id: vec![1, 2, 3, 4],
         };
 
-        // Register a content with 1234 bytes of type 1, which should be recognized.
+        // Make an attempt to register a content with 1234 bytes of type 1, which should be recognized.
         let res =
             TestDataDirectory::add_content(RawOrigin::Root.into(), owner, vec![content_parameters]);
         assert_eq!(res, Err(DispatchError::Other("Bad origin")));
     });
 }
 
+#[test]
+fn add_content_uploading_blocked() {
+    ExtBuilder::default()
+        .uploading_blocked_status(true)
+        .build()
+        .execute_with(|| {
+            let sender = 1u64;
+
+            let owner = StorageObjectOwner::Member(1u64);
+
+            let content_parameters = ContentParameters {
+                content_id: 1,
+                type_id: 1234,
+                size: 0,
+                ipfs_content_id: vec![1, 2, 3, 4],
+            };
+
+            // Make an attempt to register a content, when uploading is blocked.
+            let res = TestDataDirectory::add_content(
+                Origin::signed(sender),
+                owner,
+                vec![content_parameters],
+            );
+            assert_eq!(res, Err(Error::<Test>::ContentUploadingBlocked.into()));
+        });
+}
+
+#[test]
+fn add_content_size_limit_reached() {
+    with_default_mock_builder(|| {
+        let sender = 1u64;
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let content_parameters = ContentParameters {
+            content_id: 1,
+            type_id: 1234,
+            size: DEFAULT_VOUCHER.size_limit + 1,
+            ipfs_content_id: vec![1, 2, 3, 4],
+        };
+
+        // Make an attempt to register a content, when uploading is blocked.
+        let res =
+            TestDataDirectory::add_content(Origin::signed(sender), owner, vec![content_parameters]);
+        assert_eq!(res, Err(Error::<Test>::VoucherSizeLimitExceeded.into()));
+    });
+}
+
+#[test]
+fn add_content_objects_limit_reached() {
+    with_default_mock_builder(|| {
+        let sender = 1u64;
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let mut content = vec![];
+
+        for i in 0..=DEFAULT_VOUCHER.objects_limit {
+            let content_parameters = ContentParameters {
+                content_id: i + 1,
+                type_id: 1234,
+                size: 0,
+                ipfs_content_id: vec![1, 2, 3, 4],
+            };
+            content.push(content_parameters);
+        }
+
+        // Make an attempt to register a content, when uploading is blocked.
+        let res = TestDataDirectory::add_content(Origin::signed(sender), owner, content);
+        assert_eq!(res, Err(Error::<Test>::VoucherObjectsLimitExceeded.into()));
+    });
+}
+
+#[test]
+fn add_content_global_size_limit_reached() {
+    let global_voucher_size_limit = 0;
+    let global_voucher_objects_limit = 50;
+
+    ExtBuilder::default()
+        .global_voucher(Voucher::new(
+            global_voucher_size_limit,
+            global_voucher_objects_limit,
+        ))
+        .build()
+        .execute_with(|| {
+            let sender = 1u64;
+
+            let owner = StorageObjectOwner::Member(1u64);
+
+            let content_parameters = ContentParameters {
+                content_id: 1,
+                type_id: 1234,
+                size: global_voucher_size_limit + 1,
+                ipfs_content_id: vec![1, 2, 3, 4],
+            };
+
+            // Make an attempt to register a content, when uploading is blocked.
+            let res = TestDataDirectory::add_content(
+                Origin::signed(sender),
+                owner,
+                vec![content_parameters],
+            );
+            assert_eq!(
+                res,
+                Err(Error::<Test>::GlobalVoucherSizeLimitExceeded.into())
+            );
+        });
+}
+
+#[test]
+fn add_content_global_objects_limit_reached() {
+    let global_voucher_size_limit = 50000;
+    let global_voucher_objects_limit = 0;
+
+    ExtBuilder::default()
+        .global_voucher(Voucher::new(
+            global_voucher_size_limit,
+            global_voucher_objects_limit,
+        ))
+        .build()
+        .execute_with(|| {
+            let sender = 1u64;
+
+            let owner = StorageObjectOwner::Member(1u64);
+
+            let content_parameters = ContentParameters {
+                content_id: 1,
+                type_id: 1234,
+                size: 0,
+                ipfs_content_id: vec![1, 2, 3, 4],
+            };
+
+            // Make an attempt to register a content, when uploading is blocked.
+            let res = TestDataDirectory::add_content(
+                Origin::signed(sender),
+                owner,
+                vec![content_parameters],
+            );
+            assert_eq!(
+                res,
+                Err(Error::<Test>::GlobalVoucherObjectsLimitExceeded.into())
+            );
+        });
+}
+
+#[test]
+fn delete_content() {
+    with_default_mock_builder(|| {
+        let sender = 1u64;
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let content_id = 1;
+
+        let content_parameters = ContentParameters {
+            content_id,
+            type_id: 1234,
+            size: 1,
+            ipfs_content_id: vec![1, 2, 3, 4],
+        };
+
+        // Register a content with 1234 bytes of type 1, which should be recognized.
+
+        TestDataDirectory::add_content(
+            Origin::signed(sender),
+            owner.clone(),
+            vec![content_parameters],
+        )
+        .unwrap();
+
+        let res =
+            TestDataDirectory::remove_content(Origin::signed(sender), owner, vec![content_id]);
+
+        assert!(res.is_ok());
+    });
+}
+
+#[test]
+fn delete_content_id_not_found() {
+    with_default_mock_builder(|| {
+        let sender = 1u64;
+
+        let content_id = 1;
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        // Make an attempt to remove content under non existent id
+        let res =
+            TestDataDirectory::remove_content(Origin::signed(sender), owner, vec![content_id]);
+
+        assert_eq!(res, Err(Error::<Test>::CidNotFound.into()));
+    });
+}
+
+#[test]
+fn delete_content_owners_are_not_equal() {
+    with_default_mock_builder(|| {
+        let sender = 1u64;
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let second_owner = StorageObjectOwner::Member(10u64);
+
+        let content_id = 1;
+
+        let content_parameters = ContentParameters {
+            content_id,
+            type_id: 1234,
+            size: 1,
+            ipfs_content_id: vec![1, 2, 3, 4],
+        };
+
+        // Register a content with 1234 bytes of type 1, which should be recognized.
+
+        TestDataDirectory::add_content(
+            Origin::signed(sender),
+            owner.clone(),
+            vec![content_parameters],
+        )
+        .unwrap();
+
+        // Make an attempt to remove content, providing a wrong origin
+        let res = TestDataDirectory::remove_content(
+            Origin::signed(sender),
+            second_owner,
+            vec![content_id],
+        );
+
+        assert_eq!(res, Err(Error::<Test>::OwnersAreNotEqual.into()));
+    });
+}
+
+#[test]
+fn update_content_uploading_status() {
+    with_default_mock_builder(|| {
+        SetLeadFixture::set_default_lead();
+
+        let res = TestDataDirectory::update_content_uploading_status(
+            Origin::signed(DEFAULT_LEADER_ACCOUNT_ID),
+            true,
+        );
+
+        assert!(res.is_ok());
+
+        assert_eq!(TestDataDirectory::uploading_blocked(), true);
+    });
+}
+
+#[test]
+fn update_storage_object_owner_voucher_objects_limit() {
+    with_default_mock_builder(|| {
+        SetLeadFixture::set_default_lead();
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let new_objects_limit = 20;
+
+        let res = TestDataDirectory::update_storage_object_owner_voucher_objects_limit(
+            Origin::signed(DEFAULT_LEADER_ACCOUNT_ID),
+            owner.clone(),
+            new_objects_limit,
+        );
+
+        assert!(res.is_ok());
+
+        assert_eq!(
+            TestDataDirectory::vouchers(owner).objects_limit,
+            new_objects_limit
+        );
+    });
+}
+
+#[test]
+fn update_storage_object_owner_voucher_objects_limit_upper_bound_exceeded() {
+    with_default_mock_builder(|| {
+        SetLeadFixture::set_default_lead();
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let new_objects_limit = TestDataDirectory::voucher_objects_limit_upper_bound() + 1;
+
+        // Make an attempt to update storage object owner voucher objects limit, providing value, which exceeds upper bound.
+        let res = TestDataDirectory::update_storage_object_owner_voucher_objects_limit(
+            Origin::signed(DEFAULT_LEADER_ACCOUNT_ID),
+            owner.clone(),
+            new_objects_limit,
+        );
+
+        assert_eq!(
+            res,
+            Err(Error::<Test>::VoucherObjectsLimitUpperBoundExceeded.into())
+        );
+    });
+}
+
+#[test]
+fn update_storage_object_owner_voucher_size_limit() {
+    with_default_mock_builder(|| {
+        SetLeadFixture::set_default_lead();
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let new_objects_total_size_limit = 100;
+
+        let res = TestDataDirectory::update_storage_object_owner_voucher_size_limit(
+            Origin::signed(DEFAULT_LEADER_ACCOUNT_ID),
+            owner.clone(),
+            new_objects_total_size_limit,
+        );
+
+        assert!(res.is_ok());
+
+        assert_eq!(
+            TestDataDirectory::vouchers(owner).size_limit,
+            new_objects_total_size_limit
+        );
+    });
+}
+
+#[test]
+fn update_storage_object_owner_voucher_size_limit_upper_bound_exceeded() {
+    with_default_mock_builder(|| {
+        SetLeadFixture::set_default_lead();
+
+        let owner = StorageObjectOwner::Member(1u64);
+
+        let new_objects_total_size_limit = TestDataDirectory::voucher_size_limit_upper_bound() + 1;
+
+        // Make an attempt to update storage object owner voucher size limit, providing value, which exceeds upper bound.
+        let res = TestDataDirectory::update_storage_object_owner_voucher_size_limit(
+            Origin::signed(DEFAULT_LEADER_ACCOUNT_ID),
+            owner.clone(),
+            new_objects_total_size_limit,
+        );
+
+        assert_eq!(
+            res,
+            Err(Error::<Test>::VoucherSizeLimitUpperBoundExceeded.into())
+        );
+    });
+}
+
 #[test]
 fn accept_and_reject_content_fail_with_invalid_storage_provider() {
     with_default_mock_builder(|| {

+ 0 - 28
runtime-modules/storage/src/tests/data_object_type_registry.rs

@@ -1,37 +1,9 @@
 #![cfg(test)]
 
-use frame_support::{StorageMap, StorageValue};
 use system::{EventRecord, Phase, RawOrigin};
 
 use super::mock::*;
 
-const DEFAULT_LEADER_ACCOUNT_ID: u64 = 1;
-const DEFAULT_LEADER_MEMBER_ID: u64 = 1;
-const DEFAULT_LEADER_WORKER_ID: u32 = 1;
-
-struct SetLeadFixture;
-impl SetLeadFixture {
-    fn set_default_lead() {
-        let worker = working_group::Worker {
-            member_id: DEFAULT_LEADER_MEMBER_ID,
-            role_account_id: DEFAULT_LEADER_ACCOUNT_ID,
-            reward_relationship: None,
-            role_stake_profile: None,
-        };
-
-        // Create the worker.
-        <working_group::WorkerById<Test, StorageWorkingGroupInstance>>::insert(
-            DEFAULT_LEADER_WORKER_ID,
-            worker,
-        );
-
-        // Update current lead.
-        <working_group::CurrentLead<Test, StorageWorkingGroupInstance>>::put(
-            DEFAULT_LEADER_WORKER_ID,
-        );
-    }
-}
-
 fn get_last_data_object_type_id() -> u64 {
     let dot_id = match System::events().last().unwrap().event {
         MetaEvent::data_object_type_registry(

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