From 360329cc029c062bfb145c90f7bf1c007c39af81 Mon Sep 17 00:00:00 2001
From: Chocobozzz <me@florianbigard.com>
Date: Wed, 9 May 2018 11:23:14 +0200
Subject: [PATCH] Account/channel descriptions are not required anymore

---
 .../my-account-profile.component.ts           |  2 +-
 ...-account-video-channel-create.component.ts |  4 +-
 ...-account-video-channel-update.component.ts |  4 +-
 .../app/shared/forms/form-validators/user.ts  |  2 -
 client/src/app/shared/misc/utils.ts           |  2 +-
 client/src/app/shared/video/video.service.ts  | 10 +-
 server/helpers/custom-validators/misc.ts      | 16 +++-
 server/middlewares/validators/videos.ts       | 95 +++++++++++++++----
 server/tests/api/check-params/videos.ts       | 26 -----
 9 files changed, 100 insertions(+), 61 deletions(-)

diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
index 2b7ba353c..468be022c 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
@@ -47,7 +47,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit {
 
   updateMyProfile () {
     const displayName = this.form.value['display-name']
-    const description = this.form.value['description']
+    const description = this.form.value['description'] || null
 
     this.error = null
 
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts
index 0f03548ad..fab9cacd8 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts
@@ -64,8 +64,8 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE
     const body = this.form.value
     const videoChannelCreate: VideoChannelCreate = {
       displayName: body['display-name'],
-      description: body.description || undefined,
-      support: body.support || undefined
+      description: body.description || null,
+      support: body.support || null
     }
 
     this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe(
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
index c0dc6a939..9adc38691 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
@@ -92,8 +92,8 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
     const body = this.form.value
     const videoChannelUpdate: VideoChannelUpdate = {
       displayName: body['display-name'],
-      description: body.description || undefined,
-      support: body.support || undefined
+      description: body.description || null,
+      support: body.support || null
     }
 
     this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.uuid, videoChannelUpdate).subscribe(
diff --git a/client/src/app/shared/forms/form-validators/user.ts b/client/src/app/shared/forms/form-validators/user.ts
index c6b65e0df..0973f1b00 100644
--- a/client/src/app/shared/forms/form-validators/user.ts
+++ b/client/src/app/shared/forms/form-validators/user.ts
@@ -60,12 +60,10 @@ export const USER_DISPLAY_NAME = {
 }
 export const USER_DESCRIPTION = {
   VALIDATORS: [
-    Validators.required,
     Validators.minLength(3),
     Validators.maxLength(250)
   ],
   MESSAGES: {
-    'required': 'Description is required.',
     'minlength': 'Description must be at least 3 characters long.',
     'maxlength': 'Description cannot be more than 250 characters long.'
   }
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
index 99f6b3cf0..b5bf99be2 100644
--- a/client/src/app/shared/misc/utils.ts
+++ b/client/src/app/shared/misc/utils.ts
@@ -66,7 +66,7 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) {
 
     if (obj[key] === undefined) continue
 
-    if (typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) {
+    if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) {
       objectToFormData(obj[ key ], fd, key)
     } else {
       fd.append(formKey, obj[ key ])
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 8870cbee4..b45777c55 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -54,11 +54,11 @@ export class VideoService {
   }
 
   updateVideo (video: VideoEdit) {
-    const language = video.language || undefined
-    const licence = video.licence || undefined
-    const category = video.category || undefined
-    const description = video.description || undefined
-    const support = video.support || undefined
+    const language = video.language || null
+    const licence = video.licence || null
+    const category = video.category || null
+    const description = video.description || null
+    const support = video.support || null
 
     const body: VideoUpdate = {
       name: video.name,
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index 8a270b777..275482fa1 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -25,10 +25,22 @@ function isIdOrUUIDValid (value: string) {
   return isIdValid(value) || isUUIDValid(value)
 }
 
-function isBooleanValid (value: string) {
+function isBooleanValid (value: any) {
   return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
 }
 
+function toIntOrNull (value: string) {
+  if (value === 'null') return null
+
+  return validator.toInt(value)
+}
+
+function toStringOrNull (value: string) {
+  if (value === 'null') return null
+
+  return value
+}
+
 function isFileValid (
   files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[],
   mimeTypeRegex: string,
@@ -61,6 +73,8 @@ export {
   isUUIDValid,
   isIdOrUUIDValid,
   isDateValid,
+  toStringOrNull,
   isBooleanValid,
+  toIntOrNull,
   isFileValid
 }
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index e3543ef93..b93dccc50 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
 import 'express-validator'
 import { body, param, query } from 'express-validator/check'
 import { UserRight, VideoPrivacy } from '../../../shared'
-import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid } from '../../helpers/custom-validators/misc'
+import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntOrNull, toStringOrNull } from '../../helpers/custom-validators/misc'
 import {
   isVideoAbuseReasonValid,
   isVideoCategoryValid,
@@ -14,7 +14,8 @@ import {
   isVideoLicenceValid,
   isVideoNameValid,
   isVideoPrivacyValid,
-  isVideoRatingTypeValid, isVideoSupportValid,
+  isVideoRatingTypeValid,
+  isVideoSupportValid,
   isVideoTagsValid
 } from '../../helpers/custom-validators/videos'
 import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
@@ -41,16 +42,40 @@ const videosAddValidator = [
     + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
   ),
   body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
-  body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
-  body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
-  body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
-  body('nsfw').custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
-  body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
-  body('support').optional().custom(isVideoSupportValid).withMessage('Should have a valid support text'),
+  body('category')
+    .optional()
+    .customSanitizer(toIntOrNull)
+    .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
+  body('licence')
+    .optional()
+    .customSanitizer(toIntOrNull)
+    .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
+  body('language')
+    .optional()
+    .customSanitizer(toStringOrNull)
+    .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
+  body('nsfw')
+    .toBoolean()
+    .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
+  body('description')
+    .optional()
+    .customSanitizer(toStringOrNull)
+    .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
+  body('support')
+    .optional()
+    .customSanitizer(toStringOrNull)
+    .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
+  body('tags')
+    .optional()
+    .custom(isVideoTagsValid).withMessage('Should have correct tags'),
+  body('commentsEnabled')
+    .toBoolean()
+    .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
+  body('privacy')
+    .optional()
+    .toInt()
+    .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
   body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
-  body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
-  body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
-  body('commentsEnabled').custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
@@ -110,16 +135,44 @@ const videosUpdateValidator = [
     'This preview file is not supported. Please, make sure it is of the following type : '
     + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
   ),
-  body('name').optional().custom(isVideoNameValid).withMessage('Should have a valid name'),
-  body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
-  body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
-  body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
-  body('nsfw').optional().custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
-  body('privacy').optional().custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
-  body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
-  body('support').optional().custom(isVideoSupportValid).withMessage('Should have a valid support text'),
-  body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
-  body('commentsEnabled').optional().custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
+  body('name')
+    .optional()
+    .custom(isVideoNameValid).withMessage('Should have a valid name'),
+  body('category')
+    .optional()
+    .customSanitizer(toIntOrNull)
+    .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
+  body('licence')
+    .optional()
+    .customSanitizer(toIntOrNull)
+    .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
+  body('language')
+    .optional()
+    .customSanitizer(toStringOrNull)
+    .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
+  body('nsfw')
+    .optional()
+    .toBoolean()
+    .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
+  body('privacy')
+    .optional()
+    .toInt()
+    .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
+  body('description')
+    .optional()
+    .customSanitizer(toStringOrNull)
+    .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
+  body('support')
+    .optional()
+    .customSanitizer(toStringOrNull)
+    .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
+  body('tags')
+    .optional()
+    .custom(isVideoTagsValid).withMessage('Should have correct tags'),
+  body('commentsEnabled')
+    .optional()
+    .toBoolean()
+    .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking videosUpdate parameters', { parameters: req.body })
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index 499a4fc94..2b341a5b3 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -231,13 +231,6 @@ describe('Test videos API validator', function () {
       await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
-    it('Should fail with a bad nsfw attribute', async function () {
-      const fields = immutableAssign(baseCorrectParams, { nsfw: 2 })
-      const attaches = baseCorrectAttaches
-
-      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
-    })
-
     it('Should fail without commentsEnabled attribute', async function () {
       const fields = omit(baseCorrectParams, 'commentsEnabled')
       const attaches = baseCorrectAttaches
@@ -245,13 +238,6 @@ describe('Test videos API validator', function () {
       await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
-    it('Should fail with a bad commentsEnabled attribute', async function () {
-      const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 })
-      const attaches = baseCorrectAttaches
-
-      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
-    })
-
     it('Should fail with a long description', async function () {
       const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) })
       const attaches = baseCorrectAttaches
@@ -485,18 +471,6 @@ describe('Test videos API validator', function () {
       await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
     })
 
-    it('Should fail with a bad nsfw attribute', async function () {
-      const fields = immutableAssign(baseCorrectParams, { nsfw: 2 })
-
-      await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
-    })
-
-    it('Should fail with a bad commentsEnabled attribute', async function () {
-      const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 })
-
-      await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
-    })
-
     it('Should fail with a long description', async function () {
       const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) })