|
@@ -12,6 +12,7 @@ import {
|
|
|
EntityId,
|
|
|
Actor,
|
|
|
PropertyType,
|
|
|
+ Property,
|
|
|
} from '@joystream/types/content-directory'
|
|
|
import { Worker } from '@joystream/types/working-group'
|
|
|
import { CLIError } from '@oclif/errors'
|
|
@@ -23,6 +24,7 @@ import { RolesCommandBase } from './WorkingGroupsCommandBase'
|
|
|
import { createType } from '@joystream/types'
|
|
|
import chalk from 'chalk'
|
|
|
import { flags } from '@oclif/command'
|
|
|
+import { DistinctQuestion } from 'inquirer'
|
|
|
|
|
|
const CONTEXTS = ['Member', 'Curator', 'Lead'] as const
|
|
|
type Context = typeof CONTEXTS[number]
|
|
@@ -247,7 +249,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
|
|
|
|
|
|
async getAndParseKnownEntity<T>(id: string | number, className?: string): Promise<FlattenRelations<T>> {
|
|
|
const entity = await this.getEntity(id, className)
|
|
|
- return this.parseToKnownEntityJson<T>(entity)
|
|
|
+ return this.parseToEntityJson<T>(entity)
|
|
|
}
|
|
|
|
|
|
async entitiesByClassAndOwner(classNameOrId: number | string, ownerMemberId?: number): Promise<[EntityId, Entity][]> {
|
|
@@ -339,7 +341,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
|
|
|
}, {} as Record<string, ParsedPropertyValue>)
|
|
|
}
|
|
|
|
|
|
- async parseToKnownEntityJson<T>(entity: Entity): Promise<FlattenRelations<T>> {
|
|
|
+ async parseToEntityJson<T = unknown>(entity: Entity): Promise<FlattenRelations<T>> {
|
|
|
const entityClass = (await this.classEntryByNameOrId(entity.class_id.toString()))[1]
|
|
|
return (_.mapValues(this.parseEntityPropertyValues(entity, entityClass), (v) =>
|
|
|
this.parseStoredPropertyInnerValue(v.value)
|
|
@@ -376,4 +378,124 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
|
|
|
|
|
|
return parsedEntities.filter((entity) => filters.every(([pName, pValue]) => entity[pName] === pValue))
|
|
|
}
|
|
|
+
|
|
|
+ async getActor(context: typeof CONTEXTS[number], pickedClass: Class) {
|
|
|
+ let actor: Actor
|
|
|
+ if (context === 'Member') {
|
|
|
+ const memberId = await this.getRequiredMemberId()
|
|
|
+ actor = this.createType('Actor', { Member: memberId })
|
|
|
+ } else if (context === 'Curator') {
|
|
|
+ actor = await this.getCuratorContext([pickedClass.name.toString()])
|
|
|
+ } else {
|
|
|
+ await this.getRequiredLead()
|
|
|
+
|
|
|
+ actor = this.createType('Actor', { Lead: null })
|
|
|
+ }
|
|
|
+
|
|
|
+ return actor
|
|
|
+ }
|
|
|
+
|
|
|
+ isActorEntityController(actor: Actor, entity: Entity, isMaintainer: boolean): boolean {
|
|
|
+ const entityController = entity.entity_permissions.controller
|
|
|
+ return (
|
|
|
+ (isMaintainer && entityController.isOfType('Maintainers')) ||
|
|
|
+ (entityController.isOfType('Member') &&
|
|
|
+ actor.isOfType('Member') &&
|
|
|
+ entityController.asType('Member').eq(actor.asType('Member'))) ||
|
|
|
+ (entityController.isOfType('Lead') && actor.isOfType('Lead'))
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ async isEntityPropertyEditableByActor(entity: Entity, classPropertyId: number, actor: Actor): Promise<boolean> {
|
|
|
+ const [, entityClass] = await this.classEntryByNameOrId(entity.class_id.toString())
|
|
|
+
|
|
|
+ const isActorMaintainer =
|
|
|
+ actor.isOfType('Curator') &&
|
|
|
+ entityClass.class_permissions.maintainers.toArray().some((groupId) => groupId.eq(actor.asType('Curator')[0]))
|
|
|
+
|
|
|
+ const isActorController = this.isActorEntityController(actor, entity, isActorMaintainer)
|
|
|
+
|
|
|
+ const {
|
|
|
+ is_locked_from_controller: isLockedFromController,
|
|
|
+ is_locked_from_maintainer: isLockedFromMaintainer,
|
|
|
+ } = entityClass.properties[classPropertyId].locking_policy
|
|
|
+
|
|
|
+ return (
|
|
|
+ (isActorController && !isLockedFromController.valueOf()) ||
|
|
|
+ (isActorMaintainer && !isLockedFromMaintainer.valueOf())
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ getQuestionsFromProperties(properties: Property[], defaults?: { [key: string]: unknown }): DistinctQuestion[] {
|
|
|
+ return properties.reduce((previousValue, { name, property_type: propertyType, required }) => {
|
|
|
+ const propertySubtype = propertyType.subtype
|
|
|
+ const questionType = propertySubtype === 'Bool' ? 'list' : 'input'
|
|
|
+ const isSubtypeNumber = propertySubtype.toLowerCase().includes('int')
|
|
|
+ const isSubtypeReference = propertyType.isOfType('Single') && propertyType.asType('Single').isOfType('Reference')
|
|
|
+
|
|
|
+ const validate = async (answer: string | number | null) => {
|
|
|
+ if (answer === null) {
|
|
|
+ return true // Can only happen through "filter" if property is not required
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((isSubtypeNumber || isSubtypeReference) && parseInt(answer.toString()).toString() !== answer.toString()) {
|
|
|
+ return `Expected integer value!`
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isSubtypeReference) {
|
|
|
+ try {
|
|
|
+ await this.getEntity(+answer, propertyType.asType('Single').asType('Reference')[0].toString())
|
|
|
+ } catch (e) {
|
|
|
+ return e.message || JSON.stringify(e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ const optionalQuestionProperties = {
|
|
|
+ ...{
|
|
|
+ filter: async (answer: string) => {
|
|
|
+ if (required.isFalse && !answer) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ // Only cast to number if valid
|
|
|
+ // Prevents inquirer bug not allowing to edit invalid values when casted to number
|
|
|
+ // See: https://github.com/SBoudrias/Inquirer.js/issues/866
|
|
|
+ if ((isSubtypeNumber || isSubtypeReference) && (await validate(answer)) === true) {
|
|
|
+ return parseInt(answer)
|
|
|
+ }
|
|
|
+
|
|
|
+ return answer
|
|
|
+ },
|
|
|
+ validate,
|
|
|
+ },
|
|
|
+ ...(propertySubtype === 'Bool' && {
|
|
|
+ choices: ['true', 'false'],
|
|
|
+ filter: (answer: string) => {
|
|
|
+ return answer === 'true' || false
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ }
|
|
|
+
|
|
|
+ const isQuestionOptional = propertySubtype === 'Bool' ? '' : required.isTrue ? '(required)' : '(optional)'
|
|
|
+ const classId = isSubtypeReference
|
|
|
+ ? ` [Class Id: ${propertyType.asType('Single').asType('Reference')[0].toString()}]`
|
|
|
+ : ''
|
|
|
+
|
|
|
+ return [
|
|
|
+ ...previousValue,
|
|
|
+ {
|
|
|
+ name: name.toString(),
|
|
|
+ message: `${name} - ${propertySubtype}${classId} ${isQuestionOptional}`,
|
|
|
+ type: questionType,
|
|
|
+ ...optionalQuestionProperties,
|
|
|
+ ...(defaults && {
|
|
|
+ default: propertySubtype === 'Bool' ? JSON.stringify(defaults[name.toString()]) : defaults[name.toString()],
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ }, [] as DistinctQuestion[])
|
|
|
+ }
|
|
|
}
|