Browse Source

query node - storage provider metadata I

ondratra 3 years ago
parent
commit
40de3ce360

+ 4 - 0
query-node/generated/graphql-server/model/index.ts

@@ -14,6 +14,8 @@ import { License } from '../src/modules/license/license.model';
 export { License };
 import { Membership } from '../src/modules/membership/membership.model';
 export { Membership };
+import { StorageProvider } from '../src/modules/storage-provider/storage-provider.model';
+export { StorageProvider };
 import { Video } from '../src/modules/video/video.model';
 export { Video };
 import { VideoCategory } from '../src/modules/video-category/video-category.model';
@@ -31,6 +33,8 @@ import { AssetAvailability } from '../src/modules/enums/enums';
 export { AssetAvailability };
 import { LiaisonJudgement } from '../src/modules/enums/enums';
 export { LiaisonJudgement };
+import { StorageProviderType } from '../src/modules/enums/enums';
+export { StorageProviderType };
 
 import { DataObjectOwnerChannel } from '../src/modules/variants/variants.model';
 export { DataObjectOwnerChannel };

+ 4 - 0
query-node/generated/graphql-server/src/modules/enums/enums.ts

@@ -17,3 +17,7 @@ export enum LiaisonJudgement {
   PENDING = 'PENDING',
   ACCEPTED = 'ACCEPTED',
 }
+export enum StorageProviderType {
+  GATEWAY = 'GATEWAY',
+  STORAGE = 'STORAGE',
+}

+ 21 - 0
query-node/generated/graphql-server/src/modules/storage-provider/storage-provider.model.ts

@@ -0,0 +1,21 @@
+import { BaseModel, Model, EnumField, StringField } from 'warthog';
+
+import { StorageProviderType } from '../enums/enums';
+export { StorageProviderType };
+
+@Model({ api: {} })
+export class StorageProvider extends BaseModel {
+  @EnumField('StorageProviderType', StorageProviderType, {})
+  type!: StorageProviderType;
+
+  @StringField({
+    nullable: true,
+    description: `Custom metadata set by provider`,
+  })
+  metadata?: string;
+
+  constructor(init?: Partial<StorageProvider>) {
+    super();
+    Object.assign(this, init);
+  }
+}

+ 128 - 0
query-node/generated/graphql-server/src/modules/storage-provider/storage-provider.resolver.ts

@@ -0,0 +1,128 @@
+import {
+  Arg,
+  Args,
+  Mutation,
+  Query,
+  Root,
+  Resolver,
+  FieldResolver,
+  ObjectType,
+  Field,
+  Int,
+  ArgsType,
+  Info,
+} from 'type-graphql';
+import graphqlFields from 'graphql-fields';
+import { Inject } from 'typedi';
+import { Min } from 'class-validator';
+import { Fields, StandardDeleteResponse, UserId, PageInfo, RawFields } from 'warthog';
+
+import {
+  StorageProviderCreateInput,
+  StorageProviderCreateManyArgs,
+  StorageProviderUpdateArgs,
+  StorageProviderWhereArgs,
+  StorageProviderWhereInput,
+  StorageProviderWhereUniqueInput,
+  StorageProviderOrderByEnum,
+} from '../../../generated';
+
+import { StorageProvider } from './storage-provider.model';
+import { StorageProviderService } from './storage-provider.service';
+
+@ObjectType()
+export class StorageProviderEdge {
+  @Field(() => StorageProvider, { nullable: false })
+  node!: StorageProvider;
+
+  @Field(() => String, { nullable: false })
+  cursor!: string;
+}
+
+@ObjectType()
+export class StorageProviderConnection {
+  @Field(() => Int, { nullable: false })
+  totalCount!: number;
+
+  @Field(() => [StorageProviderEdge], { nullable: false })
+  edges!: StorageProviderEdge[];
+
+  @Field(() => PageInfo, { nullable: false })
+  pageInfo!: PageInfo;
+}
+
+@ArgsType()
+export class ConnectionPageInputOptions {
+  @Field(() => Int, { nullable: true })
+  @Min(0)
+  first?: number;
+
+  @Field(() => String, { nullable: true })
+  after?: string; // V3: TODO: should we make a RelayCursor scalar?
+
+  @Field(() => Int, { nullable: true })
+  @Min(0)
+  last?: number;
+
+  @Field(() => String, { nullable: true })
+  before?: string;
+}
+
+@ArgsType()
+export class StorageProviderConnectionWhereArgs extends ConnectionPageInputOptions {
+  @Field(() => StorageProviderWhereInput, { nullable: true })
+  where?: StorageProviderWhereInput;
+
+  @Field(() => StorageProviderOrderByEnum, { nullable: true })
+  orderBy?: StorageProviderOrderByEnum;
+}
+
+@Resolver(StorageProvider)
+export class StorageProviderResolver {
+  constructor(@Inject('StorageProviderService') public readonly service: StorageProviderService) {}
+
+  @Query(() => [StorageProvider])
+  async storageProviders(
+    @Args() { where, orderBy, limit, offset }: StorageProviderWhereArgs,
+    @Fields() fields: string[]
+  ): Promise<StorageProvider[]> {
+    return this.service.find<StorageProviderWhereInput>(where, orderBy, limit, offset, fields);
+  }
+
+  @Query(() => StorageProvider, { nullable: true })
+  async storageProviderByUniqueInput(
+    @Arg('where') where: StorageProviderWhereUniqueInput,
+    @Fields() fields: string[]
+  ): Promise<StorageProvider | null> {
+    const result = await this.service.find(where, undefined, 1, 0, fields);
+    return result && result.length >= 1 ? result[0] : null;
+  }
+
+  @Query(() => StorageProviderConnection)
+  async storageProvidersConnection(
+    @Args() { where, orderBy, ...pageOptions }: StorageProviderConnectionWhereArgs,
+    @Info() info: any
+  ): Promise<StorageProviderConnection> {
+    const rawFields = graphqlFields(info, {}, { excludedFields: ['__typename'] });
+
+    let result: any = {
+      totalCount: 0,
+      edges: [],
+      pageInfo: {
+        hasNextPage: false,
+        hasPreviousPage: false,
+      },
+    };
+    // If the related database table does not have any records then an error is thrown to the client
+    // by warthog
+    try {
+      result = await this.service.findConnection<StorageProviderWhereInput>(where, orderBy, pageOptions, rawFields);
+    } catch (err) {
+      console.log(err);
+      // TODO: should continue to return this on `Error: Items is empty` or throw the error
+      if (!(err.message as string).includes('Items is empty')) throw err;
+    }
+
+    return result as Promise<StorageProviderConnection>;
+  }
+}

+ 28 - 0
query-node/generated/graphql-server/src/modules/storage-provider/storage-provider.service.ts

@@ -0,0 +1,28 @@
+import { Service } from 'typedi';
+import { Repository } from 'typeorm';
+import { InjectRepository } from 'typeorm-typedi-extensions';
+import { BaseService, WhereInput } from 'warthog';
+
+import { StorageProvider } from './storage-provider.model';
+
+@Service('StorageProviderService')
+export class StorageProviderService extends BaseService<StorageProvider> {
+  constructor(@InjectRepository(StorageProvider) protected readonly repository: Repository<StorageProvider>) {
+    super(StorageProvider, repository);
+  }
+
+  async find<W extends WhereInput>(
+    where?: any,
+    orderBy?: string,
+    limit?: number,
+    offset?: number,
+    fields?: string[]
+  ): Promise<StorageProvider[]> {
+    let f = fields;
+    if (f == undefined) {
+      f = [];
+    }
+
+    return super.find<W>(where, orderBy, limit, offset, f);
+  }
+}

+ 1 - 1
query-node/generated/graphql-server/src/modules/video/video.model.ts

@@ -25,7 +25,7 @@ export { AssetAvailability };
 
 @Model({ api: {} })
 export class Video extends BaseModel {
-  @ManyToOne(() => Channel, (param: Channel) => param.videos, { skipGraphQLField: true, cascade: ["insert", "update"] })
+  @ManyToOne(() => Channel, (param: Channel) => param.videos, { skipGraphQLField: true, nullable: true, cascade: ["insert", "update"] })
   channel!: Channel;
 
   @ManyToOne(() => VideoCategory, (param: VideoCategory) => param.videos, { skipGraphQLField: true, nullable: true, cascade: ["insert", "update"] })

+ 421 - 0
query-node/generated/types/gateway-working-group.ts

@@ -0,0 +1,421 @@
+import { createTypeUnsafe } from "@polkadot/types/create";
+import { SubstrateEvent, SubstrateExtrinsic } from "@dzlzv/hydra-common";
+import { Codec } from "@polkadot/types/types";
+import { typeRegistry } from ".";
+
+import {
+  ApplicationIdSet,
+  ApplicationIdToWorkerIdMap,
+  OpeningId,
+  RationaleText,
+  RewardPolicy,
+  WorkerId
+} from "@joystream/types/augment";
+import { Bytes, Option, bool } from "@polkadot/types";
+
+export namespace GatewayWorkingGroup {
+  /**
+   *  Emits on updating the worker storage role.
+   *  Params:
+   *  - Id of the worker.
+   *  - Raw storage field.
+   *
+   *  Event parameters: [WorkerId, Vec<u8>, ]
+   */
+  export class WorkerStorageUpdatedEvent {
+    public readonly expectedParamTypes = ["WorkerId", "Vec<u8>"];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): WorkerStorageUpdated_Params {
+      return new WorkerStorageUpdated_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class WorkerStorageUpdated_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get bytes(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.ctx.params[1].value
+      ]);
+    }
+  }
+  /**
+   *  Emits on filling the worker opening.
+   *  Params:
+   *  - Worker opening id
+   *  - Worker application id to the worker id dictionary
+   *
+   *  Event parameters: [OpeningId, ApplicationIdToWorkerIdMap, ]
+   */
+  export class OpeningFilledEvent {
+    public readonly expectedParamTypes = [
+      "OpeningId",
+      "ApplicationIdToWorkerIdMap"
+    ];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): OpeningFilled_Params {
+      return new OpeningFilled_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class OpeningFilled_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get openingId(): OpeningId {
+      return createTypeUnsafe<OpeningId & Codec>(typeRegistry, "OpeningId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get applicationIdToWorkerIdMap(): ApplicationIdToWorkerIdMap {
+      return createTypeUnsafe<ApplicationIdToWorkerIdMap & Codec>(
+        typeRegistry,
+        "ApplicationIdToWorkerIdMap",
+        [this.ctx.params[1].value]
+      );
+    }
+  }
+  /**
+   *  Emits on terminating the worker.
+   *  Params:
+   *  - worker id.
+   *  - termination rationale text
+   *
+   *  Event parameters: [WorkerId, RationaleText, ]
+   */
+  export class TerminatedWorkerEvent {
+    public readonly expectedParamTypes = ["WorkerId", "RationaleText"];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): TerminatedWorker_Params {
+      return new TerminatedWorker_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class TerminatedWorker_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get rationaleText(): RationaleText {
+      return createTypeUnsafe<RationaleText & Codec>(
+        typeRegistry,
+        "RationaleText",
+        [this.ctx.params[1].value]
+      );
+    }
+  }
+  /**
+   *  Emits on exiting the worker.
+   *  Params:
+   *  - worker id.
+   *  - exit rationale text
+   *
+   *  Event parameters: [WorkerId, RationaleText, ]
+   */
+  export class WorkerExitedEvent {
+    public readonly expectedParamTypes = ["WorkerId", "RationaleText"];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): WorkerExited_Params {
+      return new WorkerExited_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class WorkerExited_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get rationaleText(): RationaleText {
+      return createTypeUnsafe<RationaleText & Codec>(
+        typeRegistry,
+        "RationaleText",
+        [this.ctx.params[1].value]
+      );
+    }
+  }
+
+  /**
+   *  Update the associated role storage.
+   */
+  export class UpdateRoleStorageCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = ["WorkerId", "Bytes"];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): UpdateRoleStorage_Args {
+      return new UpdateRoleStorage_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class UpdateRoleStorage_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get storage(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.extrinsic.args[1].value
+      ]);
+    }
+  }
+  /**
+   *  Fill opening for worker/lead.
+   *  Require signed leader origin or the root (to fill opening for the leader position).
+   */
+  export class FillOpeningCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = [
+      "OpeningId",
+      "ApplicationIdSet",
+      "Option<RewardPolicy>"
+    ];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): FillOpening_Args {
+      return new FillOpening_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class FillOpening_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get openingId(): OpeningId {
+      return createTypeUnsafe<OpeningId & Codec>(typeRegistry, "OpeningId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get successfulApplicationIds(): ApplicationIdSet {
+      return createTypeUnsafe<ApplicationIdSet & Codec>(
+        typeRegistry,
+        "ApplicationIdSet",
+        [this.extrinsic.args[1].value]
+      );
+    }
+
+    get rewardPolicy(): Option<RewardPolicy> {
+      return createTypeUnsafe<Option<RewardPolicy> & Codec>(
+        typeRegistry,
+        "Option<RewardPolicy>",
+        [this.extrinsic.args[2].value]
+      );
+    }
+  }
+  /**
+   *  Leave the role by the active worker.
+   */
+  export class LeaveRoleCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = ["WorkerId", "Bytes"];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): LeaveRole_Args {
+      return new LeaveRole_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class LeaveRole_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get rationaleText(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.extrinsic.args[1].value
+      ]);
+    }
+  }
+  /**
+   *  Terminate the active worker by the lead.
+   *  Require signed leader origin or the root (to terminate the leader role).
+   */
+  export class TerminateRoleCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = ["WorkerId", "Bytes", "bool"];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): TerminateRole_Args {
+      return new TerminateRole_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class TerminateRole_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get rationaleText(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.extrinsic.args[1].value
+      ]);
+    }
+
+    get slashStake(): bool {
+      return createTypeUnsafe<bool & Codec>(typeRegistry, "bool", [
+        this.extrinsic.args[2].value
+      ]);
+    }
+  }
+}

+ 2 - 0
query-node/generated/types/index.ts

@@ -12,3 +12,5 @@ export { typeRegistry };
 export * from "./members";
 export * from "./content";
 export * from "./data-directory";
+export * from "./storage-working-group";
+export * from "./gateway-working-group";

+ 421 - 0
query-node/generated/types/storage-working-group.ts

@@ -0,0 +1,421 @@
+import { createTypeUnsafe } from "@polkadot/types/create";
+import { SubstrateEvent, SubstrateExtrinsic } from "@dzlzv/hydra-common";
+import { Codec } from "@polkadot/types/types";
+import { typeRegistry } from ".";
+
+import {
+  ApplicationIdSet,
+  ApplicationIdToWorkerIdMap,
+  OpeningId,
+  RationaleText,
+  RewardPolicy,
+  WorkerId
+} from "@joystream/types/augment";
+import { Bytes, Option, bool } from "@polkadot/types";
+
+export namespace StorageWorkingGroup {
+  /**
+   *  Emits on updating the worker storage role.
+   *  Params:
+   *  - Id of the worker.
+   *  - Raw storage field.
+   *
+   *  Event parameters: [WorkerId, Vec<u8>, ]
+   */
+  export class WorkerStorageUpdatedEvent {
+    public readonly expectedParamTypes = ["WorkerId", "Vec<u8>"];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): WorkerStorageUpdated_Params {
+      return new WorkerStorageUpdated_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class WorkerStorageUpdated_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get bytes(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.ctx.params[1].value
+      ]);
+    }
+  }
+  /**
+   *  Emits on filling the worker opening.
+   *  Params:
+   *  - Worker opening id
+   *  - Worker application id to the worker id dictionary
+   *
+   *  Event parameters: [OpeningId, ApplicationIdToWorkerIdMap, ]
+   */
+  export class OpeningFilledEvent {
+    public readonly expectedParamTypes = [
+      "OpeningId",
+      "ApplicationIdToWorkerIdMap"
+    ];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): OpeningFilled_Params {
+      return new OpeningFilled_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class OpeningFilled_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get openingId(): OpeningId {
+      return createTypeUnsafe<OpeningId & Codec>(typeRegistry, "OpeningId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get applicationIdToWorkerIdMap(): ApplicationIdToWorkerIdMap {
+      return createTypeUnsafe<ApplicationIdToWorkerIdMap & Codec>(
+        typeRegistry,
+        "ApplicationIdToWorkerIdMap",
+        [this.ctx.params[1].value]
+      );
+    }
+  }
+  /**
+   *  Emits on terminating the worker.
+   *  Params:
+   *  - worker id.
+   *  - termination rationale text
+   *
+   *  Event parameters: [WorkerId, RationaleText, ]
+   */
+  export class TerminatedWorkerEvent {
+    public readonly expectedParamTypes = ["WorkerId", "RationaleText"];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): TerminatedWorker_Params {
+      return new TerminatedWorker_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class TerminatedWorker_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get rationaleText(): RationaleText {
+      return createTypeUnsafe<RationaleText & Codec>(
+        typeRegistry,
+        "RationaleText",
+        [this.ctx.params[1].value]
+      );
+    }
+  }
+  /**
+   *  Emits on exiting the worker.
+   *  Params:
+   *  - worker id.
+   *  - exit rationale text
+   *
+   *  Event parameters: [WorkerId, RationaleText, ]
+   */
+  export class WorkerExitedEvent {
+    public readonly expectedParamTypes = ["WorkerId", "RationaleText"];
+
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get data(): WorkerExited_Params {
+      return new WorkerExited_Params(this.ctx);
+    }
+
+    validateParams(): boolean {
+      if (this.expectedParamTypes.length !== this.ctx.params.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedParamTypes.forEach((type, i) => {
+        if (type !== this.ctx.params[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class WorkerExited_Params {
+    constructor(public readonly ctx: SubstrateEvent) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.ctx.params[0].value
+      ]);
+    }
+
+    get rationaleText(): RationaleText {
+      return createTypeUnsafe<RationaleText & Codec>(
+        typeRegistry,
+        "RationaleText",
+        [this.ctx.params[1].value]
+      );
+    }
+  }
+
+  /**
+   *  Update the associated role storage.
+   */
+  export class UpdateRoleStorageCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = ["WorkerId", "Bytes"];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): UpdateRoleStorage_Args {
+      return new UpdateRoleStorage_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class UpdateRoleStorage_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get storage(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.extrinsic.args[1].value
+      ]);
+    }
+  }
+  /**
+   *  Fill opening for worker/lead.
+   *  Require signed leader origin or the root (to fill opening for the leader position).
+   */
+  export class FillOpeningCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = [
+      "OpeningId",
+      "ApplicationIdSet",
+      "Option<RewardPolicy>"
+    ];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): FillOpening_Args {
+      return new FillOpening_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class FillOpening_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get openingId(): OpeningId {
+      return createTypeUnsafe<OpeningId & Codec>(typeRegistry, "OpeningId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get successfulApplicationIds(): ApplicationIdSet {
+      return createTypeUnsafe<ApplicationIdSet & Codec>(
+        typeRegistry,
+        "ApplicationIdSet",
+        [this.extrinsic.args[1].value]
+      );
+    }
+
+    get rewardPolicy(): Option<RewardPolicy> {
+      return createTypeUnsafe<Option<RewardPolicy> & Codec>(
+        typeRegistry,
+        "Option<RewardPolicy>",
+        [this.extrinsic.args[2].value]
+      );
+    }
+  }
+  /**
+   *  Leave the role by the active worker.
+   */
+  export class LeaveRoleCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = ["WorkerId", "Bytes"];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): LeaveRole_Args {
+      return new LeaveRole_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class LeaveRole_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get rationaleText(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.extrinsic.args[1].value
+      ]);
+    }
+  }
+  /**
+   *  Terminate the active worker by the lead.
+   *  Require signed leader origin or the root (to terminate the leader role).
+   */
+  export class TerminateRoleCall {
+    public readonly extrinsic: SubstrateExtrinsic;
+    public readonly expectedArgTypes = ["WorkerId", "Bytes", "bool"];
+
+    constructor(public readonly ctx: SubstrateEvent) {
+      if (ctx.extrinsic === undefined) {
+        throw new Error(`No call data has been provided`);
+      }
+      this.extrinsic = ctx.extrinsic;
+    }
+
+    get args(): TerminateRole_Args {
+      return new TerminateRole_Args(this.extrinsic);
+    }
+
+    validateArgs(): boolean {
+      if (this.expectedArgTypes.length !== this.extrinsic.args.length) {
+        return false;
+      }
+      let valid = true;
+      this.expectedArgTypes.forEach((type, i) => {
+        if (type !== this.extrinsic.args[i].type) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+  }
+
+  class TerminateRole_Args {
+    constructor(public readonly extrinsic: SubstrateExtrinsic) {}
+
+    get workerId(): WorkerId {
+      return createTypeUnsafe<WorkerId & Codec>(typeRegistry, "WorkerId", [
+        this.extrinsic.args[0].value
+      ]);
+    }
+
+    get rationaleText(): Bytes {
+      return createTypeUnsafe<Bytes & Codec>(typeRegistry, "Bytes", [
+        this.extrinsic.args[1].value
+      ]);
+    }
+
+    get slashStake(): bool {
+      return createTypeUnsafe<bool & Codec>(typeRegistry, "bool", [
+        this.extrinsic.args[2].value
+      ]);
+    }
+  }
+}

+ 42 - 0
query-node/manifest.yml

@@ -52,6 +52,17 @@ typegen:
     - data_directory.ContentAccepted
     - data_directory.ContentRejected
     - data_directory.ContentUploadingStatusUpdated
+
+    # working groups
+    - storage_working_group.WorkerStorageUpdated
+    - storage_working_group.OpeningFilled
+    - storage_working_group.TerminatedWorker
+    - storage_working_group.WorkerExited
+
+    - gateway_working_group.WorkerStorageUpdated
+    - gateway_working_group.OpeningFilled
+    - gateway_working_group.TerminatedWorker
+    - gateway_working_group.WorkerExited
   calls:
     # members
     - members.buyMembership
@@ -96,6 +107,16 @@ typegen:
     - data_directory.accept_content
     - data_directory.update_content_uploading_status
 
+    # working groups
+    - storage_working_group.update_role_storage
+    - storage_working_group.fill_opening
+    - storage_working_group.leave_role
+    - storage_working_group.terminate_role
+
+    - gateway_working_group.update_role_storage
+    - gateway_working_group.fill_opening
+    - gateway_working_group.leave_role
+    - gateway_working_group.terminate_role
   outDir: ./generated/types
   customTypes:
     lib: '@joystream/types/augment'
@@ -180,6 +201,27 @@ mappings:
     #- event: dataDirectory.ContentUploadingStatusUpdated
     #  handler: data_directory_ContentUploadingStatusUpdated(DatabaseManager, SubstrateEvent)
 
+    # working groups
+    - event: storageWorkingGroup.WorkerStorageUpdated
+      handler: storageWorkingGroup_WorkerStorageUpdated(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.OpeningFilled
+      handler: storageWorkingGroup_OpeningFilled(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.TerminatedWorker
+      handler: storageWorkingGroup_TerminatedWorker(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.WorkerExited
+      handler: storageWorkingGroup_WorkerExited(DatabaseManager, SubstrateEvent)
+
+
+
+    - event: gatewayWorkingGroup.WorkerStorageUpdated
+      handler: gatewayWorkingGroup_WorkerStorageUpdated(DatabaseManager, SubstrateEvent)
+    - event: gatewayWorkingGroup.OpeningFilled
+      handler: gatewayWorkingGroup_OpeningFilled(DatabaseManager, SubstrateEvent)
+    - event: gatewayWorkingGroup.TerminatedWorker
+      handler: gatewayWorkingGroup_TerminatedWorker(DatabaseManager, SubstrateEvent)
+    - event: gatewayWorkingGroup.WorkerExited
+      handler: gatewayWorkingGroup_WorkerExited(DatabaseManager, SubstrateEvent)
+
   extrinsicHandlers:
     # infer defaults here
     #- extrinsic: Balances.Transfer

+ 160 - 0
query-node/mappings/src/workingGroup.ts

@@ -0,0 +1,160 @@
+import { SubstrateEvent } from '@dzlzv/hydra-common'
+import { DatabaseManager } from '@dzlzv/hydra-db-utils'
+import { FindConditions } from 'typeorm'
+
+import {
+  inconsistentState,
+  logger,
+} from './common'
+
+import {
+  Channel,
+  StorageProvider,
+  StorageProviderType,
+} from 'query-node'
+import {
+  GatewayWorkingGroup,
+  StorageWorkingGroup,
+} from '../../generated/types'
+import {
+  ApplicationId,
+  ApplicationIdToWorkerIdMap,
+  WorkerId,
+} from "@joystream/types/augment";
+
+/////////////////// Storage working group //////////////////////////////////////
+
+export async function storageWorkingGroup_OpeningFilled(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {applicationIdToWorkerIdMap} = new StorageWorkingGroup.OpeningFilledEvent(event).data
+
+  // call generic processing
+  await workingGroup_OpeningFilled(db, StorageProviderType.STORAGE, applicationIdToWorkerIdMap)
+}
+
+export async function storageWorkingGroup_WorkerStorageUpdated(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {workerId: storageProviderId, bytes: newMetadata} = new StorageWorkingGroup.WorkerStorageUpdatedEvent(event).data
+
+  // call generic processing
+  await workingGroup_WorkerStorageUpdated(db, storageProviderId, newMetadata.toString())
+}
+
+export async function storageWorkingGroup_TerminatedWorker(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {workerId: storageProviderId} = new StorageWorkingGroup.TerminatedWorkerEvent(event).data
+
+  // call generic processing
+  await workingGroup_TerminatedWorker(db, storageProviderId)
+}
+
+export async function storageWorkingGroup_WorkerExited(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {workerId: storageProviderId} = new StorageWorkingGroup.WorkerExitedEvent(event).data
+
+  // call generic processing
+  await workingGroup_WorkerExited(db, storageProviderId)
+}
+
+/////////////////// Gateway working group //////////////////////////////////////
+
+export async function gatewayWorkingGroup_OpeningFilled(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {applicationIdToWorkerIdMap} = new GatewayWorkingGroup.OpeningFilledEvent(event).data
+
+  // call generic processing
+  await workingGroup_OpeningFilled(db, StorageProviderType.GATEWAY, applicationIdToWorkerIdMap)
+}
+
+export async function gatewayWorkingGroup_WorkerStorageUpdated(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {workerId: storageProviderId, bytes: newMetadata} = new GatewayWorkingGroup.WorkerStorageUpdatedEvent(event).data
+
+  // call generic processing
+  await workingGroup_WorkerStorageUpdated(db, storageProviderId, newMetadata.toString())
+}
+
+export async function gatewayWorkingGroup_TerminatedWorker(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {workerId: storageProviderId} = new StorageWorkingGroup.TerminatedWorkerEvent(event).data
+
+  // call generic processing
+  await workingGroup_TerminatedWorker(db, storageProviderId)
+}
+
+export async function gatewayWorkingGroup_WorkerExited(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
+  // read event data
+  const {workerId: storageProviderId} = new StorageWorkingGroup.WorkerExitedEvent(event).data
+
+  // call generic processing
+  await workingGroup_WorkerExited(db, storageProviderId)
+}
+
+/////////////////// Generic working group processing ///////////////////////////
+
+export async function workingGroup_OpeningFilled(
+  db: DatabaseManager,
+  storageProviderType: StorageProviderType,
+  applicationIdToWorkerIdMap: ApplicationIdToWorkerIdMap
+): Promise<void> {
+  const storageProviderIds = [...applicationIdToWorkerIdMap.values()].map(item => item[1])
+
+  for (const storageProviderId of storageProviderIds) {
+    const storageProvider = {
+      id: storageProviderId.toString(),
+      type: storageProviderType,
+    }
+
+    await db.save<StorageProvider>(storageProvider)
+  }
+
+  // emit log event
+  logger.info("Storage provider has beed created", {ids: storageProviderIds.map(item => item.toString())})
+}
+
+export async function workingGroup_WorkerStorageUpdated(db: DatabaseManager, storageProviderId: WorkerId, newMetadata: string): Promise<void> {
+  // load storage provider
+  const storageProvider = await db.get(StorageProvider, { where: { id: storageProviderId.toString() } as FindConditions<StorageProvider> })
+
+  // ensure storageProvider exists
+  if (!storageProvider) {
+    return inconsistentState('Non-existing storage provider update requested', storageProviderId)
+  }
+
+  storageProvider.metadata = newMetadata
+
+  await db.save<StorageProvider>(storageProvider)
+
+  // emit log event
+  logger.info("Storage provider has been updated", {id: storageProviderId})
+}
+
+export async function workingGroup_TerminatedWorker(db: DatabaseManager, storageProviderId: WorkerId): Promise<void> {
+  // do removal logic
+  await removeStorageProvider(db, storageProviderId)
+
+  // emit log event
+  logger.info("Storage provider has beed removed (worker terminated)", {id: storageProviderId})
+}
+
+export async function workingGroup_WorkerExited(db: DatabaseManager, storageProviderId: WorkerId): Promise<void> {
+  // do removal logic
+  await removeStorageProvider(db, storageProviderId)
+
+  // emit log event
+  logger.info("Storage provider has beed removed (worker exited)", {id: storageProviderId})
+}
+
+/////////////////// Helpers ////////////////////////////////////////////////////
+
+async function removeStorageProvider(db: DatabaseManager, storageProviderId: WorkerId) {
+  // load storage provider
+  const storageProvider = await db.get(StorageProvider, { where: { id: storageProviderId.toString() } as FindConditions<StorageProvider> })
+
+  // ensure storageProvider exists
+  if (!storageProvider) {
+    return inconsistentState('Non-existing storage provider deletion requested', storageProviderId)
+  }
+
+  await db.remove<StorageProvider>(storageProvider)
+}

+ 15 - 0
query-node/schema.graphql

@@ -386,3 +386,18 @@ type FeaturedVideo @entity {
   "Reference to a video"
   video: Video!
 }
+
+enum StorageProviderType {
+  GATEWAY
+  STORAGE
+}
+
+type StorageProvider @entity {
+  "Runtime entity identifier (EntityId)"
+  id: ID!
+
+  type: StorageProviderType!
+
+  "Custom metadata set by provider"
+  metadata: String
+}