ContentDirectoryCommandBase.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import ExitCodes from '../ExitCodes'
  2. import { WorkingGroups } from '../Types'
  3. import { Channel, CuratorGroup, CuratorGroupId, ContentActor } from '@joystream/types/content'
  4. import { Worker } from '@joystream/types/working-group'
  5. import { CLIError } from '@oclif/errors'
  6. import { RolesCommandBase } from './WorkingGroupsCommandBase'
  7. import { createType } from '@joystream/types'
  8. import { flags } from '@oclif/command'
  9. const CONTEXTS = ['Member', 'Curator', 'Lead'] as const
  10. type Context = typeof CONTEXTS[number]
  11. /**
  12. * Abstract base class for commands related to content directory
  13. */
  14. export default abstract class ContentDirectoryCommandBase extends RolesCommandBase {
  15. group = WorkingGroups.Curators // override group for RolesCommandBase
  16. static contextFlag = flags.enum({
  17. name: 'context',
  18. required: false,
  19. description: `Actor context to execute the command in (${CONTEXTS.join('/')})`,
  20. options: [...CONTEXTS],
  21. })
  22. async promptForContext(message = 'Choose in which context you wish to execute the command'): Promise<Context> {
  23. return this.simplePrompt({
  24. message,
  25. type: 'list',
  26. choices: CONTEXTS.map((c) => ({ name: c, value: c })),
  27. })
  28. }
  29. // Use when lead access is required in given command
  30. async requireLead(): Promise<void> {
  31. await this.getRequiredLead()
  32. }
  33. async getCuratorContext(): Promise<ContentActor> {
  34. const curator = await this.getRequiredWorker()
  35. const groups = await this.getApi().availableCuratorGroups()
  36. const availableGroupIds = groups
  37. .filter(
  38. ([, group]) =>
  39. group.active.valueOf() && group.curators.toArray().some((curatorId) => curatorId.eq(curator.workerId))
  40. )
  41. .map(([id]) => id)
  42. let groupId: number
  43. if (!availableGroupIds.length) {
  44. this.error('You do not have the curator access!', { exit: ExitCodes.AccessDenied })
  45. } else if (availableGroupIds.length === 1) {
  46. groupId = availableGroupIds[0].toNumber()
  47. } else {
  48. groupId = await this.promptForCuratorGroup('Select Curator Group context', availableGroupIds)
  49. }
  50. return createType('ContentActor', { Curator: [groupId, curator.workerId.toNumber()] })
  51. }
  52. async promptForChannel(message = 'Select a channel'): Promise<Channel> {
  53. const channels = await this.getApi().availableChannels()
  54. const choices = channels.map(([id, c]) => ({ id: id.toString(), value: c }))
  55. if (!choices.length) {
  56. this.warn('No channels exist to choose from!')
  57. this.exit(ExitCodes.InvalidInput)
  58. }
  59. const selectedChannel = await this.simplePrompt({ message, type: 'list', choices })
  60. return selectedChannel
  61. }
  62. private async curatorGroupChoices(ids?: CuratorGroupId[]) {
  63. const groups = await this.getApi().availableCuratorGroups()
  64. return groups
  65. .filter(([id]) => (ids ? ids.some((allowedId) => allowedId.eq(id)) : true))
  66. .map(([id, group]) => ({
  67. name:
  68. `Group ${id.toString()} (` +
  69. `${group.active.valueOf() ? 'Active' : 'Inactive'}, ` +
  70. `${group.curators.toArray().length} member(s), `,
  71. value: id.toNumber(),
  72. }))
  73. }
  74. async promptForCuratorGroup(message = 'Select a Curator Group', ids?: CuratorGroupId[]): Promise<number> {
  75. const choices = await this.curatorGroupChoices(ids)
  76. if (!choices.length) {
  77. this.warn('No Curator Groups to choose from!')
  78. this.exit(ExitCodes.InvalidInput)
  79. }
  80. const selectedId = await this.simplePrompt({ message, type: 'list', choices })
  81. return selectedId
  82. }
  83. async promptForCuratorGroups(message = 'Select Curator Groups'): Promise<number[]> {
  84. const choices = await this.curatorGroupChoices()
  85. if (!choices.length) {
  86. return []
  87. }
  88. const selectedIds = await this.simplePrompt({ message, type: 'checkbox', choices })
  89. return selectedIds
  90. }
  91. async promptForCurator(message = 'Choose a Curator', ids?: number[]): Promise<number> {
  92. const curators = await this.getApi().groupMembers(WorkingGroups.Curators)
  93. const choices = curators
  94. .filter((c) => (ids ? ids.includes(c.workerId.toNumber()) : true))
  95. .map((c) => ({
  96. name: `${c.profile.handle.toString()} (Worker ID: ${c.workerId})`,
  97. value: c.workerId.toNumber(),
  98. }))
  99. if (!choices.length) {
  100. this.warn('No Curators to choose from!')
  101. this.exit(ExitCodes.InvalidInput)
  102. }
  103. const selectedCuratorId = await this.simplePrompt({
  104. message,
  105. type: 'list',
  106. choices,
  107. })
  108. return selectedCuratorId
  109. }
  110. async getCurator(id: string | number): Promise<Worker> {
  111. if (typeof id === 'string') {
  112. id = parseInt(id)
  113. }
  114. let curator
  115. try {
  116. curator = await this.getApi().workerByWorkerId(WorkingGroups.Curators, id)
  117. } catch (e) {
  118. if (e instanceof CLIError) {
  119. throw new CLIError('Invalid Curator id!')
  120. }
  121. throw e
  122. }
  123. return curator
  124. }
  125. async getCuratorGroup(id: string | number): Promise<CuratorGroup> {
  126. if (typeof id === 'string') {
  127. id = parseInt(id)
  128. }
  129. const group = await this.getApi().curatorGroupById(id)
  130. if (!group) {
  131. this.error('Invalid Curator Group id!', { exit: ExitCodes.InvalidInput })
  132. }
  133. return group
  134. }
  135. async getActor(context: typeof CONTEXTS[number]) {
  136. let actor: ContentActor
  137. if (context === 'Member') {
  138. const memberId = await this.getRequiredMemberId()
  139. actor = this.createType('ContentActor', { Member: memberId })
  140. } else if (context === 'Curator') {
  141. actor = await this.getCuratorContext()
  142. } else {
  143. await this.getRequiredLead()
  144. actor = this.createType('ContentActor', { Lead: null })
  145. }
  146. return actor
  147. }
  148. }