|
@@ -1,9 +1,9 @@
|
|
import BN from 'bn.js'
|
|
import BN from 'bn.js'
|
|
-import { createType, types } from '@joystream/types/'
|
|
|
|
|
|
+import { createType, types } from '@joystream/types'
|
|
import { ApiPromise, WsProvider } from '@polkadot/api'
|
|
import { ApiPromise, WsProvider } from '@polkadot/api'
|
|
import { SubmittableExtrinsic, AugmentedQuery } from '@polkadot/api/types'
|
|
import { SubmittableExtrinsic, AugmentedQuery } from '@polkadot/api/types'
|
|
import { formatBalance } from '@polkadot/util'
|
|
import { formatBalance } from '@polkadot/util'
|
|
-import { Balance } from '@polkadot/types/interfaces'
|
|
|
|
|
|
+import { Balance, LockIdentifier } from '@polkadot/types/interfaces'
|
|
import { KeyringPair } from '@polkadot/keyring/types'
|
|
import { KeyringPair } from '@polkadot/keyring/types'
|
|
import { Codec, Observable } from '@polkadot/types/types'
|
|
import { Codec, Observable } from '@polkadot/types/types'
|
|
import { UInt } from '@polkadot/types'
|
|
import { UInt } from '@polkadot/types'
|
|
@@ -21,7 +21,7 @@ import { DeriveBalancesAll } from '@polkadot/api-derive/types'
|
|
import { CLIError } from '@oclif/errors'
|
|
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 { Membership, StakingAccountMemberBinding } from '@joystream/types/members'
|
|
-import { MemberId, ChannelId, AccountId } from '@joystream/types/common'
|
|
|
|
|
|
+import { MemberId, ChannelId, AccountId, ThreadId, PostId } from '@joystream/types/common'
|
|
import {
|
|
import {
|
|
Channel,
|
|
Channel,
|
|
Video,
|
|
Video,
|
|
@@ -34,6 +34,10 @@ import {
|
|
import { BagId, DataObject, DataObjectId } from '@joystream/types/storage'
|
|
import { BagId, DataObject, DataObjectId } from '@joystream/types/storage'
|
|
import QueryNodeApi from './QueryNodeApi'
|
|
import QueryNodeApi from './QueryNodeApi'
|
|
import { MembershipFieldsFragment } from './graphql/generated/queries'
|
|
import { MembershipFieldsFragment } from './graphql/generated/queries'
|
|
|
|
+import { blake2AsHex } from '@polkadot/util-crypto'
|
|
|
|
+import { Category, CategoryId, Post, Thread } from '@joystream/types/forum'
|
|
|
|
+import chalk from 'chalk'
|
|
|
|
+import _ from 'lodash'
|
|
|
|
|
|
export const DEFAULT_API_URI = 'ws://localhost:9944/'
|
|
export const DEFAULT_API_URI = 'ws://localhost:9944/'
|
|
|
|
|
|
@@ -155,7 +159,9 @@ export default class Api {
|
|
}
|
|
}
|
|
|
|
|
|
async membersDetails(entries: [MemberId, Membership][]): Promise<MemberDetails[]> {
|
|
async membersDetails(entries: [MemberId, Membership][]): Promise<MemberDetails[]> {
|
|
- const membersQnData = await this._qnApi?.membersByIds(entries.map(([id]) => id))
|
|
|
|
|
|
+ const membersQnData: MembershipFieldsFragment[] | undefined = await this._qnApi?.membersByIds(
|
|
|
|
+ entries.map(([id]) => id)
|
|
|
|
+ )
|
|
const memberQnDataById = new Map<string, MembershipFieldsFragment>()
|
|
const memberQnDataById = new Map<string, MembershipFieldsFragment>()
|
|
membersQnData?.forEach((m) => {
|
|
membersQnData?.forEach((m) => {
|
|
memberQnDataById.set(m.id, m)
|
|
memberQnDataById.set(m.id, m)
|
|
@@ -163,8 +169,8 @@ export default class Api {
|
|
|
|
|
|
return entries.map(([memberId, membership]) => ({
|
|
return entries.map(([memberId, membership]) => ({
|
|
id: memberId,
|
|
id: memberId,
|
|
- name: memberQnDataById.get(memberId.toString())?.metadata.name,
|
|
|
|
handle: memberQnDataById.get(memberId.toString())?.handle,
|
|
handle: memberQnDataById.get(memberId.toString())?.handle,
|
|
|
|
+ meta: memberQnDataById.get(memberId.toString())?.metadata,
|
|
membership,
|
|
membership,
|
|
}))
|
|
}))
|
|
}
|
|
}
|
|
@@ -175,13 +181,13 @@ export default class Api {
|
|
return memberDetails
|
|
return memberDetails
|
|
}
|
|
}
|
|
|
|
|
|
- protected async membershipById(memberId: MemberId): Promise<MemberDetails | null> {
|
|
|
|
|
|
+ async memberDetailsById(memberId: MemberId | number): Promise<MemberDetails | null> {
|
|
const membership = await this._api.query.members.membershipById(memberId)
|
|
const membership = await this._api.query.members.membershipById(memberId)
|
|
- return membership.isEmpty ? null : await this.memberDetails(memberId, membership)
|
|
|
|
|
|
+ return membership.isEmpty ? null : await this.memberDetails(createType('MemberId', memberId), membership)
|
|
}
|
|
}
|
|
|
|
|
|
- protected async expectedMembershipById(memberId: MemberId): Promise<MemberDetails> {
|
|
|
|
- const member = await this.membershipById(memberId)
|
|
|
|
|
|
+ async expectedMemberDetailsById(memberId: MemberId | number): Promise<MemberDetails> {
|
|
|
|
+ const member = await this.memberDetailsById(memberId)
|
|
if (!member) {
|
|
if (!member) {
|
|
throw new CLIError(`Expected member was not found by id: ${memberId.toString()}`)
|
|
throw new CLIError(`Expected member was not found by id: ${memberId.toString()}`)
|
|
}
|
|
}
|
|
@@ -230,11 +236,7 @@ export default class Api {
|
|
const stakingAccount = worker.staking_account_id
|
|
const stakingAccount = worker.staking_account_id
|
|
const memberId = worker.member_id
|
|
const memberId = worker.member_id
|
|
|
|
|
|
- const profile = await this.membershipById(memberId)
|
|
|
|
-
|
|
|
|
- if (!profile) {
|
|
|
|
- throw new Error(`Group member profile not found! (member id: ${memberId.toNumber()})`)
|
|
|
|
- }
|
|
|
|
|
|
+ const profile = await this.expectedMemberDetailsById(memberId)
|
|
|
|
|
|
const stake = await this.fetchStake(worker.staking_account_id, group)
|
|
const stake = await this.fetchStake(worker.staking_account_id, group)
|
|
|
|
|
|
@@ -254,18 +256,11 @@ export default class Api {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- async workerByWorkerId(group: WorkingGroups, workerId: number): Promise<Worker> {
|
|
|
|
- const nextId = await this.workingGroupApiQuery(group).nextWorkerId()
|
|
|
|
-
|
|
|
|
- // This is chain specfic, but if next id is still 0, it means no workers have been added yet
|
|
|
|
- if (workerId < 0 || workerId >= nextId.toNumber()) {
|
|
|
|
- throw new CLIError('Invalid worker id!')
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
|
|
+ async workerByWorkerId(group: WorkingGroups, workerId: WorkerId | number): Promise<Worker> {
|
|
const worker = await this.workingGroupApiQuery(group).workerById(workerId)
|
|
const worker = await this.workingGroupApiQuery(group).workerById(workerId)
|
|
|
|
|
|
if (worker.isEmpty) {
|
|
if (worker.isEmpty) {
|
|
- throw new CLIError('This worker is not active anymore')
|
|
|
|
|
|
+ throw new CLIError(`Worker ${chalk.magentaBright(workerId)} does not exist!`)
|
|
}
|
|
}
|
|
|
|
|
|
return worker
|
|
return worker
|
|
@@ -313,23 +308,26 @@ export default class Api {
|
|
}
|
|
}
|
|
|
|
|
|
protected async fetchApplicationDetails(
|
|
protected async fetchApplicationDetails(
|
|
|
|
+ group: WorkingGroups,
|
|
applicationId: number,
|
|
applicationId: number,
|
|
application: Application
|
|
application: Application
|
|
): Promise<ApplicationDetails> {
|
|
): Promise<ApplicationDetails> {
|
|
|
|
+ const qnData = await this._qnApi?.applicationDetailsById(group, applicationId)
|
|
return {
|
|
return {
|
|
applicationId,
|
|
applicationId,
|
|
- member: await this.expectedMembershipById(application.member_id),
|
|
|
|
|
|
+ member: await this.expectedMemberDetailsById(application.member_id),
|
|
roleAccout: application.role_account_id,
|
|
roleAccout: application.role_account_id,
|
|
rewardAccount: application.reward_account_id,
|
|
rewardAccount: application.reward_account_id,
|
|
stakingAccount: application.staking_account_id,
|
|
stakingAccount: application.staking_account_id,
|
|
descriptionHash: application.description_hash.toString(),
|
|
descriptionHash: application.description_hash.toString(),
|
|
openingId: application.opening_id.toNumber(),
|
|
openingId: application.opening_id.toNumber(),
|
|
|
|
+ answers: qnData?.answers,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
async groupApplication(group: WorkingGroups, applicationId: number): Promise<ApplicationDetails> {
|
|
async groupApplication(group: WorkingGroups, applicationId: number): Promise<ApplicationDetails> {
|
|
const application = await this.applicationById(group, applicationId)
|
|
const application = await this.applicationById(group, applicationId)
|
|
- return await this.fetchApplicationDetails(applicationId, application)
|
|
|
|
|
|
+ return await this.fetchApplicationDetails(group, applicationId, application)
|
|
}
|
|
}
|
|
|
|
|
|
protected async groupOpeningApplications(group: WorkingGroups, openingId: number): Promise<ApplicationDetails[]> {
|
|
protected async groupOpeningApplications(group: WorkingGroups, openingId: number): Promise<ApplicationDetails[]> {
|
|
@@ -340,7 +338,7 @@ export default class Api {
|
|
return Promise.all(
|
|
return Promise.all(
|
|
applicationEntries
|
|
applicationEntries
|
|
.filter(([, application]) => application.opening_id.eqn(openingId))
|
|
.filter(([, application]) => application.opening_id.eqn(openingId))
|
|
- .map(([id, application]) => this.fetchApplicationDetails(id.toNumber(), application))
|
|
|
|
|
|
+ .map(([id, application]) => this.fetchApplicationDetails(group, id.toNumber(), application))
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -361,6 +359,7 @@ export default class Api {
|
|
}
|
|
}
|
|
|
|
|
|
async fetchOpeningDetails(group: WorkingGroups, opening: Opening, openingId: number): Promise<OpeningDetails> {
|
|
async fetchOpeningDetails(group: WorkingGroups, opening: Opening, openingId: number): Promise<OpeningDetails> {
|
|
|
|
+ const qnData = await this._qnApi?.openingDetailsById(group, openingId)
|
|
const applications = await this.groupOpeningApplications(group, openingId)
|
|
const applications = await this.groupOpeningApplications(group, openingId)
|
|
const type = opening.opening_type
|
|
const type = opening.opening_type
|
|
const stake = {
|
|
const stake = {
|
|
@@ -375,6 +374,7 @@ export default class Api {
|
|
stake,
|
|
stake,
|
|
createdAtBlock: opening.created.toNumber(),
|
|
createdAtBlock: opening.created.toNumber(),
|
|
rewardPerBlock: opening.reward_per_block.unwrapOr(undefined),
|
|
rewardPerBlock: opening.reward_per_block.unwrapOr(undefined),
|
|
|
|
+ metadata: qnData?.metadata || undefined,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -453,7 +453,111 @@ export default class Api {
|
|
}
|
|
}
|
|
|
|
|
|
async stakingAccountStatus(account: string): Promise<StakingAccountMemberBinding | null> {
|
|
async stakingAccountStatus(account: string): Promise<StakingAccountMemberBinding | null> {
|
|
- const status = await this.getOriginalApi().query.members.stakingAccountIdMemberStatus(account)
|
|
|
|
|
|
+ const status = await this._api.query.members.stakingAccountIdMemberStatus(account)
|
|
return status.isEmpty ? null : status
|
|
return status.isEmpty ? null : status
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ async isHandleTaken(handle: string): Promise<boolean> {
|
|
|
|
+ const handleHash = blake2AsHex(handle)
|
|
|
|
+ const existingMeber = await this._api.query.members.memberIdByHandleHash(handleHash)
|
|
|
|
+ return !existingMeber.isEmpty
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ nonRivalrousLocks(): LockIdentifier[] {
|
|
|
|
+ const votingLockId = this._api.consts.referendum.stakingHandlerLockId
|
|
|
|
+ const boundStakingAccountLockId = this._api.consts.members.stakingCandidateLockId
|
|
|
|
+ const invitedMemberLockId = this._api.consts.members.invitedMemberLockId
|
|
|
|
+ const vestigLockId = this._api.createType('LockIdentifier', 'vesting ')
|
|
|
|
+
|
|
|
|
+ return [votingLockId, boundStakingAccountLockId, invitedMemberLockId, vestigLockId]
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ isLockRivalrous(lockId: LockIdentifier): boolean {
|
|
|
|
+ const nonRivalrousLocks = this.nonRivalrousLocks()
|
|
|
|
+ return !nonRivalrousLocks.some((nonRivalrousLockId) => nonRivalrousLockId.eq(lockId))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async areAccountLocksCompatibleWith(account: AccountId | string, lockId: LockIdentifier): Promise<boolean> {
|
|
|
|
+ const accountLocks = await this._api.query.balances.locks(account)
|
|
|
|
+ const accountHasRivalrousLock = accountLocks.some(({ id }) => this.isLockRivalrous(id))
|
|
|
|
+
|
|
|
|
+ return !this.isLockRivalrous(lockId) || !accountHasRivalrousLock
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumCategoryExists(categoryId: CategoryId | number): Promise<boolean> {
|
|
|
|
+ const size = await this._api.query.forum.categoryById.size(categoryId)
|
|
|
|
+ return size.gtn(0)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumThreadExists(categoryId: CategoryId | number, threadId: ThreadId | number): Promise<boolean> {
|
|
|
|
+ const size = await this._api.query.forum.threadById.size(categoryId, threadId)
|
|
|
|
+ return size.gtn(0)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumPostExists(threadId: ThreadId | number, postId: PostId | number): Promise<boolean> {
|
|
|
|
+ const size = await this._api.query.forum.postById.size(threadId, postId)
|
|
|
|
+ return size.gtn(0)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumCategoryAncestors(categoryId: CategoryId | number): Promise<[CategoryId, Category][]> {
|
|
|
|
+ const ancestors: [CategoryId, Category][] = []
|
|
|
|
+ let category = await this._api.query.forum.categoryById(categoryId)
|
|
|
|
+ while (category.parent_category_id.isSome) {
|
|
|
|
+ const parentCategoryId = category.parent_category_id.unwrap()
|
|
|
|
+ category = await this._api.query.forum.categoryById(parentCategoryId)
|
|
|
|
+ ancestors.push([parentCategoryId, category])
|
|
|
|
+ }
|
|
|
|
+ return ancestors
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumCategoryModerators(categoryId: CategoryId | number): Promise<[CategoryId, WorkerId][]> {
|
|
|
|
+ const categoryAncestors = await this.forumCategoryAncestors(categoryId)
|
|
|
|
+
|
|
|
|
+ const moderatorIds = _.uniqWith(
|
|
|
|
+ _.flatten(
|
|
|
|
+ await Promise.all(
|
|
|
|
+ categoryAncestors
|
|
|
|
+ .map(([id]) => id as CategoryId | number)
|
|
|
|
+ .reverse()
|
|
|
|
+ .concat([categoryId])
|
|
|
|
+ .map(async (id) => {
|
|
|
|
+ const storageKeys = await this._api.query.forum.categoryByModerator.keys(id)
|
|
|
|
+ return storageKeys.map((k) => k.args)
|
|
|
|
+ })
|
|
|
|
+ )
|
|
|
|
+ ),
|
|
|
|
+ (a, b) => a[1].eq(b[1])
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ return moderatorIds
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async getForumCategory(categoryId: CategoryId | number): Promise<Category> {
|
|
|
|
+ const category = await this._api.query.forum.categoryById(categoryId)
|
|
|
|
+ return category
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async getForumThread(categoryId: CategoryId | number, threadId: ThreadId | number): Promise<Thread> {
|
|
|
|
+ const thread = await this._api.query.forum.threadById(categoryId, threadId)
|
|
|
|
+ return thread
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async getForumPost(threadId: ThreadId | number, postId: PostId | number): Promise<Post> {
|
|
|
|
+ const post = await this._api.query.forum.postById(threadId, postId)
|
|
|
|
+ return post
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumCategories(): Promise<[CategoryId, Category][]> {
|
|
|
|
+ return this.entriesByIds(this._api.query.forum.categoryById)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumThreads(categoryId: CategoryId | number): Promise<[ThreadId, Thread][]> {
|
|
|
|
+ const entries = await this._api.query.forum.threadById.entries(categoryId)
|
|
|
|
+ return entries.map(([storageKey, thread]) => [storageKey.args[1], thread])
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async forumPosts(threadId: ThreadId | number): Promise<[PostId, Post][]> {
|
|
|
|
+ const entries = await this._api.query.forum.postById.entries(threadId)
|
|
|
|
+ return entries.map(([storageKey, thread]) => [storageKey.args[1], thread])
|
|
|
|
+ }
|
|
}
|
|
}
|