Use async/await in controllers
authorChocobozzz <florian.bigard@gmail.com>
Wed, 25 Oct 2017 09:55:06 +0000 (11:55 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Thu, 26 Oct 2017 07:11:38 +0000 (09:11 +0200)
20 files changed:
server/controllers/api/config.ts
server/controllers/api/oauth-clients.ts
server/controllers/api/pods.ts
server/controllers/api/remote/pods.ts
server/controllers/api/remote/videos.ts
server/controllers/api/request-schedulers.ts
server/controllers/api/users.ts
server/controllers/api/videos/abuse.ts
server/controllers/api/videos/blacklist.ts
server/controllers/api/videos/channel.ts
server/controllers/api/videos/index.ts
server/controllers/api/videos/rate.ts
server/controllers/client.ts
server/controllers/static.ts
server/helpers/database-utils.ts
server/helpers/utils.ts
server/initializers/database.ts
server/lib/video-channel.ts
server/middlewares/async.ts [new file with mode: 0644]
server/middlewares/index.ts

index c9a051bdc1839f19bd019532e43513ca1921af31..5f704f0eeef38d84645b8d072e8b618eaefe424f 100644 (file)
@@ -2,30 +2,32 @@ import * as express from 'express'
 
 import { isSignupAllowed } from '../../helpers'
 import { CONFIG } from '../../initializers'
+import { asyncMiddleware } from '../../middlewares'
 import { ServerConfig } from '../../../shared'
 
 const configRouter = express.Router()
 
-configRouter.get('/', getConfig)
+configRouter.get('/',
+  asyncMiddleware(getConfig)
+)
 
-function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const allowed = await isSignupAllowed()
 
-  isSignupAllowed().then(allowed => {
-    const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
-                                     .filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
-                                     .map(r => parseInt(r, 10))
+  const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
+   .filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
+   .map(r => parseInt(r, 10))
 
-    const json: ServerConfig = {
-      signup: {
-        allowed
-      },
-      transcoding: {
-        enabledResolutions
-      }
+  const json: ServerConfig = {
+    signup: {
+      allowed
+    },
+    transcoding: {
+      enabledResolutions
     }
+  }
 
-    res.json(json)
-  })
+  return res.json(json)
 }
 
 // ---------------------------------------------------------------------------
index f7dac598c3d62770049ca656bc27f8d35c9973a0..ac1ee9e36f44cf481c151d15d8861ff37c09feaa 100644 (file)
@@ -2,15 +2,18 @@ import * as express from 'express'
 
 import { CONFIG } from '../../initializers'
 import { logger } from '../../helpers'
+import { asyncMiddleware } from '../../middlewares'
 import { database as db } from '../../initializers/database'
 import { OAuthClientLocal } from '../../../shared'
 
 const oauthClientsRouter = express.Router()
 
-oauthClientsRouter.get('/local', getLocalClient)
+oauthClientsRouter.get('/local',
+  asyncMiddleware(getLocalClient)
+)
 
 // Get the client credentials for the PeerTube front end
-function getLocalClient (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function getLocalClient (req: express.Request, res: express.Response, next: express.NextFunction) {
   const serverHostname = CONFIG.WEBSERVER.HOSTNAME
   const serverPort = CONFIG.WEBSERVER.PORT
   let headerHostShouldBe = serverHostname
@@ -24,17 +27,14 @@ function getLocalClient (req: express.Request, res: express.Response, next: expr
     return res.type('json').status(403).end()
   }
 
-  db.OAuthClient.loadFirstClient()
-    .then(client => {
-      if (!client) throw new Error('No client available.')
-
-      const json: OAuthClientLocal = {
-        client_id: client.clientId,
-        client_secret: client.clientSecret
-      }
-      res.json(json)
-    })
-    .catch(err => next(err))
+  const client = await db.OAuthClient.loadFirstClient()
+  if (!client) throw new Error('No client available.')
+
+  const json: OAuthClientLocal = {
+    client_id: client.clientId,
+    client_secret: client.clientSecret
+  }
+  return res.json(json)
 }
 
 // ---------------------------------------------------------------------------
index 804aa0659a01143f8b3febfd30fd2c18bc44d5e3..bf1b744e55a39971492d152656bcfe884cdac412 100644 (file)
@@ -16,7 +16,8 @@ import {
   paginationValidator,
   setPagination,
   setPodsSort,
-  podsSortValidator
+  podsSortValidator,
+  asyncMiddleware
 } from '../../middlewares'
 import { PodInstance } from '../../models'
 
@@ -27,25 +28,25 @@ podsRouter.get('/',
   podsSortValidator,
   setPodsSort,
   setPagination,
-  listPods
+  asyncMiddleware(listPods)
 )
 podsRouter.post('/make-friends',
   authenticate,
   ensureIsAdmin,
   makeFriendsValidator,
   setBodyHostsPort,
-  makeFriendsController
+  asyncMiddleware(makeFriendsController)
 )
 podsRouter.get('/quit-friends',
   authenticate,
   ensureIsAdmin,
-  quitFriendsController
+  asyncMiddleware(quitFriendsController)
 )
 podsRouter.delete('/:id',
   authenticate,
   ensureIsAdmin,
   podRemoveValidator,
-  removeFriendController
+  asyncMiddleware(removeFriendController)
 )
 
 // ---------------------------------------------------------------------------
@@ -56,33 +57,33 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function listPods (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.Pod.listForApi(req.query.start, req.query.count, req.query.sort)
-    .then(resultList => res.json(getFormattedObjects(resultList.data, resultList.total)))
-    .catch(err => next(err))
+async function listPods (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.Pod.listForApi(req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
   const hosts = req.body.hosts as string[]
 
+  // Don't wait the process that could be long
   makeFriends(hosts)
     .then(() => logger.info('Made friends!'))
     .catch(err => logger.error('Could not make friends.', err))
 
-  // Don't wait the process that could be long
-  res.type('json').status(204).end()
+  return res.type('json').status(204).end()
 }
 
-function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
-  quitFriends()
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+async function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
+  await quitFriends()
+
+  return res.type('json').status(204).end()
 }
 
-function removeFriendController (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function removeFriendController (req: express.Request, res: express.Response, next: express.NextFunction) {
   const pod = res.locals.pod as PodInstance
 
-  removeFriend(pod)
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+  await removeFriend(pod)
+
+  return res.type('json').status(204).end()
 }
index a62b9c6846a6abb7ec58b80987ee03c00079e411..326eb61ac183c712176c0517bc28c178e9973a50 100644 (file)
@@ -5,7 +5,8 @@ import {
   checkSignature,
   signatureValidator,
   setBodyHostPort,
-  remotePodsAddValidator
+  remotePodsAddValidator,
+  asyncMiddleware
 } from '../../../middlewares'
 import { sendOwnedDataToPod } from '../../../lib'
 import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
@@ -18,15 +19,17 @@ const remotePodsRouter = express.Router()
 remotePodsRouter.post('/remove',
   signatureValidator,
   checkSignature,
-  removePods
+  asyncMiddleware(removePods)
 )
 
-remotePodsRouter.post('/list', remotePodsList)
+remotePodsRouter.post('/list',
+  asyncMiddleware(remotePodsList)
+)
 
 remotePodsRouter.post('/add',
   setBodyHostPort, // We need to modify the host before running the validator!
   remotePodsAddValidator,
-  addPods
+  asyncMiddleware(addPods)
 )
 
 // ---------------------------------------------------------------------------
@@ -37,35 +40,30 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
   const information = req.body
 
   const pod = db.Pod.build(information)
-  pod.save()
-     .then(podCreated => {
-       return sendOwnedDataToPod(podCreated.id)
-     })
-     .then(() => {
-       return getMyPublicCert()
-     })
-     .then(cert => {
-       return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL })
-     })
-     .catch(err => next(err))
+  const podCreated = await pod.save()
+
+  await sendOwnedDataToPod(podCreated.id)
+
+  const cert = await getMyPublicCert()
+  return res.json({ cert, email: CONFIG.ADMIN.EMAIL })
 }
 
-function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.Pod.list()
-    .then(podsList => res.json(getFormattedObjects<FormattedPod, PodInstance>(podsList, podsList.length)))
-    .catch(err => next(err))
+async function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const pods = await db.Pod.list()
+
+  return res.json(getFormattedObjects<FormattedPod, PodInstance>(pods, pods.length))
 }
 
-function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
   const signature: PodSignature = req.body.signature
   const host = signature.host
 
-  db.Pod.loadByHost(host)
-    .then(pod => pod.destroy())
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+  const pod = await db.Pod.loadByHost(host)
+  await pod.destroy()
+
+  return res.type('json').status(204).end()
 }
index c8f531490c371e6d2b34067068c76da058e0f40c..bf442c6e58627ba66c73507cfbc24285cd7a719c 100644 (file)
@@ -1,5 +1,5 @@
 import * as express from 'express'
-import * as Promise from 'bluebird'
+import * as Bluebird from 'bluebird'
 import * as Sequelize from 'sequelize'
 
 import { database as db } from '../../../initializers/database'
@@ -17,7 +17,7 @@ import {
   remoteEventsVideosValidator
 } from '../../../middlewares'
 import { logger, retryTransactionWrapper } from '../../../helpers'
-import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib'
+import { quickAndDirtyUpdatesVideoToFriends, fetchVideoChannelByHostAndUUID } from '../../../lib'
 import { PodInstance, VideoFileInstance } from '../../../models'
 import {
   RemoteVideoRequest,
@@ -87,7 +87,7 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres
   const fromPod = res.locals.secure.pod
 
   // We need to process in the same order to keep consistency
-  Promise.each(requests, request => {
+  Bluebird.each(requests, request => {
     const data = request.data
 
     // Get the function we need to call in order to process the request
@@ -109,7 +109,7 @@ function remoteVideosQadu (req: express.Request, res: express.Response, next: ex
   const requests: RemoteQaduVideoRequest[] = req.body.data
   const fromPod = res.locals.secure.pod
 
-  Promise.each(requests, request => {
+  Bluebird.each(requests, request => {
     const videoData = request.data
 
     return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
@@ -123,7 +123,7 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
   const requests: RemoteVideoEventRequest[] = req.body.data
   const fromPod = res.locals.secure.pod
 
-  Promise.each(requests, request => {
+  Bluebird.each(requests, request => {
     const eventData = request.data
 
     return processVideosEventsRetryWrapper(eventData, fromPod)
@@ -133,541 +133,447 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
   return res.type('json').status(204).end()
 }
 
-function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) {
+async function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) {
   const options = {
     arguments: [ eventData, fromPod ],
     errorMessage: 'Cannot process videos events with many retries.'
   }
 
-  return retryTransactionWrapper(processVideosEvents, options)
+  await retryTransactionWrapper(processVideosEvents, options)
 }
 
-function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
+async function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
+  await db.sequelize.transaction(async t => {
+    const sequelizeOptions = { transaction: t }
+    const videoInstance = await fetchVideoByUUID(eventData.uuid, t)
 
-  return db.sequelize.transaction(t => {
-    return fetchVideoByUUID(eventData.uuid, t)
-      .then(videoInstance => {
-        const options = { transaction: t }
+    let columnToUpdate
+    let qaduType
 
-        let columnToUpdate
-        let qaduType
+    switch (eventData.eventType) {
+    case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
+      columnToUpdate = 'views'
+      qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
+      break
 
-        switch (eventData.eventType) {
-        case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
-          columnToUpdate = 'views'
-          qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
-          break
+    case REQUEST_VIDEO_EVENT_TYPES.LIKES:
+      columnToUpdate = 'likes'
+      qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
+      break
 
-        case REQUEST_VIDEO_EVENT_TYPES.LIKES:
-          columnToUpdate = 'likes'
-          qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
-          break
+    case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
+      columnToUpdate = 'dislikes'
+      qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
+      break
 
-        case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
-          columnToUpdate = 'dislikes'
-          qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
-          break
+    default:
+      throw new Error('Unknown video event type.')
+    }
 
-        default:
-          throw new Error('Unknown video event type.')
-        }
+    const query = {}
+    query[columnToUpdate] = eventData.count
 
-        const query = {}
-        query[columnToUpdate] = eventData.count
+    await videoInstance.increment(query, sequelizeOptions)
 
-        return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType }))
-      })
-      .then(({ videoInstance, qaduType }) => {
-        const qadusParams = [
-          {
-            videoId: videoInstance.id,
-            type: qaduType
-          }
-        ]
-
-        return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
-      })
-  })
-  .then(() => logger.info('Remote video event processed for video with uuid %s.', eventData.uuid))
-  .catch(err => {
-    logger.debug('Cannot process a video event.', err)
-    throw err
+    const qadusParams = [
+      {
+        videoId: videoInstance.id,
+        type: qaduType
+      }
+    ]
+    await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
   })
+
+  logger.info('Remote video event processed for video with uuid %s.', eventData.uuid)
 }
 
-function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
+async function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
   const options = {
     arguments: [ videoData, fromPod ],
     errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
   }
 
-  return retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
+  await retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
 }
 
-function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
+async function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
   let videoUUID = ''
 
-  return db.sequelize.transaction(t => {
-    return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
-      .then(videoInstance => {
-        const options = { transaction: t }
+  await db.sequelize.transaction(async t => {
+    const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
+    const sequelizeOptions = { transaction: t }
 
-        videoUUID = videoInstance.uuid
+    videoUUID = videoInstance.uuid
 
-        if (videoData.views) {
-          videoInstance.set('views', videoData.views)
-        }
+    if (videoData.views) {
+      videoInstance.set('views', videoData.views)
+    }
 
-        if (videoData.likes) {
-          videoInstance.set('likes', videoData.likes)
-        }
+    if (videoData.likes) {
+      videoInstance.set('likes', videoData.likes)
+    }
 
-        if (videoData.dislikes) {
-          videoInstance.set('dislikes', videoData.dislikes)
-        }
+    if (videoData.dislikes) {
+      videoInstance.set('dislikes', videoData.dislikes)
+    }
 
-        return videoInstance.save(options)
-      })
+    await videoInstance.save(sequelizeOptions)
   })
-  .then(() => logger.info('Remote video with uuid %s quick and dirty updated', videoUUID))
-  .catch(err => logger.debug('Cannot quick and dirty update the remote video.', err))
+
+  logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)
 }
 
 // Handle retries on fail
-function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
+async function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
   const options = {
     arguments: [ videoToCreateData, fromPod ],
     errorMessage: 'Cannot insert the remote video with many retries.'
   }
 
-  return retryTransactionWrapper(addRemoteVideo, options)
+  await retryTransactionWrapper(addRemoteVideo, options)
 }
 
-function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
+async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
   logger.debug('Adding remote video "%s".', videoToCreateData.uuid)
 
-  return db.sequelize.transaction(t => {
-    return db.Video.loadByUUID(videoToCreateData.uuid)
-      .then(video => {
-        if (video) throw new Error('UUID already exists.')
-
-        return db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
-      })
-      .then(videoChannel => {
-        if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
+  await db.sequelize.transaction(async t => {
+    const sequelizeOptions = {
+      transaction: t
+    }
 
-        const tags = videoToCreateData.tags
+    const videoFromDatabase = await db.Video.loadByUUID(videoToCreateData.uuid)
+    if (videoFromDatabase) throw new Error('UUID already exists.')
+
+    const videoChannel = await db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
+    if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
+
+    const tags = videoToCreateData.tags
+    const tagInstances = await db.Tag.findOrCreateTags(tags, t)
+
+    const videoData = {
+      name: videoToCreateData.name,
+      uuid: videoToCreateData.uuid,
+      category: videoToCreateData.category,
+      licence: videoToCreateData.licence,
+      language: videoToCreateData.language,
+      nsfw: videoToCreateData.nsfw,
+      description: videoToCreateData.description,
+      channelId: videoChannel.id,
+      duration: videoToCreateData.duration,
+      createdAt: videoToCreateData.createdAt,
+      // FIXME: updatedAt does not seems to be considered by Sequelize
+      updatedAt: videoToCreateData.updatedAt,
+      views: videoToCreateData.views,
+      likes: videoToCreateData.likes,
+      dislikes: videoToCreateData.dislikes,
+      remote: true
+    }
 
-        return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoChannel, tagInstances }))
-      })
-      .then(({ videoChannel, tagInstances }) => {
-        const videoData = {
-          name: videoToCreateData.name,
-          uuid: videoToCreateData.uuid,
-          category: videoToCreateData.category,
-          licence: videoToCreateData.licence,
-          language: videoToCreateData.language,
-          nsfw: videoToCreateData.nsfw,
-          description: videoToCreateData.description,
-          channelId: videoChannel.id,
-          duration: videoToCreateData.duration,
-          createdAt: videoToCreateData.createdAt,
-          // FIXME: updatedAt does not seems to be considered by Sequelize
-          updatedAt: videoToCreateData.updatedAt,
-          views: videoToCreateData.views,
-          likes: videoToCreateData.likes,
-          dislikes: videoToCreateData.dislikes,
-          remote: true
-        }
-
-        const video = db.Video.build(videoData)
-        return { tagInstances, video }
-      })
-      .then(({ tagInstances, video }) => {
-        return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video }))
+    const video = db.Video.build(videoData)
+    await db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData)
+    const videoCreated = await video.save(sequelizeOptions)
+
+    const tasks = []
+    for (const fileData of videoToCreateData.files) {
+      const videoFileInstance = db.VideoFile.build({
+        extname: fileData.extname,
+        infoHash: fileData.infoHash,
+        resolution: fileData.resolution,
+        size: fileData.size,
+        videoId: videoCreated.id
       })
-      .then(({ tagInstances, video }) => {
-        const options = {
-          transaction: t
-        }
 
-        return video.save(options).then(videoCreated => ({ tagInstances, videoCreated }))
-      })
-      .then(({ tagInstances, videoCreated }) => {
-        const tasks = []
-        const options = {
-          transaction: t
-        }
-
-        videoToCreateData.files.forEach(fileData => {
-          const videoFileInstance = db.VideoFile.build({
-            extname: fileData.extname,
-            infoHash: fileData.infoHash,
-            resolution: fileData.resolution,
-            size: fileData.size,
-            videoId: videoCreated.id
-          })
-
-          tasks.push(videoFileInstance.save(options))
-        })
+      tasks.push(videoFileInstance.save(sequelizeOptions))
+    }
 
-        return Promise.all(tasks).then(() => ({ tagInstances, videoCreated }))
-      })
-      .then(({ tagInstances, videoCreated }) => {
-        const options = {
-          transaction: t
-        }
+    await Promise.all(tasks)
 
-        return videoCreated.setTags(tagInstances, options)
-      })
-  })
-  .then(() => logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid))
-  .catch(err => {
-    logger.debug('Cannot insert the remote video.', err)
-    throw err
+    await videoCreated.setTags(tagInstances, sequelizeOptions)
   })
+
+  logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
 }
 
 // Handle retries on fail
-function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
+async function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
   const options = {
     arguments: [ videoAttributesToUpdate, fromPod ],
     errorMessage: 'Cannot update the remote video with many retries'
   }
 
-  return retryTransactionWrapper(updateRemoteVideo, options)
+  await retryTransactionWrapper(updateRemoteVideo, options)
 }
 
-function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
+async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
   logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
 
-  return db.sequelize.transaction(t => {
-    return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
-      .then(videoInstance => {
-        const tags = videoAttributesToUpdate.tags
-
-        return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances }))
-      })
-      .then(({ videoInstance, tagInstances }) => {
-        const options = { transaction: t }
-
-        videoInstance.set('name', videoAttributesToUpdate.name)
-        videoInstance.set('category', videoAttributesToUpdate.category)
-        videoInstance.set('licence', videoAttributesToUpdate.licence)
-        videoInstance.set('language', videoAttributesToUpdate.language)
-        videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
-        videoInstance.set('description', videoAttributesToUpdate.description)
-        videoInstance.set('duration', videoAttributesToUpdate.duration)
-        videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
-        videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
-        videoInstance.set('views', videoAttributesToUpdate.views)
-        videoInstance.set('likes', videoAttributesToUpdate.likes)
-        videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
-
-        return videoInstance.save(options).then(() => ({ videoInstance, tagInstances }))
-      })
-      .then(({ tagInstances, videoInstance }) => {
-        const tasks: Promise<void>[] = []
-
-        // Remove old video files
-        videoInstance.VideoFiles.forEach(videoFile => {
-          tasks.push(videoFile.destroy({ transaction: t }))
+  try {
+    await db.sequelize.transaction(async t => {
+      const sequelizeOptions = {
+        transaction: t
+      }
+
+      const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
+      const tags = videoAttributesToUpdate.tags
+
+      const tagInstances = await db.Tag.findOrCreateTags(tags, t)
+
+      videoInstance.set('name', videoAttributesToUpdate.name)
+      videoInstance.set('category', videoAttributesToUpdate.category)
+      videoInstance.set('licence', videoAttributesToUpdate.licence)
+      videoInstance.set('language', videoAttributesToUpdate.language)
+      videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
+      videoInstance.set('description', videoAttributesToUpdate.description)
+      videoInstance.set('duration', videoAttributesToUpdate.duration)
+      videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
+      videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
+      videoInstance.set('views', videoAttributesToUpdate.views)
+      videoInstance.set('likes', videoAttributesToUpdate.likes)
+      videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
+
+      await videoInstance.save(sequelizeOptions)
+
+      // Remove old video files
+      const videoFileDestroyTasks: Bluebird<void>[] = []
+      for (const videoFile of videoInstance.VideoFiles) {
+        videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
+      }
+      await Promise.all(videoFileDestroyTasks)
+
+      const videoFileCreateTasks: Bluebird<VideoFileInstance>[] = []
+      for (const fileData of videoAttributesToUpdate.files) {
+        const videoFileInstance = db.VideoFile.build({
+          extname: fileData.extname,
+          infoHash: fileData.infoHash,
+          resolution: fileData.resolution,
+          size: fileData.size,
+          videoId: videoInstance.id
         })
 
-        return Promise.all(tasks).then(() => ({ tagInstances, videoInstance }))
-      })
-      .then(({ tagInstances, videoInstance }) => {
-        const tasks: Promise<VideoFileInstance>[] = []
-        const options = {
-          transaction: t
-        }
-
-        videoAttributesToUpdate.files.forEach(fileData => {
-          const videoFileInstance = db.VideoFile.build({
-            extname: fileData.extname,
-            infoHash: fileData.infoHash,
-            resolution: fileData.resolution,
-            size: fileData.size,
-            videoId: videoInstance.id
-          })
-
-          tasks.push(videoFileInstance.save(options))
-        })
+        videoFileCreateTasks.push(videoFileInstance.save(sequelizeOptions))
+      }
 
-        return Promise.all(tasks).then(() => ({ tagInstances, videoInstance }))
-      })
-      .then(({ videoInstance, tagInstances }) => {
-        const options = { transaction: t }
+      await Promise.all(videoFileCreateTasks)
 
-        return videoInstance.setTags(tagInstances, options)
-      })
-  })
-  .then(() => logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid))
-  .catch(err => {
+      await videoInstance.setTags(tagInstances, sequelizeOptions)
+    })
+
+    logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
+  } catch (err) {
     // This is just a debug because we will retry the insert
     logger.debug('Cannot update the remote video.', err)
     throw err
-  })
+  }
 }
 
-function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
+async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
   const options = {
     arguments: [ videoToRemoveData, fromPod ],
     errorMessage: 'Cannot remove the remote video channel with many retries.'
   }
 
-  return retryTransactionWrapper(removeRemoteVideo, options)
+  await retryTransactionWrapper(removeRemoteVideo, options)
 }
 
-function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
+async function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
   logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
 
-  return db.sequelize.transaction(t => {
+  await db.sequelize.transaction(async t => {
     // We need the instance because we have to remove some other stuffs (thumbnail etc)
-    return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
-      .then(video => video.destroy({ transaction: t }))
-  })
-  .then(() => logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid))
-  .catch(err => {
-    logger.debug('Cannot remove the remote video.', err)
-    throw err
+    const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
+    await videoInstance.destroy({ transaction: t })
   })
+
+  logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
 }
 
-function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
+async function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
   const options = {
     arguments: [ authorToCreateData, fromPod ],
     errorMessage: 'Cannot insert the remote video author with many retries.'
   }
 
-  return retryTransactionWrapper(addRemoteVideoAuthor, options)
+  await retryTransactionWrapper(addRemoteVideoAuthor, options)
 }
 
-function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
+async function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
   logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
 
-  return db.sequelize.transaction(t => {
-    return db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
-      .then(author => {
-        if (author) throw new Error('UUID already exists.')
+  await db.sequelize.transaction(async t => {
+    const authorInDatabase = await db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
+    if (authorInDatabase) throw new Error('Author with UUID ' + authorToCreateData.uuid + ' already exists.')
 
-        return undefined
-      })
-      .then(() => {
-        const videoAuthorData = {
-          name: authorToCreateData.name,
-          uuid: authorToCreateData.uuid,
-          userId: null, // Not on our pod
-          podId: fromPod.id
-        }
-
-        const author = db.Author.build(videoAuthorData)
-        return author.save({ transaction: t })
-      })
+    const videoAuthorData = {
+      name: authorToCreateData.name,
+      uuid: authorToCreateData.uuid,
+      userId: null, // Not on our pod
+      podId: fromPod.id
+    }
+
+    const author = db.Author.build(videoAuthorData)
+    await author.save({ transaction: t })
   })
-    .then(() => logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid))
-    .catch(err => {
-      logger.debug('Cannot insert the remote video author.', err)
-      throw err
-    })
+
+  logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid)
 }
 
-function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
+async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
   const options = {
     arguments: [ authorAttributesToRemove, fromPod ],
     errorMessage: 'Cannot remove the remote video author with many retries.'
   }
 
-  return retryTransactionWrapper(removeRemoteVideoAuthor, options)
+  await retryTransactionWrapper(removeRemoteVideoAuthor, options)
 }
 
-function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
+async function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
   logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
 
-  return db.sequelize.transaction(t => {
-    return db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
-      .then(videoAuthor => videoAuthor.destroy({ transaction: t }))
-  })
-  .then(() => logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid))
-  .catch(err => {
-    logger.debug('Cannot remove the remote video author.', err)
-    throw err
+  await db.sequelize.transaction(async t => {
+    const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
+    await videoAuthor.destroy({ transaction: t })
   })
+
+  logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)
 }
 
-function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
+async function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
   const options = {
     arguments: [ videoChannelToCreateData, fromPod ],
     errorMessage: 'Cannot insert the remote video channel with many retries.'
   }
 
-  return retryTransactionWrapper(addRemoteVideoChannel, options)
+  await retryTransactionWrapper(addRemoteVideoChannel, options)
 }
 
-function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
+async function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
   logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
 
-  return db.sequelize.transaction(t => {
-    return db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
-      .then(videoChannel => {
-        if (videoChannel) throw new Error('UUID already exists.')
+  await db.sequelize.transaction(async t => {
+    const videoChannelInDatabase = await db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
+    if (videoChannelInDatabase) {
+      throw new Error('Video channel with UUID ' + videoChannelToCreateData.uuid + ' already exists.')
+    }
 
-        return undefined
-      })
-      .then(() => {
-        const authorUUID = videoChannelToCreateData.ownerUUID
-        const podId = fromPod.id
+    const authorUUID = videoChannelToCreateData.ownerUUID
+    const podId = fromPod.id
 
-        return db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
-      })
-      .then(author => {
-        if (!author) throw new Error('Unknown author UUID.')
-
-        const videoChannelData = {
-          name: videoChannelToCreateData.name,
-          description: videoChannelToCreateData.description,
-          uuid: videoChannelToCreateData.uuid,
-          createdAt: videoChannelToCreateData.createdAt,
-          updatedAt: videoChannelToCreateData.updatedAt,
-          remote: true,
-          authorId: author.id
-        }
-
-        const videoChannel = db.VideoChannel.build(videoChannelData)
-        return videoChannel.save({ transaction: t })
-      })
-  })
-  .then(() => logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid))
-  .catch(err => {
-    logger.debug('Cannot insert the remote video channel.', err)
-    throw err
+    const author = await db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
+    if (!author) throw new Error('Unknown author UUID' + authorUUID + '.')
+
+    const videoChannelData = {
+      name: videoChannelToCreateData.name,
+      description: videoChannelToCreateData.description,
+      uuid: videoChannelToCreateData.uuid,
+      createdAt: videoChannelToCreateData.createdAt,
+      updatedAt: videoChannelToCreateData.updatedAt,
+      remote: true,
+      authorId: author.id
+    }
+
+    const videoChannel = db.VideoChannel.build(videoChannelData)
+    await videoChannel.save({ transaction: t })
   })
+
+  logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
 }
 
-function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
+async function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
   const options = {
     arguments: [ videoChannelAttributesToUpdate, fromPod ],
     errorMessage: 'Cannot update the remote video channel with many retries.'
   }
 
-  return retryTransactionWrapper(updateRemoteVideoChannel, options)
+  await retryTransactionWrapper(updateRemoteVideoChannel, options)
 }
 
-function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
+async function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
   logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
 
-  return db.sequelize.transaction(t => {
-    return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
-      .then(videoChannelInstance => {
-        const options = { transaction: t }
+  await db.sequelize.transaction(async t => {
+    const sequelizeOptions = { transaction: t }
 
-        videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
-        videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
-        videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
-        videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
+    const videoChannelInstance = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
+    videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
+    videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
+    videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
+    videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
 
-        return videoChannelInstance.save(options)
-      })
-  })
-  .then(() => logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid))
-  .catch(err => {
-    // This is just a debug because we will retry the insert
-    logger.debug('Cannot update the remote video channel.', err)
-    throw err
+    await videoChannelInstance.save(sequelizeOptions)
   })
+
+  logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid)
 }
 
-function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
+async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
   const options = {
     arguments: [ videoChannelAttributesToRemove, fromPod ],
     errorMessage: 'Cannot remove the remote video channel with many retries.'
   }
 
-  return retryTransactionWrapper(removeRemoteVideoChannel, options)
+  await retryTransactionWrapper(removeRemoteVideoChannel, options)
 }
 
-function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
+async function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
   logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
 
-  return db.sequelize.transaction(t => {
-    return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
-      .then(videoChannel => videoChannel.destroy({ transaction: t }))
-  })
-  .then(() => logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid))
-  .catch(err => {
-    logger.debug('Cannot remove the remote video channel.', err)
-    throw err
+  await db.sequelize.transaction(async t => {
+    const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
+    await videoChannel.destroy({ transaction: t })
   })
+
+  logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)
 }
 
-function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
+async function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
   const options = {
     arguments: [ reportData, fromPod ],
     errorMessage: 'Cannot create remote abuse video with many retries.'
   }
 
-  return retryTransactionWrapper(reportAbuseRemoteVideo, options)
+  await retryTransactionWrapper(reportAbuseRemoteVideo, options)
 }
 
-function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
+async function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
   logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
 
-  return db.sequelize.transaction(t => {
-    return fetchVideoByUUID(reportData.videoUUID, t)
-      .then(video => {
-        const videoAbuseData = {
-          reporterUsername: reportData.reporterUsername,
-          reason: reportData.reportReason,
-          reporterPodId: fromPod.id,
-          videoId: video.id
-        }
-
-        return db.VideoAbuse.create(videoAbuseData)
-      })
-  })
-  .then(() => logger.info('Remote abuse for video uuid %s created', reportData.videoUUID))
-  .catch(err => {
-    // This is just a debug because we will retry the insert
-    logger.debug('Cannot create remote abuse video', err)
-    throw err
-  })
-}
+  await db.sequelize.transaction(async t => {
+    const videoInstance = await fetchVideoByUUID(reportData.videoUUID, t)
+    const videoAbuseData = {
+      reporterUsername: reportData.reporterUsername,
+      reason: reportData.reportReason,
+      reporterPodId: fromPod.id,
+      videoId: videoInstance.id
+    }
 
-function fetchVideoByUUID (id: string, t: Sequelize.Transaction) {
-  return db.Video.loadByUUID(id, t)
-    .then(video => {
-      if (!video) throw new Error('Video not found')
+    await db.VideoAbuse.create(videoAbuseData)
 
-      return video
-    })
-    .catch(err => {
-      logger.error('Cannot load owned video from id.', { error: err.stack, id })
-      throw err
-    })
+  })
+
+  logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)
 }
 
-function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
-  return db.Video.loadByHostAndUUID(podHost, uuid, t)
-    .then(video => {
-      if (!video) throw new Error('Video not found')
+async function fetchVideoByUUID (id: string, t: Sequelize.Transaction) {
+  try {
+    const video = await db.Video.loadByUUID(id, t)
 
-      return video
-    })
-    .catch(err => {
-      logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
-      throw err
-    })
+    if (!video) throw new Error('Video ' + id + ' not found')
+
+    return video
+  } catch (err) {
+    logger.error('Cannot load owned video from id.', { error: err.stack, id })
+    throw err
+  }
 }
 
-function fetchVideoChannelByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
-  return db.VideoChannel.loadByHostAndUUID(podHost, uuid, t)
-    .then(videoChannel => {
-      if (!videoChannel) throw new Error('Video channel not found')
+async function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
+  try {
+    const video = await db.Video.loadByHostAndUUID(podHost, uuid, t)
+    if (!video) throw new Error('Video not found')
 
-      return videoChannel
-    })
-    .catch(err => {
-      logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid })
-      throw err
-    })
+    return video
+  } catch (err) {
+    logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
+    throw err
+  }
 }
index 2a934a51294e412a0901488f43b5681213746be6..28f46f3ee6a007a9981de9e63d7b769e25577d10 100644 (file)
@@ -1,5 +1,5 @@
 import * as express from 'express'
-import * as Promise from 'bluebird'
+import * as Bluebird from 'bluebird'
 
 import {
   AbstractRequestScheduler,
@@ -7,7 +7,7 @@ import {
   getRequestVideoQaduScheduler,
   getRequestVideoEventScheduler
 } from '../../lib'
-import { authenticate, ensureIsAdmin } from '../../middlewares'
+import { authenticate, ensureIsAdmin, asyncMiddleware } from '../../middlewares'
 import { RequestSchedulerStatsAttributes } from '../../../shared'
 
 const requestSchedulerRouter = express.Router()
@@ -15,7 +15,7 @@ const requestSchedulerRouter = express.Router()
 requestSchedulerRouter.get('/stats',
   authenticate,
   ensureIsAdmin,
-  getRequestSchedulersStats
+  asyncMiddleware(getRequestSchedulersStats)
 )
 
 // ---------------------------------------------------------------------------
@@ -26,28 +26,28 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) {
-  Promise.props({
+async function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const result = await Bluebird.props({
     requestScheduler: buildRequestSchedulerStats(getRequestScheduler()),
     requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()),
     requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler())
   })
-  .then(result => res.json(result))
-  .catch(err => next(err))
+
+  return res.json(result)
 }
 
 // ---------------------------------------------------------------------------
 
-function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) {
-  return requestScheduler.remainingRequestsCount().then(count => {
-    const result: RequestSchedulerStatsAttributes = {
-      totalRequests: count,
-      requestsLimitPods: requestScheduler.limitPods,
-      requestsLimitPerPod: requestScheduler.limitPerPod,
-      remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
-      milliSecondsInterval: requestScheduler.requestInterval
-    }
-
-    return result
-  })
+async function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) {
+  const count = await requestScheduler.remainingRequestsCount()
+
+  const result: RequestSchedulerStatsAttributes = {
+    totalRequests: count,
+    requestsLimitPods: requestScheduler.limitPods,
+    requestsLimitPerPod: requestScheduler.limitPerPod,
+    remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
+    milliSecondsInterval: requestScheduler.requestInterval
+  }
+
+  return result
 }
index 6576e4333f19d75688183f6aa7f28209c8107b35..a7528328ad39f2866b2933bb7bd87ff1dfd951f7 100644 (file)
@@ -18,7 +18,8 @@ import {
   setPagination,
   usersSortValidator,
   setUsersSort,
-  token
+  token,
+  asyncMiddleware
 } from '../../middlewares'
 import {
   UserVideoRate as FormattedUserVideoRate,
@@ -33,13 +34,13 @@ const usersRouter = express.Router()
 
 usersRouter.get('/me',
   authenticate,
-  getUserInformation
+  asyncMiddleware(getUserInformation)
 )
 
 usersRouter.get('/me/videos/:videoId/rating',
   authenticate,
   usersVideoRatingValidator,
-  getUserVideoRating
+  asyncMiddleware(getUserVideoRating)
 )
 
 usersRouter.get('/',
@@ -47,7 +48,7 @@ usersRouter.get('/',
   usersSortValidator,
   setUsersSort,
   setPagination,
-  listUsers
+  asyncMiddleware(listUsers)
 )
 
 usersRouter.get('/:id',
@@ -65,27 +66,27 @@ usersRouter.post('/',
 usersRouter.post('/register',
   ensureUserRegistrationAllowed,
   usersRegisterValidator,
-  registerUser
+  asyncMiddleware(registerUser)
 )
 
 usersRouter.put('/me',
   authenticate,
   usersUpdateMeValidator,
-  updateMe
+  asyncMiddleware(updateMe)
 )
 
 usersRouter.put('/:id',
   authenticate,
   ensureIsAdmin,
   usersUpdateValidator,
-  updateUser
+  asyncMiddleware(updateUser)
 )
 
 usersRouter.delete('/:id',
   authenticate,
   ensureIsAdmin,
   usersRemoveValidator,
-  removeUser
+  asyncMiddleware(removeUser)
 )
 
 usersRouter.post('/token', token, success)
@@ -99,21 +100,19 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot insert the user with many retries.'
   }
 
-  retryTransactionWrapper(createUser, options)
-    .then(() => {
-      // TODO : include Location of the new user -> 201
-      res.type('json').status(204).end()
-    })
-    .catch(err => next(err))
+  await retryTransactionWrapper(createUser, options)
+
+  // TODO : include Location of the new user -> 201
+  return res.type('json').status(204).end()
 }
 
-function createUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function createUser (req: express.Request, res: express.Response, next: express.NextFunction) {
   const body: UserCreate = req.body
   const user = db.User.build({
     username: body.username,
@@ -124,15 +123,12 @@ function createUser (req: express.Request, res: express.Response, next: express.
     videoQuota: body.videoQuota
   })
 
-  return createUserAuthorAndChannel(user)
-    .then(() => logger.info('User %s with its channel and author created.', body.username))
-    .catch((err: Error) => {
-      logger.debug('Cannot insert the user.', err)
-      throw err
-    })
+  await createUserAuthorAndChannel(user)
+
+  logger.info('User %s with its channel and author created.', body.username)
 }
 
-function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
   const body: UserCreate = req.body
 
   const user = db.User.build({
@@ -144,22 +140,21 @@ function registerUser (req: express.Request, res: express.Response, next: expres
     videoQuota: CONFIG.USER.VIDEO_QUOTA
   })
 
-  return createUserAuthorAndChannel(user)
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+  await createUserAuthorAndChannel(user)
+  return res.type('json').status(204).end()
 }
 
-function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
-    .then(user => res.json(user.toFormattedJSON()))
-    .catch(err => next(err))
+async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
+
+  return res.json(user.toFormattedJSON())
 }
 
 function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
   return res.json(res.locals.user.toFormattedJSON())
 }
 
-function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
   const videoId = +req.params.videoId
   const userId = +res.locals.oauth.token.User.id
 
@@ -175,50 +170,45 @@ function getUserVideoRating (req: express.Request, res: express.Response, next:
     .catch(err => next(err))
 }
 
-function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.User.listForApi(req.query.start, req.query.count, req.query.sort)
-    .then(resultList => {
-      res.json(getFormattedObjects(resultList.data, resultList.total))
-    })
-    .catch(err => next(err))
+async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.User.listForApi(req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.User.loadById(req.params.id)
-    .then(user => user.destroy())
-    .then(() => res.sendStatus(204))
-    .catch(err => {
-      logger.error('Errors when removed the user.', err)
-      return next(err)
-    })
+async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const user = await db.User.loadById(req.params.id)
+
+  await user.destroy()
+
+  return res.sendStatus(204)
 }
 
-function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
   const body: UserUpdateMe = req.body
 
   // FIXME: user is not already a Sequelize instance?
-  db.User.loadByUsername(res.locals.oauth.token.user.username)
-    .then(user => {
-      if (body.password !== undefined) user.password = body.password
-      if (body.email !== undefined) user.email = body.email
-      if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
+  const user = res.locals.oauth.token.user
 
-      return user.save()
-    })
-    .then(() => res.sendStatus(204))
-    .catch(err => next(err))
+  if (body.password !== undefined) user.password = body.password
+  if (body.email !== undefined) user.email = body.email
+  if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
+
+  await user.save()
+
+  return await res.sendStatus(204)
 }
 
-function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
   const body: UserUpdate = req.body
   const user: UserInstance = res.locals.user
 
   if (body.email !== undefined) user.email = body.email
   if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
 
-  return user.save()
-    .then(() => res.sendStatus(204))
-    .catch(err => next(err))
+  await user.save()
+
+  return res.sendStatus(204)
 }
 
 function success (req: express.Request, res: express.Response, next: express.NextFunction) {
index c9313d5f5b680fb2b42326a266342930cc63c32b..4c7abf3952109ac3d1c8e13d4c7652c2124b6dab 100644 (file)
@@ -14,7 +14,8 @@ import {
   videoAbuseReportValidator,
   videoAbusesSortValidator,
   setVideoAbusesSort,
-  setPagination
+  setPagination,
+  asyncMiddleware
 } from '../../../middlewares'
 import { VideoInstance } from '../../../models'
 import { VideoAbuseCreate } from '../../../../shared'
@@ -28,12 +29,12 @@ abuseVideoRouter.get('/abuse',
   videoAbusesSortValidator,
   setVideoAbusesSort,
   setPagination,
-  listVideoAbuses
+  asyncMiddleware(listVideoAbuses)
 )
 abuseVideoRouter.post('/:id/abuse',
   authenticate,
   videoAbuseReportValidator,
-  reportVideoAbuseRetryWrapper
+  asyncMiddleware(reportVideoAbuseRetryWrapper)
 )
 
 // ---------------------------------------------------------------------------
@@ -44,55 +45,48 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort)
-    .then(result => res.json(getFormattedObjects(result.data, result.total)))
-    .catch(err => next(err))
+async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot report abuse to the video with many retries.'
   }
 
-  retryTransactionWrapper(reportVideoAbuse, options)
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+  await retryTransactionWrapper(reportVideoAbuse, options)
+
+  return res.type('json').status(204).end()
 }
 
-function reportVideoAbuse (req: express.Request, res: express.Response) {
+async function reportVideoAbuse (req: express.Request, res: express.Response) {
   const videoInstance = res.locals.video as VideoInstance
   const reporterUsername = res.locals.oauth.token.User.username
   const body: VideoAbuseCreate = req.body
 
-  const abuse = {
+  const abuseToCreate = {
     reporterUsername,
     reason: body.reason,
     videoId: videoInstance.id,
     reporterPodId: null // This is our pod that reported this abuse
   }
 
-  return db.sequelize.transaction(t => {
-    return db.VideoAbuse.create(abuse, { transaction: t })
-      .then(abuse => {
-        // We send the information to the destination pod
-        if (videoInstance.isOwned() === false) {
-          const reportData = {
-            reporterUsername,
-            reportReason: abuse.reason,
-            videoUUID: videoInstance.uuid
-          }
-
-          return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance)
-        }
-
-        return videoInstance
-      })
-  })
-  .then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name))
-  .catch(err => {
-    logger.debug('Cannot update the video.', err)
-    throw err
+  await db.sequelize.transaction(async t => {
+    const abuse = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
+    // We send the information to the destination pod
+    if (videoInstance.isOwned() === false) {
+      const reportData = {
+        reporterUsername,
+        reportReason: abuse.reason,
+        videoUUID: videoInstance.uuid
+      }
+
+      await friends.reportAbuseVideoToFriend(reportData, videoInstance, t)
+    }
   })
+
+  logger.info('Abuse report for video %s created.', videoInstance.name)
 }
index 66311598ecf4996437aebf065bef9e2c5238fd35..5a2c3fd8005478e8c1b4b71ab6f11816998df898 100644 (file)
@@ -10,7 +10,8 @@ import {
   paginationValidator,
   blacklistSortValidator,
   setBlacklistSort,
-  setPagination
+  setPagination,
+  asyncMiddleware
 } from '../../../middlewares'
 import { BlacklistedVideoInstance } from '../../../models'
 import { BlacklistedVideo } from '../../../../shared'
@@ -21,7 +22,7 @@ blacklistRouter.post('/:videoId/blacklist',
   authenticate,
   ensureIsAdmin,
   videosBlacklistAddValidator,
-  addVideoToBlacklist
+  asyncMiddleware(addVideoToBlacklist)
 )
 
 blacklistRouter.get('/blacklist',
@@ -31,14 +32,14 @@ blacklistRouter.get('/blacklist',
   blacklistSortValidator,
   setBlacklistSort,
   setPagination,
-  listBlacklist
+  asyncMiddleware(listBlacklist)
 )
 
 blacklistRouter.delete('/:videoId/blacklist',
   authenticate,
   ensureIsAdmin,
   videosBlacklistRemoveValidator,
-  removeVideoFromBlacklistController
+  asyncMiddleware(removeVideoFromBlacklistController)
 )
 
 // ---------------------------------------------------------------------------
@@ -49,37 +50,34 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function addVideoToBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function addVideoToBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
   const videoInstance = res.locals.video
 
   const toCreate = {
     videoId: videoInstance.id
   }
 
-  db.BlacklistedVideo.create(toCreate)
-    .then(() => res.type('json').status(204).end())
-    .catch(err => {
-      logger.error('Errors when blacklisting video ', err)
-      return next(err)
-    })
+  await db.BlacklistedVideo.create(toCreate)
+  return res.type('json').status(204).end()
 }
 
-function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort)
-    .then(resultList => res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total)))
-    .catch(err => next(err))
+async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total))
 }
 
-function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
   const blacklistedVideo = res.locals.blacklistedVideo as BlacklistedVideoInstance
 
-  blacklistedVideo.destroy()
-    .then(() => {
-      logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
-      res.sendStatus(204)
-    })
-    .catch(err => {
-      logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, err)
-      next(err)
-    })
+  try {
+    await blacklistedVideo.destroy()
+
+    logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
+
+    return res.sendStatus(204)
+  } catch (err) {
+    logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, err)
+    throw err
+  }
 }
index 630fc4f53d02bce16cad4aed37fee0f044c324c1..ab54eedee335947936a9e26abc8d379738c5f70d 100644 (file)
@@ -4,7 +4,8 @@ import { database as db } from '../../../initializers'
 import {
   logger,
   getFormattedObjects,
-  retryTransactionWrapper
+  retryTransactionWrapper,
+  resetSequelizeInstance
 } from '../../../helpers'
 import {
   authenticate,
@@ -16,7 +17,8 @@ import {
   videoChannelsRemoveValidator,
   videoChannelGetValidator,
   videoChannelsUpdateValidator,
-  listVideoAuthorChannelsValidator
+  listVideoAuthorChannelsValidator,
+  asyncMiddleware
 } from '../../../middlewares'
 import {
   createVideoChannel,
@@ -32,18 +34,18 @@ videoChannelRouter.get('/channels',
   videoChannelsSortValidator,
   setVideoChannelsSort,
   setPagination,
-  listVideoChannels
+  asyncMiddleware(listVideoChannels)
 )
 
 videoChannelRouter.get('/authors/:authorId/channels',
   listVideoAuthorChannelsValidator,
-  listVideoAuthorChannels
+  asyncMiddleware(listVideoAuthorChannels)
 )
 
 videoChannelRouter.post('/channels',
   authenticate,
   videoChannelsAddValidator,
-  addVideoChannelRetryWrapper
+  asyncMiddleware(addVideoChannelRetryWrapper)
 )
 
 videoChannelRouter.put('/channels/:id',
@@ -55,12 +57,12 @@ videoChannelRouter.put('/channels/:id',
 videoChannelRouter.delete('/channels/:id',
   authenticate,
   videoChannelsRemoveValidator,
-  removeVideoChannelRetryWrapper
+  asyncMiddleware(removeVideoChannelRetryWrapper)
 )
 
 videoChannelRouter.get('/channels/:id',
   videoChannelGetValidator,
-  getVideoChannel
+  asyncMiddleware(getVideoChannel)
 )
 
 // ---------------------------------------------------------------------------
@@ -71,126 +73,113 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort)
-    .then(result => res.json(getFormattedObjects(result.data, result.total)))
-    .catch(err => next(err))
+async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.VideoChannel.listByAuthor(res.locals.author.id)
-    .then(result => res.json(getFormattedObjects(result.data, result.total)))
-    .catch(err => next(err))
+async function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.VideoChannel.listByAuthor(res.locals.author.id)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-// Wrapper to video channel add that retry the function if there is a database error
+// Wrapper to video channel add that retry the async function if there is a database error
 // We need this because we run the transaction in SERIALIZABLE isolation that can fail
-function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot insert the video video channel with many retries.'
   }
 
-  retryTransactionWrapper(addVideoChannel, options)
-    .then(() => {
-      // TODO : include Location of the new video channel -> 201
-      res.type('json').status(204).end()
-    })
-    .catch(err => next(err))
+  await retryTransactionWrapper(addVideoChannel, options)
+
+  // TODO : include Location of the new video channel -> 201
+  return res.type('json').status(204).end()
 }
 
-function addVideoChannel (req: express.Request, res: express.Response) {
+async function addVideoChannel (req: express.Request, res: express.Response) {
   const videoChannelInfo: VideoChannelCreate = req.body
   const author: AuthorInstance = res.locals.oauth.token.User.Author
+  let videoChannelCreated: VideoChannelInstance
 
-  return db.sequelize.transaction(t => {
-    return createVideoChannel(videoChannelInfo, author, t)
-  })
-  .then(videoChannelUUID => logger.info('Video channel with uuid %s created.', videoChannelUUID))
-  .catch((err: Error) => {
-    logger.debug('Cannot insert the video channel.', err)
-    throw err
+  await db.sequelize.transaction(async t => {
+    videoChannelCreated = await createVideoChannel(videoChannelInfo, author, t)
   })
+
+  logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
 }
 
-function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot update the video with many retries.'
   }
 
-  retryTransactionWrapper(updateVideoChannel, options)
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+  await retryTransactionWrapper(updateVideoChannel, options)
+
+  return res.type('json').status(204).end()
 }
 
-function updateVideoChannel (req: express.Request, res: express.Response) {
+async function updateVideoChannel (req: express.Request, res: express.Response) {
   const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
   const videoChannelFieldsSave = videoChannelInstance.toJSON()
   const videoChannelInfoToUpdate: VideoChannelUpdate = req.body
 
-  return db.sequelize.transaction(t => {
-    const options = {
-      transaction: t
-    }
+  try {
+    await db.sequelize.transaction(async t => {
+      const sequelizeOptions = {
+        transaction: t
+      }
 
-    if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
-    if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
+      if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
+      if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
 
-    return videoChannelInstance.save(options)
-      .then(() => {
-        const json = videoChannelInstance.toUpdateRemoteJSON()
+      await videoChannelInstance.save(sequelizeOptions)
+      const json = videoChannelInstance.toUpdateRemoteJSON()
+
+      // Now we'll update the video channel's meta data to our friends
+      return updateVideoChannelToFriends(json, t)
 
-        // Now we'll update the video channel's meta data to our friends
-        return updateVideoChannelToFriends(json, t)
-      })
-  })
-    .then(() => {
-      logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
-    })
-    .catch(err => {
-      logger.debug('Cannot update the video channel.', err)
-
-      // Force fields we want to update
-      // If the transaction is retried, sequelize will think the object has not changed
-      // So it will skip the SQL request, even if the last one was ROLLBACKed!
-      Object.keys(videoChannelFieldsSave).forEach(key => {
-        const value = videoChannelFieldsSave[key]
-        videoChannelInstance.set(key, value)
-      })
-
-      throw err
     })
+
+    logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
+  } catch (err) {
+    logger.debug('Cannot update the video channel.', err)
+
+    // Force fields we want to update
+    // If the transaction is retried, sequelize will think the object has not changed
+    // So it will skip the SQL request, even if the last one was ROLLBACKed!
+    resetSequelizeInstance(videoChannelInstance, videoChannelFieldsSave)
+
+    throw err
+  }
 }
 
-function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot remove the video channel with many retries.'
   }
 
-  retryTransactionWrapper(removeVideoChannel, options)
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+  await retryTransactionWrapper(removeVideoChannel, options)
+
+  return res.type('json').status(204).end()
 }
 
-function removeVideoChannel (req: express.Request, res: express.Response) {
+async function removeVideoChannel (req: express.Request, res: express.Response) {
   const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
 
-  return db.sequelize.transaction(t => {
-    return videoChannelInstance.destroy({ transaction: t })
-  })
-  .then(() => {
-    logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
-  })
-  .catch(err => {
-    logger.error('Errors when removed the video channel.', err)
-    throw err
+  await db.sequelize.transaction(async t => {
+    await videoChannelInstance.destroy({ transaction: t })
   })
+
+  logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
 }
 
-function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id)
-    .then(videoChannelWithVideos => res.json(videoChannelWithVideos.toFormattedJSON()))
-    .catch(err => next(err))
+async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const videoChannelWithVideos = await db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id)
+
+  return res.json(videoChannelWithVideos.toFormattedJSON())
 }
index ec855ee8e12597a27707f508f9531d600d0cb24d..7ebbf4d6ebf4e09b10b70e7f24ccf4bcc3288ff7 100644 (file)
@@ -1,5 +1,4 @@
 import * as express from 'express'
-import * as Promise from 'bluebird'
 import * as multer from 'multer'
 import { extname, join } from 'path'
 
@@ -30,7 +29,8 @@ import {
   videosSearchValidator,
   videosAddValidator,
   videosGetValidator,
-  videosRemoveValidator
+  videosRemoveValidator,
+  asyncMiddleware
 } from '../../../middlewares'
 import {
   logger,
@@ -38,7 +38,8 @@ import {
   generateRandomString,
   getFormattedObjects,
   renamePromise,
-  getVideoFileHeight
+  getVideoFileHeight,
+  resetSequelizeInstance
 } from '../../../helpers'
 import { TagInstance, VideoInstance } from '../../../models'
 import { VideoCreate, VideoUpdate } from '../../../../shared'
@@ -88,18 +89,18 @@ videosRouter.get('/',
   videosSortValidator,
   setVideosSort,
   setPagination,
-  listVideos
+  asyncMiddleware(listVideos)
 )
 videosRouter.put('/:id',
   authenticate,
   videosUpdateValidator,
-  updateVideoRetryWrapper
+  asyncMiddleware(updateVideoRetryWrapper)
 )
 videosRouter.post('/upload',
   authenticate,
   reqFiles,
   videosAddValidator,
-  addVideoRetryWrapper
+  asyncMiddleware(addVideoRetryWrapper)
 )
 videosRouter.get('/:id',
   videosGetValidator,
@@ -109,7 +110,7 @@ videosRouter.get('/:id',
 videosRouter.delete('/:id',
   authenticate,
   videosRemoveValidator,
-  removeVideoRetryWrapper
+  asyncMiddleware(removeVideoRetryWrapper)
 )
 
 videosRouter.get('/search/:value',
@@ -119,7 +120,7 @@ videosRouter.get('/search/:value',
   setVideosSort,
   setPagination,
   setVideosSearch,
-  searchVideos
+  asyncMiddleware(searchVideos)
 )
 
 // ---------------------------------------------------------------------------
@@ -144,220 +145,157 @@ function listVideoLanguages (req: express.Request, res: express.Response) {
 
 // Wrapper to video add that retry the function if there is a database error
 // We need this because we run the transaction in SERIALIZABLE isolation that can fail
-function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res, req.files['videofile'][0] ],
     errorMessage: 'Cannot insert the video with many retries.'
   }
 
-  retryTransactionWrapper(addVideo, options)
-    .then(() => {
-      // TODO : include Location of the new video -> 201
-      res.type('json').status(204).end()
-    })
-    .catch(err => next(err))
+  await retryTransactionWrapper(addVideo, options)
+
+  // TODO : include Location of the new video -> 201
+  res.type('json').status(204).end()
 }
 
-function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
+async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
   const videoInfo: VideoCreate = req.body
   let videoUUID = ''
 
-  return db.sequelize.transaction(t => {
-    let p: Promise<TagInstance[]>
-
-    if (!videoInfo.tags) p = Promise.resolve(undefined)
-    else p = db.Tag.findOrCreateTags(videoInfo.tags, t)
-
-    return p
-      .then(tagInstances => {
-        const videoData = {
-          name: videoInfo.name,
-          remote: false,
-          extname: extname(videoPhysicalFile.filename),
-          category: videoInfo.category,
-          licence: videoInfo.licence,
-          language: videoInfo.language,
-          nsfw: videoInfo.nsfw,
-          description: videoInfo.description,
-          duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
-          channelId: res.locals.videoChannel.id
-        }
+  await db.sequelize.transaction(async t => {
+    const sequelizeOptions = { transaction: t }
+
+    const videoData = {
+      name: videoInfo.name,
+      remote: false,
+      extname: extname(videoPhysicalFile.filename),
+      category: videoInfo.category,
+      licence: videoInfo.licence,
+      language: videoInfo.language,
+      nsfw: videoInfo.nsfw,
+      description: videoInfo.description,
+      duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
+      channelId: res.locals.videoChannel.id
+    }
+    const video = db.Video.build(videoData)
 
-        const video = db.Video.build(videoData)
-        return { tagInstances, video }
-      })
-      .then(({ tagInstances, video }) => {
-        const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
-        return getVideoFileHeight(videoFilePath)
-          .then(height => ({ tagInstances, video, videoFileHeight: height }))
-      })
-      .then(({ tagInstances, video, videoFileHeight }) => {
-        const videoFileData = {
-          extname: extname(videoPhysicalFile.filename),
-          resolution: videoFileHeight,
-          size: videoPhysicalFile.size
-        }
+    const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
+    const videoFileHeight = await getVideoFileHeight(videoFilePath)
 
-        const videoFile = db.VideoFile.build(videoFileData)
-        return { tagInstances, video, videoFile }
-      })
-      .then(({ tagInstances, video, videoFile }) => {
-        const videoDir = CONFIG.STORAGE.VIDEOS_DIR
-        const source = join(videoDir, videoPhysicalFile.filename)
-        const destination = join(videoDir, video.getVideoFilename(videoFile))
-
-        return renamePromise(source, destination)
-          .then(() => {
-            // This is important in case if there is another attempt in the retry process
-            videoPhysicalFile.filename = video.getVideoFilename(videoFile)
-            return { tagInstances, video, videoFile }
-          })
-      })
-      .then(({ tagInstances, video, videoFile }) => {
-        const tasks = []
-
-        tasks.push(
-          video.createTorrentAndSetInfoHash(videoFile),
-          video.createThumbnail(videoFile),
-          video.createPreview(videoFile)
-        )
-
-        if (CONFIG.TRANSCODING.ENABLED === true) {
-          // Put uuid because we don't have id auto incremented for now
-          const dataInput = {
-            videoUUID: video.uuid
-          }
-
-          tasks.push(
-            JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput)
-          )
-        }
+    const videoFileData = {
+      extname: extname(videoPhysicalFile.filename),
+      resolution: videoFileHeight,
+      size: videoPhysicalFile.size
+    }
+    const videoFile = db.VideoFile.build(videoFileData)
+    const videoDir = CONFIG.STORAGE.VIDEOS_DIR
+    const source = join(videoDir, videoPhysicalFile.filename)
+    const destination = join(videoDir, video.getVideoFilename(videoFile))
+
+    await renamePromise(source, destination)
+    // This is important in case if there is another attempt in the retry process
+    videoPhysicalFile.filename = video.getVideoFilename(videoFile)
+
+    const tasks = []
+
+    tasks.push(
+      video.createTorrentAndSetInfoHash(videoFile),
+      video.createThumbnail(videoFile),
+      video.createPreview(videoFile)
+    )
+
+    if (CONFIG.TRANSCODING.ENABLED === true) {
+      // Put uuid because we don't have id auto incremented for now
+      const dataInput = {
+        videoUUID: video.uuid
+      }
+
+      tasks.push(
+        JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput)
+      )
+    }
+    await Promise.all(tasks)
 
-        return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile }))
-      })
-      .then(({ tagInstances, video, videoFile }) => {
-        const options = { transaction: t }
+    const videoCreated = await video.save(sequelizeOptions)
+    // Do not forget to add video channel information to the created video
+    videoCreated.VideoChannel = res.locals.videoChannel
+    videoUUID = videoCreated.uuid
 
-        return video.save(options)
-          .then(videoCreated => {
-            // Do not forget to add video channel information to the created video
-            videoCreated.VideoChannel = res.locals.videoChannel
-            videoUUID = videoCreated.uuid
+    videoFile.videoId = video.id
 
-            return { tagInstances, video: videoCreated, videoFile }
-          })
-      })
-      .then(({ tagInstances, video, videoFile }) => {
-        const options = { transaction: t }
-        videoFile.videoId = video.id
+    await videoFile.save(sequelizeOptions)
+    video.VideoFiles = [videoFile]
 
-        return videoFile.save(options)
-          .then(() => video.VideoFiles = [ videoFile ])
-          .then(() => ({ tagInstances, video }))
-      })
-      .then(({ tagInstances, video }) => {
-        if (!tagInstances) return video
-
-        const options = { transaction: t }
-        return video.setTags(tagInstances, options)
-          .then(() => {
-            video.Tags = tagInstances
-            return video
-          })
-      })
-      .then(video => {
-        // Let transcoding job send the video to friends because the video file extension might change
-        if (CONFIG.TRANSCODING.ENABLED === true) return undefined
-
-        return video.toAddRemoteJSON()
-          .then(remoteVideo => {
-            // Now we'll add the video's meta data to our friends
-            return addVideoToFriends(remoteVideo, t)
-          })
-      })
-  })
-  .then(() => logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID))
-  .catch((err: Error) => {
-    logger.debug('Cannot insert the video.', err)
-    throw err
+    if (videoInfo.tags) {
+      const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t)
+
+      await video.setTags(tagInstances, sequelizeOptions)
+      video.Tags = tagInstances
+    }
+
+    // Let transcoding job send the video to friends because the video file extension might change
+    if (CONFIG.TRANSCODING.ENABLED === true) return undefined
+
+    const remoteVideo = await video.toAddRemoteJSON()
+    // Now we'll add the video's meta data to our friends
+    return addVideoToFriends(remoteVideo, t)
   })
+
+  logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID)
 }
 
-function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot update the video with many retries.'
   }
 
-  retryTransactionWrapper(updateVideo, options)
-    .then(() => {
-      return res.type('json').status(204).end()
-    })
-    .catch(err => next(err))
+  await retryTransactionWrapper(updateVideo, options)
+
+  return res.type('json').status(204).end()
 }
 
-function updateVideo (req: express.Request, res: express.Response) {
+async function updateVideo (req: express.Request, res: express.Response) {
   const videoInstance = res.locals.video
   const videoFieldsSave = videoInstance.toJSON()
   const videoInfoToUpdate: VideoUpdate = req.body
 
-  return db.sequelize.transaction(t => {
-    let tagsPromise: Promise<TagInstance[]>
-    if (!videoInfoToUpdate.tags) {
-      tagsPromise = Promise.resolve(null)
-    } else {
-      tagsPromise = db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
-    }
+  try {
+    await db.sequelize.transaction(async t => {
+      const sequelizeOptions = {
+        transaction: t
+      }
 
-    return tagsPromise
-      .then(tagInstances => {
-        const options = {
-          transaction: t
-        }
+      if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
+      if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
+      if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
+      if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
+      if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
+      if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
 
-        if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
-        if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
-        if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
-        if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
-        if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
-        if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
+      await videoInstance.save(sequelizeOptions)
 
-        return videoInstance.save(options).then(() => tagInstances)
-      })
-      .then(tagInstances => {
-        if (!tagInstances) return
+      if (videoInfoToUpdate.tags) {
+        const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
 
-        const options = { transaction: t }
-        return videoInstance.setTags(tagInstances, options)
-          .then(() => {
-            videoInstance.Tags = tagInstances
+        await videoInstance.setTags(tagInstances, sequelizeOptions)
+        videoInstance.Tags = tagInstances
+      }
 
-            return
-          })
-      })
-      .then(() => {
-        const json = videoInstance.toUpdateRemoteJSON()
+      const json = videoInstance.toUpdateRemoteJSON()
 
-        // Now we'll update the video's meta data to our friends
-        return updateVideoToFriends(json, t)
-      })
-  })
-  .then(() => {
-    logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
-  })
-  .catch(err => {
-    logger.debug('Cannot update the video.', err)
+      // Now we'll update the video's meta data to our friends
+      return updateVideoToFriends(json, t)
+    })
 
+    logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
+  } catch (err) {
     // Force fields we want to update
     // If the transaction is retried, sequelize will think the object has not changed
     // So it will skip the SQL request, even if the last one was ROLLBACKed!
-    Object.keys(videoFieldsSave).forEach(key => {
-      const value = videoFieldsSave[key]
-      videoInstance.set(key, value)
-    })
+    resetSequelizeInstance(videoInstance, videoFieldsSave)
 
     throw err
-  })
+  }
 }
 
 function getVideo (req: express.Request, res: express.Response) {
@@ -365,17 +303,17 @@ function getVideo (req: express.Request, res: express.Response) {
 
   if (videoInstance.isOwned()) {
     // The increment is done directly in the database, not using the instance value
+    // FIXME: make a real view system
+    // For example, only add a view when a user watch a video during 30s etc
     videoInstance.increment('views')
       .then(() => {
-        // FIXME: make a real view system
-        // For example, only add a view when a user watch a video during 30s etc
         const qaduParams = {
           videoId: videoInstance.id,
           type: REQUEST_VIDEO_QADU_TYPES.VIEWS
         }
         return quickAndDirtyUpdateVideoToFriends(qaduParams)
       })
-      .catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, err))
+      .catch(err => logger.error('Cannot add view to video %s.', videoInstance.uuid, err))
   } else {
     // Just send the event to our friends
     const eventParams = {
@@ -383,48 +321,48 @@ function getVideo (req: express.Request, res: express.Response) {
       type: REQUEST_VIDEO_EVENT_TYPES.VIEWS
     }
     addEventToRemoteVideo(eventParams)
+      .catch(err => logger.error('Cannot add event to remote video %s.', videoInstance.uuid, err))
   }
 
   // Do not wait the view system
-  res.json(videoInstance.toFormattedDetailsJSON())
+  return res.json(videoInstance.toFormattedDetailsJSON())
 }
 
-function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
-    .then(result => res.json(getFormattedObjects(result.data, result.total)))
-    .catch(err => next(err))
+async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-function removeVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function removeVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot remove the video with many retries.'
   }
 
-  retryTransactionWrapper(removeVideo, options)
-    .then(() => {
-      return res.type('json').status(204).end()
-    })
-    .catch(err => next(err))
+  await retryTransactionWrapper(removeVideo, options)
+
+  return res.type('json').status(204).end()
 }
 
-function removeVideo (req: express.Request, res: express.Response) {
+async function removeVideo (req: express.Request, res: express.Response) {
   const videoInstance: VideoInstance = res.locals.video
 
-  return db.sequelize.transaction(t => {
-    return videoInstance.destroy({ transaction: t })
-  })
-  .then(() => {
-    logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
-  })
-  .catch(err => {
-    logger.error('Errors when removed the video.', err)
-    throw err
+  await db.sequelize.transaction(async t => {
+    await videoInstance.destroy({ transaction: t })
   })
+
+  logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
 }
 
-function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
-  db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort)
-    .then(result => res.json(getFormattedObjects(result.data, result.total)))
-    .catch(err => next(err))
+async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await db.Video.searchAndPopulateAuthorAndPodAndTags(
+    req.params.value,
+    req.query.field,
+    req.query.start,
+    req.query.count,
+    req.query.sort
+  )
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
index 6ddc698173f4e2d2d66cfc99190dad7b31a3a437..354c3d8f93d43c3288e82c6a2457b919fd4f4d6e 100644 (file)
@@ -1,5 +1,4 @@
 import * as express from 'express'
-import * as Promise from 'bluebird'
 
 import { database as db } from '../../../initializers/database'
 import {
@@ -17,7 +16,8 @@ import {
 } from '../../../lib'
 import {
   authenticate,
-  videoRateValidator
+  videoRateValidator,
+  asyncMiddleware
 } from '../../../middlewares'
 import { UserVideoRateUpdate, VideoRateType } from '../../../../shared'
 
@@ -26,7 +26,7 @@ const rateVideoRouter = express.Router()
 rateVideoRouter.put('/:id/rate',
   authenticate,
   videoRateValidator,
-  rateVideoRetryWrapper
+  asyncMiddleware(rateVideoRetryWrapper)
 )
 
 // ---------------------------------------------------------------------------
@@ -37,126 +37,107 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function rateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function rateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
   const options = {
     arguments: [ req, res ],
     errorMessage: 'Cannot update the user video rate.'
   }
 
-  retryTransactionWrapper(rateVideo, options)
-    .then(() => res.type('json').status(204).end())
-    .catch(err => next(err))
+  await retryTransactionWrapper(rateVideo, options)
+
+  return res.type('json').status(204).end()
 }
 
-function rateVideo (req: express.Request, res: express.Response) {
+async function rateVideo (req: express.Request, res: express.Response) {
   const body: UserVideoRateUpdate = req.body
   const rateType = body.rating
   const videoInstance = res.locals.video
   const userInstance = res.locals.oauth.token.User
 
-  return db.sequelize.transaction(t => {
-    return db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
-      .then(previousRate => {
-        const options = { transaction: t }
-
-        let likesToIncrement = 0
-        let dislikesToIncrement = 0
-
-        if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
-        else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
-
-        let promise: Promise<any>
-
-        // There was a previous rate, update it
-        if (previousRate) {
-          // We will remove the previous rate, so we will need to update the video count attribute
-          if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
-          else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
-
-          if (rateType === 'none') { // Destroy previous rate
-            promise = previousRate.destroy()
-          } else { // Update previous rate
-            previousRate.type = rateType as VideoRateType
-
-            promise = previousRate.save()
-          }
-        } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
-          const query = {
-            userId: userInstance.id,
-            videoId: videoInstance.id,
-            type: rateType
-          }
-
-          promise = db.UserVideoRate.create(query, options)
-        } else {
-          promise = Promise.resolve()
-        }
-
-        return promise.then(() => ({ likesToIncrement, dislikesToIncrement }))
-      })
-      .then(({ likesToIncrement, dislikesToIncrement }) => {
-        const options = { transaction: t }
-        const incrementQuery = {
-          likes: likesToIncrement,
-          dislikes: dislikesToIncrement
-        }
-
-        // Even if we do not own the video we increment the attributes
-        // It is usefull for the user to have a feedback
-        return videoInstance.increment(incrementQuery, options).then(() => ({ likesToIncrement, dislikesToIncrement }))
-      })
-      .then(({ likesToIncrement, dislikesToIncrement }) => {
-        // No need for an event type, we own the video
-        if (videoInstance.isOwned()) return { likesToIncrement, dislikesToIncrement }
-
-        const eventsParams = []
-
-        if (likesToIncrement !== 0) {
-          eventsParams.push({
-            videoId: videoInstance.id,
-            type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
-            count: likesToIncrement
-          })
-        }
-
-        if (dislikesToIncrement !== 0) {
-          eventsParams.push({
-            videoId: videoInstance.id,
-            type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
-            count: dislikesToIncrement
-          })
-        }
-
-        return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement }))
-      })
-      .then(({ likesToIncrement, dislikesToIncrement }) => {
-        // We do not own the video, there is no need to send a quick and dirty update to friends
-        // Our rate was already sent by the addEvent function
-        if (videoInstance.isOwned() === false) return undefined
-
-        const qadusParams = []
-
-        if (likesToIncrement !== 0) {
-          qadusParams.push({
-            videoId: videoInstance.id,
-            type: REQUEST_VIDEO_QADU_TYPES.LIKES
-          })
-        }
-
-        if (dislikesToIncrement !== 0) {
-          qadusParams.push({
-            videoId: videoInstance.id,
-            type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
-          })
-        }
-
-        return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
-      })
-  })
-  .then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username))
-  .catch(err => {
-    // This is just a debug because we will retry the insert
-    logger.debug('Cannot add the user video rate.', err)
-    throw err
+  await db.sequelize.transaction(async t => {
+    const sequelizeOptions = { transaction: t }
+    const previousRate = await db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
+
+    let likesToIncrement = 0
+    let dislikesToIncrement = 0
+
+    if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
+    else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
+
+    // There was a previous rate, update it
+    if (previousRate) {
+      // We will remove the previous rate, so we will need to update the video count attribute
+      if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
+      else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
+
+      if (rateType === 'none') { // Destroy previous rate
+        await previousRate.destroy()
+      } else { // Update previous rate
+        previousRate.type = rateType as VideoRateType
+
+        await previousRate.save()
+      }
+    } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
+      const query = {
+        userId: userInstance.id,
+        videoId: videoInstance.id,
+        type: rateType
+      }
+
+      await db.UserVideoRate.create(query, sequelizeOptions)
+    }
+
+    const incrementQuery = {
+      likes: likesToIncrement,
+      dislikes: dislikesToIncrement
+    }
+
+    // Even if we do not own the video we increment the attributes
+    // It is useful for the user to have a feedback
+    await videoInstance.increment(incrementQuery, sequelizeOptions)
+
+    // Send a event to original pod
+    if (videoInstance.isOwned() === false) {
+
+      const eventsParams = []
+
+      if (likesToIncrement !== 0) {
+        eventsParams.push({
+          videoId: videoInstance.id,
+          type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
+          count: likesToIncrement
+        })
+      }
+
+      if (dislikesToIncrement !== 0) {
+        eventsParams.push({
+          videoId: videoInstance.id,
+          type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
+          count: dislikesToIncrement
+        })
+      }
+
+      await addEventsToRemoteVideo(eventsParams, t)
+    } else { // We own the video, we need to send a quick and dirty update to friends to notify the counts changed
+      const qadusParams = []
+
+      if (likesToIncrement !== 0) {
+        qadusParams.push({
+          videoId: videoInstance.id,
+          type: REQUEST_VIDEO_QADU_TYPES.LIKES
+        })
+      }
+
+      if (dislikesToIncrement !== 0) {
+        qadusParams.push({
+          videoId: videoInstance.id,
+          type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
+        })
+      }
+
+      await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
+    }
   })
+
+  logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username)
 }
index 6a2ac4aabc151f779eb8882fdeaa0cbf4a5e23fb..1391993a7a1c4c39130efe8efaa185a717948b62 100644 (file)
@@ -1,7 +1,7 @@
 import * as express from 'express'
 import { join } from 'path'
 import * as validator from 'validator'
-import * as Promise from 'bluebird'
+import * as Bluebird from 'bluebird'
 
 import { database as db } from '../initializers/database'
 import {
@@ -11,6 +11,7 @@ import {
   OPENGRAPH_AND_OEMBED_COMMENT
 } from '../initializers'
 import { root, readFileBufferPromise, escapeHTML } from '../helpers'
+import { asyncMiddleware } from '../middlewares'
 import { VideoInstance } from '../models'
 
 const clientsRouter = express.Router()
@@ -21,7 +22,9 @@ const indexPath = join(distPath, 'index.html')
 
 // Special route that add OpenGraph and oEmbed tags
 // Do not use a template engine for a so little thing
-clientsRouter.use('/videos/watch/:id', generateWatchHtmlPage)
+clientsRouter.use('/videos/watch/:id',
+  asyncMiddleware(generateWatchHtmlPage)
+)
 
 clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => {
   res.sendFile(embedPath)
@@ -90,9 +93,9 @@ function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance
   return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString)
 }
 
-function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
   const videoId = '' + req.params.id
-  let videoPromise: Promise<VideoInstance>
+  let videoPromise: Bluebird<VideoInstance>
 
   // Let Angular application handle errors
   if (validator.isUUID(videoId, 4)) {
@@ -103,21 +106,19 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
     return res.sendFile(indexPath)
   }
 
-  Promise.all([
+  let [ file, video ] = await Promise.all([
     readFileBufferPromise(indexPath),
     videoPromise
   ])
-  .then(([ file, video ]) => {
-    file = file as Buffer
-    video = video as VideoInstance
 
-    const html = file.toString()
+  file = file as Buffer
+  video = video as VideoInstance
 
-    // Let Angular application handle errors
-    if (!video) return res.sendFile(indexPath)
+  const html = file.toString()
 
-    const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
-    res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
-  })
-  .catch(err => next(err))
+  // Let Angular application handle errors
+  if (!video) return res.sendFile(indexPath)
+
+  const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
+  res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
 }
index 8fbf9cc9741b209933472a7a21f0af526ef29f67..c7c952d6fcc86b8470debbf02ba02b6af4ff0c4d 100644 (file)
@@ -7,6 +7,7 @@ import {
   STATIC_PATHS
 } from '../initializers'
 import { VideosPreviewCache } from '../lib'
+import { asyncMiddleware } from '../middlewares'
 
 const staticRouter = express.Router()
 
@@ -39,7 +40,7 @@ staticRouter.use(
 // Video previews path for express
 staticRouter.use(
   STATIC_PATHS.PREVIEWS + ':uuid.jpg',
-  getPreview
+  asyncMiddleware(getPreview)
 )
 
 // ---------------------------------------------------------------------------
@@ -50,11 +51,9 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) {
-  VideosPreviewCache.Instance.getPreviewPath(req.params.uuid)
-    .then(path => {
-      if (!path) return res.sendStatus(404)
+async function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const path = await VideosPreviewCache.Instance.getPreviewPath(req.params.uuid)
+  if (!path) return res.sendStatus(404)
 
-      return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
-    })
+  return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
 }
index 987e42eb074a0441fab5f2c43aa6d646ea0054c6..dcc9e257714cbdf25667a151770e219d908c7b08 100644 (file)
@@ -1,6 +1,5 @@
 // TODO: import from ES6 when retry typing file will include errorFilter function
 import * as retry from 'async/retry'
-import * as Promise from 'bluebird'
 
 import { logger } from './logger'
 
index 3317dddc33de5cfea04bcbc2081004b33e9f55bc..6cabe117c061755947d342792475de1c55f88b12 100644 (file)
@@ -1,4 +1,5 @@
 import * as express from 'express'
+import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
 
 import { pseudoRandomBytesPromise } from './core-utils'
@@ -69,6 +70,13 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
   return resolutionsEnabled
 }
 
+function resetSequelizeInstance (instance: Sequelize.Instance<any>, savedFields: object) {
+  Object.keys(savedFields).forEach(key => {
+    const value = savedFields[key]
+    instance.set(key, value)
+  })
+}
+
 type SortType = { sortModel: any, sortValue: string }
 
 // ---------------------------------------------------------------------------
@@ -79,5 +87,6 @@ export {
   getFormattedObjects,
   isSignupAllowed,
   computeResolutionsToTranscode,
+  resetSequelizeInstance,
   SortType
 }
index d461cb440d9f26cbd816465c412a0108a702e280..ea2b68f596baf75250775aefdc4ce6d48010c31f 100644 (file)
@@ -63,6 +63,7 @@ const sequelize = new Sequelize(dbname, username, password, {
   host: CONFIG.DATABASE.HOSTNAME,
   port: CONFIG.DATABASE.PORT,
   benchmark: isTestInstance(),
+  isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE,
 
   logging: (message: string, benchmark: number) => {
     let newMessage = message
index 2241799737b8c80d97be7d44e4a0f2df4e9c8b07..678ffe643aff6d48e8b8b660b6ebffdf4c0b3060 100644 (file)
@@ -2,12 +2,11 @@ import * as Sequelize from 'sequelize'
 
 import { addVideoChannelToFriends } from './friends'
 import { database as db } from '../initializers'
+import { logger } from '../helpers'
 import { AuthorInstance } from '../models'
 import { VideoChannelCreate } from '../../shared/models'
 
-function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: AuthorInstance, t: Sequelize.Transaction) {
-  let videoChannelUUID = ''
-
+async function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: AuthorInstance, t: Sequelize.Transaction) {
   const videoChannelData = {
     name: videoChannelInfo.name,
     description: videoChannelInfo.description,
@@ -18,25 +17,34 @@ function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: Autho
   const videoChannel = db.VideoChannel.build(videoChannelData)
   const options = { transaction: t }
 
-  return videoChannel.save(options)
-    .then(videoChannelCreated => {
-      // Do not forget to add Author information to the created video channel
-      videoChannelCreated.Author = author
-      videoChannelUUID = videoChannelCreated.uuid
-
-      return videoChannelCreated
-    })
-    .then(videoChannel => {
-      const remoteVideoChannel = videoChannel.toAddRemoteJSON()
-
-      // Now we'll add the video channel's meta data to our friends
-      return addVideoChannelToFriends(remoteVideoChannel, t)
-    })
-    .then(() => videoChannelUUID) // Return video channel UUID
+  const videoChannelCreated = await videoChannel.save(options)
+
+  // Do not forget to add Author information to the created video channel
+  videoChannelCreated.Author = author
+
+  const remoteVideoChannel = videoChannelCreated.toAddRemoteJSON()
+
+  // Now we'll add the video channel's meta data to our friends
+  await addVideoChannelToFriends(remoteVideoChannel, t)
+
+  return videoChannelCreated
+}
+
+async function fetchVideoChannelByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
+  try {
+    const videoChannel = await db.VideoChannel.loadByHostAndUUID(podHost, uuid, t)
+    if (!videoChannel) throw new Error('Video channel not found')
+
+    return videoChannel
+  } catch (err) {
+    logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid })
+    throw err
+  }
 }
 
 // ---------------------------------------------------------------------------
 
 export {
-  createVideoChannel
+  createVideoChannel,
+  fetchVideoChannelByHostAndUUID
 }
diff --git a/server/middlewares/async.ts b/server/middlewares/async.ts
new file mode 100644 (file)
index 0000000..29ebd16
--- /dev/null
@@ -0,0 +1,16 @@
+import { Request, Response, NextFunction } from 'express'
+
+// Syntactic sugar to avoid try/catch in express controllers
+// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
+function asyncMiddleware (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) {
+  return (req: Request, res: Response, next: NextFunction) => {
+    return Promise.resolve(fn(req, res, next))
+      .catch(next)
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  asyncMiddleware
+}
index d71dd245265a6fbbebef6bf1acf2ef3188304a98..0e2c850e18def51210430193dadaf70b3cc8e9fd 100644 (file)
@@ -1,5 +1,6 @@
 export * from './validators'
 export * from './admin'
+export * from './async'
 export * from './oauth'
 export * from './pagination'
 export * from './pods'