deleteChannel.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import ContentDirectoryCommandBase from '../../base/ContentDirectoryCommandBase'
  2. import { flags } from '@oclif/command'
  3. import chalk from 'chalk'
  4. import { createTypeFromConstructor } from '@joystream/types'
  5. import { BagId } from '@joystream/types/storage'
  6. import ExitCodes from '../../ExitCodes'
  7. import { formatBalance } from '@polkadot/util'
  8. import BN from 'bn.js'
  9. export default class DeleteChannelCommand extends ContentDirectoryCommandBase {
  10. static description = 'Delete the channel and optionally all associated data objects.'
  11. static flags = {
  12. channelId: flags.integer({
  13. char: 'c',
  14. required: true,
  15. description: 'ID of the Channel',
  16. }),
  17. force: flags.boolean({
  18. char: 'f',
  19. default: false,
  20. description: 'Force-remove all associated channel data objects',
  21. }),
  22. }
  23. async getDataObjectsInfoFromQueryNode(channelId: number): Promise<[string, BN][]> {
  24. const dataObjects = await this.getQNApi().dataObjectsByBagId(`dynamic:channel:${channelId}`)
  25. if (dataObjects.length) {
  26. this.log('Following data objects are still associated with the channel:')
  27. dataObjects.forEach((o) => {
  28. let parentStr = ''
  29. if ('video' in o.type && o.type.video) {
  30. parentStr = ` (video: ${o.type.video.id})`
  31. }
  32. this.log(`- ${o.id} - ${o.type.__typename}${parentStr}`)
  33. })
  34. }
  35. return dataObjects.map((o) => [o.id, new BN(o.deletionPrize)])
  36. }
  37. async getDataObjectsInfoFromChain(channelId: number): Promise<[string, BN][]> {
  38. const dataObjects = await this.getApi().dataObjectsInBag(
  39. createTypeFromConstructor(BagId, { Dynamic: { Channel: channelId } })
  40. )
  41. if (dataObjects.length) {
  42. const dataObjectIds = dataObjects.map(([id]) => id.toString())
  43. this.log(`Following data objects are still associated with the channel: ${dataObjectIds.join(', ')}`)
  44. }
  45. return dataObjects.map(([id, o]) => [id.toString(), o.deletion_prize])
  46. }
  47. async run(): Promise<void> {
  48. const {
  49. flags: { channelId, force },
  50. } = this.parse(DeleteChannelCommand)
  51. // Context
  52. const channel = await this.getApi().channelById(channelId)
  53. const [actor, address] = await this.getChannelOwnerActor(channel)
  54. if (channel.num_videos.toNumber()) {
  55. this.error(
  56. `This channel still has ${channel.num_videos.toNumber()} associated video(s)!\n` +
  57. `Delete the videos first using ${chalk.magentaBright('content:deleteVideo')} command`
  58. )
  59. }
  60. const dataObjectsInfo = this.isQueryNodeUriSet()
  61. ? await this.getDataObjectsInfoFromQueryNode(channelId)
  62. : await this.getDataObjectsInfoFromChain(channelId)
  63. if (dataObjectsInfo.length) {
  64. if (!force) {
  65. this.error(`Cannot remove associated data objects unless ${chalk.magentaBright('--force')} flag is used`, {
  66. exit: ExitCodes.InvalidInput,
  67. })
  68. }
  69. const deletionPrize = dataObjectsInfo.reduce((sum, [, prize]) => sum.add(prize), new BN(0))
  70. this.log(
  71. `Data objects deletion prize of ${chalk.cyanBright(
  72. formatBalance(deletionPrize)
  73. )} will be transferred to ${chalk.magentaBright(address)}`
  74. )
  75. }
  76. await this.requireConfirmation(
  77. `Are you sure you want to remove channel ${chalk.magentaBright(channelId.toString())}${
  78. force ? ' and all associated data objects' : ''
  79. }?`
  80. )
  81. await this.sendAndFollowNamedTx(await this.getDecodedPair(address), 'content', 'deleteChannel', [
  82. actor,
  83. channelId,
  84. force ? dataObjectsInfo.length : 0,
  85. ])
  86. }
  87. }