Browse Source

Small fixes (optional values handling, UserDefinedLicense)

Leszek Wiesner 4 years ago
parent
commit
432800e6b3

+ 4 - 4
cli/src/base/ContentDirectoryCommandBase.ts

@@ -262,7 +262,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
     className: string,
     propName?: string,
     ownerMemberId?: number,
-    defaultId?: number
+    defaultId?: number | null
   ): Promise<[EntityId, Entity]> {
     const [classId, entityClass] = await this.classEntryByNameOrId(className)
     const entityEntries = await this.entitiesByClassAndOwner(classId.toNumber(), ownerMemberId)
@@ -282,7 +282,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
           value: id.toString(), // With numbers there are issues with "default"
         }
       }),
-      default: defaultId?.toString(),
+      default: typeof defaultId === 'number' ? defaultId.toString() : undefined,
     })
 
     return entityEntries.find(([id]) => choosenEntityId === id.toString())!
@@ -293,7 +293,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
     className: string,
     propName?: string,
     ownerMemberId?: number,
-    defaultId?: number
+    defaultId?: number | null
   ): Promise<number> {
     return (await this.promptForEntityEntry(message, className, propName, ownerMemberId, defaultId))[0].toNumber()
   }
@@ -322,7 +322,7 @@ export default abstract class ContentDirectoryCommandBase extends RolesCommandBa
   async parseToKnownEntityJson<T>(entity: Entity): Promise<FlattenRelations<T>> {
     const entityClass = (await this.classEntryByNameOrId(entity.class_id.toString()))[1]
     return (_.mapValues(this.parseEntityPropertyValues(entity, entityClass), (v) =>
-      v.value.toJSON()
+      v.type !== 'Single<Bool>' && v.value.toJSON() === false ? null : v.value.toJSON()
     ) as unknown) as FlattenRelations<T>
   }
 

+ 9 - 5
cli/src/base/MediaCommandBase.ts

@@ -2,6 +2,8 @@ import ContentDirectoryCommandBase from './ContentDirectoryCommandBase'
 import { VideoEntity } from 'cd-schemas/types/entities'
 import fs from 'fs'
 import { DistinctQuestion } from 'inquirer'
+import path from 'path'
+import os from 'os'
 
 const MAX_USER_LICENSE_CONTENT_LENGTH = 4096
 
@@ -25,7 +27,8 @@ export default abstract class MediaCommandBase extends ContentDirectoryCommandBa
       let licenseContent: null | string = null
       while (licenseContent === null) {
         try {
-          const licensePath = await this.simplePrompt({ message: 'Path to license file:' })
+          let licensePath: string = await this.simplePrompt({ message: 'Path to license file:' })
+          licensePath = path.resolve(process.cwd(), licensePath.replace(/^~/, os.homedir()))
           licenseContent = fs.readFileSync(licensePath).toString()
         } catch (e) {
           this.warn("The file was not found or couldn't be accessed, try again...")
@@ -41,21 +44,22 @@ export default abstract class MediaCommandBase extends ContentDirectoryCommandBa
     return license
   }
 
-  async promptForPublishedBeforeJoystream(type: 'add' | 'update', current?: number): Promise<number | undefined> {
+  async promptForPublishedBeforeJoystream(current?: number | null): Promise<number | null> {
     const publishedBefore = await this.simplePrompt({
       type: 'confirm',
-      message: `Do you want to ${type} first publication date (publishedBeforeJoystream)?`,
-      default: false,
+      message: `Do you want to set optional first publication date (publishedBeforeJoystream)?`,
+      default: typeof current === 'number',
     })
     if (publishedBefore) {
       const options = ({
         type: 'datetime',
         message: 'Date of first publication',
         format: ['yyyy', '-', 'mm', '-', 'dd', ' ', 'hh', ':', 'MM', ' ', 'TT'],
+        initial: current && new Date(current * 1000),
       } as unknown) as DistinctQuestion // Need to assert, because we use datetime plugin which has no TS support
       const date = await this.simplePrompt(options)
       return Math.floor(new Date(date).getTime() / 1000)
     }
-    return current
+    return null
   }
 }

+ 1 - 4
cli/src/commands/media/updateVideo.ts

@@ -73,10 +73,7 @@ export default class UpdateVideoCommand extends MediaCommandBase {
         'category',
         () => this.promptForEntityId('Choose Video category', 'ContentCategory', 'name', undefined, currCategoryId),
       ],
-      [
-        'publishedBeforeJoystream',
-        () => this.promptForPublishedBeforeJoystream('update', currPublishedBeforeJoystream),
-      ],
+      ['publishedBeforeJoystream', () => this.promptForPublishedBeforeJoystream(currPublishedBeforeJoystream)],
     ]
     const videoPrompter = new JsonSchemaPrompter<VideoEntity>(videoJsonSchema, currentValues, customizedPrompts)
 

+ 5 - 1
cli/src/commands/media/uploadVideo.ts

@@ -312,6 +312,7 @@ export default class UploadVideoCommand extends MediaCommandBase {
     }
     const videoDefaults: Partial<VideoEntity> = {
       duration: videoMetadata?.duration,
+      skippableIntroDuration: 0,
     }
     // Create prompting helpers
     const videoJsonSchema = (VideoEntitySchema as unknown) as JSONSchema
@@ -344,7 +345,7 @@ export default class UploadVideoCommand extends MediaCommandBase {
 
     const license = await videoPrompter.promptSingleProp('license', () => this.promptForNewLicense())
     const publishedBeforeJoystream = await videoPrompter.promptSingleProp('publishedBeforeJoystream', () =>
-      this.promptForPublishedBeforeJoystream('add')
+      this.promptForPublishedBeforeJoystream()
     )
 
     // Create final inputs
@@ -365,6 +366,9 @@ export default class UploadVideoCommand extends MediaCommandBase {
       publishedBeforeJoystream,
     }
 
+    this.jsonPrettyPrint(JSON.stringify(videoInput))
+    await this.requireConfirmation('Do you confirm the provided input?')
+
     // Parse inputs into operations and send final extrinsic
     const inputParser = InputParser.createWithKnownSchemas(this.getOriginalApi(), [
       {

+ 11 - 8
cli/src/helpers/JsonSchemaPrompt.ts

@@ -85,13 +85,14 @@ export class JsonSchemaPrompter<JsonResult> {
     const customPrompt: CustomPrompt | undefined = custom || this.getCustomPrompt(propertyPath)
     const propDisplayName = this.propertyDisplayName(propertyPath)
     const currentValue = _.get(this.filledObject, propertyPath)
+    const type = Array.isArray(schema.type) ? schema.type[0] : schema.type
 
     if (customPrompt === 'skip') {
       return
     }
 
     // Automatically handle "null" values (useful for enum variants)
-    if (schema.type === 'null') {
+    if (type === 'null') {
       _.set(this.filledObject, propertyPath, null)
       return null
     }
@@ -113,7 +114,7 @@ export class JsonSchemaPrompter<JsonResult> {
     }
 
     // object
-    if (schema.type === 'object' && schema.properties) {
+    if (type === 'object' && schema.properties) {
       const value: Record<string, any> = {}
       for (const [pName, pSchema] of Object.entries(schema.properties)) {
         const objectPropertyPath = propertyPath ? `${propertyPath}.${pName}` : pName
@@ -133,7 +134,9 @@ export class JsonSchemaPrompter<JsonResult> {
                 message: `Do you want to provide optional ${chalk.greenBright(objectPropertyPath)}?`,
                 type: 'confirm',
                 name: 'confirmed',
-                default: _.get(this.filledObject, objectPropertyPath) !== undefined,
+                default:
+                  _.get(this.filledObject, objectPropertyPath) !== undefined &&
+                  _.get(this.filledObject, objectPropertyPath) !== null,
               },
             ])
           ).confirmed
@@ -141,14 +144,14 @@ export class JsonSchemaPrompter<JsonResult> {
         if (confirmed) {
           value[pName] = await this.prompt(pSchema, objectPropertyPath)
         } else {
-          _.set(this.filledObject, objectPropertyPath, undefined)
+          _.set(this.filledObject, objectPropertyPath, null)
         }
       }
       return value
     }
 
     // array
-    if (schema.type === 'array' && schema.items) {
+    if (type === 'array' && schema.items) {
       return await this.promptWithRetry(() => this.promptArray(schema, propertyPath), propertyPath, true)
     }
 
@@ -164,16 +167,16 @@ export class JsonSchemaPrompter<JsonResult> {
     // Prompt options
     if (schema.enum) {
       additionalPromptOptions = { type: 'list', choices: schema.enum as any[] }
-    } else if (schema.type === 'boolean') {
+    } else if (type === 'boolean') {
       additionalPromptOptions = BOOL_PROMPT_OPTIONS
     }
 
     // Normalizers
-    if (schema.type === 'integer') {
+    if (type === 'integer') {
       normalizer = (v) => (parseInt(v).toString() === v ? parseInt(v) : v)
     }
 
-    if (schema.type === 'number') {
+    if (type === 'number') {
       normalizer = (v) => (Number(v).toString() === v ? Number(v) : v)
     }
 

+ 1 - 2
content-directory-schemas/inputs/schemas/VideoSchema.json

@@ -79,8 +79,7 @@
       "name": "isExplicit",
       "description": "Whether the Video contains explicit material.",
       "required": true,
-      "property_type": { "Single": "Bool" },
-      "locking_policy": { "is_locked_from_controller": true }
+      "property_type": { "Single": "Bool" }
     },
     {
       "name": "license",

+ 1 - 1
content-directory-schemas/scripts/initializeContentDir.ts

@@ -26,7 +26,7 @@ async function main() {
   const ALICE = getAlicePair()
 
   // Emptiness check
-  if ((await api.query.contentDirectory.nextClassId()).toNumber() > 1) {
+  if ((await api.query.contentDirectory.classById.keys()).length > 0) {
     console.log('Content directory is not empty! Skipping...')
     process.exit()
   }

+ 20 - 7
content-directory-schemas/scripts/inputSchemasToEntitySchemas.ts

@@ -12,7 +12,7 @@ import {
 import PRIMITIVE_PROPERTY_DEFS from '../schemas/propertyValidationDefs.schema.json'
 import { getInputs } from '../src/helpers/inputs'
 import { getSchemasLocation, SCHEMA_TYPES } from '../src/helpers/schemas'
-import { JSONSchema7 } from 'json-schema'
+import { JSONSchema7, JSONSchema7TypeName } from 'json-schema'
 
 const schemaInputs = getInputs<AddClassSchema>('schemas')
 
@@ -68,12 +68,25 @@ const VecPropertyDef = ({ Vector: vec }: VecPropertyVariant): JSONSchema7 => ({
   'items': SinglePropertyDef({ Single: vec.vec_type }),
 })
 
-const PropertyDef = ({ property_type: propertyType, description }: Property): JSONSchema7 => ({
-  ...((propertyType as SinglePropertyVariant).Single
-    ? SinglePropertyDef(propertyType as SinglePropertyVariant)
-    : VecPropertyDef(propertyType as VecPropertyVariant)),
-  description,
-})
+const PropertyDef = ({ property_type: propertyType, description, required }: Property): JSONSchema7 => {
+  const def = {
+    ...((propertyType as SinglePropertyVariant).Single
+      ? SinglePropertyDef(propertyType as SinglePropertyVariant)
+      : VecPropertyDef(propertyType as VecPropertyVariant)),
+    description,
+  }
+  // Non-required fields:
+  // Simple fields:
+  if (!required && def.type) {
+    def.type = [def.type as JSONSchema7TypeName, 'null']
+  }
+  // Relationships:
+  else if (!required && def.oneOf) {
+    def.oneOf = [...def.oneOf, { type: 'null' }]
+  }
+
+  return def
+}
 
 // Mkdir entity schemas directories if they do not exist
 SCHEMA_TYPES.forEach((type) => {

+ 12 - 7
content-directory-schemas/src/helpers/InputParser.ts

@@ -232,11 +232,16 @@ export class InputParser {
 
       let value = customHandler && (await customHandler(schemaProperty, propertyValue))
       if (value === undefined) {
-        value = createType('ParametrizedPropertyValue', {
-          InputPropertyValue: (await this.parsePropertyType(schemaProperty.property_type)).toInputPropertyValue(
-            propertyValue
-          ),
-        })
+        if (propertyValue === null) {
+          // Optional values: (can be cleared by setting them to Bool(false)):
+          value = createType('ParametrizedPropertyValue', { InputPropertyValue: { Single: { Bool: false } } })
+        } else {
+          value = createType('ParametrizedPropertyValue', {
+            InputPropertyValue: (await this.parsePropertyType(schemaProperty.property_type)).toInputPropertyValue(
+              propertyValue
+            ),
+          })
+        }
       }
 
       parametrizedClassPropValues.push(
@@ -286,10 +291,10 @@ export class InputParser {
         const { property_type: propertyType } = property
         if (isSingle(propertyType) && isReference(propertyType.Single)) {
           const refEntitySchema = this.schemaByClassName(propertyType.Single.Reference.className)
-          if (Object.keys(value).includes('new')) {
+          if (value !== null && Object.keys(value).includes('new')) {
             const entityIndex = await this.parseEntityInput(value.new, refEntitySchema)
             return createType('ParametrizedPropertyValue', { InternalEntityJustAdded: entityIndex })
-          } else if (Object.keys(value).includes('existing')) {
+          } else if (value !== null && Object.keys(value).includes('existing')) {
             return this.existingEntityQueryToParametrizedPropertyValue(refEntitySchema.className, value.existing)
           }
         }