Browse Source

Merge branch 'sumer' into sumer-content-dir-module

Mokhtar Naamani 4 years ago
parent
commit
a767969508
46 changed files with 5635 additions and 197 deletions
  1. 0 20
      .github/workflows/content-directory-schemas.yml
  2. 28 0
      .github/workflows/content-metadata.yml
  3. 4 0
      content-metadata-protobuf/.eslintignore
  4. 16 0
      content-metadata-protobuf/.eslintrc.js
  5. 2 0
      content-metadata-protobuf/.gitignore
  6. 4 0
      content-metadata-protobuf/.prettierignore
  7. 53 0
      content-metadata-protobuf/README.md
  8. 15 0
      content-metadata-protobuf/compile.sh
  9. 79 0
      content-metadata-protobuf/compiled/proto/Channel_pb.d.ts
  10. 597 0
      content-metadata-protobuf/compiled/proto/Channel_pb.js
  11. 57 0
      content-metadata-protobuf/compiled/proto/Person_pb.d.ts
  12. 427 0
      content-metadata-protobuf/compiled/proto/Person_pb.js
  13. 27 0
      content-metadata-protobuf/compiled/proto/Playlist_pb.d.ts
  14. 187 0
      content-metadata-protobuf/compiled/proto/Playlist_pb.js
  15. 73 0
      content-metadata-protobuf/compiled/proto/Series_pb.d.ts
  16. 549 0
      content-metadata-protobuf/compiled/proto/Series_pb.js
  17. 223 0
      content-metadata-protobuf/compiled/proto/Video_pb.d.ts
  18. 1740 0
      content-metadata-protobuf/compiled/proto/Video_pb.js
  19. 41 0
      content-metadata-protobuf/doc-appendix.md
  20. 368 0
      content-metadata-protobuf/doc/index.md
  21. 13 0
      content-metadata-protobuf/generate-md-doc.sh
  22. 41 0
      content-metadata-protobuf/package.json
  23. 25 0
      content-metadata-protobuf/proto/Channel.proto
  24. 13 0
      content-metadata-protobuf/proto/Person.proto
  25. 5 0
      content-metadata-protobuf/proto/Playlist.proto
  26. 15 0
      content-metadata-protobuf/proto/Series.proto
  27. 82 0
      content-metadata-protobuf/proto/Video.proto
  28. 74 0
      content-metadata-protobuf/src/KnownLicenses.json
  29. 10 0
      content-metadata-protobuf/src/index.ts
  30. 70 0
      content-metadata-protobuf/src/licenses.ts
  31. 31 0
      content-metadata-protobuf/test/channel.ts
  32. 42 0
      content-metadata-protobuf/test/license-codes.ts
  33. 112 0
      content-metadata-protobuf/test/video.ts
  34. 15 0
      content-metadata-protobuf/tsconfig.json
  35. 67 9
      node/src/chain_spec/content_config.rs
  36. 2 1
      package.json
  37. 3 9
      runtime-modules/common/src/storage.rs
  38. 255 28
      runtime-modules/storage/src/data_directory.rs
  39. 5 2
      runtime-modules/storage/src/data_object_storage_registry.rs
  40. 9 9
      runtime-modules/storage/src/data_object_type_registry.rs
  41. 20 35
      runtime-modules/storage/src/tests/data_directory.rs
  42. 3 1
      runtime-modules/storage/src/tests/data_object_storage_registry.rs
  43. 12 8
      runtime-modules/storage/src/tests/data_object_type_registry.rs
  44. 24 0
      runtime-modules/storage/src/tests/mock.rs
  45. 3 0
      runtime/src/lib.rs
  46. 194 75
      yarn.lock

+ 0 - 20
.github/workflows/content-directory-schemas.yml

@@ -1,20 +0,0 @@
-name: content-directory-schemas
-on: [pull_request, push]
-
-jobs:
-  schemas_checks:
-    name: Checks
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        node-version: [12.x]
-    steps:
-    - uses: actions/checkout@v1
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
-      with:
-        node-version: ${{ matrix.node-version }}
-    - name: validate
-      run: |
-        yarn install --frozen-lockfile
-        yarn workspace @joystream/cd-schemas checks --quiet

+ 28 - 0
.github/workflows/content-metadata.yml

@@ -0,0 +1,28 @@
+name: content-metadata
+on: [pull_request, push]
+
+jobs:
+  schemas_checks:
+    name: Checks
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: test protobuf
+      run: |
+        # # Install protoc compiler
+        # sudo apt-get install -y protobuf-compiler
+        # protoc --version
+        # # Install documentation plugin
+        # sudo apt-get install -y golang-go
+        # go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
+        yarn install --frozen-lockfile
+        # yarn workspace @joystream/content-metadata-protobuf build
+        yarn workspace @joystream/content-metadata-protobuf checks --quiet
+        yarn workspace @joystream/content-metadata-protobuf test

+ 4 - 0
content-metadata-protobuf/.eslintignore

@@ -0,0 +1,4 @@
+lib/
+proto/
+compiled/
+.eslintrc.js

+ 16 - 0
content-metadata-protobuf/.eslintrc.js

@@ -0,0 +1,16 @@
+module.exports = {
+  env: {
+    mocha: true,
+  },
+  parserOptions: {
+    project: './tsconfig.json'
+  },
+  extends: [
+    '@joystream/eslint-config'
+  ],
+  rules: {
+    'no-unused-vars': 'off', // Required by the typescript rule below
+    '@typescript-eslint/no-unused-vars': ['error'],
+    '@typescript-eslint/no-floating-promises': 'error',
+  },
+}

+ 2 - 0
content-metadata-protobuf/.gitignore

@@ -0,0 +1,2 @@
+node_modules/
+lib/

+ 4 - 0
content-metadata-protobuf/.prettierignore

@@ -0,0 +1,4 @@
+lib/
+doc/
+proto/
+compiled/

+ 53 - 0
content-metadata-protobuf/README.md

@@ -0,0 +1,53 @@
+## Joystream Content Directory Metadata Library
+
+This package contains protobuf message definitions compiled to Javascript/Typescript used for creating and updating various metadata blobs in the joystream content directory.
+
+### Message Specs
+
+Documented in [doc](./doc) folder
+
+### Choice of protobuf protocol v2
+
+For our usecase we wish to re-use same message to create and update  subset of fields.
+For this reason we need the explicit information about wether a field has been set or not and this is only possible with proto v2.
+
+Background: required/optional feilds are deprecated in [proto v3](https://www.ben-morris.com/handling-protocol-buffers-backwards-compatibility-between-versions-2-and-3-using-c/)
+
+
+### Helper methods
+The custom Joystream types such as License have helper methods to construct pre-defined well known values.
+
+### Example code:
+
+Best place to look at are the [tests specs](./test)
+
+### Opaque types
+We use simple [ISO_639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code representation for Language.
+useful npm package https://www.npmjs.com/package/iso-639-1
+
+### Building the package
+
+Building will compile the protofiles and build the library from source.
+
+- pre-requisists for compiling protofiles:
+    - [protoc](https://github.com/protocolbuffers/protobuf/releases)
+
+- pre-requisists for generating documentation:
+    - [golang](https://golang.org/)
+    - [protoc-gen-doc](https://github.com/pseudomuto/protoc-gen-doc) to generate docs
+
+```
+yarn && yarn build
+```
+
+### Generating docs
+
+```
+yarn generate-docs
+```
+
+### Tests
+
+```
+yarn test
+```

+ 15 - 0
content-metadata-protobuf/compile.sh

@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+# Path to this plugin
+PROTOC_GEN_TS_PATH="./node_modules/.bin/protoc-gen-ts"
+
+# Directory to write generated code to (.js and .d.ts files)
+OUT_DIR="./compiled"
+mkdir -p ${OUT_DIR}
+
+# Compile proto files
+protoc \
+    --plugin="protoc-gen-ts=${PROTOC_GEN_TS_PATH}" \
+    --js_out="import_style=commonjs,binary:${OUT_DIR}" \
+    --ts_out="${OUT_DIR}" \
+    proto/*.proto

+ 79 - 0
content-metadata-protobuf/compiled/proto/Channel_pb.d.ts

@@ -0,0 +1,79 @@
+// package: 
+// file: proto/Channel.proto
+
+import * as jspb from "google-protobuf";
+
+export class ChannelMetadata extends jspb.Message {
+  hasTitle(): boolean;
+  clearTitle(): void;
+  getTitle(): string | undefined;
+  setTitle(value: string): void;
+
+  hasDescription(): boolean;
+  clearDescription(): void;
+  getDescription(): string | undefined;
+  setDescription(value: string): void;
+
+  hasIsPublic(): boolean;
+  clearIsPublic(): void;
+  getIsPublic(): boolean | undefined;
+  setIsPublic(value: boolean): void;
+
+  hasLanguage(): boolean;
+  clearLanguage(): void;
+  getLanguage(): string | undefined;
+  setLanguage(value: string): void;
+
+  hasCoverPhoto(): boolean;
+  clearCoverPhoto(): void;
+  getCoverPhoto(): number | undefined;
+  setCoverPhoto(value: number): void;
+
+  hasAvatarPhoto(): boolean;
+  clearAvatarPhoto(): void;
+  getAvatarPhoto(): number | undefined;
+  setAvatarPhoto(value: number): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): ChannelMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: ChannelMetadata): ChannelMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: ChannelMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): ChannelMetadata;
+  static deserializeBinaryFromReader(message: ChannelMetadata, reader: jspb.BinaryReader): ChannelMetadata;
+}
+
+export namespace ChannelMetadata {
+  export type AsObject = {
+    title?: string,
+    description?: string,
+    isPublic?: boolean,
+    language?: string,
+    coverPhoto?: number,
+    avatarPhoto?: number,
+  }
+}
+
+export class ChannelCategoryMetadata extends jspb.Message {
+  hasName(): boolean;
+  clearName(): void;
+  getName(): string | undefined;
+  setName(value: string): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): ChannelCategoryMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: ChannelCategoryMetadata): ChannelCategoryMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: ChannelCategoryMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): ChannelCategoryMetadata;
+  static deserializeBinaryFromReader(message: ChannelCategoryMetadata, reader: jspb.BinaryReader): ChannelCategoryMetadata;
+}
+
+export namespace ChannelCategoryMetadata {
+  export type AsObject = {
+    name?: string,
+  }
+}
+

+ 597 - 0
content-metadata-protobuf/compiled/proto/Channel_pb.js

@@ -0,0 +1,597 @@
+// source: proto/Channel.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ *     field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf');
+var goog = jspb;
+var global = Function('return this')();
+
+goog.exportSymbol('proto.ChannelCategoryMetadata', null, global);
+goog.exportSymbol('proto.ChannelMetadata', null, global);
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.ChannelMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.ChannelMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.ChannelMetadata.displayName = 'proto.ChannelMetadata';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.ChannelCategoryMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.ChannelCategoryMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.ChannelCategoryMetadata.displayName = 'proto.ChannelCategoryMetadata';
+}
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.ChannelMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.ChannelMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.ChannelMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.ChannelMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    title: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+    description: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+    isPublic: (f = jspb.Message.getBooleanField(msg, 3)) == null ? undefined : f,
+    language: (f = jspb.Message.getField(msg, 4)) == null ? undefined : f,
+    coverPhoto: (f = jspb.Message.getField(msg, 5)) == null ? undefined : f,
+    avatarPhoto: (f = jspb.Message.getField(msg, 6)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.ChannelMetadata}
+ */
+proto.ChannelMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.ChannelMetadata;
+  return proto.ChannelMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.ChannelMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.ChannelMetadata}
+ */
+proto.ChannelMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setTitle(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setDescription(value);
+      break;
+    case 3:
+      var value = /** @type {boolean} */ (reader.readBool());
+      msg.setIsPublic(value);
+      break;
+    case 4:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setLanguage(value);
+      break;
+    case 5:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setCoverPhoto(value);
+      break;
+    case 6:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setAvatarPhoto(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.ChannelMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.ChannelMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.ChannelMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.ChannelMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 3));
+  if (f != null) {
+    writer.writeBool(
+      3,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 4));
+  if (f != null) {
+    writer.writeString(
+      4,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 5));
+  if (f != null) {
+    writer.writeUint32(
+      5,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 6));
+  if (f != null) {
+    writer.writeUint32(
+      6,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string title = 1;
+ * @return {string}
+ */
+proto.ChannelMetadata.prototype.getTitle = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setTitle = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearTitle = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasTitle = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string description = 2;
+ * @return {string}
+ */
+proto.ChannelMetadata.prototype.getDescription = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setDescription = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearDescription = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasDescription = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional bool is_public = 3;
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.getIsPublic = function() {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setIsPublic = function(value) {
+  return jspb.Message.setField(this, 3, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearIsPublic = function() {
+  return jspb.Message.setField(this, 3, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasIsPublic = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
+/**
+ * optional string language = 4;
+ * @return {string}
+ */
+proto.ChannelMetadata.prototype.getLanguage = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setLanguage = function(value) {
+  return jspb.Message.setField(this, 4, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearLanguage = function() {
+  return jspb.Message.setField(this, 4, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasLanguage = function() {
+  return jspb.Message.getField(this, 4) != null;
+};
+
+
+/**
+ * optional uint32 cover_photo = 5;
+ * @return {number}
+ */
+proto.ChannelMetadata.prototype.getCoverPhoto = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setCoverPhoto = function(value) {
+  return jspb.Message.setField(this, 5, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearCoverPhoto = function() {
+  return jspb.Message.setField(this, 5, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasCoverPhoto = function() {
+  return jspb.Message.getField(this, 5) != null;
+};
+
+
+/**
+ * optional uint32 avatar_photo = 6;
+ * @return {number}
+ */
+proto.ChannelMetadata.prototype.getAvatarPhoto = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 6, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.setAvatarPhoto = function(value) {
+  return jspb.Message.setField(this, 6, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelMetadata} returns this
+ */
+proto.ChannelMetadata.prototype.clearAvatarPhoto = function() {
+  return jspb.Message.setField(this, 6, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelMetadata.prototype.hasAvatarPhoto = function() {
+  return jspb.Message.getField(this, 6) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.ChannelCategoryMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.ChannelCategoryMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.ChannelCategoryMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.ChannelCategoryMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    name: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.ChannelCategoryMetadata}
+ */
+proto.ChannelCategoryMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.ChannelCategoryMetadata;
+  return proto.ChannelCategoryMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.ChannelCategoryMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.ChannelCategoryMetadata}
+ */
+proto.ChannelCategoryMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setName(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.ChannelCategoryMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.ChannelCategoryMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.ChannelCategoryMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.ChannelCategoryMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string name = 1;
+ * @return {string}
+ */
+proto.ChannelCategoryMetadata.prototype.getName = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.ChannelCategoryMetadata} returns this
+ */
+proto.ChannelCategoryMetadata.prototype.setName = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.ChannelCategoryMetadata} returns this
+ */
+proto.ChannelCategoryMetadata.prototype.clearName = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.ChannelCategoryMetadata.prototype.hasName = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+goog.object.extend(exports, proto);

+ 57 - 0
content-metadata-protobuf/compiled/proto/Person_pb.d.ts

@@ -0,0 +1,57 @@
+// package: 
+// file: proto/Person.proto
+
+import * as jspb from "google-protobuf";
+
+export class PersonMetadata extends jspb.Message {
+  hasFirstName(): boolean;
+  clearFirstName(): void;
+  getFirstName(): string | undefined;
+  setFirstName(value: string): void;
+
+  hasMiddleName(): boolean;
+  clearMiddleName(): void;
+  getMiddleName(): string | undefined;
+  setMiddleName(value: string): void;
+
+  hasLastName(): boolean;
+  clearLastName(): void;
+  getLastName(): string | undefined;
+  setLastName(value: string): void;
+
+  hasAbout(): boolean;
+  clearAbout(): void;
+  getAbout(): string | undefined;
+  setAbout(value: string): void;
+
+  hasCoverPhoto(): boolean;
+  clearCoverPhoto(): void;
+  getCoverPhoto(): number | undefined;
+  setCoverPhoto(value: number): void;
+
+  hasAvatarPhoto(): boolean;
+  clearAvatarPhoto(): void;
+  getAvatarPhoto(): number | undefined;
+  setAvatarPhoto(value: number): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): PersonMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: PersonMetadata): PersonMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: PersonMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): PersonMetadata;
+  static deserializeBinaryFromReader(message: PersonMetadata, reader: jspb.BinaryReader): PersonMetadata;
+}
+
+export namespace PersonMetadata {
+  export type AsObject = {
+    firstName?: string,
+    middleName?: string,
+    lastName?: string,
+    about?: string,
+    coverPhoto?: number,
+    avatarPhoto?: number,
+  }
+}
+

+ 427 - 0
content-metadata-protobuf/compiled/proto/Person_pb.js

@@ -0,0 +1,427 @@
+// source: proto/Person.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ *     field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf');
+var goog = jspb;
+var global = Function('return this')();
+
+goog.exportSymbol('proto.PersonMetadata', null, global);
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.PersonMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.PersonMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.PersonMetadata.displayName = 'proto.PersonMetadata';
+}
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.PersonMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.PersonMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.PersonMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.PersonMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    firstName: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+    middleName: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+    lastName: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f,
+    about: (f = jspb.Message.getField(msg, 4)) == null ? undefined : f,
+    coverPhoto: (f = jspb.Message.getField(msg, 5)) == null ? undefined : f,
+    avatarPhoto: (f = jspb.Message.getField(msg, 6)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.PersonMetadata}
+ */
+proto.PersonMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.PersonMetadata;
+  return proto.PersonMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.PersonMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.PersonMetadata}
+ */
+proto.PersonMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setFirstName(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setMiddleName(value);
+      break;
+    case 3:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setLastName(value);
+      break;
+    case 4:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setAbout(value);
+      break;
+    case 5:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setCoverPhoto(value);
+      break;
+    case 6:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setAvatarPhoto(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.PersonMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.PersonMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.PersonMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.PersonMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 3));
+  if (f != null) {
+    writer.writeString(
+      3,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 4));
+  if (f != null) {
+    writer.writeString(
+      4,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 5));
+  if (f != null) {
+    writer.writeUint32(
+      5,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 6));
+  if (f != null) {
+    writer.writeUint32(
+      6,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string first_name = 1;
+ * @return {string}
+ */
+proto.PersonMetadata.prototype.getFirstName = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.setFirstName = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.clearFirstName = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PersonMetadata.prototype.hasFirstName = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string middle_name = 2;
+ * @return {string}
+ */
+proto.PersonMetadata.prototype.getMiddleName = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.setMiddleName = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.clearMiddleName = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PersonMetadata.prototype.hasMiddleName = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional string last_name = 3;
+ * @return {string}
+ */
+proto.PersonMetadata.prototype.getLastName = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.setLastName = function(value) {
+  return jspb.Message.setField(this, 3, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.clearLastName = function() {
+  return jspb.Message.setField(this, 3, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PersonMetadata.prototype.hasLastName = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
+/**
+ * optional string about = 4;
+ * @return {string}
+ */
+proto.PersonMetadata.prototype.getAbout = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.setAbout = function(value) {
+  return jspb.Message.setField(this, 4, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.clearAbout = function() {
+  return jspb.Message.setField(this, 4, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PersonMetadata.prototype.hasAbout = function() {
+  return jspb.Message.getField(this, 4) != null;
+};
+
+
+/**
+ * optional uint32 cover_photo = 5;
+ * @return {number}
+ */
+proto.PersonMetadata.prototype.getCoverPhoto = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.setCoverPhoto = function(value) {
+  return jspb.Message.setField(this, 5, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.clearCoverPhoto = function() {
+  return jspb.Message.setField(this, 5, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PersonMetadata.prototype.hasCoverPhoto = function() {
+  return jspb.Message.getField(this, 5) != null;
+};
+
+
+/**
+ * optional uint32 avatar_photo = 6;
+ * @return {number}
+ */
+proto.PersonMetadata.prototype.getAvatarPhoto = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 6, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.setAvatarPhoto = function(value) {
+  return jspb.Message.setField(this, 6, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PersonMetadata} returns this
+ */
+proto.PersonMetadata.prototype.clearAvatarPhoto = function() {
+  return jspb.Message.setField(this, 6, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PersonMetadata.prototype.hasAvatarPhoto = function() {
+  return jspb.Message.getField(this, 6) != null;
+};
+
+
+goog.object.extend(exports, proto);

+ 27 - 0
content-metadata-protobuf/compiled/proto/Playlist_pb.d.ts

@@ -0,0 +1,27 @@
+// package: 
+// file: proto/Playlist.proto
+
+import * as jspb from "google-protobuf";
+
+export class PlaylistMetadata extends jspb.Message {
+  hasTitle(): boolean;
+  clearTitle(): void;
+  getTitle(): string | undefined;
+  setTitle(value: string): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): PlaylistMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: PlaylistMetadata): PlaylistMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: PlaylistMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): PlaylistMetadata;
+  static deserializeBinaryFromReader(message: PlaylistMetadata, reader: jspb.BinaryReader): PlaylistMetadata;
+}
+
+export namespace PlaylistMetadata {
+  export type AsObject = {
+    title?: string,
+  }
+}
+

+ 187 - 0
content-metadata-protobuf/compiled/proto/Playlist_pb.js

@@ -0,0 +1,187 @@
+// source: proto/Playlist.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ *     field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf');
+var goog = jspb;
+var global = Function('return this')();
+
+goog.exportSymbol('proto.PlaylistMetadata', null, global);
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.PlaylistMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.PlaylistMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.PlaylistMetadata.displayName = 'proto.PlaylistMetadata';
+}
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.PlaylistMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.PlaylistMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.PlaylistMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.PlaylistMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    title: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.PlaylistMetadata}
+ */
+proto.PlaylistMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.PlaylistMetadata;
+  return proto.PlaylistMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.PlaylistMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.PlaylistMetadata}
+ */
+proto.PlaylistMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setTitle(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.PlaylistMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.PlaylistMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.PlaylistMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.PlaylistMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string title = 1;
+ * @return {string}
+ */
+proto.PlaylistMetadata.prototype.getTitle = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.PlaylistMetadata} returns this
+ */
+proto.PlaylistMetadata.prototype.setTitle = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PlaylistMetadata} returns this
+ */
+proto.PlaylistMetadata.prototype.clearTitle = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PlaylistMetadata.prototype.hasTitle = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+goog.object.extend(exports, proto);

+ 73 - 0
content-metadata-protobuf/compiled/proto/Series_pb.d.ts

@@ -0,0 +1,73 @@
+// package: 
+// file: proto/Series.proto
+
+import * as jspb from "google-protobuf";
+
+export class SeriesMetadata extends jspb.Message {
+  hasTitle(): boolean;
+  clearTitle(): void;
+  getTitle(): string | undefined;
+  setTitle(value: string): void;
+
+  hasDescription(): boolean;
+  clearDescription(): void;
+  getDescription(): string | undefined;
+  setDescription(value: string): void;
+
+  hasCoverPhoto(): boolean;
+  clearCoverPhoto(): void;
+  getCoverPhoto(): number | undefined;
+  setCoverPhoto(value: number): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): SeriesMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: SeriesMetadata): SeriesMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: SeriesMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): SeriesMetadata;
+  static deserializeBinaryFromReader(message: SeriesMetadata, reader: jspb.BinaryReader): SeriesMetadata;
+}
+
+export namespace SeriesMetadata {
+  export type AsObject = {
+    title?: string,
+    description?: string,
+    coverPhoto?: number,
+  }
+}
+
+export class SeasonMetadata extends jspb.Message {
+  hasTitle(): boolean;
+  clearTitle(): void;
+  getTitle(): string | undefined;
+  setTitle(value: string): void;
+
+  hasDescription(): boolean;
+  clearDescription(): void;
+  getDescription(): string | undefined;
+  setDescription(value: string): void;
+
+  hasCoverPhoto(): boolean;
+  clearCoverPhoto(): void;
+  getCoverPhoto(): number | undefined;
+  setCoverPhoto(value: number): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): SeasonMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: SeasonMetadata): SeasonMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: SeasonMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): SeasonMetadata;
+  static deserializeBinaryFromReader(message: SeasonMetadata, reader: jspb.BinaryReader): SeasonMetadata;
+}
+
+export namespace SeasonMetadata {
+  export type AsObject = {
+    title?: string,
+    description?: string,
+    coverPhoto?: number,
+  }
+}
+

+ 549 - 0
content-metadata-protobuf/compiled/proto/Series_pb.js

@@ -0,0 +1,549 @@
+// source: proto/Series.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ *     field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf');
+var goog = jspb;
+var global = Function('return this')();
+
+goog.exportSymbol('proto.SeasonMetadata', null, global);
+goog.exportSymbol('proto.SeriesMetadata', null, global);
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.SeriesMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.SeriesMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.SeriesMetadata.displayName = 'proto.SeriesMetadata';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.SeasonMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.SeasonMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.SeasonMetadata.displayName = 'proto.SeasonMetadata';
+}
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.SeriesMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.SeriesMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.SeriesMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.SeriesMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    title: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+    description: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+    coverPhoto: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.SeriesMetadata}
+ */
+proto.SeriesMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.SeriesMetadata;
+  return proto.SeriesMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.SeriesMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.SeriesMetadata}
+ */
+proto.SeriesMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setTitle(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setDescription(value);
+      break;
+    case 3:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setCoverPhoto(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.SeriesMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.SeriesMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.SeriesMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.SeriesMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 3));
+  if (f != null) {
+    writer.writeUint32(
+      3,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string title = 1;
+ * @return {string}
+ */
+proto.SeriesMetadata.prototype.getTitle = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.SeriesMetadata} returns this
+ */
+proto.SeriesMetadata.prototype.setTitle = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.SeriesMetadata} returns this
+ */
+proto.SeriesMetadata.prototype.clearTitle = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.SeriesMetadata.prototype.hasTitle = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string description = 2;
+ * @return {string}
+ */
+proto.SeriesMetadata.prototype.getDescription = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.SeriesMetadata} returns this
+ */
+proto.SeriesMetadata.prototype.setDescription = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.SeriesMetadata} returns this
+ */
+proto.SeriesMetadata.prototype.clearDescription = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.SeriesMetadata.prototype.hasDescription = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional uint32 cover_photo = 3;
+ * @return {number}
+ */
+proto.SeriesMetadata.prototype.getCoverPhoto = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.SeriesMetadata} returns this
+ */
+proto.SeriesMetadata.prototype.setCoverPhoto = function(value) {
+  return jspb.Message.setField(this, 3, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.SeriesMetadata} returns this
+ */
+proto.SeriesMetadata.prototype.clearCoverPhoto = function() {
+  return jspb.Message.setField(this, 3, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.SeriesMetadata.prototype.hasCoverPhoto = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.SeasonMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.SeasonMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.SeasonMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.SeasonMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    title: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+    description: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+    coverPhoto: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.SeasonMetadata}
+ */
+proto.SeasonMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.SeasonMetadata;
+  return proto.SeasonMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.SeasonMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.SeasonMetadata}
+ */
+proto.SeasonMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setTitle(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setDescription(value);
+      break;
+    case 3:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setCoverPhoto(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.SeasonMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.SeasonMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.SeasonMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.SeasonMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 3));
+  if (f != null) {
+    writer.writeUint32(
+      3,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string title = 1;
+ * @return {string}
+ */
+proto.SeasonMetadata.prototype.getTitle = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.SeasonMetadata} returns this
+ */
+proto.SeasonMetadata.prototype.setTitle = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.SeasonMetadata} returns this
+ */
+proto.SeasonMetadata.prototype.clearTitle = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.SeasonMetadata.prototype.hasTitle = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string description = 2;
+ * @return {string}
+ */
+proto.SeasonMetadata.prototype.getDescription = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.SeasonMetadata} returns this
+ */
+proto.SeasonMetadata.prototype.setDescription = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.SeasonMetadata} returns this
+ */
+proto.SeasonMetadata.prototype.clearDescription = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.SeasonMetadata.prototype.hasDescription = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional uint32 cover_photo = 3;
+ * @return {number}
+ */
+proto.SeasonMetadata.prototype.getCoverPhoto = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.SeasonMetadata} returns this
+ */
+proto.SeasonMetadata.prototype.setCoverPhoto = function(value) {
+  return jspb.Message.setField(this, 3, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.SeasonMetadata} returns this
+ */
+proto.SeasonMetadata.prototype.clearCoverPhoto = function() {
+  return jspb.Message.setField(this, 3, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.SeasonMetadata.prototype.hasCoverPhoto = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
+goog.object.extend(exports, proto);

+ 223 - 0
content-metadata-protobuf/compiled/proto/Video_pb.d.ts

@@ -0,0 +1,223 @@
+// package: 
+// file: proto/Video.proto
+
+import * as jspb from "google-protobuf";
+
+export class PublishedBeforeJoystream extends jspb.Message {
+  hasIsPublished(): boolean;
+  clearIsPublished(): void;
+  getIsPublished(): boolean | undefined;
+  setIsPublished(value: boolean): void;
+
+  hasDate(): boolean;
+  clearDate(): void;
+  getDate(): string | undefined;
+  setDate(value: string): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): PublishedBeforeJoystream.AsObject;
+  static toObject(includeInstance: boolean, msg: PublishedBeforeJoystream): PublishedBeforeJoystream.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: PublishedBeforeJoystream, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): PublishedBeforeJoystream;
+  static deserializeBinaryFromReader(message: PublishedBeforeJoystream, reader: jspb.BinaryReader): PublishedBeforeJoystream;
+}
+
+export namespace PublishedBeforeJoystream {
+  export type AsObject = {
+    isPublished?: boolean,
+    date?: string,
+  }
+}
+
+export class License extends jspb.Message {
+  hasCode(): boolean;
+  clearCode(): void;
+  getCode(): number | undefined;
+  setCode(value: number): void;
+
+  hasAttribution(): boolean;
+  clearAttribution(): void;
+  getAttribution(): string | undefined;
+  setAttribution(value: string): void;
+
+  hasCustomText(): boolean;
+  clearCustomText(): void;
+  getCustomText(): string | undefined;
+  setCustomText(value: string): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): License.AsObject;
+  static toObject(includeInstance: boolean, msg: License): License.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: License, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): License;
+  static deserializeBinaryFromReader(message: License, reader: jspb.BinaryReader): License;
+}
+
+export namespace License {
+  export type AsObject = {
+    code?: number,
+    attribution?: string,
+    customText?: string,
+  }
+}
+
+export class MediaType extends jspb.Message {
+  hasCodecName(): boolean;
+  clearCodecName(): void;
+  getCodecName(): string | undefined;
+  setCodecName(value: string): void;
+
+  hasContainer(): boolean;
+  clearContainer(): void;
+  getContainer(): string | undefined;
+  setContainer(value: string): void;
+
+  hasMimeMediaType(): boolean;
+  clearMimeMediaType(): void;
+  getMimeMediaType(): string | undefined;
+  setMimeMediaType(value: string): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): MediaType.AsObject;
+  static toObject(includeInstance: boolean, msg: MediaType): MediaType.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: MediaType, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): MediaType;
+  static deserializeBinaryFromReader(message: MediaType, reader: jspb.BinaryReader): MediaType;
+}
+
+export namespace MediaType {
+  export type AsObject = {
+    codecName?: string,
+    container?: string,
+    mimeMediaType?: string,
+  }
+}
+
+export class VideoMetadata extends jspb.Message {
+  hasTitle(): boolean;
+  clearTitle(): void;
+  getTitle(): string | undefined;
+  setTitle(value: string): void;
+
+  hasDescription(): boolean;
+  clearDescription(): void;
+  getDescription(): string | undefined;
+  setDescription(value: string): void;
+
+  hasVideo(): boolean;
+  clearVideo(): void;
+  getVideo(): number | undefined;
+  setVideo(value: number): void;
+
+  hasThumbnailPhoto(): boolean;
+  clearThumbnailPhoto(): void;
+  getThumbnailPhoto(): number | undefined;
+  setThumbnailPhoto(value: number): void;
+
+  hasDuration(): boolean;
+  clearDuration(): void;
+  getDuration(): number | undefined;
+  setDuration(value: number): void;
+
+  hasMediaPixelHeight(): boolean;
+  clearMediaPixelHeight(): void;
+  getMediaPixelHeight(): number | undefined;
+  setMediaPixelHeight(value: number): void;
+
+  hasMediaPixelWidth(): boolean;
+  clearMediaPixelWidth(): void;
+  getMediaPixelWidth(): number | undefined;
+  setMediaPixelWidth(value: number): void;
+
+  hasMediaType(): boolean;
+  clearMediaType(): void;
+  getMediaType(): MediaType | undefined;
+  setMediaType(value?: MediaType): void;
+
+  hasLanguage(): boolean;
+  clearLanguage(): void;
+  getLanguage(): string | undefined;
+  setLanguage(value: string): void;
+
+  hasLicense(): boolean;
+  clearLicense(): void;
+  getLicense(): License | undefined;
+  setLicense(value?: License): void;
+
+  hasPublishedBeforeJoystream(): boolean;
+  clearPublishedBeforeJoystream(): void;
+  getPublishedBeforeJoystream(): PublishedBeforeJoystream | undefined;
+  setPublishedBeforeJoystream(value?: PublishedBeforeJoystream): void;
+
+  hasHasMarketing(): boolean;
+  clearHasMarketing(): void;
+  getHasMarketing(): boolean | undefined;
+  setHasMarketing(value: boolean): void;
+
+  hasIsPublic(): boolean;
+  clearIsPublic(): void;
+  getIsPublic(): boolean | undefined;
+  setIsPublic(value: boolean): void;
+
+  hasIsExplicit(): boolean;
+  clearIsExplicit(): void;
+  getIsExplicit(): boolean | undefined;
+  setIsExplicit(value: boolean): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): VideoMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: VideoMetadata): VideoMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: VideoMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): VideoMetadata;
+  static deserializeBinaryFromReader(message: VideoMetadata, reader: jspb.BinaryReader): VideoMetadata;
+}
+
+export namespace VideoMetadata {
+  export type AsObject = {
+    title?: string,
+    description?: string,
+    video?: number,
+    thumbnailPhoto?: number,
+    duration?: number,
+    mediaPixelHeight?: number,
+    mediaPixelWidth?: number,
+    mediaType?: MediaType.AsObject,
+    language?: string,
+    license?: License.AsObject,
+    publishedBeforeJoystream?: PublishedBeforeJoystream.AsObject,
+    hasMarketing?: boolean,
+    isPublic?: boolean,
+    isExplicit?: boolean,
+  }
+}
+
+export class VideoCategoryMetadata extends jspb.Message {
+  hasName(): boolean;
+  clearName(): void;
+  getName(): string | undefined;
+  setName(value: string): void;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): VideoCategoryMetadata.AsObject;
+  static toObject(includeInstance: boolean, msg: VideoCategoryMetadata): VideoCategoryMetadata.AsObject;
+  static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
+  static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
+  static serializeBinaryToWriter(message: VideoCategoryMetadata, writer: jspb.BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): VideoCategoryMetadata;
+  static deserializeBinaryFromReader(message: VideoCategoryMetadata, reader: jspb.BinaryReader): VideoCategoryMetadata;
+}
+
+export namespace VideoCategoryMetadata {
+  export type AsObject = {
+    name?: string,
+  }
+}
+

+ 1740 - 0
content-metadata-protobuf/compiled/proto/Video_pb.js

@@ -0,0 +1,1740 @@
+// source: proto/Video.proto
+/**
+ * @fileoverview
+ * @enhanceable
+ * @suppress {messageConventions} JS Compiler reports an error if a variable or
+ *     field starts with 'MSG_' and isn't a translatable message.
+ * @public
+ */
+// GENERATED CODE -- DO NOT EDIT!
+/* eslint-disable */
+// @ts-nocheck
+
+var jspb = require('google-protobuf');
+var goog = jspb;
+var global = Function('return this')();
+
+goog.exportSymbol('proto.License', null, global);
+goog.exportSymbol('proto.MediaType', null, global);
+goog.exportSymbol('proto.PublishedBeforeJoystream', null, global);
+goog.exportSymbol('proto.VideoCategoryMetadata', null, global);
+goog.exportSymbol('proto.VideoMetadata', null, global);
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.PublishedBeforeJoystream = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.PublishedBeforeJoystream, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.PublishedBeforeJoystream.displayName = 'proto.PublishedBeforeJoystream';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.License = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.License, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.License.displayName = 'proto.License';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.MediaType = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.MediaType, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.MediaType.displayName = 'proto.MediaType';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.VideoMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.VideoMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.VideoMetadata.displayName = 'proto.VideoMetadata';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.VideoCategoryMetadata = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.VideoCategoryMetadata, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.VideoCategoryMetadata.displayName = 'proto.VideoCategoryMetadata';
+}
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.PublishedBeforeJoystream.prototype.toObject = function(opt_includeInstance) {
+  return proto.PublishedBeforeJoystream.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.PublishedBeforeJoystream} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.PublishedBeforeJoystream.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    isPublished: (f = jspb.Message.getBooleanField(msg, 1)) == null ? undefined : f,
+    date: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.PublishedBeforeJoystream}
+ */
+proto.PublishedBeforeJoystream.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.PublishedBeforeJoystream;
+  return proto.PublishedBeforeJoystream.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.PublishedBeforeJoystream} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.PublishedBeforeJoystream}
+ */
+proto.PublishedBeforeJoystream.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {boolean} */ (reader.readBool());
+      msg.setIsPublished(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setDate(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.PublishedBeforeJoystream.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.PublishedBeforeJoystream.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.PublishedBeforeJoystream} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.PublishedBeforeJoystream.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeBool(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional bool is_published = 1;
+ * @return {boolean}
+ */
+proto.PublishedBeforeJoystream.prototype.getIsPublished = function() {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.setIsPublished = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.clearIsPublished = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PublishedBeforeJoystream.prototype.hasIsPublished = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string date = 2;
+ * @return {string}
+ */
+proto.PublishedBeforeJoystream.prototype.getDate = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.setDate = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.PublishedBeforeJoystream} returns this
+ */
+proto.PublishedBeforeJoystream.prototype.clearDate = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.PublishedBeforeJoystream.prototype.hasDate = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.License.prototype.toObject = function(opt_includeInstance) {
+  return proto.License.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.License} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.License.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    code: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+    attribution: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+    customText: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.License}
+ */
+proto.License.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.License;
+  return proto.License.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.License} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.License}
+ */
+proto.License.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setCode(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setAttribution(value);
+      break;
+    case 3:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setCustomText(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.License.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.License.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.License} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.License.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {number} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeUint32(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 3));
+  if (f != null) {
+    writer.writeString(
+      3,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional uint32 code = 1;
+ * @return {number}
+ */
+proto.License.prototype.getCode = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.setCode = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.clearCode = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.License.prototype.hasCode = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string attribution = 2;
+ * @return {string}
+ */
+proto.License.prototype.getAttribution = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.setAttribution = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.clearAttribution = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.License.prototype.hasAttribution = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional string custom_text = 3;
+ * @return {string}
+ */
+proto.License.prototype.getCustomText = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.setCustomText = function(value) {
+  return jspb.Message.setField(this, 3, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.License} returns this
+ */
+proto.License.prototype.clearCustomText = function() {
+  return jspb.Message.setField(this, 3, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.License.prototype.hasCustomText = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.MediaType.prototype.toObject = function(opt_includeInstance) {
+  return proto.MediaType.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.MediaType} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.MediaType.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    codecName: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+    container: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+    mimeMediaType: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.MediaType}
+ */
+proto.MediaType.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.MediaType;
+  return proto.MediaType.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.MediaType} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.MediaType}
+ */
+proto.MediaType.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setCodecName(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setContainer(value);
+      break;
+    case 3:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setMimeMediaType(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.MediaType.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.MediaType.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.MediaType} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.MediaType.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 3));
+  if (f != null) {
+    writer.writeString(
+      3,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string codec_name = 1;
+ * @return {string}
+ */
+proto.MediaType.prototype.getCodecName = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.setCodecName = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.clearCodecName = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.MediaType.prototype.hasCodecName = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string container = 2;
+ * @return {string}
+ */
+proto.MediaType.prototype.getContainer = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.setContainer = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.clearContainer = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.MediaType.prototype.hasContainer = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional string mime_media_type = 3;
+ * @return {string}
+ */
+proto.MediaType.prototype.getMimeMediaType = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.setMimeMediaType = function(value) {
+  return jspb.Message.setField(this, 3, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.MediaType} returns this
+ */
+proto.MediaType.prototype.clearMimeMediaType = function() {
+  return jspb.Message.setField(this, 3, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.MediaType.prototype.hasMimeMediaType = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.VideoMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.VideoMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.VideoMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.VideoMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    title: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f,
+    description: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f,
+    video: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f,
+    thumbnailPhoto: (f = jspb.Message.getField(msg, 4)) == null ? undefined : f,
+    duration: (f = jspb.Message.getField(msg, 5)) == null ? undefined : f,
+    mediaPixelHeight: (f = jspb.Message.getField(msg, 6)) == null ? undefined : f,
+    mediaPixelWidth: (f = jspb.Message.getField(msg, 7)) == null ? undefined : f,
+    mediaType: (f = msg.getMediaType()) && proto.MediaType.toObject(includeInstance, f),
+    language: (f = jspb.Message.getField(msg, 9)) == null ? undefined : f,
+    license: (f = msg.getLicense()) && proto.License.toObject(includeInstance, f),
+    publishedBeforeJoystream: (f = msg.getPublishedBeforeJoystream()) && proto.PublishedBeforeJoystream.toObject(includeInstance, f),
+    hasMarketing: (f = jspb.Message.getBooleanField(msg, 12)) == null ? undefined : f,
+    isPublic: (f = jspb.Message.getBooleanField(msg, 13)) == null ? undefined : f,
+    isExplicit: (f = jspb.Message.getBooleanField(msg, 14)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.VideoMetadata}
+ */
+proto.VideoMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.VideoMetadata;
+  return proto.VideoMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.VideoMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.VideoMetadata}
+ */
+proto.VideoMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setTitle(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setDescription(value);
+      break;
+    case 3:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setVideo(value);
+      break;
+    case 4:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setThumbnailPhoto(value);
+      break;
+    case 5:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setDuration(value);
+      break;
+    case 6:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setMediaPixelHeight(value);
+      break;
+    case 7:
+      var value = /** @type {number} */ (reader.readUint32());
+      msg.setMediaPixelWidth(value);
+      break;
+    case 8:
+      var value = new proto.MediaType;
+      reader.readMessage(value,proto.MediaType.deserializeBinaryFromReader);
+      msg.setMediaType(value);
+      break;
+    case 9:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setLanguage(value);
+      break;
+    case 10:
+      var value = new proto.License;
+      reader.readMessage(value,proto.License.deserializeBinaryFromReader);
+      msg.setLicense(value);
+      break;
+    case 11:
+      var value = new proto.PublishedBeforeJoystream;
+      reader.readMessage(value,proto.PublishedBeforeJoystream.deserializeBinaryFromReader);
+      msg.setPublishedBeforeJoystream(value);
+      break;
+    case 12:
+      var value = /** @type {boolean} */ (reader.readBool());
+      msg.setHasMarketing(value);
+      break;
+    case 13:
+      var value = /** @type {boolean} */ (reader.readBool());
+      msg.setIsPublic(value);
+      break;
+    case 14:
+      var value = /** @type {boolean} */ (reader.readBool());
+      msg.setIsExplicit(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.VideoMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.VideoMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.VideoMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.VideoMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 2));
+  if (f != null) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 3));
+  if (f != null) {
+    writer.writeUint32(
+      3,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 4));
+  if (f != null) {
+    writer.writeUint32(
+      4,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 5));
+  if (f != null) {
+    writer.writeUint32(
+      5,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 6));
+  if (f != null) {
+    writer.writeUint32(
+      6,
+      f
+    );
+  }
+  f = /** @type {number} */ (jspb.Message.getField(message, 7));
+  if (f != null) {
+    writer.writeUint32(
+      7,
+      f
+    );
+  }
+  f = message.getMediaType();
+  if (f != null) {
+    writer.writeMessage(
+      8,
+      f,
+      proto.MediaType.serializeBinaryToWriter
+    );
+  }
+  f = /** @type {string} */ (jspb.Message.getField(message, 9));
+  if (f != null) {
+    writer.writeString(
+      9,
+      f
+    );
+  }
+  f = message.getLicense();
+  if (f != null) {
+    writer.writeMessage(
+      10,
+      f,
+      proto.License.serializeBinaryToWriter
+    );
+  }
+  f = message.getPublishedBeforeJoystream();
+  if (f != null) {
+    writer.writeMessage(
+      11,
+      f,
+      proto.PublishedBeforeJoystream.serializeBinaryToWriter
+    );
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 12));
+  if (f != null) {
+    writer.writeBool(
+      12,
+      f
+    );
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 13));
+  if (f != null) {
+    writer.writeBool(
+      13,
+      f
+    );
+  }
+  f = /** @type {boolean} */ (jspb.Message.getField(message, 14));
+  if (f != null) {
+    writer.writeBool(
+      14,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string title = 1;
+ * @return {string}
+ */
+proto.VideoMetadata.prototype.getTitle = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setTitle = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearTitle = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasTitle = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional string description = 2;
+ * @return {string}
+ */
+proto.VideoMetadata.prototype.getDescription = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setDescription = function(value) {
+  return jspb.Message.setField(this, 2, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearDescription = function() {
+  return jspb.Message.setField(this, 2, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasDescription = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+/**
+ * optional uint32 video = 3;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getVideo = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setVideo = function(value) {
+  return jspb.Message.setField(this, 3, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearVideo = function() {
+  return jspb.Message.setField(this, 3, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasVideo = function() {
+  return jspb.Message.getField(this, 3) != null;
+};
+
+
+/**
+ * optional uint32 thumbnail_photo = 4;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getThumbnailPhoto = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setThumbnailPhoto = function(value) {
+  return jspb.Message.setField(this, 4, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearThumbnailPhoto = function() {
+  return jspb.Message.setField(this, 4, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasThumbnailPhoto = function() {
+  return jspb.Message.getField(this, 4) != null;
+};
+
+
+/**
+ * optional uint32 duration = 5;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getDuration = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setDuration = function(value) {
+  return jspb.Message.setField(this, 5, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearDuration = function() {
+  return jspb.Message.setField(this, 5, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasDuration = function() {
+  return jspb.Message.getField(this, 5) != null;
+};
+
+
+/**
+ * optional uint32 media_pixel_height = 6;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getMediaPixelHeight = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 6, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setMediaPixelHeight = function(value) {
+  return jspb.Message.setField(this, 6, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearMediaPixelHeight = function() {
+  return jspb.Message.setField(this, 6, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasMediaPixelHeight = function() {
+  return jspb.Message.getField(this, 6) != null;
+};
+
+
+/**
+ * optional uint32 media_pixel_width = 7;
+ * @return {number}
+ */
+proto.VideoMetadata.prototype.getMediaPixelWidth = function() {
+  return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 7, 0));
+};
+
+
+/**
+ * @param {number} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setMediaPixelWidth = function(value) {
+  return jspb.Message.setField(this, 7, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearMediaPixelWidth = function() {
+  return jspb.Message.setField(this, 7, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasMediaPixelWidth = function() {
+  return jspb.Message.getField(this, 7) != null;
+};
+
+
+/**
+ * optional MediaType media_type = 8;
+ * @return {?proto.MediaType}
+ */
+proto.VideoMetadata.prototype.getMediaType = function() {
+  return /** @type{?proto.MediaType} */ (
+    jspb.Message.getWrapperField(this, proto.MediaType, 8));
+};
+
+
+/**
+ * @param {?proto.MediaType|undefined} value
+ * @return {!proto.VideoMetadata} returns this
+*/
+proto.VideoMetadata.prototype.setMediaType = function(value) {
+  return jspb.Message.setWrapperField(this, 8, value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearMediaType = function() {
+  return this.setMediaType(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasMediaType = function() {
+  return jspb.Message.getField(this, 8) != null;
+};
+
+
+/**
+ * optional string language = 9;
+ * @return {string}
+ */
+proto.VideoMetadata.prototype.getLanguage = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 9, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setLanguage = function(value) {
+  return jspb.Message.setField(this, 9, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearLanguage = function() {
+  return jspb.Message.setField(this, 9, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasLanguage = function() {
+  return jspb.Message.getField(this, 9) != null;
+};
+
+
+/**
+ * optional License license = 10;
+ * @return {?proto.License}
+ */
+proto.VideoMetadata.prototype.getLicense = function() {
+  return /** @type{?proto.License} */ (
+    jspb.Message.getWrapperField(this, proto.License, 10));
+};
+
+
+/**
+ * @param {?proto.License|undefined} value
+ * @return {!proto.VideoMetadata} returns this
+*/
+proto.VideoMetadata.prototype.setLicense = function(value) {
+  return jspb.Message.setWrapperField(this, 10, value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearLicense = function() {
+  return this.setLicense(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasLicense = function() {
+  return jspb.Message.getField(this, 10) != null;
+};
+
+
+/**
+ * optional PublishedBeforeJoystream published_before_joystream = 11;
+ * @return {?proto.PublishedBeforeJoystream}
+ */
+proto.VideoMetadata.prototype.getPublishedBeforeJoystream = function() {
+  return /** @type{?proto.PublishedBeforeJoystream} */ (
+    jspb.Message.getWrapperField(this, proto.PublishedBeforeJoystream, 11));
+};
+
+
+/**
+ * @param {?proto.PublishedBeforeJoystream|undefined} value
+ * @return {!proto.VideoMetadata} returns this
+*/
+proto.VideoMetadata.prototype.setPublishedBeforeJoystream = function(value) {
+  return jspb.Message.setWrapperField(this, 11, value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearPublishedBeforeJoystream = function() {
+  return this.setPublishedBeforeJoystream(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasPublishedBeforeJoystream = function() {
+  return jspb.Message.getField(this, 11) != null;
+};
+
+
+/**
+ * optional bool has_marketing = 12;
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.getHasMarketing = function() {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 12, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setHasMarketing = function(value) {
+  return jspb.Message.setField(this, 12, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearHasMarketing = function() {
+  return jspb.Message.setField(this, 12, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasHasMarketing = function() {
+  return jspb.Message.getField(this, 12) != null;
+};
+
+
+/**
+ * optional bool is_public = 13;
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.getIsPublic = function() {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 13, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setIsPublic = function(value) {
+  return jspb.Message.setField(this, 13, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearIsPublic = function() {
+  return jspb.Message.setField(this, 13, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasIsPublic = function() {
+  return jspb.Message.getField(this, 13) != null;
+};
+
+
+/**
+ * optional bool is_explicit = 14;
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.getIsExplicit = function() {
+  return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 14, false));
+};
+
+
+/**
+ * @param {boolean} value
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.setIsExplicit = function(value) {
+  return jspb.Message.setField(this, 14, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoMetadata} returns this
+ */
+proto.VideoMetadata.prototype.clearIsExplicit = function() {
+  return jspb.Message.setField(this, 14, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoMetadata.prototype.hasIsExplicit = function() {
+  return jspb.Message.getField(this, 14) != null;
+};
+
+
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.VideoCategoryMetadata.prototype.toObject = function(opt_includeInstance) {
+  return proto.VideoCategoryMetadata.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.VideoCategoryMetadata} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.VideoCategoryMetadata.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    name: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.VideoCategoryMetadata}
+ */
+proto.VideoCategoryMetadata.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.VideoCategoryMetadata;
+  return proto.VideoCategoryMetadata.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.VideoCategoryMetadata} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.VideoCategoryMetadata}
+ */
+proto.VideoCategoryMetadata.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setName(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.VideoCategoryMetadata.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.VideoCategoryMetadata.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.VideoCategoryMetadata} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.VideoCategoryMetadata.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string name = 1;
+ * @return {string}
+ */
+proto.VideoCategoryMetadata.prototype.getName = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.VideoCategoryMetadata} returns this
+ */
+proto.VideoCategoryMetadata.prototype.setName = function(value) {
+  return jspb.Message.setField(this, 1, value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.VideoCategoryMetadata} returns this
+ */
+proto.VideoCategoryMetadata.prototype.clearName = function() {
+  return jspb.Message.setField(this, 1, undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.VideoCategoryMetadata.prototype.hasName = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+goog.object.extend(exports, proto);

+ 41 - 0
content-metadata-protobuf/doc-appendix.md

@@ -0,0 +1,41 @@
+<!-- 
+    This extra documentation will be appended to the generated docs.
+-->
+
+## Referencing Assets
+<a name=".Assets"></a>
+
+Applications that process messages that contain a `uint32` field that references an asset such as a cover photo or video, should interpret this value as a zero based index into an array/vector that is received external (out of band) to the protobuf message.
+
+Example in context of query-node processing the runtime event `VideoCreated`
+
+```rust
+// Runtime event associated with creating a Video
+VideoCreated(video_id: VideoId, video: Video, assets: Vec<NewAsset>, params: VideoCreationParameters)
+
+struct VideoCreationParameters {
+  in_category: VideoCategoryId,
+  // binary serialized VideoMetadata protobuf message
+  meta: Vec<u8>,
+}
+
+// suppose assets is a vector of two elements. This is the "out of band" array being referenced by the VideoMetadata message
+assets = [
+    NewAsset::Uri("https://mydomain.net/thumbnail.png"),
+    NewAsset::Upload({
+       content_id,
+       ipfs_hash,
+       size,
+       ...
+    }),
+];
+
+meta = VideoMetadata {
+    ...
+    // refers to second element: assets[1] which is being uploaded to the storage system
+    video: 1,
+    // refers to the first element assets[0] which is being referneced by a url string.
+    thumbnail_photo: 0,
+    ...
+};
+```

+ 368 - 0
content-metadata-protobuf/doc/index.md

@@ -0,0 +1,368 @@
+# Protocol Documentation
+<a name="top"></a>
+
+## Table of Contents
+
+- [proto/Channel.proto](#proto/Channel.proto)
+    - [ChannelCategoryMetadata](#.ChannelCategoryMetadata)
+    - [ChannelMetadata](#.ChannelMetadata)
+  
+- [proto/Person.proto](#proto/Person.proto)
+    - [PersonMetadata](#.PersonMetadata)
+  
+- [proto/Playlist.proto](#proto/Playlist.proto)
+    - [PlaylistMetadata](#.PlaylistMetadata)
+  
+- [proto/Series.proto](#proto/Series.proto)
+    - [SeasonMetadata](#.SeasonMetadata)
+    - [SeriesMetadata](#.SeriesMetadata)
+  
+- [proto/Video.proto](#proto/Video.proto)
+    - [License](#.License)
+    - [MediaType](#.MediaType)
+    - [PublishedBeforeJoystream](#.PublishedBeforeJoystream)
+    - [VideoCategoryMetadata](#.VideoCategoryMetadata)
+    - [VideoMetadata](#.VideoMetadata)
+  
+- [Scalar Value Types](#scalar-value-types)
+
+
+
+<a name="proto/Channel.proto"></a>
+<p align="right"><a href="#top">Top</a></p>
+
+## proto/Channel.proto
+
+
+
+<a name=".ChannelCategoryMetadata"></a>
+
+### ChannelCategoryMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| name | [string](#string) | optional | Category Name |
+
+
+
+
+
+
+<a name=".ChannelMetadata"></a>
+
+### ChannelMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| title | [string](#string) | optional | Channel Title |
+| description | [string](#string) | optional | Channel Description |
+| is_public | [bool](#bool) | optional | Wether to display channel to the public |
+| language | [string](#string) | optional | ISO_639-1 Language [Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) |
+| cover_photo | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+| avatar_photo | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+
+
+
+
+
+ 
+
+ 
+
+ 
+
+ 
+
+
+
+<a name="proto/Person.proto"></a>
+<p align="right"><a href="#top">Top</a></p>
+
+## proto/Person.proto
+
+
+
+<a name=".PersonMetadata"></a>
+
+### PersonMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| first_name | [string](#string) | optional |  |
+| middle_name | [string](#string) | optional |  |
+| last_name | [string](#string) | optional |  |
+| about | [string](#string) | optional |  |
+| cover_photo | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+| avatar_photo | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+
+
+
+
+
+ 
+
+ 
+
+ 
+
+ 
+
+
+
+<a name="proto/Playlist.proto"></a>
+<p align="right"><a href="#top">Top</a></p>
+
+## proto/Playlist.proto
+
+
+
+<a name=".PlaylistMetadata"></a>
+
+### PlaylistMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| title | [string](#string) | optional |  |
+
+
+
+
+
+ 
+
+ 
+
+ 
+
+ 
+
+
+
+<a name="proto/Series.proto"></a>
+<p align="right"><a href="#top">Top</a></p>
+
+## proto/Series.proto
+
+
+
+<a name=".SeasonMetadata"></a>
+
+### SeasonMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| title | [string](#string) | optional |  |
+| description | [string](#string) | optional |  |
+| cover_photo | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+
+
+
+
+
+
+<a name=".SeriesMetadata"></a>
+
+### SeriesMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| title | [string](#string) | optional |  |
+| description | [string](#string) | optional |  |
+| cover_photo | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+
+
+
+
+
+ 
+
+ 
+
+ 
+
+ 
+
+
+
+<a name="proto/Video.proto"></a>
+<p align="right"><a href="#top">Top</a></p>
+
+## proto/Video.proto
+
+
+
+<a name=".License"></a>
+
+### License
+License types defined by Joystream
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| code | [uint32](#uint32) | optional | License code defined by Joystream. [reference](../src/KnownLicenses.json) |
+| attribution | [string](#string) | optional | Text for licenses that require an attribution |
+| custom_text | [string](#string) | optional | Text for custom license type |
+
+
+
+
+
+
+<a name=".MediaType"></a>
+
+### MediaType
+Codec, Container, MIME media-type information
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| codec_name | [string](#string) | optional | Codec corresponding to `name` field from [FFmpeg](https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/codec_desc.c) |
+| container | [string](#string) | optional | Video container format, eg. &#39;MP4&#39;, &#39;WebM&#39;, &#39;Ogg&#39; [ref](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs) |
+| mime_media_type | [string](#string) | optional | MIME Media Type, eg. &#39;video/mp4&#39; [ref](https://www.iana.org/assignments/media-types/media-types.xhtml#video) |
+
+
+
+
+
+
+<a name=".PublishedBeforeJoystream"></a>
+
+### PublishedBeforeJoystream
+Publication status before joystream
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| is_published | [bool](#bool) | optional | Was video published before joystream platform |
+| date | [string](#string) | optional | Date of publication: &#39;YYYY-MM-DD&#39; [ISO-8601](https://www.iso.org/iso-8601-date-and-time-format.html) |
+
+
+
+
+
+
+<a name=".VideoCategoryMetadata"></a>
+
+### VideoCategoryMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| name | [string](#string) | optional | Category name |
+
+
+
+
+
+
+<a name=".VideoMetadata"></a>
+
+### VideoMetadata
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| title | [string](#string) | optional | Video Title |
+| description | [string](#string) | optional | Video Description |
+| video | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+| thumbnail_photo | [uint32](#uint32) | optional | index into external [assets array](#.Assets) |
+| duration | [uint32](#uint32) | optional | Lengths of video in seconds |
+| media_pixel_height | [uint32](#uint32) | optional | Resolution of the video (Height) |
+| media_pixel_width | [uint32](#uint32) | optional | Resolution of the video (Width) |
+| media_type | [MediaType](#MediaType) | optional | Encoding and Container format used |
+| language | [string](#string) | optional | ISO_639-1 Language [Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) |
+| license | [License](#License) | optional | License type for the media |
+| published_before_joystream | [PublishedBeforeJoystream](#PublishedBeforeJoystream) | optional | Date of publication |
+| has_marketing | [bool](#bool) | optional | Does video have marketing or advertising in the stream |
+| is_public | [bool](#bool) | optional | Should video be publicy visible yet |
+| is_explicit | [bool](#bool) | optional | Does Video have explicit language or scenes |
+
+
+
+
+
+ 
+
+ 
+
+ 
+
+ 
+
+
+
+## Scalar Value Types
+
+| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
+| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- |
+| <a name="double" /> double |  | double | double | float | float64 | double | float | Float |
+| <a name="float" /> float |  | float | float | float | float32 | float | float | Float |
+| <a name="int32" /> int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
+| <a name="int64" /> int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
+| <a name="uint32" /> uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
+| <a name="uint64" /> uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
+| <a name="sint32" /> sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
+| <a name="sint64" /> sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
+| <a name="fixed32" /> fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
+| <a name="fixed64" /> fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
+| <a name="sfixed32" /> sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
+| <a name="sfixed64" /> sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
+| <a name="bool" /> bool |  | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
+| <a name="string" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
+| <a name="bytes" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |
+
+<!-- 
+    This extra documentation will be appended to the generated docs.
+-->
+
+## Referencing Assets
+<a name=".Assets"></a>
+
+Applications that process messages that contain a `uint32` field that references an asset such as a cover photo or video, should interpret this value as a zero based index into an array/vector that is received external (out of band) to the protobuf message.
+
+Example in context of query-node processing the runtime event `VideoCreated`
+
+```rust
+// Runtime event associated with creating a Video
+VideoCreated(video_id: VideoId, video: Video, assets: Vec<NewAsset>, params: VideoCreationParameters)
+
+struct VideoCreationParameters {
+  in_category: VideoCategoryId,
+  // binary serialized VideoMetadata protobuf message
+  meta: Vec<u8>,
+}
+
+// suppose assets is a vector of two elements. This is the "out of band" array being referenced by the VideoMetadata message
+assets = [
+    NewAsset::Uri("https://mydomain.net/thumbnail.png"),
+    NewAsset::Upload({
+       content_id,
+       ipfs_hash,
+       size,
+       ...
+    }),
+];
+
+meta = VideoMetadata {
+    ...
+    // refers to second element: assets[1] which is being uploaded to the storage system
+    video: 1,
+    // refers to the first element assets[0] which is being referneced by a url string.
+    thumbnail_photo: 0,
+    ...
+};
+```

+ 13 - 0
content-metadata-protobuf/generate-md-doc.sh

@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+# Directory to write generated documentation to
+OUT_DIR_DOC="./doc"
+mkdir -p ${OUT_DIR_DOC}
+
+# Gernerate Markdown docs
+protoc \
+    --doc_out="${OUT_DIR_DOC}" --doc_opt=markdown,index.md \
+    proto/*.proto
+
+# Append some custom docs to generated protocol docs
+cat doc-appendix.md >> ${OUT_DIR_DOC}/index.md

+ 41 - 0
content-metadata-protobuf/package.json

@@ -0,0 +1,41 @@
+{
+  "name": "@joystream/content-metadata-protobuf",
+  "version": "1.0.0",
+  "description": "Joystream Content Metadata Protobuf Library ",
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "repository": "https://github.com/joystream/joystream",
+  "author": "Joystream Contributors",
+  "license": "MIT",
+  "private": false,
+  "scripts": {
+    "build": "./compile.sh && tsc",
+    "compile": "./compile.sh",
+    "generate-doc": "./generate-md-doc.sh",
+    "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register 'test/**/*.ts'",
+    "lint": "eslint ./src --ext .ts",
+    "checks": "tsc --noEmit --pretty && prettier ./ --check && yarn lint",
+    "format": "prettier ./ --write"
+  },
+  "files": [
+    "lib/**/*",
+    "doc/**",
+    "proto/**",
+    "compiled/**/*",
+    "README.md"
+  ],
+  "dependencies": {
+    "google-protobuf": "^3.14.0"
+  },
+  "devDependencies": {
+    "@types/chai": "^4.2.11",
+    "@types/mocha": "^8.2.0",
+    "chai": "^4.2.0",
+    "eslint": "^7.6.0",
+    "mocha": "^8.2.1",
+    "prettier": "2.0.2",
+    "ts-node": "^8.8.1",
+    "ts-protoc-gen": "^0.14.0",
+    "typescript": "^4.1.3"
+  }
+}

+ 25 - 0
content-metadata-protobuf/proto/Channel.proto

@@ -0,0 +1,25 @@
+syntax = "proto2";
+
+message ChannelMetadata {
+    // Channel Title
+    optional string title = 1;
+
+    // Channel Description
+    optional string description = 2;
+
+    // Wether to display channel to the public
+    optional bool is_public = 3;
+
+    // ISO_639-1 Language [Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
+    optional string language = 4;
+
+    // index into external [assets array](#.Assets)
+    optional uint32 cover_photo = 5;
+    // index into external [assets array](#.Assets)
+    optional uint32 avatar_photo  = 6;
+}
+
+message ChannelCategoryMetadata {
+    // Category Name
+    optional string name = 1;
+}

+ 13 - 0
content-metadata-protobuf/proto/Person.proto

@@ -0,0 +1,13 @@
+syntax = "proto2";
+
+message PersonMetadata {
+    optional string first_name = 1;
+    optional string middle_name = 2;
+    optional string last_name = 3;
+    optional string about = 4;
+    
+    // index into external [assets array](#.Assets)
+    optional uint32 cover_photo = 5;
+    // index into external [assets array](#.Assets)
+    optional uint32 avatar_photo = 6;
+}

+ 5 - 0
content-metadata-protobuf/proto/Playlist.proto

@@ -0,0 +1,5 @@
+syntax = "proto2";
+
+message PlaylistMetadata {
+    optional string title = 1;
+}

+ 15 - 0
content-metadata-protobuf/proto/Series.proto

@@ -0,0 +1,15 @@
+syntax = "proto2";
+
+message SeriesMetadata {
+    optional string title = 1;
+    optional string description = 2;
+    // index into external [assets array](#.Assets)
+    optional uint32 cover_photo = 3;
+}
+
+message SeasonMetadata {
+    optional string title = 1;
+    optional string description = 2; 
+    // index into external [assets array](#.Assets)
+    optional uint32 cover_photo = 3;
+}

+ 82 - 0
content-metadata-protobuf/proto/Video.proto

@@ -0,0 +1,82 @@
+syntax = "proto2";
+
+// Publication status before joystream
+message PublishedBeforeJoystream {
+    // Was video published before joystream platform
+    optional bool is_published = 1;
+    // Date of publication: 'YYYY-MM-DD' [ISO-8601](https://www.iso.org/iso-8601-date-and-time-format.html)
+    optional string date = 2;
+}
+
+// License types defined by Joystream
+message License {
+    // License code defined by Joystream. [reference](../src/KnownLicenses.json)
+    optional uint32 code = 1;
+    // Text for licenses that require an attribution
+    optional string attribution = 2;
+    // Text for custom license type
+    optional string custom_text = 3;
+}
+
+// Codec, Container, MIME media-type information
+message MediaType {
+    // Codec corresponding to `name` field from [FFmpeg](https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/codec_desc.c)
+    optional string codec_name = 1;
+
+    // Video container format, eg. 'MP4', 'WebM', 'Ogg' [ref](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs)
+    optional string container = 2;
+
+    // MIME Media Type, eg. 'video/mp4' [ref](https://www.iana.org/assignments/media-types/media-types.xhtml#video)
+    optional string mime_media_type = 3;
+}
+
+message VideoMetadata {
+    // Video Title
+    optional string title = 1;
+
+    // Video Description
+    optional string description = 2;
+
+    // Assets
+    
+    // index into external [assets array](#.Assets)
+    optional uint32 video = 3;
+
+    // index into external [assets array](#.Assets)
+    optional uint32 thumbnail_photo = 4;
+
+    // Lengths of video in seconds
+    optional uint32 duration = 5;
+
+    // Resolution of the video (Height)
+    optional uint32 media_pixel_height = 6;
+    
+    // Resolution of the video (Width)
+    optional uint32 media_pixel_width = 7;
+
+    // Encoding and Container format used
+    optional MediaType media_type = 8;
+
+    // ISO_639-1 Language [Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
+    optional string language = 9;
+
+    // License type for the media
+    optional License license = 10;
+
+    // Date of publication
+    optional PublishedBeforeJoystream published_before_joystream = 11;
+
+    // Does video have marketing or advertising in the stream
+    optional bool has_marketing = 12;
+
+    // Should video be publicy visible yet
+    optional bool is_public = 13;
+
+    // Does Video have explicit language or scenes
+    optional bool is_explicit = 14;
+}
+
+message VideoCategoryMetadata {
+    // Category name
+    optional string name = 1;
+}

+ 74 - 0
content-metadata-protobuf/src/KnownLicenses.json

@@ -0,0 +1,74 @@
+[
+  {
+    "code": 1000,
+    "name": "CUSTOM",
+    "longName": "Custom License",
+    "description": "A user defined License",
+    "url": "",
+    "attributionRequired": false
+  },
+  {
+    "code": 1001,
+    "name": "PDM",
+    "longName": "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": 1002,
+    "name": "CC0",
+    "longName": "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": 1003,
+    "name": "CC_BY",
+    "longName": "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": 1004,
+    "name": "CC_BY_SA",
+    "longName": "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": 1005,
+    "name": "CC_BY_ND",
+    "longName": "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": 1006,
+    "name": "CC_BY_NC",
+    "longName": "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": 1007,
+    "name": "CC_BY_NC_SA",
+    "longName": "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": 1008,
+    "name": "CC_BY_NC_ND",
+    "longName": "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
+  }
+]

+ 10 - 0
content-metadata-protobuf/src/index.ts

@@ -0,0 +1,10 @@
+// Some helpers for constructing known licences
+import licences from './licenses'
+export { licences }
+
+// protobuf message constructors
+export * from '../compiled/proto/Video_pb'
+export * from '../compiled/proto/Channel_pb'
+export * from '../compiled/proto/Person_pb'
+export * from '../compiled/proto/Playlist_pb'
+export * from '../compiled/proto/Series_pb'

+ 70 - 0
content-metadata-protobuf/src/licenses.ts

@@ -0,0 +1,70 @@
+// Helper methods to handle joystream defined licence types
+// This should be factored out into a separate package
+
+import LICENSES from './KnownLicenses.json'
+import { License } from '../compiled/proto/Video_pb'
+
+export type LicenseCode = number
+export const CUSTOM_LICENSE_CODE: LicenseCode = 1000
+
+type KnownLicense = {
+  code: LicenseCode
+  name: string
+  longName: string
+  description: string
+  url: string
+  attributionRequired: boolean
+}
+
+export const KnownLicenses = new Map<LicenseCode, KnownLicense>()
+
+LICENSES.forEach((license: KnownLicense) => {
+  KnownLicenses.set(license.code, license)
+})
+
+export function getLicenseCodeByName(name: string): LicenseCode | undefined {
+  for (const [code, license] of KnownLicenses) {
+    if (license.name === name) return code
+  }
+}
+
+export function createKnownLicenseFromCode(code: LicenseCode, attribution?: string): License {
+  if (code === CUSTOM_LICENSE_CODE) {
+    throw new Error('Use createCustomLicense() instead')
+  }
+
+  const knownLicense = KnownLicenses.get(code)
+
+  if (!knownLicense) {
+    throw new Error('Unknown License Code')
+  }
+
+  const license = new License()
+
+  license.setCode(code)
+
+  if (knownLicense.attributionRequired) {
+    if (attribution === undefined) {
+      throw new Error('Attribution required for selected license')
+    }
+    license.setAttribution(attribution)
+  }
+
+  return license
+}
+
+export function createCustomKnownLicense(customText: string): License {
+  const license = new License()
+
+  license.setCode(CUSTOM_LICENSE_CODE)
+  license.setCustomText(customText)
+  return license
+}
+
+export default {
+  CUSTOM_LICENSE_CODE,
+  KnownLicenses,
+  createCustomKnownLicense,
+  createKnownLicenseFromCode,
+  getLicenseCodeByName,
+}

+ 31 - 0
content-metadata-protobuf/test/channel.ts

@@ -0,0 +1,31 @@
+import { ChannelMetadata } from '../src'
+import { assert } from 'chai'
+
+describe('Channel Metadata', () => {
+  it('Message', () => {
+    const channel = new ChannelMetadata()
+
+    const title = 'title'
+    const description = 'description'
+    const isPublic = false
+    const language = 'fr'
+
+    channel.setTitle(title)
+    channel.setDescription(description)
+    channel.setIsPublic(isPublic)
+    channel.setLanguage(language)
+    channel.setAvatarPhoto(0)
+    channel.setCoverPhoto(1)
+
+    assert.deepEqual(channel.toObject(), {
+      title,
+      description,
+      isPublic,
+      language,
+      avatarPhoto: 0,
+      coverPhoto: 1,
+    })
+
+    assert.deepEqual(ChannelMetadata.deserializeBinary(channel.serializeBinary()), channel)
+  })
+})

+ 42 - 0
content-metadata-protobuf/test/license-codes.ts

@@ -0,0 +1,42 @@
+import {
+  KnownLicenses,
+  CUSTOM_LICENSE_CODE,
+  getLicenseCodeByName,
+  createKnownLicenseFromCode,
+  createCustomKnownLicense,
+} from '../src/licenses'
+import { VideoMetadata } from '../src/index'
+import { assert } from 'chai'
+
+describe('Known License Codes', () => {
+  it('Excludes default value 0', () => {
+    assert(!KnownLicenses.has(0))
+  })
+
+  it('Pre-defined Joystream license codes', () => {
+    // Make sure we have correct known custom licence
+    assert(KnownLicenses.has(CUSTOM_LICENSE_CODE))
+    assert.equal(KnownLicenses.get(CUSTOM_LICENSE_CODE)!.name, 'CUSTOM')
+
+    assert(KnownLicenses.has(1001))
+    assert(KnownLicenses.has(1002))
+    assert(KnownLicenses.has(1003))
+    assert(KnownLicenses.has(1004))
+    assert(KnownLicenses.has(1005))
+    assert(KnownLicenses.has(1006))
+    assert(KnownLicenses.has(1007))
+    assert(KnownLicenses.has(1008))
+  })
+
+  it('createCustomKnownLicense(): uses correct code', () => {
+    const license = createCustomKnownLicense('custom text')
+    assert.equal(license.getCode(), CUSTOM_LICENSE_CODE)
+  })
+
+  it('createKnownLicenseFromCode(): Licence can be created by name', () => {
+    const licenseCode = getLicenseCodeByName('CC_BY') as number
+    const license = createKnownLicenseFromCode(licenseCode as number, 'Attribution: Joystream')
+    const videoMeta = new VideoMetadata()
+    videoMeta.setLicense(license)
+  })
+})

+ 112 - 0
content-metadata-protobuf/test/video.ts

@@ -0,0 +1,112 @@
+import { VideoMetadata, PublishedBeforeJoystream, MediaType, License } from '../src'
+import { assert, expect } from 'chai'
+
+describe('Video Metadata', () => {
+  it('Message', () => {
+    const meta = new VideoMetadata()
+
+    const title = 'Video Title'
+    const description = 'Video Description'
+    const duration = 100
+
+    meta.setTitle(title)
+    meta.setDescription(description)
+    meta.setDuration(duration)
+    meta.setMediaPixelHeight(1)
+    meta.setMediaPixelWidth(2)
+    meta.setMediaType(new MediaType())
+    meta.setLanguage('en')
+    meta.setLicense(new License())
+    meta.setPublishedBeforeJoystream(new PublishedBeforeJoystream())
+    meta.setHasMarketing(true)
+    meta.setIsPublic(true)
+    meta.setIsExplicit(false)
+    meta.setVideo(0)
+    meta.setThumbnailPhoto(1)
+
+    assert.deepEqual(meta.toObject(), {
+      title,
+      description,
+      duration,
+      mediaPixelHeight: 1,
+      mediaPixelWidth: 2,
+      mediaType: {
+        codecName: undefined,
+        container: undefined,
+        mimeMediaType: undefined,
+      },
+      language: 'en',
+      license: {
+        code: undefined,
+        attribution: undefined,
+        customText: undefined,
+      },
+      publishedBeforeJoystream: { isPublished: undefined, date: undefined },
+      hasMarketing: true,
+      isPublic: true,
+      isExplicit: false,
+      thumbnailPhoto: 1,
+      video: 0,
+    })
+
+    // sanity check - encoding / decoding works
+    assert.deepEqual(VideoMetadata.deserializeBinary(meta.serializeBinary()), meta)
+  })
+
+  it('Message: PublishedBeforeJoystream', () => {
+    const meta = new VideoMetadata()
+
+    expect(meta.hasPublishedBeforeJoystream()).equals(false, 'PublishedBeforeJoystream field should NOT be set')
+
+    const published = new PublishedBeforeJoystream()
+    const isPublished = true
+    const date = '1950-12-24'
+    published.setIsPublished(isPublished)
+    published.setDate(date)
+
+    meta.setPublishedBeforeJoystream(published)
+
+    // Field should now be set
+    expect(meta.hasPublishedBeforeJoystream()).equals(true, 'PublishedBeforeJoystream field should be set')
+
+    assert.deepEqual(published.toObject(), {
+      isPublished,
+      date,
+    })
+  })
+
+  it('Message: Licence', () => {
+    const license = new License()
+
+    const code = 1000
+    const attribution = 'Attribution Text'
+    const customText = 'Custom License Details'
+    license.setCode(code)
+    license.setAttribution(attribution)
+    license.setCustomText(customText)
+
+    assert.deepEqual(license.toObject(), {
+      code,
+      attribution,
+      customText,
+    })
+  })
+
+  it('Message: MediaType', () => {
+    const mediaType = new MediaType()
+
+    const codecName = 'mpeg4'
+    const container = 'avi'
+    const mimeMediaType = 'videp/mp4'
+
+    mediaType.setCodecName(codecName)
+    mediaType.setContainer(container)
+    mediaType.setMimeMediaType(mimeMediaType)
+
+    assert.deepEqual(mediaType.toObject(), {
+      codecName,
+      container,
+      mimeMediaType,
+    })
+  })
+})

+ 15 - 0
content-metadata-protobuf/tsconfig.json

@@ -0,0 +1,15 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "commonjs",
+    "outDir": "lib",
+    "strict": true,
+    "declaration": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "skipLibCheck": true,
+    "resolveJsonModule": true,
+  },
+  "include": ["src"],
+  "exclude": ["node_modules", "test"]
+}

+ 67 - 9
node/src/chain_spec/content_config.rs

@@ -1,5 +1,9 @@
 use codec::Decode;
-use node_runtime::{data_directory::DataObject, ContentId, DataDirectoryConfig, Runtime};
+use node_runtime::{
+    common::storage::StorageObjectOwner,
+    data_directory::{DataObject, Quota},
+    ChannelId, ContentId, DAOId, DataDirectoryConfig, MemberId, Runtime,
+};
 use serde::Deserialize;
 use std::{fs, path::Path};
 
@@ -8,43 +12,65 @@ use std::{fs, path::Path};
 // them to json we get a string rather than an array of bytes, so deserializing them
 // is failing. So we are relying on parity codec encoding instead..
 #[derive(Decode)]
-struct DataObjectAndContentId {
+struct Content {
     content_id: ContentId,
     data_object: DataObject<Runtime>,
+    storage_object_owner: StorageObjectOwner<MemberId, ChannelId, DAOId>,
+    quota: Quota,
 }
 
 #[derive(Decode)]
 struct ContentData {
     /// DataObject(s) and ContentId
-    data_objects: Vec<DataObjectAndContentId>,
+    data_objects: Vec<Content>,
+    quota_size_limit_upper_bound: u64,
+    quota_objects_limit_upper_bound: u64,
+    global_quota: Quota,
 }
 
 #[derive(Deserialize)]
-struct EncodedDataObjectAndContentId {
+struct EncodedContent {
     /// hex encoded ContentId
     content_id: String,
     /// hex encoded DataObject<Runtime>
     data_object: String,
+    /// hex encoded StorageObjectOwner
+    storage_object_owner: String,
+    /// hex encoded Quota
+    quota: String,
 }
 
-impl EncodedDataObjectAndContentId {
-    fn decode(&self) -> DataObjectAndContentId {
+impl EncodedContent {
+    fn decode(&self) -> Content {
         // hex string must not include '0x' prefix!
         let encoded_content_id = hex::decode(&self.content_id[2..].as_bytes())
             .expect("failed to parse content_id hex string");
         let encoded_data_object = hex::decode(&self.data_object[2..].as_bytes())
             .expect("failed to parse data_object hex string");
-        DataObjectAndContentId {
+        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())
+            .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(),
         }
     }
 }
 
 #[derive(Deserialize)]
 struct EncodedContentData {
-    /// DataObject(s) and ContentId
-    data_objects: Vec<EncodedDataObjectAndContentId>,
+    /// 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,
 }
 
 fn parse_content_data(data_file: &Path) -> EncodedContentData {
@@ -60,6 +86,26 @@ 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())
+                        .expect("failed to parse data_object hex string");
+
+                Decode::decode(&mut encoded_quota_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())
+                        .expect("failed to parse data_object hex string");
+
+                Decode::decode(&mut encoded_quota_objects_limit_upper_bound.as_slice()).unwrap()
+            },
+            global_quota: {
+                let encoded_global_quota = hex::decode(&self.global_quota[2..].as_bytes())
+                    .expect("failed to parse data_object hex string");
+
+                Decode::decode(&mut encoded_global_quota.as_slice()).unwrap()
+            },
         }
     }
 }
@@ -69,6 +115,10 @@ pub fn empty_data_directory_config() -> DataDirectoryConfig {
     DataDirectoryConfig {
         data_object_by_content_id: vec![],
         known_content_ids: vec![],
+        quotas: vec![],
+        quota_size_limit_upper_bound: 20000,
+        quota_objects_limit_upper_bound: 200,
+        global_quota: Quota::new(2000000, 2000),
     }
 }
 
@@ -84,10 +134,18 @@ pub fn data_directory_config_from_json(data_file: &Path) -> DataDirectoryConfig
             .iter()
             .map(|object| (object.content_id, object.data_object.clone()))
             .collect(),
+        quotas: content
+            .data_objects
+            .iter()
+            .map(|object| (object.storage_object_owner.clone(), object.quota))
+            .collect(),
         known_content_ids: content
             .data_objects
             .into_iter()
             .map(|object| object.content_id)
             .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,
     }
 }

+ 2 - 1
package.json

@@ -23,7 +23,8 @@
     "utils/api-scripts",
     "content-directory-schemas",
     "query-node",
-    "query-node/generated/*"
+    "query-node/generated/*",
+    "content-metadata-protobuf"
   ],
   "resolutions": {
     "@polkadot/api": "1.26.1",

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

@@ -13,23 +13,17 @@ pub struct ContentParameters<ContentId, DataObjectTypeId> {
     pub ipfs_content_id: Vec<u8>,
 }
 
+// New owner type for storage object struct
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
-pub enum AbstractStorageObjectOwner<ChannelId, DAOId> {
+pub enum StorageObjectOwner<MemberId, ChannelId, DAOId> {
+    Member(MemberId),
     Channel(ChannelId), // acts through content directory module, where again DAOs can own channels for example
     DAO(DAOId),         // acts through upcoming `content_finance` module
     Council,            // acts through proposal system
     WorkingGroup(WorkingGroup), // acts through new extrinsic in working group
 }
 
-// New owner type for storage object struct
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-#[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
-pub enum StorageObjectOwner<MemberId, ChannelId, DAOId> {
-    Member(MemberId),
-    AbstractStorageObjectOwner(AbstractStorageObjectOwner<ChannelId, DAOId>),
-}
-
 // To be implemented by current storage data_directory runtime module.
 // Defined in 'common' package
 pub trait StorageSystem<T: crate::StorageOwnership + crate::MembershipTypes> {

+ 255 - 28
runtime-modules/storage/src/data_directory.rs

@@ -33,7 +33,7 @@ use system::ensure_root;
 use serde::{Deserialize, Serialize};
 
 use common::origin::ActorOriginValidator;
-pub use common::storage::{AbstractStorageObjectOwner, ContentParameters, StorageObjectOwner};
+pub use common::storage::{ContentParameters, StorageObjectOwner};
 pub(crate) use common::BlockAndTime;
 
 use crate::data_object_type_registry;
@@ -56,13 +56,16 @@ pub trait Trait:
     /// Provides random storage provider id.
     type StorageProviderHelper: StorageProviderHelper<Self>;
 
-    ///Active data object type validator.
+    /// Active data object type validator.
     type IsActiveDataObjectType: data_object_type_registry::IsActiveDataObjectType<Self>;
 
     /// Validates member id and origin combination.
     type MemberOriginValidator: ActorOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
 
     type MaxObjectsPerInjection: Get<u32>;
+
+    /// Default content quota for all actors.
+    type DefaultQuota: Get<Quota>;
 }
 
 decl_error! {
@@ -84,7 +87,25 @@ decl_error! {
         RequireRootOrigin,
 
         /// DataObject Injection Failed. Too Many DataObjects.
-        DataObjectsInjectionExceededLimit
+        DataObjectsInjectionExceededLimit,
+
+        /// Contant uploading failed. Actor quota objects limit exceeded.
+        QuotaObjectsLimitExceeded,
+
+        /// Contant uploading failed. Actor quota size limit exceeded.
+        QuotaSizeLimitExceeded,
+
+        /// Quota size limit upper bound exceeded
+        QuotaSizeLimitUpperBoundExceeded,
+
+        /// Quota objects limit upper bound exceeded
+        QuotaObjectsLimitUpperBoundExceeded,
+
+        /// Contant uploading failed. Actor quota size limit exceeded.
+        GlobalQuotaSizeLimitExceeded,
+
+        /// Contant uploading failed. Actor quota objects limit exceeded.
+        GlobalQuotaObjectsLimitExceeded,
     }
 }
 
@@ -153,6 +174,68 @@ pub struct DataObjectInternal<
     pub ipfs_content_id: Vec<u8>,
 }
 
+#[derive(Clone, Copy)]
+pub struct Voucher {
+    pub size: u64,
+    pub objects: u64,
+}
+
+/// Uploading quota for StorageObjectOwner
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, Default)]
+pub struct Quota {
+    // Total objects size limit per StorageObjectOwner
+    pub size_limit: u64,
+    // Total objects number limit per StorageObjectOwner
+    pub objects_limit: u64,
+    pub size_used: u64,
+    pub objects_used: u64,
+}
+
+impl Quota {
+    /// Create new quota with provided size & objects limits
+    pub const fn new(size_limit: u64, objects_limit: u64) -> Self {
+        Self {
+            size_limit,
+            objects_limit,
+            size_used: 0,
+            objects_used: 0,
+        }
+    }
+
+    /// Calculate free quota
+    pub fn calculate_voucher(&self) -> Voucher {
+        Voucher {
+            size: self.size_limit - self.size_used,
+            objects: self.objects_limit - self.objects_used,
+        }
+    }
+
+    pub fn fill_quota(self, voucher: Voucher) -> Self {
+        Self {
+            size_used: self.size_used + voucher.size,
+            objects_used: self.objects_used + voucher.objects,
+            ..self
+        }
+    }
+
+    pub fn release_quota(self, voucher: Voucher) -> Self {
+        Self {
+            size_used: self.size_used - voucher.size,
+            objects_used: self.objects_used - voucher.objects,
+            ..self
+        }
+    }
+
+    pub fn set_new_size_limit(&mut self, new_size_limit: u64) {
+        self.size_limit = new_size_limit;
+    }
+
+    pub fn set_new_objects_limit(&mut self, new_objects_limit: u64) {
+        self.objects_limit = new_objects_limit;
+    }
+}
+
 /// A map collection of unique DataObjects keyed by the ContentId
 pub type DataObjectsMap<T> = BTreeMap<ContentId<T>, DataObject<T>>;
 
@@ -164,6 +247,19 @@ decl_storage! {
         /// 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>>;
+
+        /// 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;
+
+        /// Upper bound for the Quota size limit.
+        pub QuotaSizeLimitUpperBound get(fn quota_size_limit_upper_bound) config(): u64;
+
+        /// Upper bound for the Quota objects number limit.
+        pub QuotaObjectsLimitUpperBound get(fn quota_objects_limit_upper_bound) config(): u64;
+
+        /// Global quota.
+        pub GlobalQuota get(fn global_quota) config(): Quota;
     }
 }
 
@@ -174,6 +270,7 @@ decl_event! {
         StorageProviderId = StorageProviderId<T>,
         Content = Vec<ContentParameters<ContentId<T>, DataObjectTypeId<T>>>,
         ContentId = ContentId<T>,
+        QuotaLimit = u64
     {
         /// Emits on adding of the content.
         /// Params:
@@ -192,6 +289,18 @@ decl_event! {
         /// - Id of the relationship.
         /// - Id of the storage provider.
         ContentRejected(ContentId, StorageProviderId),
+
+        /// Emits when the storage object owner quota size limit update performed.
+        /// Params:
+        /// - StorageObjectOwner enum.
+        /// - quota size limit.
+        StorageObjectOwnerQuotaSizeLimitUpdated(StorageObjectOwner, QuotaLimit),
+
+        /// Emits when the storage object owner quota objects limit update performed.
+        /// Params:
+        /// - StorageObjectOwner enum.
+        /// - quota objects limit.
+        StorageObjectOwnerQuotaObjectsLimitUpdated(StorageObjectOwner, QuotaLimit),
     }
 }
 
@@ -207,23 +316,29 @@ decl_module! {
         /// Maximum objects allowed per inject_data_objects() transaction
         const MaxObjectsPerInjection: u32 = T::MaxObjectsPerInjection::get();
 
-        /// Adds the content to the system. Member id should match its origin. The created DataObject
+        /// Adds the content to the system. The created DataObject
         /// awaits liaison to accept or reject it.
         #[weight = 10_000_000] // TODO: adjust weight
-        pub fn add_content_as_member(
+        pub fn add_content(
             origin,
-            member_id: MemberId<T>,
-            content: Vec<ContentParameters<T::ContentId, DataObjectTypeId<T>>>
+            owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+            content: Vec<ContentParameters<ContentId<T>, DataObjectTypeId<T>>>
         ) {
-            T::MemberOriginValidator::ensure_actor_origin(
-                origin,
-                member_id,
-            )?;
-
-            let owner = StorageObjectOwner::Member(member_id);
+            
+            // Ensure given origin can perform operation under specific storage object owner
+            Self::ensure_storage_object_owner_origin(origin, &owner)?;
 
             Self::ensure_content_is_valid(&content)?;
 
+            let owner_quota = Self::get_quota(&owner);
+
+            // Ensure owner quota constraints satisfied.
+            // Calculate upload voucher
+            let upload_voucher = Self::ensure_owner_quota_constraints_satisfied(owner_quota, &content)?;
+
+            // Ensure global quota constraints satisfied.
+            Self::ensure_global_quota_constraints_satisfied(upload_voucher)?;
+
             let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
 
             //
@@ -231,35 +346,63 @@ decl_module! {
             //
 
             // Let's create the entry then
-            Self::upload_content(liaison, content.clone(), owner.clone());
+            Self::upload_content(owner_quota, upload_voucher, liaison, content.clone(), owner.clone());
 
             Self::deposit_event(RawEvent::ContentAdded(content, owner));
         }
 
-        /// Adds the content to the system. Requires root privileges. The created DataObject
-        /// awaits liaison to accept or reject it.
+        /// Updates storage object owner quota objects limit. Requires leader privileges.
         #[weight = 10_000_000] // TODO: adjust weight
-        pub fn add_content(
+        pub fn update_storage_object_owner_quota_objects_limit(
             origin,
-            abstract_owner: AbstractStorageObjectOwner<ChannelId<T>, DAOId<T>>,
-            content: Vec<ContentParameters<ContentId<T>, DataObjectTypeId<T>>>
+            abstract_owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+            new_quota_objects_limit: u64
         ) {
-            ensure_root(origin)?;
+            <StorageWorkingGroup<T>>::ensure_origin_is_active_leader(origin)?;
+            ensure!(new_quota_objects_limit <= Self::quota_objects_limit_upper_bound(), Error::<T>::QuotaSizeLimitUpperBoundExceeded);
 
-            Self::ensure_content_is_valid(&content)?;
+            //
+            // == MUTATION SAFE ==
+            //
+
+            if <Quotas<T>>::contains_key(&abstract_owner) {
+                <Quotas<T>>::mutate(&abstract_owner, |quota| {
+                    quota.set_new_objects_limit(new_quota_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 owner = StorageObjectOwner::AbstractStorageObjectOwner(abstract_owner);
+            Self::deposit_event(RawEvent::StorageObjectOwnerQuotaObjectsLimitUpdated(abstract_owner, new_quota_objects_limit));
+        }
 
-            let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
+        /// Updates storage object owner quota size limit. Requires leader privileges.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_storage_object_owner_quota_size_limit(
+            origin,
+            abstract_owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+            new_quota_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);
 
             //
             // == MUTATION SAFE ==
             //
 
-            // Let's create the entry then
-            Self::upload_content(liaison, content.clone(), owner.clone());
+            if <Quotas<T>>::contains_key(&abstract_owner) {
+                <Quotas<T>>::mutate(&abstract_owner, |quota| {
+                    quota.set_new_size_limit(new_quota_size_limit);
+                });
+            } else {
+                let mut quota = T::DefaultQuota::get();
+                quota.set_new_size_limit(new_quota_size_limit);
+                <Quotas<T>>::insert(&abstract_owner, quota);
+            };
 
-            Self::deposit_event(RawEvent::ContentAdded(content, owner));
+            Self::deposit_event(RawEvent::StorageObjectOwnerQuotaSizeLimitUpdated(abstract_owner, new_quota_size_limit));
         }
 
         /// Storage provider accepts a content. Requires signed storage provider account and its id.
@@ -342,7 +485,80 @@ decl_module! {
 }
 
 impl<T: Trait> Module<T> {
+    // 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>>,
+    ) -> DispatchResult {
+        if let StorageObjectOwner::Member(member_id) = owner {
+            T::MemberOriginValidator::ensure_actor_origin(origin, *member_id)?;
+        } else {
+            ensure_root(origin)?;
+        };
+        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)
+        } else {
+            T::DefaultQuota::get()
+        }
+    }
+
+    // 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,
+        content: &[ContentParameters<T::ContentId, DataObjectTypeId<T>>],
+    ) -> Result<Voucher, Error<T>> {
+        let owner_quota_voucher = owner_quota.calculate_voucher();
+
+        // Ensure total content length is less or equal then available per given owner quota
+        let content_length = content.len() as u64;
+
+        ensure!(
+            owner_quota_voucher.objects >= content_length,
+            Error::<T>::QuotaObjectsLimitExceeded
+        );
+
+        // Ensure total content size is less or equal then available per given owner quota
+        let content_size = content
+            .iter()
+            .fold(0, |total_size, content| total_size + content.size);
+
+        ensure!(
+            owner_quota_voucher.size >= content_size,
+            Error::<T>::QuotaSizeLimitExceeded
+        );
+
+        Ok(Voucher {
+            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();
+
+        ensure!(
+            global_quota_voucher.objects >= upload_voucher.objects,
+            Error::<T>::GlobalQuotaObjectsLimitExceeded
+        );
+
+        ensure!(
+            global_quota_voucher.size >= upload_voucher.size,
+            Error::<T>::GlobalQuotaSizeLimitExceeded
+        );
+
+        Ok(())
+    }
+
+    // Complete content upload, update quotas
     fn upload_content(
+        owner_quota: Quota,
+        upload_voucher: Voucher,
         liaison: StorageProviderId<T>,
         multi_content: Vec<ContentParameters<T::ContentId, DataObjectTypeId<T>>>,
         owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
@@ -360,6 +576,12 @@ impl<T: Trait> Module<T> {
 
             <DataObjectByContentId<T>>::insert(content.content_id, data);
         }
+
+        // Updade or create owner quota.
+        <Quotas<T>>::insert(owner, owner_quota.fill_quota(upload_voucher));
+
+        // Update global quota
+        <GlobalQuota>::mutate(|global_quota| global_quota.fill_quota(upload_voucher));
     }
 
     fn ensure_content_is_valid(
@@ -437,15 +659,20 @@ impl<T: Trait> common::storage::StorageSystem<T> for Module<T> {
 
         let liaison = T::StorageProviderHelper::get_random_storage_provider()?;
 
-        Self::upload_content(liaison, content, owner);
+        let owner_quota = Self::get_quota(&owner);
+        let upload_voucher = Self::ensure_owner_quota_constraints_satisfied(owner_quota, &content)?;
+
+        Self::upload_content(owner_quota, upload_voucher, liaison, content, owner);
         Ok(())
     }
 
     fn can_add_content(
-        _owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
+        owner: StorageObjectOwner<MemberId<T>, ChannelId<T>, DAOId<T>>,
         content: Vec<ContentParameters<T::ContentId, DataObjectTypeId<T>>>,
     ) -> DispatchResult {
         T::StorageProviderHelper::get_random_storage_provider()?;
+        let owner_quota = Self::get_quota(&owner);
+        Self::ensure_owner_quota_constraints_satisfied(owner_quota, &content)?;
         Self::ensure_content_is_valid(&content)
     }
 }

+ 5 - 2
runtime-modules/storage/src/data_object_storage_registry.rs

@@ -124,9 +124,10 @@ decl_event! {
 
         /// Emits on adding of the data object storage relationship.
         /// Params:
+        /// - Id of the storage provider.
         /// - Id of the relationship.
         /// - Current state of the relationship (True=Active).
-        DataObjectStorageRelationshipReadyUpdated(DataObjectStorageRelationshipId, bool),
+        DataObjectStorageRelationshipReadyUpdated(StorageProviderId, DataObjectStorageRelationshipId, bool),
     }
 }
 
@@ -226,7 +227,9 @@ impl<T: Trait> Module<T> {
         // Update DOSR and fire event.
         <Relationships<T>>::insert(id, dosr);
         Self::deposit_event(RawEvent::DataObjectStorageRelationshipReadyUpdated(
-            id, ready,
+            storage_provider_id,
+            id,
+            ready,
         ));
 
         Ok(())

+ 9 - 9
runtime-modules/storage/src/data_object_type_registry.rs

@@ -56,7 +56,7 @@ decl_error! {
 }
 
 /// Contains description and constrains for the data object.
-#[derive(Clone, Encode, Decode, PartialEq, Debug)]
+#[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
 pub struct DataObjectType {
     /// Data object description.
     pub description: Vec<u8>,
@@ -98,12 +98,12 @@ decl_event! {
         /// Emits on the data object type registration.
         /// Params:
         /// - Id of the new data object type.
-        DataObjectTypeRegistered(DataObjectTypeId),
+        DataObjectTypeRegistered(DataObjectType, DataObjectTypeId),
 
         /// Emits on the data object type update.
         /// Params:
         /// - Id of the updated data object type.
-        DataObjectTypeUpdated(DataObjectTypeId),
+        DataObjectTypeUpdated(DataObjectTypeId, DataObjectType),
     }
 }
 
@@ -147,7 +147,7 @@ decl_module! {
             <DataObjectTypes<T>>::insert(new_do_type_id, do_type);
             <NextDataObjectTypeId<T>>::mutate(|n| { *n += T::DataObjectTypeId::from(1); });
 
-            Self::deposit_event(RawEvent::DataObjectTypeRegistered(new_do_type_id));
+            Self::deposit_event(RawEvent::DataObjectTypeRegistered(data_object_type, new_do_type_id));
         }
 
         /// Updates existing data object type. Requires leader privileges.
@@ -166,7 +166,7 @@ decl_module! {
 
             <DataObjectTypes<T>>::insert(id, do_type);
 
-            Self::deposit_event(RawEvent::DataObjectTypeUpdated(id));
+            Self::deposit_event(RawEvent::DataObjectTypeUpdated(id, data_object_type));
         }
 
         /// Activates existing data object type. Requires leader privileges.
@@ -182,9 +182,9 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            <DataObjectTypes<T>>::insert(id, do_type);
+            <DataObjectTypes<T>>::insert(id, do_type.clone());
 
-            Self::deposit_event(RawEvent::DataObjectTypeUpdated(id));
+            Self::deposit_event(RawEvent::DataObjectTypeUpdated(id, do_type));
         }
 
         /// Deactivates existing data object type. Requires leader privileges.
@@ -200,9 +200,9 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            <DataObjectTypes<T>>::insert(id, do_type);
+            <DataObjectTypes<T>>::insert(id, do_type.clone());
 
-            Self::deposit_event(RawEvent::DataObjectTypeUpdated(id));
+            Self::deposit_event(RawEvent::DataObjectTypeUpdated(id, do_type));
         }
     }
 }

+ 20 - 35
runtime-modules/storage/src/tests/data_directory.rs

@@ -1,5 +1,6 @@
 #![cfg(test)]
 
+use common::storage::StorageObjectOwner;
 use frame_support::dispatch::DispatchError;
 use sp_std::collections::btree_map::BTreeMap;
 use system::RawOrigin;
@@ -10,7 +11,7 @@ use super::mock::*;
 fn succeed_adding_content() {
     with_default_mock_builder(|| {
         let sender = 1u64;
-        let member_id = 1u64;
+        let owner = StorageObjectOwner::Member(1u64);
 
         let first_content_parameters = ContentParameters {
             content_id: 1,
@@ -29,11 +30,7 @@ fn succeed_adding_content() {
         let multi_content = vec![first_content_parameters, second_content_parameters];
 
         // Register a content with 1234 bytes of type 1, which should be recognized.
-        let res = TestDataDirectory::add_content_as_member(
-            Origin::signed(sender),
-            member_id,
-            multi_content,
-        );
+        let res = TestDataDirectory::add_content(Origin::signed(sender), owner, multi_content);
         assert!(res.is_ok());
     });
 }
@@ -41,7 +38,7 @@ fn succeed_adding_content() {
 #[test]
 fn add_content_fails_with_invalid_origin() {
     with_default_mock_builder(|| {
-        let member_id = 1u64;
+        let owner = StorageObjectOwner::Member(1u64);
 
         let content_parameters = ContentParameters {
             content_id: 1,
@@ -51,11 +48,8 @@ fn add_content_fails_with_invalid_origin() {
         };
 
         // Register a content with 1234 bytes of type 1, which should be recognized.
-        let res = TestDataDirectory::add_content_as_member(
-            RawOrigin::Root.into(),
-            member_id,
-            vec![content_parameters],
-        );
+        let res =
+            TestDataDirectory::add_content(RawOrigin::Root.into(), owner, vec![content_parameters]);
         assert_eq!(res, Err(DispatchError::Other("Bad origin")));
     });
 }
@@ -71,7 +65,7 @@ fn accept_and_reject_content_fail_with_invalid_storage_provider() {
         run_to_block(1);
 
         let sender = 1u64;
-        let member_id = 1u64;
+        let owner = StorageObjectOwner::Member(1u64);
 
         let content_parameters = ContentParameters {
             content_id: 1,
@@ -80,11 +74,8 @@ fn accept_and_reject_content_fail_with_invalid_storage_provider() {
             ipfs_content_id: vec![1, 2, 3, 4],
         };
 
-        let res = TestDataDirectory::add_content_as_member(
-            Origin::signed(sender),
-            member_id,
-            vec![content_parameters],
-        );
+        let res =
+            TestDataDirectory::add_content(Origin::signed(sender), owner, vec![content_parameters]);
         assert!(res.is_ok());
 
         let content_id = match &System::events().last().unwrap().event {
@@ -124,7 +115,7 @@ fn accept_content_as_liaison() {
         run_to_block(1);
 
         let sender = 1u64;
-        let member_id = 1u64;
+        let owner = StorageObjectOwner::Member(1u64);
 
         let content_parameters = ContentParameters {
             content_id: 1,
@@ -133,11 +124,8 @@ fn accept_content_as_liaison() {
             ipfs_content_id: vec![1, 2, 3, 4],
         };
 
-        let res = TestDataDirectory::add_content_as_member(
-            Origin::signed(sender),
-            member_id,
-            vec![content_parameters],
-        );
+        let res =
+            TestDataDirectory::add_content(Origin::signed(sender), owner, vec![content_parameters]);
         assert!(res.is_ok());
 
         // An appropriate event should have been fired.
@@ -178,7 +166,7 @@ fn reject_content_as_liaison() {
         run_to_block(1);
 
         let sender = 1u64;
-        let member_id = 1u64;
+        let owner = StorageObjectOwner::Member(1u64);
 
         let content_parameters = ContentParameters {
             content_id: 1,
@@ -187,11 +175,8 @@ fn reject_content_as_liaison() {
             ipfs_content_id: vec![1, 2, 3, 4],
         };
 
-        let res = TestDataDirectory::add_content_as_member(
-            Origin::signed(sender),
-            member_id,
-            vec![content_parameters],
-        );
+        let res =
+            TestDataDirectory::add_content(Origin::signed(sender), owner, vec![content_parameters]);
         assert!(res.is_ok());
 
         // An appropriate event should have been fired.
@@ -273,7 +258,7 @@ fn data_object_injection_works() {
 fn data_object_injection_overwrites_and_removes_duplicate_ids() {
     with_default_mock_builder(|| {
         let sender = 1u64;
-        let member_id = 1u64;
+        let owner = StorageObjectOwner::Member(1u64);
         let content_id_1 = 1;
         let content_id_2 = 2;
 
@@ -293,15 +278,15 @@ fn data_object_injection_overwrites_and_removes_duplicate_ids() {
 
         // Start with some existing objects in directory which will be
         // overwritten
-        let res = TestDataDirectory::add_content_as_member(
+        let res = TestDataDirectory::add_content(
             Origin::signed(sender),
-            member_id,
+            owner.clone(),
             vec![content_parameters_first],
         );
         assert!(res.is_ok());
-        let res = TestDataDirectory::add_content_as_member(
+        let res = TestDataDirectory::add_content(
             Origin::signed(sender),
-            member_id,
+            owner,
             vec![content_parameters_second],
         );
         assert!(res.is_ok());

+ 3 - 1
runtime-modules/storage/src/tests/data_object_storage_registry.rs

@@ -156,7 +156,9 @@ fn test_toggle_ready() {
             System::events().last().unwrap().event,
             MetaEvent::data_object_storage_registry(
                 data_object_storage_registry::RawEvent::DataObjectStorageRelationshipReadyUpdated(
-                    dosr_id, true,
+                    storage_provider_id,
+                    dosr_id,
+                    true,
                 )
             )
         );

+ 12 - 8
runtime-modules/storage/src/tests/data_object_type_registry.rs

@@ -35,7 +35,7 @@ impl SetLeadFixture {
 fn get_last_data_object_type_id() -> u64 {
     let dot_id = match System::events().last().unwrap().event {
         MetaEvent::data_object_type_registry(
-            data_object_type_registry::RawEvent::DataObjectTypeRegistered(dot_id),
+            data_object_type_registry::RawEvent::DataObjectTypeRegistered(_, dot_id),
         ) => dot_id,
         _ => 0xdeadbeefu64, // unlikely value
     };
@@ -242,7 +242,7 @@ fn update_existing() {
         let res = TestDataObjectTypeRegistry::update_data_object_type(
             RawOrigin::Signed(DEFAULT_LEADER_ACCOUNT_ID).into(),
             dot_id,
-            updated3,
+            updated3.clone(),
         );
         assert!(res.is_ok());
         assert_eq!(
@@ -250,7 +250,7 @@ fn update_existing() {
             EventRecord {
                 phase: Phase::Initialization,
                 event: MetaEvent::data_object_type_registry(
-                    data_object_type_registry::RawEvent::DataObjectTypeUpdated(dot_id)
+                    data_object_type_registry::RawEvent::DataObjectTypeUpdated(dot_id, updated3)
                 ),
                 topics: vec![],
             }
@@ -295,7 +295,7 @@ fn activate_existing() {
         };
         let id_res = TestDataObjectTypeRegistry::register_data_object_type(
             RawOrigin::Signed(DEFAULT_LEADER_ACCOUNT_ID).into(),
-            data,
+            data.clone(),
         );
         assert!(id_res.is_ok());
         assert_eq!(
@@ -304,6 +304,7 @@ fn activate_existing() {
                 phase: Phase::Initialization,
                 event: MetaEvent::data_object_type_registry(
                     data_object_type_registry::RawEvent::DataObjectTypeRegistered(
+                        data,
                         expected_data_object_type_id
                     )
                 ),
@@ -312,9 +313,9 @@ fn activate_existing() {
         );
 
         // Retrieve, and ensure it's not active.
-        let data = TestDataObjectTypeRegistry::data_object_types(expected_data_object_type_id);
-        assert!(data.is_some());
-        assert!(!data.unwrap().active);
+        let mut data =
+            TestDataObjectTypeRegistry::data_object_types(expected_data_object_type_id).unwrap();
+        assert!(!data.active);
 
         // Now activate the data object type
         let res = TestDataObjectTypeRegistry::activate_data_object_type(
@@ -322,13 +323,16 @@ fn activate_existing() {
             expected_data_object_type_id,
         );
         assert!(res.is_ok());
+
+        data.active = true;
         assert_eq!(
             *System::events().last().unwrap(),
             EventRecord {
                 phase: Phase::Initialization,
                 event: MetaEvent::data_object_type_registry(
                     data_object_type_registry::RawEvent::DataObjectTypeUpdated(
-                        expected_data_object_type_id
+                        expected_data_object_type_id,
+                        data
                     )
                 ),
                 topics: vec![],

+ 24 - 0
runtime-modules/storage/src/tests/mock.rs

@@ -11,6 +11,7 @@ use sp_runtime::{
 };
 
 use crate::data_directory::ContentIdExists;
+use crate::data_directory::Quota;
 pub use crate::data_directory::{ContentParameters, StorageObjectOwner};
 use crate::data_object_type_registry::IsActiveDataObjectType;
 use crate::ContentId;
@@ -96,6 +97,7 @@ parameter_types! {
     pub const AvailableBlockRatio: Perbill = Perbill::one();
     pub const MinimumPeriod: u64 = 5;
     pub const MaxObjectsPerInjection: u32 = 5;
+    pub const DefaultQuota: Quota = Quota::new(5000, 50);
 }
 
 impl system::Trait for Test {
@@ -179,6 +181,7 @@ impl data_directory::Trait for Test {
     type IsActiveDataObjectType = AnyDataObjectTypeIsActive;
     type MemberOriginValidator = ();
     type MaxObjectsPerInjection = MaxObjectsPerInjection;
+    type DefaultQuota = DefaultQuota;
 }
 
 impl crate::data_directory::StorageProviderHelper<Test> for () {
@@ -236,6 +239,9 @@ impl hiring::Trait for Test {
 }
 
 pub struct ExtBuilder {
+    quota_objects_limit_upper_bound: u64,
+    quota_size_limit_upper_bound: u64,
+    global_quota: Quota,
     first_data_object_type_id: u64,
     first_content_id: u64,
     first_relationship_id: u64,
@@ -245,6 +251,9 @@ pub struct ExtBuilder {
 impl Default for ExtBuilder {
     fn default() -> Self {
         Self {
+            quota_objects_limit_upper_bound: 200,
+            quota_size_limit_upper_bound: 20000,
+            global_quota: Quota::new(2000000, 2000),
             first_data_object_type_id: 1,
             first_content_id: 2,
             first_relationship_id: 3,
@@ -258,23 +267,38 @@ impl ExtBuilder {
         self.first_data_object_type_id = first_data_object_type_id;
         self
     }
+
     pub fn first_content_id(mut self, first_content_id: u64) -> Self {
         self.first_content_id = first_content_id;
         self
     }
+
     pub fn first_relationship_id(mut self, first_relationship_id: u64) -> Self {
         self.first_relationship_id = first_relationship_id;
         self
     }
+
     pub fn first_metadata_id(mut self, first_metadata_id: u64) -> Self {
         self.first_metadata_id = first_metadata_id;
         self
     }
+
     pub fn build(self) -> sp_io::TestExternalities {
         let mut t = system::GenesisConfig::default()
             .build_storage::<Test>()
             .unwrap();
 
+        data_directory::GenesisConfig::<Test> {
+            quota_size_limit_upper_bound: self.quota_size_limit_upper_bound,
+            quota_objects_limit_upper_bound: self.quota_objects_limit_upper_bound,
+            global_quota: self.global_quota,
+            data_object_by_content_id: vec![],
+            known_content_ids: vec![],
+            quotas: vec![],
+        }
+        .assimilate_storage(&mut t)
+        .unwrap();
+
         data_object_type_registry::GenesisConfig::<Test> {
             first_data_object_type_id: self.first_data_object_type_id,
         }

+ 3 - 0
runtime/src/lib.rs

@@ -59,6 +59,7 @@ pub use membership;
 pub use pallet_balances::Call as BalancesCall;
 pub use pallet_staking::StakerStatus;
 pub use proposals_codex::ProposalsConfigParameters;
+use storage::data_directory::Quota;
 pub use storage::{data_directory, data_object_type_registry};
 pub use working_group;
 
@@ -455,6 +456,7 @@ impl memo::Trait for Runtime {
 
 parameter_types! {
     pub const MaxObjectsPerInjection: u32 = 100;
+    pub const DefaultQuota: Quota = Quota::new(5000, 50);
 }
 
 impl storage::data_object_type_registry::Trait for Runtime {
@@ -467,6 +469,7 @@ impl storage::data_directory::Trait for Runtime {
     type IsActiveDataObjectType = DataObjectTypeRegistry;
     type MemberOriginValidator = MembershipOriginValidator<Self>;
     type MaxObjectsPerInjection = MaxObjectsPerInjection;
+    type DefaultQuota = DefaultQuota;
 }
 
 impl storage::data_object_storage_registry::Trait for Runtime {

+ 194 - 75
yarn.lock

@@ -5033,6 +5033,11 @@
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
   integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
 
+"@types/mocha@^8.2.0":
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44"
+  integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ==
+
 "@types/mustache@^4.0.1":
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.0.1.tgz#e4d421ed2d06d463b120621774185a5cd1b92d77"
@@ -5621,6 +5626,11 @@
   dependencies:
     eslint-visitor-keys "^1.1.0"
 
+"@ungap/promise-all-settled@1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
+  integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
+
 "@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
@@ -6423,16 +6433,16 @@ ansi-align@^3.0.0:
   dependencies:
     string-width "^3.0.0"
 
+ansi-colors@4.1.1, ansi-colors@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
 ansi-colors@^3.0.0, ansi-colors@^3.2.1:
   version "3.2.4"
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
   integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
 
-ansi-colors@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
-  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
-
 ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
@@ -8813,6 +8823,21 @@ check-error@^1.0.2:
   resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
   integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
 
+chokidar@3.4.3, chokidar@^3.4.0:
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
+  integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.5.0"
+  optionalDependencies:
+    fsevents "~2.1.2"
+
 chokidar@^1.6.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
@@ -8848,21 +8873,6 @@ chokidar@^2.0.3, chokidar@^2.0.4, chokidar@^2.1.8:
   optionalDependencies:
     fsevents "^1.2.7"
 
-chokidar@^3.4.0:
-  version "3.4.3"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
-  integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
-  dependencies:
-    anymatch "~3.1.1"
-    braces "~3.0.2"
-    glob-parent "~5.1.0"
-    is-binary-path "~2.1.0"
-    is-glob "~4.0.1"
-    normalize-path "~3.0.0"
-    readdirp "~3.5.0"
-  optionalDependencies:
-    fsevents "~2.1.2"
-
 chokidar@^3.4.1:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1"
@@ -10604,6 +10614,13 @@ debug@3.1.0, debug@=3.1.0, debug@~3.1.0:
   dependencies:
     ms "2.0.0"
 
+debug@4.2.0, debug@^4, debug@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
+  integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
+  dependencies:
+    ms "2.1.2"
+
 debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -10611,13 +10628,6 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
   dependencies:
     ms "^2.1.1"
 
-debug@^4, debug@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
-  integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
-  dependencies:
-    ms "2.1.2"
-
 debuglog@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@@ -10636,6 +10646,11 @@ decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
+decamelize@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
+  integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
+
 decimal.js@^10.2.0:
   version "10.2.0"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
@@ -11085,7 +11100,7 @@ diff@3.5.0, diff@^3.1.0, diff@^3.5.0:
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
   integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
 
-diff@^4.0.1:
+diff@4.0.2, diff@^4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
   integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
@@ -13220,6 +13235,14 @@ find-up@3.0.0, find-up@^3.0.0:
   dependencies:
     locate-path "^3.0.0"
 
+find-up@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+  dependencies:
+    locate-path "^6.0.0"
+    path-exists "^4.0.0"
+
 find-up@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@@ -13276,6 +13299,11 @@ flat-cache@^2.0.1:
     rimraf "2.6.3"
     write "1.0.3"
 
+flat@^5.0.2:
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
+  integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+
 flatmap@0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/flatmap/-/flatmap-0.0.3.tgz#1f18a4d938152d495965f9c958d923ab2dd669b4"
@@ -13956,7 +13984,7 @@ glob2base@^0.0.12:
   dependencies:
     find-index "^0.1.1"
 
-glob@*, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1:
+glob@*, glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
   integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -14267,6 +14295,11 @@ google-libphonenumber@^3.1.6, google-libphonenumber@^3.2.8:
   resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.14.tgz#f206fa466511427c83aa6b893dd8d949eb8e30ae"
   integrity sha512-4r7mQRbk7EUYV1gyfP1SInYuQsjuDtRXCGLSotxeYDJaj/aF1xFO5PV/GSQeIxXWhIw050DujROICvWpZ1XYRw==
 
+google-protobuf@^3.14.0, google-protobuf@^3.6.1:
+  version "3.14.0"
+  resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.14.0.tgz#20373d22046e63831a5110e11a84f713cc43651e"
+  integrity sha512-bwa8dBuMpOxg7COyqkW6muQuvNnWgVN8TX/epDRGW5m0jcrmq2QJyCyiV8ZE2/6LaIIqJtiv9bYokFhfpy/o6w==
+
 got@^6.3.0, got@^6.7.1:
   version "6.7.1"
   resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@@ -14670,7 +14703,7 @@ he@1.1.1:
   resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
   integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
 
-he@1.2.x, he@^1.1.0, he@^1.2.0:
+he@1.2.0, he@1.2.x, he@^1.1.0, he@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -16514,6 +16547,11 @@ is-plain-obj@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.0.0.tgz#7fd1a7f1b69e160cde9181d2313f445c68aa2679"
   integrity sha512-EYisGhpgSCwspmIuRHGjROWTon2Xp8Z7U03Wubk/bTL5TTRC5R1rGVgyjzBrk9+ULdH6cRD06KRcw/xfqhVYKQ==
 
+is-plain-obj@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
+  integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+
 is-plain-object@3.0.0, is-plain-object@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928"
@@ -18185,7 +18223,7 @@ js-tokens@^3.0.2:
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
 
-js-yaml@^3.10.0, js-yaml@^3.14.0:
+js-yaml@3.14.0, js-yaml@^3.10.0, js-yaml@^3.14.0:
   version "3.14.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
   integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
@@ -19089,6 +19127,13 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+locate-path@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+  dependencies:
+    p-locate "^5.0.0"
+
 lodash-es@^4.17.14:
   version "4.17.15"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
@@ -19294,6 +19339,13 @@ log-driver@^1.2.7:
   resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
   integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
 
+log-symbols@4.0.0, log-symbols@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
+  integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
+  dependencies:
+    chalk "^4.0.0"
+
 log-symbols@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
@@ -19315,13 +19367,6 @@ log-symbols@^3.0.0:
   dependencies:
     chalk "^2.4.2"
 
-log-symbols@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
-  integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
-  dependencies:
-    chalk "^4.0.0"
-
 log-update@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708"
@@ -20292,6 +20337,37 @@ mocha@^5.2.0:
     mkdirp "0.5.1"
     supports-color "5.4.0"
 
+mocha@^8.2.1:
+  version "8.2.1"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.2.1.tgz#f2fa68817ed0e53343d989df65ccd358bc3a4b39"
+  integrity sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==
+  dependencies:
+    "@ungap/promise-all-settled" "1.1.2"
+    ansi-colors "4.1.1"
+    browser-stdout "1.3.1"
+    chokidar "3.4.3"
+    debug "4.2.0"
+    diff "4.0.2"
+    escape-string-regexp "4.0.0"
+    find-up "5.0.0"
+    glob "7.1.6"
+    growl "1.10.5"
+    he "1.2.0"
+    js-yaml "3.14.0"
+    log-symbols "4.0.0"
+    minimatch "3.0.4"
+    ms "2.1.2"
+    nanoid "3.1.12"
+    serialize-javascript "5.0.1"
+    strip-json-comments "3.1.1"
+    supports-color "7.2.0"
+    which "2.0.2"
+    wide-align "1.1.3"
+    workerpool "6.0.2"
+    yargs "13.3.2"
+    yargs-parser "13.1.2"
+    yargs-unparser "2.0.0"
+
 mock-stdin@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/mock-stdin/-/mock-stdin-0.3.1.tgz#c657d9642d90786435c64ca5e99bbd4d09bd7dd3"
@@ -20645,16 +20721,16 @@ nan@^2.13.2:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
   integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
 
+nanoid@3.1.12, nanoid@^3.0.2, nanoid@^3.1.3:
+  version "3.1.12"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654"
+  integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==
+
 nanoid@^2.1.0:
   version "2.1.11"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
   integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
 
-nanoid@^3.0.2, nanoid@^3.1.3:
-  version "3.1.12"
-  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654"
-  integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==
-
 nanomatch@^1.2.9:
   version "1.2.13"
   resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -21902,6 +21978,13 @@ p-locate@^4.1.0:
   dependencies:
     p-limit "^2.2.0"
 
+p-locate@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+  dependencies:
+    p-limit "^3.0.2"
+
 p-map-series@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca"
@@ -25664,6 +25747,13 @@ serialize-error@^7.0.1:
   dependencies:
     type-fest "^0.13.1"
 
+serialize-javascript@5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
+  integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
+  dependencies:
+    randombytes "^2.1.0"
+
 serialize-javascript@^2.1.0, serialize-javascript@^2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
@@ -26799,6 +26889,11 @@ strip-indent@^3.0.0:
   dependencies:
     min-indent "^1.0.0"
 
+strip-json-comments@3.1.1, strip-json-comments@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
 strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -26809,11 +26904,6 @@ strip-json-comments@^3.0.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
   integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
 
-strip-json-comments@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
-  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-
 strip-markdown@^3.0.3:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/strip-markdown/-/strip-markdown-3.1.2.tgz#172f6f89f9a98896e65a65422e0507f2bbac1667"
@@ -27041,6 +27131,13 @@ supports-color@5.4.0:
   dependencies:
     has-flag "^3.0.0"
 
+supports-color@7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+  dependencies:
+    has-flag "^4.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -27920,6 +28017,13 @@ ts-pnp@^1.1.2:
   resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
   integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
 
+ts-protoc-gen@^0.14.0:
+  version "0.14.0"
+  resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.14.0.tgz#a6f4c3fc37d1d449915551c18404fb7e9aa8fef6"
+  integrity sha512-2z6w2HioMCMVNcgNHBcEvudmQfzrn+3BjAlz+xgYZ9L0o8n8UG8WUiTJcbXHFiEg2SU8IltwH2pm1otLoMSKwg==
+  dependencies:
+    google-protobuf "^3.6.1"
+
 tsconfig-paths-webpack-plugin@^3.2.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.3.0.tgz#a7461723c20623ca9148621a5ce36532682ad2ff"
@@ -28202,7 +28306,7 @@ typescript-formatter@^7.2.2:
     commandpost "^1.0.0"
     editorconfig "^0.15.0"
 
-typescript@3.5.2, typescript@^3.0.3, typescript@^3.7.2, typescript@^3.7.5, typescript@^3.8.3, typescript@^3.9.5, typescript@^3.9.6, typescript@^3.9.7:
+typescript@3.5.2, typescript@^3.0.3, typescript@^3.7.2, typescript@^3.7.5, typescript@^3.8.3, typescript@^3.9.5, typescript@^3.9.6, typescript@^3.9.7, typescript@^4.1.3:
   version "3.9.7"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
   integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
@@ -29600,14 +29704,14 @@ which@1, which@^1.1.1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
   dependencies:
     isexe "^2.0.0"
 
-which@^2.0.0, which@^2.0.1, which@^2.0.2:
+which@2.0.2, which@^2.0.0, which@^2.0.1, which@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
   integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
   dependencies:
     isexe "^2.0.0"
 
-wide-align@^1.1.0:
+wide-align@1.1.3, wide-align@^1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
   integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
@@ -29674,6 +29778,11 @@ worker-rpc@^0.1.0:
   dependencies:
     microevent.ts "~0.1.1"
 
+workerpool@6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.2.tgz#e241b43d8d033f1beb52c7851069456039d1d438"
+  integrity sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==
+
 wrap-ansi@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -29987,6 +30096,14 @@ yargs-parser@10.x, yargs-parser@^10.0.0:
   dependencies:
     camelcase "^4.1.0"
 
+yargs-parser@13.1.2, yargs-parser@^13.0.0, yargs-parser@^13.1.2:
+  version "13.1.2"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
+  integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@18.x, yargs-parser@^18.1.2, yargs-parser@^18.1.3:
   version "18.1.3"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
@@ -30003,14 +30120,6 @@ yargs-parser@^11.1.1:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs-parser@^13.0.0, yargs-parser@^13.1.2:
-  version "13.1.2"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
-  integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
-  dependencies:
-    camelcase "^5.0.0"
-    decamelize "^1.2.0"
-
 yargs-parser@^13.1.1:
   version "13.1.1"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
@@ -30055,6 +30164,32 @@ yargs-parser@^8.1.0:
   dependencies:
     camelcase "^4.1.0"
 
+yargs-unparser@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
+  integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
+  dependencies:
+    camelcase "^6.0.0"
+    decamelize "^4.0.0"
+    flat "^5.0.2"
+    is-plain-obj "^2.1.0"
+
+yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.2:
+  version "13.3.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
+  integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
+  dependencies:
+    cliui "^5.0.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^3.0.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^13.1.2"
+
 yargs@^10.0.3:
   version "10.1.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
@@ -30091,22 +30226,6 @@ yargs@^12.0.2:
     y18n "^3.2.1 || ^4.0.0"
     yargs-parser "^11.1.1"
 
-yargs@^13.2.2, yargs@^13.3.2:
-  version "13.3.2"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
-  integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
-  dependencies:
-    cliui "^5.0.0"
-    find-up "^3.0.0"
-    get-caller-file "^2.0.1"
-    require-directory "^2.1.1"
-    require-main-filename "^2.0.0"
-    set-blocking "^2.0.0"
-    string-width "^3.0.0"
-    which-module "^2.0.0"
-    y18n "^4.0.0"
-    yargs-parser "^13.1.2"
-
 yargs@^13.3.0:
   version "13.3.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"