From: Chocobozzz Date: Wed, 25 Oct 2017 09:55:06 +0000 (+0200) Subject: Use async/await in controllers X-Git-Tag: v0.0.1-alpha~272 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=eb08047657e739bcd9e592d76307befa3998482b;p=oweals%2Fpeertube.git Use async/await in controllers --- diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index c9a051bdc..5f704f0ee 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -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) } // --------------------------------------------------------------------------- diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts index f7dac598c..ac1ee9e36 100644 --- a/server/controllers/api/oauth-clients.ts +++ b/server/controllers/api/oauth-clients.ts @@ -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) } // --------------------------------------------------------------------------- diff --git a/server/controllers/api/pods.ts b/server/controllers/api/pods.ts index 804aa0659..bf1b744e5 100644 --- a/server/controllers/api/pods.ts +++ b/server/controllers/api/pods.ts @@ -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() } diff --git a/server/controllers/api/remote/pods.ts b/server/controllers/api/remote/pods.ts index a62b9c684..326eb61ac 100644 --- a/server/controllers/api/remote/pods.ts +++ b/server/controllers/api/remote/pods.ts @@ -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(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(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() } diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts index c8f531490..bf442c6e5 100644 --- a/server/controllers/api/remote/videos.ts +++ b/server/controllers/api/remote/videos.ts @@ -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[] = [] - - // 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[] = [] + for (const videoFile of videoInstance.VideoFiles) { + videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions)) + } + await Promise.all(videoFileDestroyTasks) + + const videoFileCreateTasks: Bluebird[] = [] + 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[] = [] - 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 + } } diff --git a/server/controllers/api/request-schedulers.ts b/server/controllers/api/request-schedulers.ts index 2a934a512..28f46f3ee 100644 --- a/server/controllers/api/request-schedulers.ts +++ b/server/controllers/api/request-schedulers.ts @@ -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) { - 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) { + const count = await requestScheduler.remainingRequestsCount() + + const result: RequestSchedulerStatsAttributes = { + totalRequests: count, + requestsLimitPods: requestScheduler.limitPods, + requestsLimitPerPod: requestScheduler.limitPerPod, + remainingMilliSeconds: requestScheduler.remainingMilliSeconds(), + milliSecondsInterval: requestScheduler.requestInterval + } + + return result } diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index 6576e4333..a7528328a 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts @@ -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) { diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index c9313d5f5..4c7abf395 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts @@ -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) } diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 66311598e..5a2c3fd80 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts @@ -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(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(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 + } } diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts index 630fc4f53..ab54eedee 100644 --- a/server/controllers/api/videos/channel.ts +++ b/server/controllers/api/videos/channel.ts @@ -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()) } diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index ec855ee8e..7ebbf4d6e 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -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 - - 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 - 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)) } diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts index 6ddc69817..354c3d8f9 100644 --- a/server/controllers/api/videos/rate.ts +++ b/server/controllers/api/videos/rate.ts @@ -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 - - // 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) } diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 6a2ac4aab..1391993a7 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts @@ -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 + let videoPromise: Bluebird // 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) } diff --git a/server/controllers/static.ts b/server/controllers/static.ts index 8fbf9cc97..c7c952d6f 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts @@ -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 }) } diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts index 987e42eb0..dcc9e2577 100644 --- a/server/helpers/database-utils.ts +++ b/server/helpers/database-utils.ts @@ -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' diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 3317dddc3..6cabe117c 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -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, 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 } diff --git a/server/initializers/database.ts b/server/initializers/database.ts index d461cb440..ea2b68f59 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts @@ -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 diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 224179973..678ffe643 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts @@ -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 index 000000000..29ebd169d --- /dev/null +++ b/server/middlewares/async.ts @@ -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) { + return (req: Request, res: Response, next: NextFunction) => { + return Promise.resolve(fn(req, res, next)) + .catch(next) + } +} + +// --------------------------------------------------------------------------- + +export { + asyncMiddleware +} diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts index d71dd2452..0e2c850e1 100644 --- a/server/middlewares/index.ts +++ b/server/middlewares/index.ts @@ -1,5 +1,6 @@ export * from './validators' export * from './admin' +export * from './async' export * from './oauth' export * from './pagination' export * from './pods'