Browse Source

CLI - update membership data fetching & context selection

Leszek Wiesner 3 years ago
parent
commit
a6442215e6

+ 44 - 37
cli/src/Api.ts

@@ -19,14 +19,7 @@ import {
 } from './Types'
 import { DeriveBalancesAll } from '@polkadot/api-derive/types'
 import { CLIError } from '@oclif/errors'
-import {
-  Worker,
-  WorkerId,
-  OpeningId,
-  Application,
-  ApplicationId,
-  Opening,
-} from '@joystream/types/working-group'
+import { Worker, WorkerId, OpeningId, Application, ApplicationId, Opening } from '@joystream/types/working-group'
 import { Membership, StakingAccountMemberBinding } from '@joystream/types/members'
 import { MemberId, ChannelId, AccountId } from '@joystream/types/common'
 import {
@@ -39,6 +32,8 @@ import {
   VideoCategoryId,
 } from '@joystream/types/content'
 import { BagId, DataObject, DataObjectId } from '@joystream/types/storage'
+import QueryNodeApi from './QueryNodeApi'
+import { MembershipFieldsFragment } from './graphql/generated/queries'
 
 export const DEFAULT_API_URI = 'ws://localhost:9944/'
 
@@ -67,11 +62,13 @@ export const lockIdByWorkingGroup: { [K in WorkingGroups]: string } = {
 // Api wrapper for handling most common api calls and allowing easy API implementation switch in the future
 export default class Api {
   private _api: ApiPromise
+  private _qnApi: QueryNodeApi | undefined
   public isDevelopment = false
 
-  private constructor(originalApi: ApiPromise, isDevelopment: boolean) {
+  private constructor(originalApi: ApiPromise, isDevelopment: boolean, qnApi?: QueryNodeApi) {
     this.isDevelopment = isDevelopment
     this._api = originalApi
+    this._qnApi = qnApi
   }
 
   public getOriginalApi(): ApiPromise {
@@ -103,9 +100,13 @@ export default class Api {
     return { api, properties, chainType }
   }
 
-  static async create(apiUri = DEFAULT_API_URI, metadataCache: Record<string, any>): Promise<Api> {
+  static async create(
+    apiUri = DEFAULT_API_URI,
+    metadataCache: Record<string, any>,
+    qnApi?: QueryNodeApi
+  ): Promise<Api> {
     const { api, chainType } = await Api.initApi(apiUri, metadataCache)
-    return new Api(api, chainType.isDevelopment || chainType.isLocal)
+    return new Api(api, chainType.isDevelopment || chainType.isLocal, qnApi)
   }
 
   async bestNumber(): Promise<number> {
@@ -134,10 +135,6 @@ export default class Api {
     return paymentInfo.partialFee
   }
 
-  createTransferTx(recipient: string, amount: BN) {
-    return this._api.tx.balances.transfer(recipient, amount)
-  }
-
   // Working groups
   // TODO: This is a lot of repeated logic from "/pioneer/joy-utils/transport"
   // It will be refactored to "joystream-js" soon
@@ -164,22 +161,30 @@ export default class Api {
     return new Date(blockTime.toNumber())
   }
 
-  protected workingGroupApiQuery(group: WorkingGroups) {
+  protected workingGroupApiQuery<T extends WorkingGroups>(group: T): ApiPromise['query'][typeof apiModuleByGroup[T]] {
     const module = apiModuleByGroup[group]
     return this._api.query[module]
   }
 
-  // TODO: old fetchMemberQueryNodeData moved to QueryNodeApi and will be made available here
-
-  async memberDetails(memberId: MemberId, membership: Membership): Promise<MemberDetails> {
-    const memberData = await this.fetchMemberQueryNodeData(memberId)
+  async membersDetails(entries: [MemberId, Membership][]): Promise<MemberDetails[]> {
+    const membersQnData = await this._qnApi?.membersByIds(entries.map(([id]) => id))
+    const memberQnDataById = new Map<string, MembershipFieldsFragment>()
+    membersQnData?.forEach((m) => {
+      memberQnDataById.set(m.id, m)
+    })
 
-    return {
+    return entries.map(([memberId, membership]) => ({
       id: memberId,
-      name: memberData?.metadata.name,
-      handle: memberData?.handle,
+      name: memberQnDataById.get(memberId.toString())?.metadata.name,
+      handle: memberQnDataById.get(memberId.toString())?.handle,
       membership,
-    }
+    }))
+  }
+
+  // TODO: Try to avoid fetching members "one-by-one" whenever possible
+  async memberDetails(memberId: MemberId, membership: Membership): Promise<MemberDetails> {
+    const [memberDetails] = await this.membersDetails([[memberId, membership]])
+    return memberDetails
   }
 
   protected async membershipById(memberId: MemberId): Promise<MemberDetails | null> {
@@ -196,6 +201,21 @@ export default class Api {
     return member
   }
 
+  async getMembers(ids: MemberId[] | number[]): Promise<Membership[]> {
+    return this._api.query.members.membershipById.multi(ids)
+  }
+
+  async membersDetailsByIds(ids: MemberId[] | number[]): Promise<MemberDetails[]> {
+    const memberships = await this.getMembers(ids)
+    const entries: [MemberId, Membership][] = ids.map((id, i) => [createType('MemberId', id), memberships[i]])
+    return this.membersDetails(entries)
+  }
+
+  async allMembersDetails(): Promise<MemberDetails[]> {
+    const entries = await this.entriesByIds(this._api.query.members.membershipById)
+    return this.membersDetails(entries)
+  }
+
   async groupLead(group: WorkingGroups): Promise<GroupMember | null> {
     const optLeadId = await this.workingGroupApiQuery(group).currentLead()
 
@@ -446,19 +466,6 @@ export default class Api {
     ])
   }
 
-  async getMembers(ids: MemberId[] | number[]): Promise<Membership[]> {
-    return this._api.query.members.membershipById.multi(ids)
-  }
-
-  async memberEntriesByIds(ids: MemberId[] | number[]): Promise<[MemberId, Membership][]> {
-    const memberships = await this._api.query.members.membershipById.multi<Membership>(ids)
-    return ids.map((id, i) => [createType('MemberId', id), memberships[i]])
-  }
-
-  allMemberEntries(): Promise<[MemberId, Membership][]> {
-    return this.entriesByIds(this._api.query.members.membershipById)
-  }
-
   async stakingAccountStatus(account: string): Promise<StakingAccountMemberBinding | null> {
     const status = await this.getOriginalApi().query.members.stakingAccountIdMemberStatus(account)
     return status.isEmpty ? null : status

+ 8 - 8
cli/src/QueryNodeApi.ts

@@ -24,9 +24,9 @@ import {
   GetDataObjectsByChannelId,
   GetDataObjectsByChannelIdQuery,
   GetDataObjectsByChannelIdQueryVariables,
-  GetMemberById,
-  GetMemberByIdQuery,
-  GetMemberByIdQueryVariables,
+  GetMembersByIds,
+  GetMembersByIdsQuery,
+  GetMembersByIdsQueryVariables,
   MembershipFieldsFragment,
 } from './graphql/generated/queries'
 import { URL } from 'url'
@@ -128,13 +128,13 @@ export default class QueryNodeApi {
     return validNodesInfo
   }
 
-  async fetchMemberQueryNodeData(memberId: MemberId): Promise<MembershipFieldsFragment | null | undefined> {
-    return this.uniqueEntityQuery<GetMemberByIdQuery, GetMemberByIdQueryVariables>(
-      GetMemberById,
+  async membersByIds(ids: MemberId[] | string[]): Promise<MembershipFieldsFragment[]> {
+    return this.multipleEntitiesQuery<GetMembersByIdsQuery, GetMembersByIdsQueryVariables>(
+      GetMembersByIds,
       {
-        id: memberId.toString(),
+        ids: ids.map((id) => id.toString()),
       },
-      'membershipByUniqueInput'
+      'memberships'
     )
   }
 }

+ 0 - 636
cli/src/QueryNodeApiSchema.generated.ts

@@ -1,636 +0,0 @@
-export type Maybe<T> = T | null
-export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }
-export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> }
-export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> }
-/** All built-in and custom scalars, mapped to their actual values */
-export type Scalars = {
-  ID: string
-  String: string
-  Boolean: boolean
-  Int: number
-  Float: number
-  /** The javascript `Date` as string. Type represents date and time as the ISO Date string. */
-  DateTime: any
-  /** GraphQL representation of BigInt */
-  BigInt: any
-}
-
-export type BaseGraphQlObject = {
-  id: Scalars['ID']
-  createdAt: Scalars['DateTime']
-  createdById: Scalars['String']
-  updatedAt?: Maybe<Scalars['DateTime']>
-  updatedById?: Maybe<Scalars['String']>
-  deletedAt?: Maybe<Scalars['DateTime']>
-  deletedById?: Maybe<Scalars['String']>
-  version: Scalars['Int']
-}
-
-export type BaseModel = BaseGraphQlObject & {
-  __typename?: 'BaseModel'
-  id: Scalars['ID']
-  createdAt: Scalars['DateTime']
-  createdById: Scalars['String']
-  updatedAt?: Maybe<Scalars['DateTime']>
-  updatedById?: Maybe<Scalars['String']>
-  deletedAt?: Maybe<Scalars['DateTime']>
-  deletedById?: Maybe<Scalars['String']>
-  version: Scalars['Int']
-}
-
-export type BaseModelUuid = BaseGraphQlObject & {
-  __typename?: 'BaseModelUUID'
-  id: Scalars['ID']
-  createdAt: Scalars['DateTime']
-  createdById: Scalars['String']
-  updatedAt?: Maybe<Scalars['DateTime']>
-  updatedById?: Maybe<Scalars['String']>
-  deletedAt?: Maybe<Scalars['DateTime']>
-  deletedById?: Maybe<Scalars['String']>
-  version: Scalars['Int']
-}
-
-export type BaseWhereInput = {
-  id_eq?: Maybe<Scalars['String']>
-  id_in?: Maybe<Array<Scalars['String']>>
-  createdAt_eq?: Maybe<Scalars['String']>
-  createdAt_lt?: Maybe<Scalars['String']>
-  createdAt_lte?: Maybe<Scalars['String']>
-  createdAt_gt?: Maybe<Scalars['String']>
-  createdAt_gte?: Maybe<Scalars['String']>
-  createdById_eq?: Maybe<Scalars['String']>
-  updatedAt_eq?: Maybe<Scalars['String']>
-  updatedAt_lt?: Maybe<Scalars['String']>
-  updatedAt_lte?: Maybe<Scalars['String']>
-  updatedAt_gt?: Maybe<Scalars['String']>
-  updatedAt_gte?: Maybe<Scalars['String']>
-  updatedById_eq?: Maybe<Scalars['String']>
-  deletedAt_all?: Maybe<Scalars['Boolean']>
-  deletedAt_eq?: Maybe<Scalars['String']>
-  deletedAt_lt?: Maybe<Scalars['String']>
-  deletedAt_lte?: Maybe<Scalars['String']>
-  deletedAt_gt?: Maybe<Scalars['String']>
-  deletedAt_gte?: Maybe<Scalars['String']>
-  deletedById_eq?: Maybe<Scalars['String']>
-}
-
-export type Block = BaseGraphQlObject & {
-  __typename?: 'Block'
-  id: Scalars['ID']
-  createdAt: Scalars['DateTime']
-  createdById: Scalars['String']
-  updatedAt?: Maybe<Scalars['DateTime']>
-  updatedById?: Maybe<Scalars['String']>
-  deletedAt?: Maybe<Scalars['DateTime']>
-  deletedById?: Maybe<Scalars['String']>
-  version: Scalars['Int']
-  block: Scalars['Int']
-  executedAt: Scalars['DateTime']
-  network: Network
-  membershipregisteredAtBlock?: Maybe<Array<Membership>>
-}
-
-export type BlockConnection = {
-  __typename?: 'BlockConnection'
-  totalCount: Scalars['Int']
-  edges: Array<BlockEdge>
-  pageInfo: PageInfo
-}
-
-export type BlockCreateInput = {
-  block: Scalars['Float']
-  executedAt: Scalars['DateTime']
-  network: Network
-}
-
-export type BlockEdge = {
-  __typename?: 'BlockEdge'
-  node: Block
-  cursor: Scalars['String']
-}
-
-export enum BlockOrderByInput {
-  CreatedAtAsc = 'createdAt_ASC',
-  CreatedAtDesc = 'createdAt_DESC',
-  UpdatedAtAsc = 'updatedAt_ASC',
-  UpdatedAtDesc = 'updatedAt_DESC',
-  DeletedAtAsc = 'deletedAt_ASC',
-  DeletedAtDesc = 'deletedAt_DESC',
-  BlockAsc = 'block_ASC',
-  BlockDesc = 'block_DESC',
-  ExecutedAtAsc = 'executedAt_ASC',
-  ExecutedAtDesc = 'executedAt_DESC',
-  NetworkAsc = 'network_ASC',
-  NetworkDesc = 'network_DESC',
-}
-
-export type BlockUpdateInput = {
-  block?: Maybe<Scalars['Float']>
-  executedAt?: Maybe<Scalars['DateTime']>
-  network?: Maybe<Network>
-}
-
-export type BlockWhereInput = {
-  id_eq?: Maybe<Scalars['ID']>
-  id_in?: Maybe<Array<Scalars['ID']>>
-  createdAt_eq?: Maybe<Scalars['DateTime']>
-  createdAt_lt?: Maybe<Scalars['DateTime']>
-  createdAt_lte?: Maybe<Scalars['DateTime']>
-  createdAt_gt?: Maybe<Scalars['DateTime']>
-  createdAt_gte?: Maybe<Scalars['DateTime']>
-  createdById_eq?: Maybe<Scalars['ID']>
-  createdById_in?: Maybe<Array<Scalars['ID']>>
-  updatedAt_eq?: Maybe<Scalars['DateTime']>
-  updatedAt_lt?: Maybe<Scalars['DateTime']>
-  updatedAt_lte?: Maybe<Scalars['DateTime']>
-  updatedAt_gt?: Maybe<Scalars['DateTime']>
-  updatedAt_gte?: Maybe<Scalars['DateTime']>
-  updatedById_eq?: Maybe<Scalars['ID']>
-  updatedById_in?: Maybe<Array<Scalars['ID']>>
-  deletedAt_all?: Maybe<Scalars['Boolean']>
-  deletedAt_eq?: Maybe<Scalars['DateTime']>
-  deletedAt_lt?: Maybe<Scalars['DateTime']>
-  deletedAt_lte?: Maybe<Scalars['DateTime']>
-  deletedAt_gt?: Maybe<Scalars['DateTime']>
-  deletedAt_gte?: Maybe<Scalars['DateTime']>
-  deletedById_eq?: Maybe<Scalars['ID']>
-  deletedById_in?: Maybe<Array<Scalars['ID']>>
-  block_eq?: Maybe<Scalars['Int']>
-  block_gt?: Maybe<Scalars['Int']>
-  block_gte?: Maybe<Scalars['Int']>
-  block_lt?: Maybe<Scalars['Int']>
-  block_lte?: Maybe<Scalars['Int']>
-  block_in?: Maybe<Array<Scalars['Int']>>
-  executedAt_eq?: Maybe<Scalars['DateTime']>
-  executedAt_lt?: Maybe<Scalars['DateTime']>
-  executedAt_lte?: Maybe<Scalars['DateTime']>
-  executedAt_gt?: Maybe<Scalars['DateTime']>
-  executedAt_gte?: Maybe<Scalars['DateTime']>
-  network_eq?: Maybe<Network>
-  network_in?: Maybe<Array<Network>>
-}
-
-export type BlockWhereUniqueInput = {
-  id: Scalars['ID']
-}
-
-export type DeleteResponse = {
-  id: Scalars['ID']
-}
-
-export type MembersByHandleFtsOutput = {
-  __typename?: 'MembersByHandleFTSOutput'
-  item: MembersByHandleSearchResult
-  rank: Scalars['Float']
-  isTypeOf: Scalars['String']
-  highlight: Scalars['String']
-}
-
-export type MembersByHandleSearchResult = Membership
-
-/** Stored information about a registered user */
-export type Membership = BaseGraphQlObject & {
-  __typename?: 'Membership'
-  id: Scalars['ID']
-  createdAt: Scalars['DateTime']
-  createdById: Scalars['String']
-  updatedAt?: Maybe<Scalars['DateTime']>
-  updatedById?: Maybe<Scalars['String']>
-  deletedAt?: Maybe<Scalars['DateTime']>
-  deletedById?: Maybe<Scalars['String']>
-  version: Scalars['Int']
-  /** The unique handle chosen by member */
-  handle: Scalars['String']
-  /** Member's name */
-  name?: Maybe<Scalars['String']>
-  /** A Url to member's Avatar image */
-  avatarUri?: Maybe<Scalars['String']>
-  /** Short text chosen by member to share information about themselves */
-  about?: Maybe<Scalars['String']>
-  /** Member's controller account id */
-  controllerAccount: Scalars['String']
-  /** Member's root account id */
-  rootAccount: Scalars['String']
-  registeredAtBlock: Block
-  registeredAtBlockId: Scalars['String']
-  /** Timestamp when member was registered */
-  registeredAtTime: Scalars['DateTime']
-  /** How the member was registered */
-  entry: MembershipEntryMethod
-  /** Whether member has been verified by membership working group. */
-  isVerified: Scalars['Boolean']
-  /** Staking accounts bounded to membership. */
-  boundAccounts: Array<Scalars['String']>
-  /** Current count of invites left to send. */
-  inviteCount: Scalars['Int']
-  invitees: Array<Membership>
-  invitedBy?: Maybe<Membership>
-  invitedById?: Maybe<Scalars['String']>
-  referredMembers: Array<Membership>
-  referredBy?: Maybe<Membership>
-  referredById?: Maybe<Scalars['String']>
-}
-
-export type MembershipConnection = {
-  __typename?: 'MembershipConnection'
-  totalCount: Scalars['Int']
-  edges: Array<MembershipEdge>
-  pageInfo: PageInfo
-}
-
-export type MembershipCreateInput = {
-  handle: Scalars['String']
-  name?: Maybe<Scalars['String']>
-  avatarUri?: Maybe<Scalars['String']>
-  about?: Maybe<Scalars['String']>
-  controllerAccount: Scalars['String']
-  rootAccount: Scalars['String']
-  registeredAtBlockId: Scalars['ID']
-  registeredAtTime: Scalars['DateTime']
-  entry: MembershipEntryMethod
-  isVerified: Scalars['Boolean']
-  boundAccounts: Array<Scalars['String']>
-  inviteCount: Scalars['Float']
-  invitedById?: Maybe<Scalars['ID']>
-  referredById?: Maybe<Scalars['ID']>
-}
-
-export type MembershipEdge = {
-  __typename?: 'MembershipEdge'
-  node: Membership
-  cursor: Scalars['String']
-}
-
-export enum MembershipEntryMethod {
-  Paid = 'PAID',
-  Invited = 'INVITED',
-  Genesis = 'GENESIS',
-}
-
-export enum MembershipOrderByInput {
-  CreatedAtAsc = 'createdAt_ASC',
-  CreatedAtDesc = 'createdAt_DESC',
-  UpdatedAtAsc = 'updatedAt_ASC',
-  UpdatedAtDesc = 'updatedAt_DESC',
-  DeletedAtAsc = 'deletedAt_ASC',
-  DeletedAtDesc = 'deletedAt_DESC',
-  HandleAsc = 'handle_ASC',
-  HandleDesc = 'handle_DESC',
-  NameAsc = 'name_ASC',
-  NameDesc = 'name_DESC',
-  AvatarUriAsc = 'avatarUri_ASC',
-  AvatarUriDesc = 'avatarUri_DESC',
-  AboutAsc = 'about_ASC',
-  AboutDesc = 'about_DESC',
-  ControllerAccountAsc = 'controllerAccount_ASC',
-  ControllerAccountDesc = 'controllerAccount_DESC',
-  RootAccountAsc = 'rootAccount_ASC',
-  RootAccountDesc = 'rootAccount_DESC',
-  RegisteredAtBlockIdAsc = 'registeredAtBlockId_ASC',
-  RegisteredAtBlockIdDesc = 'registeredAtBlockId_DESC',
-  RegisteredAtTimeAsc = 'registeredAtTime_ASC',
-  RegisteredAtTimeDesc = 'registeredAtTime_DESC',
-  EntryAsc = 'entry_ASC',
-  EntryDesc = 'entry_DESC',
-  IsVerifiedAsc = 'isVerified_ASC',
-  IsVerifiedDesc = 'isVerified_DESC',
-  InviteCountAsc = 'inviteCount_ASC',
-  InviteCountDesc = 'inviteCount_DESC',
-  InvitedByIdAsc = 'invitedById_ASC',
-  InvitedByIdDesc = 'invitedById_DESC',
-  ReferredByIdAsc = 'referredById_ASC',
-  ReferredByIdDesc = 'referredById_DESC',
-}
-
-export type MembershipSystem = BaseGraphQlObject & {
-  __typename?: 'MembershipSystem'
-  id: Scalars['ID']
-  createdAt: Scalars['DateTime']
-  createdById: Scalars['String']
-  updatedAt?: Maybe<Scalars['DateTime']>
-  updatedById?: Maybe<Scalars['String']>
-  deletedAt?: Maybe<Scalars['DateTime']>
-  deletedById?: Maybe<Scalars['String']>
-  version: Scalars['Int']
-  /** Initial invitation count of a new member. */
-  defaultInviteCount: Scalars['Int']
-  /** Current price to buy a membership. */
-  membershipPrice: Scalars['BigInt']
-  /** Amount of tokens diverted to invitor. */
-  referralCut: Scalars['BigInt']
-  /** The initial, locked, balance credited to controller account of invitee. */
-  invitedInitialBalance: Scalars['BigInt']
-}
-
-export type MembershipSystemConnection = {
-  __typename?: 'MembershipSystemConnection'
-  totalCount: Scalars['Int']
-  edges: Array<MembershipSystemEdge>
-  pageInfo: PageInfo
-}
-
-export type MembershipSystemCreateInput = {
-  defaultInviteCount: Scalars['Float']
-  membershipPrice: Scalars['BigInt']
-  referralCut: Scalars['BigInt']
-  invitedInitialBalance: Scalars['BigInt']
-}
-
-export type MembershipSystemEdge = {
-  __typename?: 'MembershipSystemEdge'
-  node: MembershipSystem
-  cursor: Scalars['String']
-}
-
-export enum MembershipSystemOrderByInput {
-  CreatedAtAsc = 'createdAt_ASC',
-  CreatedAtDesc = 'createdAt_DESC',
-  UpdatedAtAsc = 'updatedAt_ASC',
-  UpdatedAtDesc = 'updatedAt_DESC',
-  DeletedAtAsc = 'deletedAt_ASC',
-  DeletedAtDesc = 'deletedAt_DESC',
-  DefaultInviteCountAsc = 'defaultInviteCount_ASC',
-  DefaultInviteCountDesc = 'defaultInviteCount_DESC',
-  MembershipPriceAsc = 'membershipPrice_ASC',
-  MembershipPriceDesc = 'membershipPrice_DESC',
-  ReferralCutAsc = 'referralCut_ASC',
-  ReferralCutDesc = 'referralCut_DESC',
-  InvitedInitialBalanceAsc = 'invitedInitialBalance_ASC',
-  InvitedInitialBalanceDesc = 'invitedInitialBalance_DESC',
-}
-
-export type MembershipSystemUpdateInput = {
-  defaultInviteCount?: Maybe<Scalars['Float']>
-  membershipPrice?: Maybe<Scalars['BigInt']>
-  referralCut?: Maybe<Scalars['BigInt']>
-  invitedInitialBalance?: Maybe<Scalars['BigInt']>
-}
-
-export type MembershipSystemWhereInput = {
-  id_eq?: Maybe<Scalars['ID']>
-  id_in?: Maybe<Array<Scalars['ID']>>
-  createdAt_eq?: Maybe<Scalars['DateTime']>
-  createdAt_lt?: Maybe<Scalars['DateTime']>
-  createdAt_lte?: Maybe<Scalars['DateTime']>
-  createdAt_gt?: Maybe<Scalars['DateTime']>
-  createdAt_gte?: Maybe<Scalars['DateTime']>
-  createdById_eq?: Maybe<Scalars['ID']>
-  createdById_in?: Maybe<Array<Scalars['ID']>>
-  updatedAt_eq?: Maybe<Scalars['DateTime']>
-  updatedAt_lt?: Maybe<Scalars['DateTime']>
-  updatedAt_lte?: Maybe<Scalars['DateTime']>
-  updatedAt_gt?: Maybe<Scalars['DateTime']>
-  updatedAt_gte?: Maybe<Scalars['DateTime']>
-  updatedById_eq?: Maybe<Scalars['ID']>
-  updatedById_in?: Maybe<Array<Scalars['ID']>>
-  deletedAt_all?: Maybe<Scalars['Boolean']>
-  deletedAt_eq?: Maybe<Scalars['DateTime']>
-  deletedAt_lt?: Maybe<Scalars['DateTime']>
-  deletedAt_lte?: Maybe<Scalars['DateTime']>
-  deletedAt_gt?: Maybe<Scalars['DateTime']>
-  deletedAt_gte?: Maybe<Scalars['DateTime']>
-  deletedById_eq?: Maybe<Scalars['ID']>
-  deletedById_in?: Maybe<Array<Scalars['ID']>>
-  defaultInviteCount_eq?: Maybe<Scalars['Int']>
-  defaultInviteCount_gt?: Maybe<Scalars['Int']>
-  defaultInviteCount_gte?: Maybe<Scalars['Int']>
-  defaultInviteCount_lt?: Maybe<Scalars['Int']>
-  defaultInviteCount_lte?: Maybe<Scalars['Int']>
-  defaultInviteCount_in?: Maybe<Array<Scalars['Int']>>
-  membershipPrice_eq?: Maybe<Scalars['BigInt']>
-  membershipPrice_gt?: Maybe<Scalars['BigInt']>
-  membershipPrice_gte?: Maybe<Scalars['BigInt']>
-  membershipPrice_lt?: Maybe<Scalars['BigInt']>
-  membershipPrice_lte?: Maybe<Scalars['BigInt']>
-  membershipPrice_in?: Maybe<Array<Scalars['BigInt']>>
-  referralCut_eq?: Maybe<Scalars['BigInt']>
-  referralCut_gt?: Maybe<Scalars['BigInt']>
-  referralCut_gte?: Maybe<Scalars['BigInt']>
-  referralCut_lt?: Maybe<Scalars['BigInt']>
-  referralCut_lte?: Maybe<Scalars['BigInt']>
-  referralCut_in?: Maybe<Array<Scalars['BigInt']>>
-  invitedInitialBalance_eq?: Maybe<Scalars['BigInt']>
-  invitedInitialBalance_gt?: Maybe<Scalars['BigInt']>
-  invitedInitialBalance_gte?: Maybe<Scalars['BigInt']>
-  invitedInitialBalance_lt?: Maybe<Scalars['BigInt']>
-  invitedInitialBalance_lte?: Maybe<Scalars['BigInt']>
-  invitedInitialBalance_in?: Maybe<Array<Scalars['BigInt']>>
-}
-
-export type MembershipSystemWhereUniqueInput = {
-  id: Scalars['ID']
-}
-
-export type MembershipUpdateInput = {
-  handle?: Maybe<Scalars['String']>
-  name?: Maybe<Scalars['String']>
-  avatarUri?: Maybe<Scalars['String']>
-  about?: Maybe<Scalars['String']>
-  controllerAccount?: Maybe<Scalars['String']>
-  rootAccount?: Maybe<Scalars['String']>
-  registeredAtBlockId?: Maybe<Scalars['ID']>
-  registeredAtTime?: Maybe<Scalars['DateTime']>
-  entry?: Maybe<MembershipEntryMethod>
-  isVerified?: Maybe<Scalars['Boolean']>
-  boundAccounts?: Maybe<Array<Scalars['String']>>
-  inviteCount?: Maybe<Scalars['Float']>
-  invitedById?: Maybe<Scalars['ID']>
-  referredById?: Maybe<Scalars['ID']>
-}
-
-export type MembershipWhereInput = {
-  id_eq?: Maybe<Scalars['ID']>
-  id_in?: Maybe<Array<Scalars['ID']>>
-  createdAt_eq?: Maybe<Scalars['DateTime']>
-  createdAt_lt?: Maybe<Scalars['DateTime']>
-  createdAt_lte?: Maybe<Scalars['DateTime']>
-  createdAt_gt?: Maybe<Scalars['DateTime']>
-  createdAt_gte?: Maybe<Scalars['DateTime']>
-  createdById_eq?: Maybe<Scalars['ID']>
-  createdById_in?: Maybe<Array<Scalars['ID']>>
-  updatedAt_eq?: Maybe<Scalars['DateTime']>
-  updatedAt_lt?: Maybe<Scalars['DateTime']>
-  updatedAt_lte?: Maybe<Scalars['DateTime']>
-  updatedAt_gt?: Maybe<Scalars['DateTime']>
-  updatedAt_gte?: Maybe<Scalars['DateTime']>
-  updatedById_eq?: Maybe<Scalars['ID']>
-  updatedById_in?: Maybe<Array<Scalars['ID']>>
-  deletedAt_all?: Maybe<Scalars['Boolean']>
-  deletedAt_eq?: Maybe<Scalars['DateTime']>
-  deletedAt_lt?: Maybe<Scalars['DateTime']>
-  deletedAt_lte?: Maybe<Scalars['DateTime']>
-  deletedAt_gt?: Maybe<Scalars['DateTime']>
-  deletedAt_gte?: Maybe<Scalars['DateTime']>
-  deletedById_eq?: Maybe<Scalars['ID']>
-  deletedById_in?: Maybe<Array<Scalars['ID']>>
-  handle_eq?: Maybe<Scalars['String']>
-  handle_contains?: Maybe<Scalars['String']>
-  handle_startsWith?: Maybe<Scalars['String']>
-  handle_endsWith?: Maybe<Scalars['String']>
-  handle_in?: Maybe<Array<Scalars['String']>>
-  name_eq?: Maybe<Scalars['String']>
-  name_contains?: Maybe<Scalars['String']>
-  name_startsWith?: Maybe<Scalars['String']>
-  name_endsWith?: Maybe<Scalars['String']>
-  name_in?: Maybe<Array<Scalars['String']>>
-  avatarUri_eq?: Maybe<Scalars['String']>
-  avatarUri_contains?: Maybe<Scalars['String']>
-  avatarUri_startsWith?: Maybe<Scalars['String']>
-  avatarUri_endsWith?: Maybe<Scalars['String']>
-  avatarUri_in?: Maybe<Array<Scalars['String']>>
-  about_eq?: Maybe<Scalars['String']>
-  about_contains?: Maybe<Scalars['String']>
-  about_startsWith?: Maybe<Scalars['String']>
-  about_endsWith?: Maybe<Scalars['String']>
-  about_in?: Maybe<Array<Scalars['String']>>
-  controllerAccount_eq?: Maybe<Scalars['String']>
-  controllerAccount_contains?: Maybe<Scalars['String']>
-  controllerAccount_startsWith?: Maybe<Scalars['String']>
-  controllerAccount_endsWith?: Maybe<Scalars['String']>
-  controllerAccount_in?: Maybe<Array<Scalars['String']>>
-  rootAccount_eq?: Maybe<Scalars['String']>
-  rootAccount_contains?: Maybe<Scalars['String']>
-  rootAccount_startsWith?: Maybe<Scalars['String']>
-  rootAccount_endsWith?: Maybe<Scalars['String']>
-  rootAccount_in?: Maybe<Array<Scalars['String']>>
-  registeredAtBlockId_eq?: Maybe<Scalars['ID']>
-  registeredAtBlockId_in?: Maybe<Array<Scalars['ID']>>
-  registeredAtTime_eq?: Maybe<Scalars['DateTime']>
-  registeredAtTime_lt?: Maybe<Scalars['DateTime']>
-  registeredAtTime_lte?: Maybe<Scalars['DateTime']>
-  registeredAtTime_gt?: Maybe<Scalars['DateTime']>
-  registeredAtTime_gte?: Maybe<Scalars['DateTime']>
-  entry_eq?: Maybe<MembershipEntryMethod>
-  entry_in?: Maybe<Array<MembershipEntryMethod>>
-  isVerified_eq?: Maybe<Scalars['Boolean']>
-  isVerified_in?: Maybe<Array<Scalars['Boolean']>>
-  inviteCount_eq?: Maybe<Scalars['Int']>
-  inviteCount_gt?: Maybe<Scalars['Int']>
-  inviteCount_gte?: Maybe<Scalars['Int']>
-  inviteCount_lt?: Maybe<Scalars['Int']>
-  inviteCount_lte?: Maybe<Scalars['Int']>
-  inviteCount_in?: Maybe<Array<Scalars['Int']>>
-  invitedById_eq?: Maybe<Scalars['ID']>
-  invitedById_in?: Maybe<Array<Scalars['ID']>>
-  referredById_eq?: Maybe<Scalars['ID']>
-  referredById_in?: Maybe<Array<Scalars['ID']>>
-}
-
-export type MembershipWhereUniqueInput = {
-  id?: Maybe<Scalars['ID']>
-  handle?: Maybe<Scalars['String']>
-}
-
-export enum Network {
-  Babylon = 'BABYLON',
-  Alexandria = 'ALEXANDRIA',
-  Rome = 'ROME',
-  Olympia = 'OLYMPIA',
-}
-
-export type PageInfo = {
-  __typename?: 'PageInfo'
-  hasNextPage: Scalars['Boolean']
-  hasPreviousPage: Scalars['Boolean']
-  startCursor?: Maybe<Scalars['String']>
-  endCursor?: Maybe<Scalars['String']>
-}
-
-export type ProcessorState = {
-  __typename?: 'ProcessorState'
-  lastCompleteBlock: Scalars['Float']
-  lastProcessedEvent: Scalars['String']
-  indexerHead: Scalars['Float']
-  chainHead: Scalars['Float']
-}
-
-export type Query = {
-  __typename?: 'Query'
-  blocks: Array<Block>
-  block?: Maybe<Block>
-  blocksConnection: BlockConnection
-  membershipSystems: Array<MembershipSystem>
-  membershipSystem?: Maybe<MembershipSystem>
-  membershipSystemsConnection: MembershipSystemConnection
-  memberships: Array<Membership>
-  membership?: Maybe<Membership>
-  membershipsConnection: MembershipConnection
-  membersByHandle: Array<MembersByHandleFtsOutput>
-}
-
-export type QueryBlocksArgs = {
-  offset?: Maybe<Scalars['Int']>
-  limit?: Maybe<Scalars['Int']>
-  where?: Maybe<BlockWhereInput>
-  orderBy?: Maybe<BlockOrderByInput>
-}
-
-export type QueryBlockArgs = {
-  where: BlockWhereUniqueInput
-}
-
-export type QueryBlocksConnectionArgs = {
-  first?: Maybe<Scalars['Int']>
-  after?: Maybe<Scalars['String']>
-  last?: Maybe<Scalars['Int']>
-  before?: Maybe<Scalars['String']>
-  where?: Maybe<BlockWhereInput>
-  orderBy?: Maybe<BlockOrderByInput>
-}
-
-export type QueryMembershipSystemsArgs = {
-  offset?: Maybe<Scalars['Int']>
-  limit?: Maybe<Scalars['Int']>
-  where?: Maybe<MembershipSystemWhereInput>
-  orderBy?: Maybe<MembershipSystemOrderByInput>
-}
-
-export type QueryMembershipSystemArgs = {
-  where: MembershipSystemWhereUniqueInput
-}
-
-export type QueryMembershipSystemsConnectionArgs = {
-  first?: Maybe<Scalars['Int']>
-  after?: Maybe<Scalars['String']>
-  last?: Maybe<Scalars['Int']>
-  before?: Maybe<Scalars['String']>
-  where?: Maybe<MembershipSystemWhereInput>
-  orderBy?: Maybe<MembershipSystemOrderByInput>
-}
-
-export type QueryMembershipsArgs = {
-  offset?: Maybe<Scalars['Int']>
-  limit?: Maybe<Scalars['Int']>
-  where?: Maybe<MembershipWhereInput>
-  orderBy?: Maybe<MembershipOrderByInput>
-}
-
-export type QueryMembershipArgs = {
-  where: MembershipWhereUniqueInput
-}
-
-export type QueryMembershipsConnectionArgs = {
-  first?: Maybe<Scalars['Int']>
-  after?: Maybe<Scalars['String']>
-  last?: Maybe<Scalars['Int']>
-  before?: Maybe<Scalars['String']>
-  where?: Maybe<MembershipWhereInput>
-  orderBy?: Maybe<MembershipOrderByInput>
-}
-
-export type QueryMembersByHandleArgs = {
-  whereMembership?: Maybe<MembershipWhereInput>
-  skip?: Maybe<Scalars['Int']>
-  limit?: Maybe<Scalars['Int']>
-  text: Scalars['String']
-}
-
-export type StandardDeleteResponse = {
-  __typename?: 'StandardDeleteResponse'
-  id: Scalars['ID']
-}
-
-export type Subscription = {
-  __typename?: 'Subscription'
-  stateSubscription: ProcessorState
-}

+ 10 - 13
cli/src/base/AccountsCommandBase.ts

@@ -35,7 +35,7 @@ export const STAKING_ACCOUNT_CANDIDATE_STAKE = new BN(200)
  * Where: APP_DATA_PATH is provided by StateAwareCommandBase and ACCOUNTS_DIRNAME is a const (see above).
  */
 export default abstract class AccountsCommandBase extends ApiCommandBase {
-  private selectedMember: [MemberId, Membership] | undefined
+  private selectedMember: MemberDetails | undefined
   private _keyring: KeyringInstance | undefined
 
   private get keyring(): KeyringInstance {
@@ -321,20 +321,20 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
     }
   }
 
-  async getRequiredMemberContext(useSelected = false, allowedIds?: MemberId[]): Promise<[MemberId, Membership]> {
+  async getRequiredMemberContext(useSelected = false, allowedIds?: MemberId[]): Promise<MemberDetails> {
     if (
       useSelected &&
       this.selectedMember &&
-      (!allowedIds || allowedIds.some((id) => id.eq(this.selectedMember?.[0])))
+      (!allowedIds || allowedIds.some((id) => id.eq(this.selectedMember?.id)))
     ) {
       return this.selectedMember
     }
 
-    const membersEntries = allowedIds
-      ? await this.getApi().memberEntriesByIds(allowedIds)
-      : await this.getApi().allMemberEntries()
+    const membersDetails = allowedIds
+      ? await this.getApi().membersDetailsByIds(allowedIds)
+      : await this.getApi().allMembersDetails()
     const availableMemberships = await Promise.all(
-      membersEntries.filter(([, m]) => this.isKeyAvailable(m.controller_account.toString()))
+      membersDetails.filter((m) => this.isKeyAvailable(m.membership.controller_account.toString()))
     )
 
     if (!availableMemberships.length) {
@@ -354,15 +354,12 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
     return this.selectedMember
   }
 
-  async promptForMember(
-    availableMemberships: [MemberId, Membership][],
-    message = 'Choose a member'
-  ): Promise<[MemberId, Membership]> {
+  async promptForMember(availableMemberships: MemberDetails[], message = 'Choose a member'): Promise<MemberDetails> {
     const memberIndex = await this.simplePrompt({
       type: 'list',
       message,
-      choices: availableMemberships.map(([, membership], i) => ({
-        name: membership.handle.toString(),
+      choices: availableMemberships.map((m, i) => ({
+        name: `id: ${m.id}, handle: ${memberHandle(m)}`,
         value: i,
       })),
     })

+ 8 - 8
cli/src/base/ApiCommandBase.ts

@@ -84,8 +84,8 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
         apiUri = await this.promptForApiUri()
       }
 
+      // Query node api
       let queryNodeUri: string | null | undefined = this.getPreservedState().queryNodeUri
-
       if (this.requiresQueryNode && !queryNodeUri) {
         this.warn('Query node endpoint uri is required in order to run this command!')
         queryNodeUri = await this.promptForQueryNodeUri(true)
@@ -93,9 +93,15 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
         this.warn("You haven't provided a Joystream query node uri for the CLI to connect to yet!")
         queryNodeUri = await this.promptForQueryNodeUri()
       }
+      this.queryNodeApi = queryNodeUri
+        ? new QueryNodeApi(queryNodeUri, (err) => {
+            this.warn(`Query node error: ${err.networkError?.message || err.graphQLErrors?.join('\n')}`)
+          })
+        : null
 
+      // Substrate api
       const { metadataCache } = this.getPreservedState()
-      this.api = await Api.create(apiUri, metadataCache, queryNodeUri === 'none' ? undefined : queryNodeUri)
+      this.api = await Api.create(apiUri, metadataCache, this.queryNodeApi || undefined)
 
       const { genesisHash, runtimeVersion } = this.getOriginalApi()
       const metadataKey = `${genesisHash}-${runtimeVersion.specVersion}`
@@ -104,12 +110,6 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
         metadataCache[metadataKey] = await this.getOriginalApi().runtimeMetadata.toJSON()
         await this.setPreservedState({ metadataCache })
       }
-
-      this.queryNodeApi = queryNodeUri
-        ? new QueryNodeApi(queryNodeUri, (err) => {
-            this.warn(`Query node error: ${err.networkError?.message || err.graphQLErrors?.join('\n')}`)
-          })
-        : null
     }
   }
 

+ 3 - 3
cli/src/base/ContentDirectoryCommandBase.ts

@@ -81,7 +81,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
         return this.getCuratorContext(channel.owner.asType('Curators'))
       }
     } else {
-      const [id, membership] = await this.getRequiredMemberContext(false, [channel.owner.asType('Member')])
+      const { id, membership } = await this.getRequiredMemberContext(false, [channel.owner.asType('Member')])
       return [
         createType<ContentActor, 'ContentActor'>('ContentActor', { Member: id }),
         membership.controller_account.toString(),
@@ -90,7 +90,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
   }
 
   async getChannelCollaboratorActor(channel: Channel): Promise<[ContentActor, string]> {
-    const [id, membership] = await this.getRequiredMemberContext(false, Array.from(channel.collaborators))
+    const { id, membership } = await this.getRequiredMemberContext(false, Array.from(channel.collaborators))
     return [
       createType<ContentActor, 'ContentActor'>('ContentActor', { Collaborator: id }),
       membership.controller_account.toString(),
@@ -280,7 +280,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
     context: Exclude<keyof typeof ContentActor.typeDefinitions, 'Collaborator'>
   ): Promise<[ContentActor, string]> {
     if (context === 'Member') {
-      const [id, membership] = await this.getRequiredMemberContext()
+      const { id, membership } = await this.getRequiredMemberContext()
       return [
         createType<ContentActor, 'ContentActor'>('ContentActor', { Member: id }),
         membership.controller_account.toString(),

+ 3 - 3
cli/src/commands/content/channel.ts

@@ -1,5 +1,5 @@
 import ContentDirectoryCommandBase from '../../base/ContentDirectoryCommandBase'
-import { displayCollapsedRow, displayHeader } from '../../helpers/display'
+import { displayCollapsedRow, displayHeader, memberHandle } from '../../helpers/display'
 
 export default class ChannelCommand extends ContentDirectoryCommandBase {
   static description = 'Show Channel details by id.'
@@ -29,8 +29,8 @@ export default class ChannelCommand extends ContentDirectoryCommandBase {
 
       displayHeader(`Collaborators`)
       const collaboratorIds = Array.from(channel.collaborators)
-      const collaborators = await this.getApi().getMembers(collaboratorIds)
-      this.log(collaborators.map((c, i) => `${collaboratorIds[i].toString()} (${c.handle.toString()})`).join(', '))
+      const collaborators = await this.getApi().membersDetailsByIds(collaboratorIds)
+      this.log(collaborators.map((c) => `${c.id} (${memberHandle(c)})`).join(', '))
     } else {
       this.error(`Channel not found by channel id: "${channelId}"!`)
     }

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

@@ -29,7 +29,7 @@ export default class CreateChannelCommand extends UploadCommandBase {
       context = await this.promptForChannelCreationContext()
     }
     const [actor, address] = await this.getContentActor(context)
-    const [memberId] = await this.getRequiredMemberContext(true)
+    const { id: memberId } = await this.getRequiredMemberContext(true)
     const keypair = await this.getDecodedPair(address)
 
     const channelInput = await getInputJson<ChannelInputParameters>(input, ChannelInputSchema)

+ 9 - 6
cli/src/commands/content/createVideo.ts

@@ -2,7 +2,7 @@ import UploadCommandBase from '../../base/UploadCommandBase'
 import { getInputJson } from '../../helpers/InputOutput'
 import { asValidatedMetadata, metadataToBytes } from '../../helpers/serialization'
 import { VideoInputParameters, VideoFileMetadata } from '../../Types'
-import { createTypeFromConstructor } from '@joystream/types'
+import { createType } from '@joystream/types'
 import { flags } from '@oclif/command'
 import { VideoCreationParameters } from '@joystream/types/content'
 import { IVideoMetadata, VideoMetadata } from '@joystream/metadata-protobuf'
@@ -46,7 +46,7 @@ export default class CreateVideoCommand extends UploadCommandBase {
     // Get context
     const channel = await this.getApi().channelById(channelId)
     const [actor, address] = await this.getChannelManagementActor(channel, context)
-    const [memberId] = await this.getRequiredMemberContext(true)
+    const { id: memberId } = await this.getRequiredMemberContext(true)
     const keypair = await this.getDecodedPair(address)
 
     // Get input from file
@@ -69,10 +69,13 @@ export default class CreateVideoCommand extends UploadCommandBase {
 
     // Preare and send the extrinsic
     const assets = await this.prepareAssetsForExtrinsic(resolvedAssets)
-    const videoCreationParameters = createTypeFromConstructor(VideoCreationParameters, {
-      assets,
-      meta: metadataToBytes(VideoMetadata, meta),
-    })
+    const videoCreationParameters = createType<VideoCreationParameters, 'VideoCreationParameters'>(
+      'VideoCreationParameters',
+      {
+        assets,
+        meta: metadataToBytes(VideoMetadata, meta),
+      }
+    )
 
     this.jsonPrettyPrint(JSON.stringify({ assets: assets?.toJSON(), metadata: meta }))
 

+ 1 - 1
cli/src/commands/content/reuploadAssets.ts

@@ -20,7 +20,7 @@ export default class ReuploadVideoAssetsCommand extends UploadCommandBase {
     const { input } = this.parse(ReuploadVideoAssetsCommand).flags
 
     // Get context
-    const [memberId, membership] = await this.getRequiredMemberContext()
+    const { id: memberId, membership } = await this.getRequiredMemberContext()
 
     // Get input from file
     const inputData = await getInputJson<AssetsInput>(input, AssetsSchema)

+ 1 - 1
cli/src/commands/content/updateChannel.ts

@@ -82,7 +82,7 @@ export default class UpdateChannelCommand extends UploadCommandBase {
     // Context
     const channel = await this.getApi().channelById(channelId)
     const [actor, address] = await this.getChannelManagementActor(channel, context)
-    const [memberId] = await this.getRequiredMemberContext(true)
+    const { id: memberId } = await this.getRequiredMemberContext(true)
     const keypair = await this.getDecodedPair(address)
 
     const channelInput = await getInputJson<ChannelInputParameters>(input, ChannelInputSchema)

+ 1 - 1
cli/src/commands/content/updateVideo.ts

@@ -69,7 +69,7 @@ export default class UpdateVideoCommand extends UploadCommandBase {
     const video = await this.getApi().videoById(videoId)
     const channel = await this.getApi().channelById(video.in_channel.toNumber())
     const [actor, address] = await this.getChannelManagementActor(channel, context)
-    const [memberId] = await this.getRequiredMemberContext(true)
+    const { id: memberId } = await this.getRequiredMemberContext(true)
     const keypair = await this.getDecodedPair(address)
 
     const videoInput = await getInputJson<VideoInputParameters>(input, VideoInputSchema)

+ 2 - 2
cli/src/graphql/queries/membership.graphql

@@ -11,8 +11,8 @@ fragment MembershipFields on Membership {
   }
 }
 
-query getMemberById($id: ID!) {
-  membershipByUniqueInput(where: { id: $id }) {
+query getMembersByIds($ids: [ID!]) {
+  memberships(where: { id_in: $ids }) {
     ...MembershipFields
   }
 }