Fix updating video tags to empty field
authorChocobozzz <me@florianbigard.com>
Wed, 16 May 2018 07:28:18 +0000 (09:28 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 16 May 2018 07:42:56 +0000 (09:42 +0200)
client/src/app/shared/misc/utils.ts
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/assets/player/peertube-chunk-store.ts
server/controllers/api/videos/index.ts
server/helpers/custom-validators/misc.ts
server/helpers/custom-validators/videos.ts
server/middlewares/validators/videos.ts
server/models/video/tag.ts

index b5bf99be2336a93ece30ba9e78c1a8fdb942a5cb..b9aa223cf67d34d4a556aa6a681c6d7aaad075a2 100644 (file)
@@ -66,6 +66,11 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) {
 
     if (obj[key] === undefined) continue
 
+    if (Array.isArray(obj[key]) && obj[key].length === 0) {
+      fd.append(key, null)
+      continue
+    }
+
     if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) {
       objectToFormData(obj[ key ], fd, key)
     } else {
index af4438bd269835ed3faedb31b84273549f4a89c3..d4567e26c9db71a2900f05c04ef80a20278be689 100644 (file)
@@ -80,7 +80,7 @@ export class VideoEditComponent implements OnInit {
     this.form.addControl('licence', new FormControl('', VIDEO_LICENCE.VALIDATORS))
     this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS))
     this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS))
-    this.form.addControl('tags', new FormControl(''))
+    this.form.addControl('tags', new FormControl([]))
     this.form.addControl('thumbnailfile', new FormControl(''))
     this.form.addControl('previewfile', new FormControl(''))
     this.form.addControl('support', new FormControl('', VIDEO_SUPPORT.VALIDATORS))
index 84fbaf1460c16723746ef5ae0aef9a7a783838a7..e14e31c0466c23285eb4f68d64a2b08b5d7e3e5a 100644 (file)
@@ -155,17 +155,17 @@ export class PeertubeChunkStore extends EventEmitter {
         this.cleanerInterval = null
       }
 
+      if (this.db) {
+        await this.db.close()
+
+        await this.dropDatabase(this.databaseName)
+      }
+
       if (this.expirationDB) {
         await this.expirationDB.close()
         this.expirationDB = null
       }
 
-      if (this.db) {
-        console.log('Destroying IndexDB database %s.', this.databaseName)
-        await this.db.close()
-        await this.db.delete()
-      }
-
       return cb()
     } catch (err) {
       console.error('Cannot destroy peertube chunk store.', err)
@@ -181,31 +181,42 @@ export class PeertubeChunkStore extends EventEmitter {
     }, PeertubeChunkStore.CLEANER_INTERVAL_MS)
   }
 
-  private checkExpiration () {
-    this.expirationDB.transaction('rw', this.expirationDB.databases, async () => {
-      // Update our database expiration since we are alive
-      await this.expirationDB.databases.put({
-        name: this.databaseName,
-        expiration: new Date().getTime() + PeertubeChunkStore.CLEANER_EXPIRATION_MS
-      })
+  private async checkExpiration () {
+    let databasesToDeleteInfo: { name: string }[] = []
 
-      const now = new Date().getTime()
-      const databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray()
+    try {
+      await this.expirationDB.transaction('rw', this.expirationDB.databases, async () => {
+        // Update our database expiration since we are alive
+        await this.expirationDB.databases.put({
+          name: this.databaseName,
+          expiration: new Date().getTime() + PeertubeChunkStore.CLEANER_EXPIRATION_MS
+        })
 
-      for (const databaseToDeleteInfo of databasesToDeleteInfo) {
-        await this.dropDatabase(databaseToDeleteInfo.name)
+        const now = new Date().getTime()
+        databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray()
+      })
+    } catch (err) {
+      console.error('Cannot update expiration of fetch expired databases.', err)
+    }
 
-        await this.expirationDB.databases.where({ name: databaseToDeleteInfo.name }).delete()
-      }
-    }).catch(err => console.error('Cannot check expiration.', err))
+    for (const databaseToDeleteInfo of databasesToDeleteInfo) {
+      await this.dropDatabase(databaseToDeleteInfo.name)
+    }
   }
 
-  private dropDatabase (databaseName: string) {
+  private async dropDatabase (databaseName: string) {
     const dbToDelete = new ChunkDatabase(databaseName)
+    console.log('Destroying IndexDB database %s.', databaseName)
 
-    console.log('Deleting %s.', databaseName)
-    return dbToDelete.delete()
-      .catch(err => console.error('Cannot delete %s.', databaseName))
+    try {
+      await dbToDelete.delete()
+
+      await this.expirationDB.transaction('rw', this.expirationDB.databases, () => {
+        return this.expirationDB.databases.where({ name: databaseName }).delete()
+      })
+    } catch (err) {
+      console.error('Cannot delete %s.', databaseName, err)
+    }
   }
 
   private nextTick (cb, err, val?) {
index c07430e6c85589a1c71de45f4d05853dbc63f3bb..bcf1eaee6974b3676ac798ab2dabd41423e07c2e 100644 (file)
@@ -244,7 +244,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
 
     video.VideoFiles = [ videoFile ]
 
-    if (videoInfo.tags) {
+    if (videoInfo.tags !== undefined) {
       const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t)
 
       await video.$set('Tags', tagInstances, sequelizeOptions)
@@ -332,7 +332,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
       const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
 
       // Video tags update?
-      if (videoInfoToUpdate.tags) {
+      if (videoInfoToUpdate.tags !== undefined) {
         const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t)
 
         await videoInstanceUpdated.$set('Tags', tagInstances, sequelizeOptions)
index 275482fa19ca2d2c9bc5c1df5fd54106783cc167..254b4db6ccba92b7d0f43dfacc1e5a6956e44b4f 100644 (file)
@@ -35,7 +35,7 @@ function toIntOrNull (value: string) {
   return validator.toInt(value)
 }
 
-function toStringOrNull (value: string) {
+function toValueOrNull (value: string) {
   if (value === 'null') return null
 
   return value
@@ -73,7 +73,7 @@ export {
   isUUIDValid,
   isIdOrUUIDValid,
   isDateValid,
-  toStringOrNull,
+  toValueOrNull,
   isBooleanValid,
   toIntOrNull,
   isFileValid
index c35db49ac426dee63418b352d437a4af2d543e04..002324fe01ebfdb661d9be31968662dda542d7d5 100644 (file)
@@ -57,9 +57,11 @@ function isVideoTagValid (tag: string) {
 }
 
 function isVideoTagsValid (tags: string[]) {
-  return isArray(tags) &&
-         validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
-         tags.every(tag => isVideoTagValid(tag))
+  return tags === null || (
+    isArray(tags) &&
+    validator.isInt(tags.length.toString(), VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
+    tags.every(tag => isVideoTagValid(tag))
+  )
 }
 
 function isVideoAbuseReasonValid (value: string) {
index aa2afb068e24be1ee9ef55975d9055df3379f0fd..dd0246a63ec00fe327317f2f8bed9ebd045ae90c 100644 (file)
@@ -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, toIntOrNull, toStringOrNull } from '../../helpers/custom-validators/misc'
+import { isBooleanValid, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntOrNull, toValueOrNull } from '../../helpers/custom-validators/misc'
 import {
   isVideoAbuseReasonValid,
   isVideoCategoryValid,
@@ -52,21 +52,22 @@ const videosAddValidator = [
     .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
   body('language')
     .optional()
-    .customSanitizer(toStringOrNull)
+    .customSanitizer(toValueOrNull)
     .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)
+    .customSanitizer(toValueOrNull)
     .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
   body('support')
     .optional()
-    .customSanitizer(toStringOrNull)
+    .customSanitizer(toValueOrNull)
     .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
   body('tags')
     .optional()
+    .customSanitizer(toValueOrNull)
     .custom(isVideoTagsValid).withMessage('Should have correct tags'),
   body('commentsEnabled')
     .toBoolean()
@@ -142,7 +143,7 @@ const videosUpdateValidator = [
     .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
   body('language')
     .optional()
-    .customSanitizer(toStringOrNull)
+    .customSanitizer(toValueOrNull)
     .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
   body('nsfw')
     .optional()
@@ -154,14 +155,15 @@ const videosUpdateValidator = [
     .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
   body('description')
     .optional()
-    .customSanitizer(toStringOrNull)
+    .customSanitizer(toValueOrNull)
     .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
   body('support')
     .optional()
-    .customSanitizer(toStringOrNull)
+    .customSanitizer(toValueOrNull)
     .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
   body('tags')
     .optional()
+    .customSanitizer(toValueOrNull)
     .custom(isVideoTagsValid).withMessage('Should have correct tags'),
   body('commentsEnabled')
     .optional()
index 0ae74d80850bbe8f1b0d2a30754967863deb4a22..6d79a55756ad212f84977a383be0757525d950dd 100644 (file)
@@ -37,6 +37,8 @@ export class TagModel extends Model<TagModel> {
   Videos: VideoModel[]
 
   static findOrCreateTags (tags: string[], transaction: Transaction) {
+    if (tags === null) return []
+
     const tasks: Bluebird<TagModel>[] = []
     tags.forEach(tag => {
       const query = {