ContentDirectoryCommandBase.ts 6.3 KB

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