Use async/await in controllers

This commit is contained in:
Chocobozzz 2017-10-25 11:55:06 +02:00
parent 5f04dd2f74
commit eb08047657
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
20 changed files with 823 additions and 992 deletions

View File

@ -2,30 +2,32 @@ import * as express from 'express'
import { isSignupAllowed } from '../../helpers' import { isSignupAllowed } from '../../helpers'
import { CONFIG } from '../../initializers' import { CONFIG } from '../../initializers'
import { asyncMiddleware } from '../../middlewares'
import { ServerConfig } from '../../../shared' import { ServerConfig } from '../../../shared'
const configRouter = express.Router() 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)
const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) .filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
.filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true) .map(r => parseInt(r, 10))
.map(r => parseInt(r, 10))
const json: ServerConfig = { const json: ServerConfig = {
signup: { signup: {
allowed allowed
}, },
transcoding: { transcoding: {
enabledResolutions enabledResolutions
}
} }
}
res.json(json) return res.json(json)
})
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -2,15 +2,18 @@ import * as express from 'express'
import { CONFIG } from '../../initializers' import { CONFIG } from '../../initializers'
import { logger } from '../../helpers' import { logger } from '../../helpers'
import { asyncMiddleware } from '../../middlewares'
import { database as db } from '../../initializers/database' import { database as db } from '../../initializers/database'
import { OAuthClientLocal } from '../../../shared' import { OAuthClientLocal } from '../../../shared'
const oauthClientsRouter = express.Router() const oauthClientsRouter = express.Router()
oauthClientsRouter.get('/local', getLocalClient) oauthClientsRouter.get('/local',
asyncMiddleware(getLocalClient)
)
// Get the client credentials for the PeerTube front end // 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 serverHostname = CONFIG.WEBSERVER.HOSTNAME
const serverPort = CONFIG.WEBSERVER.PORT const serverPort = CONFIG.WEBSERVER.PORT
let headerHostShouldBe = serverHostname let headerHostShouldBe = serverHostname
@ -24,17 +27,14 @@ function getLocalClient (req: express.Request, res: express.Response, next: expr
return res.type('json').status(403).end() return res.type('json').status(403).end()
} }
db.OAuthClient.loadFirstClient() const client = await db.OAuthClient.loadFirstClient()
.then(client => { if (!client) throw new Error('No client available.')
if (!client) throw new Error('No client available.')
const json: OAuthClientLocal = { const json: OAuthClientLocal = {
client_id: client.clientId, client_id: client.clientId,
client_secret: client.clientSecret client_secret: client.clientSecret
} }
res.json(json) return res.json(json)
})
.catch(err => next(err))
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -16,7 +16,8 @@ import {
paginationValidator, paginationValidator,
setPagination, setPagination,
setPodsSort, setPodsSort,
podsSortValidator podsSortValidator,
asyncMiddleware
} from '../../middlewares' } from '../../middlewares'
import { PodInstance } from '../../models' import { PodInstance } from '../../models'
@ -27,25 +28,25 @@ podsRouter.get('/',
podsSortValidator, podsSortValidator,
setPodsSort, setPodsSort,
setPagination, setPagination,
listPods asyncMiddleware(listPods)
) )
podsRouter.post('/make-friends', podsRouter.post('/make-friends',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
makeFriendsValidator, makeFriendsValidator,
setBodyHostsPort, setBodyHostsPort,
makeFriendsController asyncMiddleware(makeFriendsController)
) )
podsRouter.get('/quit-friends', podsRouter.get('/quit-friends',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
quitFriendsController asyncMiddleware(quitFriendsController)
) )
podsRouter.delete('/:id', podsRouter.delete('/:id',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
podRemoveValidator, podRemoveValidator,
removeFriendController asyncMiddleware(removeFriendController)
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -56,33 +57,33 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function listPods (req: express.Request, res: express.Response, next: express.NextFunction) { async function listPods (req: express.Request, res: express.Response, next: express.NextFunction) {
db.Pod.listForApi(req.query.start, req.query.count, req.query.sort) const resultList = await 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)) 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[] const hosts = req.body.hosts as string[]
// Don't wait the process that could be long
makeFriends(hosts) makeFriends(hosts)
.then(() => logger.info('Made friends!')) .then(() => logger.info('Made friends!'))
.catch(err => logger.error('Could not make friends.', err)) .catch(err => logger.error('Could not make friends.', err))
// Don't wait the process that could be long return res.type('json').status(204).end()
res.type('json').status(204).end()
} }
function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { async function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
quitFriends() await quitFriends()
.then(() => res.type('json').status(204).end())
.catch(err => next(err)) 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 const pod = res.locals.pod as PodInstance
removeFriend(pod) await removeFriend(pod)
.then(() => res.type('json').status(204).end())
.catch(err => next(err)) return res.type('json').status(204).end()
} }

View File

@ -5,7 +5,8 @@ import {
checkSignature, checkSignature,
signatureValidator, signatureValidator,
setBodyHostPort, setBodyHostPort,
remotePodsAddValidator remotePodsAddValidator,
asyncMiddleware
} from '../../../middlewares' } from '../../../middlewares'
import { sendOwnedDataToPod } from '../../../lib' import { sendOwnedDataToPod } from '../../../lib'
import { getMyPublicCert, getFormattedObjects } from '../../../helpers' import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
@ -18,15 +19,17 @@ const remotePodsRouter = express.Router()
remotePodsRouter.post('/remove', remotePodsRouter.post('/remove',
signatureValidator, signatureValidator,
checkSignature, checkSignature,
removePods asyncMiddleware(removePods)
) )
remotePodsRouter.post('/list', remotePodsList) remotePodsRouter.post('/list',
asyncMiddleware(remotePodsList)
)
remotePodsRouter.post('/add', remotePodsRouter.post('/add',
setBodyHostPort, // We need to modify the host before running the validator! setBodyHostPort, // We need to modify the host before running the validator!
remotePodsAddValidator, 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 information = req.body
const pod = db.Pod.build(information) const pod = db.Pod.build(information)
pod.save() const podCreated = await pod.save()
.then(podCreated => {
return sendOwnedDataToPod(podCreated.id) await sendOwnedDataToPod(podCreated.id)
})
.then(() => { const cert = await getMyPublicCert()
return getMyPublicCert() return res.json({ cert, email: CONFIG.ADMIN.EMAIL })
})
.then(cert => {
return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL })
})
.catch(err => next(err))
} }
function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) { async function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) {
db.Pod.list() const pods = await db.Pod.list()
.then(podsList => res.json(getFormattedObjects<FormattedPod, PodInstance>(podsList, podsList.length)))
.catch(err => next(err)) return res.json(getFormattedObjects<FormattedPod, PodInstance>(pods, pods.length))
} }
function removePods (req: express.Request, res: express.Response, next: express.NextFunction) { async function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
const signature: PodSignature = req.body.signature const signature: PodSignature = req.body.signature
const host = signature.host const host = signature.host
db.Pod.loadByHost(host) const pod = await db.Pod.loadByHost(host)
.then(pod => pod.destroy()) await pod.destroy()
.then(() => res.type('json').status(204).end())
.catch(err => next(err)) return res.type('json').status(204).end()
} }

View File

@ -1,5 +1,5 @@
import * as express from 'express' import * as express from 'express'
import * as Promise from 'bluebird' import * as Bluebird from 'bluebird'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { database as db } from '../../../initializers/database' import { database as db } from '../../../initializers/database'
@ -17,7 +17,7 @@ import {
remoteEventsVideosValidator remoteEventsVideosValidator
} from '../../../middlewares' } from '../../../middlewares'
import { logger, retryTransactionWrapper } from '../../../helpers' import { logger, retryTransactionWrapper } from '../../../helpers'
import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' import { quickAndDirtyUpdatesVideoToFriends, fetchVideoChannelByHostAndUUID } from '../../../lib'
import { PodInstance, VideoFileInstance } from '../../../models' import { PodInstance, VideoFileInstance } from '../../../models'
import { import {
RemoteVideoRequest, RemoteVideoRequest,
@ -87,7 +87,7 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres
const fromPod = res.locals.secure.pod const fromPod = res.locals.secure.pod
// We need to process in the same order to keep consistency // We need to process in the same order to keep consistency
Promise.each(requests, request => { Bluebird.each(requests, request => {
const data = request.data const data = request.data
// Get the function we need to call in order to process the request // 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 requests: RemoteQaduVideoRequest[] = req.body.data
const fromPod = res.locals.secure.pod const fromPod = res.locals.secure.pod
Promise.each(requests, request => { Bluebird.each(requests, request => {
const videoData = request.data const videoData = request.data
return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod) return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
@ -123,7 +123,7 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
const requests: RemoteVideoEventRequest[] = req.body.data const requests: RemoteVideoEventRequest[] = req.body.data
const fromPod = res.locals.secure.pod const fromPod = res.locals.secure.pod
Promise.each(requests, request => { Bluebird.each(requests, request => {
const eventData = request.data const eventData = request.data
return processVideosEventsRetryWrapper(eventData, fromPod) return processVideosEventsRetryWrapper(eventData, fromPod)
@ -133,541 +133,447 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
return res.type('json').status(204).end() return res.type('json').status(204).end()
} }
function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) { async function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) {
const options = { const options = {
arguments: [ eventData, fromPod ], arguments: [ eventData, fromPod ],
errorMessage: 'Cannot process videos events with many retries.' 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 => { let columnToUpdate
return fetchVideoByUUID(eventData.uuid, t) let qaduType
.then(videoInstance => {
const options = { transaction: t }
let columnToUpdate switch (eventData.eventType) {
let qaduType case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
columnToUpdate = 'views'
qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
break
switch (eventData.eventType) { case REQUEST_VIDEO_EVENT_TYPES.LIKES:
case REQUEST_VIDEO_EVENT_TYPES.VIEWS: columnToUpdate = 'likes'
columnToUpdate = 'views' qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS break
break
case REQUEST_VIDEO_EVENT_TYPES.LIKES: case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
columnToUpdate = 'likes' columnToUpdate = 'dislikes'
qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
break break
case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: default:
columnToUpdate = 'dislikes' throw new Error('Unknown video event type.')
qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES }
break
default: const query = {}
throw new Error('Unknown video event type.') query[columnToUpdate] = eventData.count
}
const query = {} await videoInstance.increment(query, sequelizeOptions)
query[columnToUpdate] = eventData.count
return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType })) const qadusParams = [
}) {
.then(({ videoInstance, qaduType }) => { videoId: videoInstance.id,
const qadusParams = [ type: qaduType
{ }
videoId: videoInstance.id, ]
type: qaduType await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
}
]
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
}) })
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 = { const options = {
arguments: [ videoData, fromPod ], arguments: [ videoData, fromPod ],
errorMessage: 'Cannot update quick and dirty the remote video with many retries.' 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 = '' let videoUUID = ''
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t) const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
.then(videoInstance => { const sequelizeOptions = { transaction: t }
const options = { transaction: t }
videoUUID = videoInstance.uuid videoUUID = videoInstance.uuid
if (videoData.views) { if (videoData.views) {
videoInstance.set('views', videoData.views) videoInstance.set('views', videoData.views)
} }
if (videoData.likes) { if (videoData.likes) {
videoInstance.set('likes', videoData.likes) videoInstance.set('likes', videoData.likes)
} }
if (videoData.dislikes) { if (videoData.dislikes) {
videoInstance.set('dislikes', 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 // Handle retries on fail
function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) { async function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
const options = { const options = {
arguments: [ videoToCreateData, fromPod ], arguments: [ videoToCreateData, fromPod ],
errorMessage: 'Cannot insert the remote video with many retries.' 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) logger.debug('Adding remote video "%s".', videoToCreateData.uuid)
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return db.Video.loadByUUID(videoToCreateData.uuid) const sequelizeOptions = {
.then(video => { transaction: t
if (video) throw new Error('UUID already exists.') }
return db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t) 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
}
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(videoChannel => {
if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
const tags = videoToCreateData.tags tasks.push(videoFileInstance.save(sequelizeOptions))
}
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoChannel, tagInstances })) await Promise.all(tasks)
})
.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) await videoCreated.setTags(tagInstances, sequelizeOptions)
return { tagInstances, video }
})
.then(({ tagInstances, video }) => {
return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video }))
})
.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))
})
return Promise.all(tasks).then(() => ({ tagInstances, videoCreated }))
})
.then(({ tagInstances, videoCreated }) => {
const options = {
transaction: t
}
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
}) })
logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
} }
// Handle retries on fail // Handle retries on fail
function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) { async function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
const options = { const options = {
arguments: [ videoAttributesToUpdate, fromPod ], arguments: [ videoAttributesToUpdate, fromPod ],
errorMessage: 'Cannot update the remote video with many retries' 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) logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
return db.sequelize.transaction(t => { try {
return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t) await db.sequelize.transaction(async t => {
.then(videoInstance => { const sequelizeOptions = {
const tags = videoAttributesToUpdate.tags transaction: t
}
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances })) const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
}) const tags = videoAttributesToUpdate.tags
.then(({ videoInstance, tagInstances }) => {
const options = { transaction: t }
videoInstance.set('name', videoAttributesToUpdate.name) const tagInstances = await db.Tag.findOrCreateTags(tags, t)
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 })) videoInstance.set('name', videoAttributesToUpdate.name)
}) videoInstance.set('category', videoAttributesToUpdate.category)
.then(({ tagInstances, videoInstance }) => { videoInstance.set('licence', videoAttributesToUpdate.licence)
const tasks: Promise<void>[] = [] 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)
// Remove old video files await videoInstance.save(sequelizeOptions)
videoInstance.VideoFiles.forEach(videoFile => {
tasks.push(videoFile.destroy({ transaction: t })) // Remove old video files
const videoFileDestroyTasks: Bluebird<void>[] = []
for (const videoFile of videoInstance.VideoFiles) {
videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
}
await Promise.all(videoFileDestroyTasks)
const videoFileCreateTasks: Bluebird<VideoFileInstance>[] = []
for (const fileData of videoAttributesToUpdate.files) {
const videoFileInstance = db.VideoFile.build({
extname: fileData.extname,
infoHash: fileData.infoHash,
resolution: fileData.resolution,
size: fileData.size,
videoId: videoInstance.id
}) })
return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) videoFileCreateTasks.push(videoFileInstance.save(sequelizeOptions))
}) }
.then(({ tagInstances, videoInstance }) => {
const tasks: Promise<VideoFileInstance>[] = []
const options = {
transaction: t
}
videoAttributesToUpdate.files.forEach(fileData => { await Promise.all(videoFileCreateTasks)
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)) await videoInstance.setTags(tagInstances, sequelizeOptions)
}) })
return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
}) } catch (err) {
.then(({ videoInstance, tagInstances }) => {
const options = { transaction: t }
return videoInstance.setTags(tagInstances, options)
})
})
.then(() => logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid))
.catch(err => {
// This is just a debug because we will retry the insert // This is just a debug because we will retry the insert
logger.debug('Cannot update the remote video.', err) logger.debug('Cannot update the remote video.', err)
throw err throw err
}) }
} }
function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) { async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
const options = { const options = {
arguments: [ videoToRemoveData, fromPod ], arguments: [ videoToRemoveData, fromPod ],
errorMessage: 'Cannot remove the remote video channel with many retries.' 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) 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) // We need the instance because we have to remove some other stuffs (thumbnail etc)
return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t) const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
.then(video => video.destroy({ transaction: t })) await videoInstance.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
}) })
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 = { const options = {
arguments: [ authorToCreateData, fromPod ], arguments: [ authorToCreateData, fromPod ],
errorMessage: 'Cannot insert the remote video author with many retries.' 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) logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t) const authorInDatabase = await db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
.then(author => { if (authorInDatabase) throw new Error('Author with UUID ' + authorToCreateData.uuid + ' already exists.')
if (author) throw new Error('UUID already exists.')
return undefined const videoAuthorData = {
}) name: authorToCreateData.name,
.then(() => { uuid: authorToCreateData.uuid,
const videoAuthorData = { userId: null, // Not on our pod
name: authorToCreateData.name, podId: fromPod.id
uuid: authorToCreateData.uuid, }
userId: null, // Not on our pod
podId: fromPod.id
}
const author = db.Author.build(videoAuthorData) const author = db.Author.build(videoAuthorData)
return author.save({ transaction: t }) await author.save({ transaction: t })
})
}) })
.then(() => logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid))
.catch(err => { logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid)
logger.debug('Cannot insert the remote video author.', err)
throw err
})
} }
function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) { async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
const options = { const options = {
arguments: [ authorAttributesToRemove, fromPod ], arguments: [ authorAttributesToRemove, fromPod ],
errorMessage: 'Cannot remove the remote video author with many retries.' 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) logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t) const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
.then(videoAuthor => videoAuthor.destroy({ transaction: t })) await 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
}) })
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 = { const options = {
arguments: [ videoChannelToCreateData, fromPod ], arguments: [ videoChannelToCreateData, fromPod ],
errorMessage: 'Cannot insert the remote video channel with many retries.' 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) logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid) const videoChannelInDatabase = await db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
.then(videoChannel => { if (videoChannelInDatabase) {
if (videoChannel) throw new Error('UUID already exists.') throw new Error('Video channel with UUID ' + videoChannelToCreateData.uuid + ' already exists.')
}
return undefined const authorUUID = videoChannelToCreateData.ownerUUID
}) const podId = fromPod.id
.then(() => {
const authorUUID = videoChannelToCreateData.ownerUUID
const podId = fromPod.id
return db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t) const author = await db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
}) if (!author) throw new Error('Unknown author UUID' + authorUUID + '.')
.then(author => {
if (!author) throw new Error('Unknown author UUID.')
const videoChannelData = { const videoChannelData = {
name: videoChannelToCreateData.name, name: videoChannelToCreateData.name,
description: videoChannelToCreateData.description, description: videoChannelToCreateData.description,
uuid: videoChannelToCreateData.uuid, uuid: videoChannelToCreateData.uuid,
createdAt: videoChannelToCreateData.createdAt, createdAt: videoChannelToCreateData.createdAt,
updatedAt: videoChannelToCreateData.updatedAt, updatedAt: videoChannelToCreateData.updatedAt,
remote: true, remote: true,
authorId: author.id authorId: author.id
} }
const videoChannel = db.VideoChannel.build(videoChannelData) const videoChannel = db.VideoChannel.build(videoChannelData)
return videoChannel.save({ transaction: t }) await 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
}) })
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 = { const options = {
arguments: [ videoChannelAttributesToUpdate, fromPod ], arguments: [ videoChannelAttributesToUpdate, fromPod ],
errorMessage: 'Cannot update the remote video channel with many retries.' 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) logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t) const sequelizeOptions = { transaction: t }
.then(videoChannelInstance => {
const options = { transaction: t }
videoChannelInstance.set('name', videoChannelAttributesToUpdate.name) const videoChannelInstance = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
videoChannelInstance.set('description', videoChannelAttributesToUpdate.description) videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt) videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt) videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
return videoChannelInstance.save(options) await videoChannelInstance.save(sequelizeOptions)
})
})
.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
}) })
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 = { const options = {
arguments: [ videoChannelAttributesToRemove, fromPod ], arguments: [ videoChannelAttributesToRemove, fromPod ],
errorMessage: 'Cannot remove the remote video channel with many retries.' 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) logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t) const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
.then(videoChannel => videoChannel.destroy({ transaction: t })) await 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
}) })
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 = { const options = {
arguments: [ reportData, fromPod ], arguments: [ reportData, fromPod ],
errorMessage: 'Cannot create remote abuse video with many retries.' 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) logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return fetchVideoByUUID(reportData.videoUUID, t) const videoInstance = await fetchVideoByUUID(reportData.videoUUID, t)
.then(video => { const videoAbuseData = {
const videoAbuseData = { reporterUsername: reportData.reporterUsername,
reporterUsername: reportData.reporterUsername, reason: reportData.reportReason,
reason: reportData.reportReason, reporterPodId: fromPod.id,
reporterPodId: fromPod.id, videoId: videoInstance.id
videoId: video.id }
}
await db.VideoAbuse.create(videoAbuseData)
return db.VideoAbuse.create(videoAbuseData)
})
}) })
.then(() => logger.info('Remote abuse for video uuid %s created', reportData.videoUUID))
.catch(err => { logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)
// This is just a debug because we will retry the insert }
logger.debug('Cannot create remote abuse video', err)
async function fetchVideoByUUID (id: string, t: Sequelize.Transaction) {
try {
const video = await db.Video.loadByUUID(id, t)
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 throw err
}) }
} }
function fetchVideoByUUID (id: string, t: Sequelize.Transaction) { async function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
return db.Video.loadByUUID(id, t) try {
.then(video => { const video = await db.Video.loadByHostAndUUID(podHost, uuid, t)
if (!video) throw new Error('Video not found') if (!video) throw new Error('Video not found')
return video return video
}) } catch (err) {
.catch(err => { logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
logger.error('Cannot load owned video from id.', { error: err.stack, id }) throw err
throw err }
})
}
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')
return video
})
.catch(err => {
logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
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')
return videoChannel
})
.catch(err => {
logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid })
throw err
})
} }

View File

@ -1,5 +1,5 @@
import * as express from 'express' import * as express from 'express'
import * as Promise from 'bluebird' import * as Bluebird from 'bluebird'
import { import {
AbstractRequestScheduler, AbstractRequestScheduler,
@ -7,7 +7,7 @@ import {
getRequestVideoQaduScheduler, getRequestVideoQaduScheduler,
getRequestVideoEventScheduler getRequestVideoEventScheduler
} from '../../lib' } from '../../lib'
import { authenticate, ensureIsAdmin } from '../../middlewares' import { authenticate, ensureIsAdmin, asyncMiddleware } from '../../middlewares'
import { RequestSchedulerStatsAttributes } from '../../../shared' import { RequestSchedulerStatsAttributes } from '../../../shared'
const requestSchedulerRouter = express.Router() const requestSchedulerRouter = express.Router()
@ -15,7 +15,7 @@ const requestSchedulerRouter = express.Router()
requestSchedulerRouter.get('/stats', requestSchedulerRouter.get('/stats',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
getRequestSchedulersStats asyncMiddleware(getRequestSchedulersStats)
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -26,28 +26,28 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) { async function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) {
Promise.props({ const result = await Bluebird.props({
requestScheduler: buildRequestSchedulerStats(getRequestScheduler()), requestScheduler: buildRequestSchedulerStats(getRequestScheduler()),
requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()), requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()),
requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler()) requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler())
}) })
.then(result => res.json(result))
.catch(err => next(err)) return res.json(result)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) { async function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) {
return requestScheduler.remainingRequestsCount().then(count => { const count = await requestScheduler.remainingRequestsCount()
const result: RequestSchedulerStatsAttributes = {
totalRequests: count,
requestsLimitPods: requestScheduler.limitPods,
requestsLimitPerPod: requestScheduler.limitPerPod,
remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
milliSecondsInterval: requestScheduler.requestInterval
}
return result const result: RequestSchedulerStatsAttributes = {
}) totalRequests: count,
requestsLimitPods: requestScheduler.limitPods,
requestsLimitPerPod: requestScheduler.limitPerPod,
remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
milliSecondsInterval: requestScheduler.requestInterval
}
return result
} }

View File

@ -18,7 +18,8 @@ import {
setPagination, setPagination,
usersSortValidator, usersSortValidator,
setUsersSort, setUsersSort,
token token,
asyncMiddleware
} from '../../middlewares' } from '../../middlewares'
import { import {
UserVideoRate as FormattedUserVideoRate, UserVideoRate as FormattedUserVideoRate,
@ -33,13 +34,13 @@ const usersRouter = express.Router()
usersRouter.get('/me', usersRouter.get('/me',
authenticate, authenticate,
getUserInformation asyncMiddleware(getUserInformation)
) )
usersRouter.get('/me/videos/:videoId/rating', usersRouter.get('/me/videos/:videoId/rating',
authenticate, authenticate,
usersVideoRatingValidator, usersVideoRatingValidator,
getUserVideoRating asyncMiddleware(getUserVideoRating)
) )
usersRouter.get('/', usersRouter.get('/',
@ -47,7 +48,7 @@ usersRouter.get('/',
usersSortValidator, usersSortValidator,
setUsersSort, setUsersSort,
setPagination, setPagination,
listUsers asyncMiddleware(listUsers)
) )
usersRouter.get('/:id', usersRouter.get('/:id',
@ -65,27 +66,27 @@ usersRouter.post('/',
usersRouter.post('/register', usersRouter.post('/register',
ensureUserRegistrationAllowed, ensureUserRegistrationAllowed,
usersRegisterValidator, usersRegisterValidator,
registerUser asyncMiddleware(registerUser)
) )
usersRouter.put('/me', usersRouter.put('/me',
authenticate, authenticate,
usersUpdateMeValidator, usersUpdateMeValidator,
updateMe asyncMiddleware(updateMe)
) )
usersRouter.put('/:id', usersRouter.put('/:id',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
usersUpdateValidator, usersUpdateValidator,
updateUser asyncMiddleware(updateUser)
) )
usersRouter.delete('/:id', usersRouter.delete('/:id',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
usersRemoveValidator, usersRemoveValidator,
removeUser asyncMiddleware(removeUser)
) )
usersRouter.post('/token', token, success) 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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot insert the user with many retries.' errorMessage: 'Cannot insert the user with many retries.'
} }
retryTransactionWrapper(createUser, options) await retryTransactionWrapper(createUser, options)
.then(() => {
// TODO : include Location of the new user -> 201 // TODO : include Location of the new user -> 201
res.type('json').status(204).end() return res.type('json').status(204).end()
})
.catch(err => next(err))
} }
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 body: UserCreate = req.body
const user = db.User.build({ const user = db.User.build({
username: body.username, username: body.username,
@ -124,15 +123,12 @@ function createUser (req: express.Request, res: express.Response, next: express.
videoQuota: body.videoQuota videoQuota: body.videoQuota
}) })
return createUserAuthorAndChannel(user) await createUserAuthorAndChannel(user)
.then(() => logger.info('User %s with its channel and author created.', body.username))
.catch((err: Error) => { logger.info('User %s with its channel and author created.', body.username)
logger.debug('Cannot insert the user.', err)
throw err
})
} }
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 body: UserCreate = req.body
const user = db.User.build({ const user = db.User.build({
@ -144,22 +140,21 @@ function registerUser (req: express.Request, res: express.Response, next: expres
videoQuota: CONFIG.USER.VIDEO_QUOTA videoQuota: CONFIG.USER.VIDEO_QUOTA
}) })
return createUserAuthorAndChannel(user) await createUserAuthorAndChannel(user)
.then(() => res.type('json').status(204).end()) return res.type('json').status(204).end()
.catch(err => next(err))
} }
function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
.then(user => res.json(user.toFormattedJSON()))
.catch(err => next(err)) return res.json(user.toFormattedJSON())
} }
function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
return res.json(res.locals.user.toFormattedJSON()) 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 videoId = +req.params.videoId
const userId = +res.locals.oauth.token.User.id 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)) .catch(err => next(err))
} }
function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
db.User.listForApi(req.query.start, req.query.count, req.query.sort) const resultList = await db.User.listForApi(req.query.start, req.query.count, req.query.sort)
.then(resultList => {
res.json(getFormattedObjects(resultList.data, resultList.total)) return res.json(getFormattedObjects(resultList.data, resultList.total))
})
.catch(err => next(err))
} }
function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
db.User.loadById(req.params.id) const user = await db.User.loadById(req.params.id)
.then(user => user.destroy())
.then(() => res.sendStatus(204)) await user.destroy()
.catch(err => {
logger.error('Errors when removed the user.', err) return res.sendStatus(204)
return next(err)
})
} }
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 const body: UserUpdateMe = req.body
// FIXME: user is not already a Sequelize instance? // FIXME: user is not already a Sequelize instance?
db.User.loadByUsername(res.locals.oauth.token.user.username) const user = res.locals.oauth.token.user
.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
return user.save() if (body.password !== undefined) user.password = body.password
}) if (body.email !== undefined) user.email = body.email
.then(() => res.sendStatus(204)) if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
.catch(err => next(err))
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 body: UserUpdate = req.body
const user: UserInstance = res.locals.user const user: UserInstance = res.locals.user
if (body.email !== undefined) user.email = body.email if (body.email !== undefined) user.email = body.email
if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
return user.save() await user.save()
.then(() => res.sendStatus(204))
.catch(err => next(err)) return res.sendStatus(204)
} }
function success (req: express.Request, res: express.Response, next: express.NextFunction) { function success (req: express.Request, res: express.Response, next: express.NextFunction) {

View File

@ -14,7 +14,8 @@ import {
videoAbuseReportValidator, videoAbuseReportValidator,
videoAbusesSortValidator, videoAbusesSortValidator,
setVideoAbusesSort, setVideoAbusesSort,
setPagination setPagination,
asyncMiddleware
} from '../../../middlewares' } from '../../../middlewares'
import { VideoInstance } from '../../../models' import { VideoInstance } from '../../../models'
import { VideoAbuseCreate } from '../../../../shared' import { VideoAbuseCreate } from '../../../../shared'
@ -28,12 +29,12 @@ abuseVideoRouter.get('/abuse',
videoAbusesSortValidator, videoAbusesSortValidator,
setVideoAbusesSort, setVideoAbusesSort,
setPagination, setPagination,
listVideoAbuses asyncMiddleware(listVideoAbuses)
) )
abuseVideoRouter.post('/:id/abuse', abuseVideoRouter.post('/:id/abuse',
authenticate, authenticate,
videoAbuseReportValidator, videoAbuseReportValidator,
reportVideoAbuseRetryWrapper asyncMiddleware(reportVideoAbuseRetryWrapper)
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -44,55 +45,48 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort) const resultList = await 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)) 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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot report abuse to the video with many retries.' errorMessage: 'Cannot report abuse to the video with many retries.'
} }
retryTransactionWrapper(reportVideoAbuse, options) await retryTransactionWrapper(reportVideoAbuse, options)
.then(() => res.type('json').status(204).end())
.catch(err => next(err)) 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 videoInstance = res.locals.video as VideoInstance
const reporterUsername = res.locals.oauth.token.User.username const reporterUsername = res.locals.oauth.token.User.username
const body: VideoAbuseCreate = req.body const body: VideoAbuseCreate = req.body
const abuse = { const abuseToCreate = {
reporterUsername, reporterUsername,
reason: body.reason, reason: body.reason,
videoId: videoInstance.id, videoId: videoInstance.id,
reporterPodId: null // This is our pod that reported this abuse reporterPodId: null // This is our pod that reported this abuse
} }
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return db.VideoAbuse.create(abuse, { transaction: t }) const abuse = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
.then(abuse => { // We send the information to the destination pod
// We send the information to the destination pod if (videoInstance.isOwned() === false) {
if (videoInstance.isOwned() === false) { const reportData = {
const reportData = { reporterUsername,
reporterUsername, reportReason: abuse.reason,
reportReason: abuse.reason, videoUUID: videoInstance.uuid
videoUUID: videoInstance.uuid }
}
return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance) await friends.reportAbuseVideoToFriend(reportData, videoInstance, t)
} }
})
return videoInstance logger.info('Abuse report for video %s created.', videoInstance.name)
})
})
.then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name))
.catch(err => {
logger.debug('Cannot update the video.', err)
throw err
})
} }

View File

@ -10,7 +10,8 @@ import {
paginationValidator, paginationValidator,
blacklistSortValidator, blacklistSortValidator,
setBlacklistSort, setBlacklistSort,
setPagination setPagination,
asyncMiddleware
} from '../../../middlewares' } from '../../../middlewares'
import { BlacklistedVideoInstance } from '../../../models' import { BlacklistedVideoInstance } from '../../../models'
import { BlacklistedVideo } from '../../../../shared' import { BlacklistedVideo } from '../../../../shared'
@ -21,7 +22,7 @@ blacklistRouter.post('/:videoId/blacklist',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
videosBlacklistAddValidator, videosBlacklistAddValidator,
addVideoToBlacklist asyncMiddleware(addVideoToBlacklist)
) )
blacklistRouter.get('/blacklist', blacklistRouter.get('/blacklist',
@ -31,14 +32,14 @@ blacklistRouter.get('/blacklist',
blacklistSortValidator, blacklistSortValidator,
setBlacklistSort, setBlacklistSort,
setPagination, setPagination,
listBlacklist asyncMiddleware(listBlacklist)
) )
blacklistRouter.delete('/:videoId/blacklist', blacklistRouter.delete('/:videoId/blacklist',
authenticate, authenticate,
ensureIsAdmin, ensureIsAdmin,
videosBlacklistRemoveValidator, 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 videoInstance = res.locals.video
const toCreate = { const toCreate = {
videoId: videoInstance.id videoId: videoInstance.id
} }
db.BlacklistedVideo.create(toCreate) await db.BlacklistedVideo.create(toCreate)
.then(() => res.type('json').status(204).end()) return res.type('json').status(204).end()
.catch(err => {
logger.error('Errors when blacklisting video ', err)
return next(err)
})
} }
function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort) const resultList = await db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort)
.then(resultList => res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total)))
.catch(err => next(err)) return res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total))
} }
function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
const blacklistedVideo = res.locals.blacklistedVideo as BlacklistedVideoInstance const blacklistedVideo = res.locals.blacklistedVideo as BlacklistedVideoInstance
blacklistedVideo.destroy() try {
.then(() => { await blacklistedVideo.destroy()
logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
res.sendStatus(204) logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
})
.catch(err => { return res.sendStatus(204)
logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, err) } catch (err) {
next(err) logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, err)
}) throw err
}
} }

View File

@ -4,7 +4,8 @@ import { database as db } from '../../../initializers'
import { import {
logger, logger,
getFormattedObjects, getFormattedObjects,
retryTransactionWrapper retryTransactionWrapper,
resetSequelizeInstance
} from '../../../helpers' } from '../../../helpers'
import { import {
authenticate, authenticate,
@ -16,7 +17,8 @@ import {
videoChannelsRemoveValidator, videoChannelsRemoveValidator,
videoChannelGetValidator, videoChannelGetValidator,
videoChannelsUpdateValidator, videoChannelsUpdateValidator,
listVideoAuthorChannelsValidator listVideoAuthorChannelsValidator,
asyncMiddleware
} from '../../../middlewares' } from '../../../middlewares'
import { import {
createVideoChannel, createVideoChannel,
@ -32,18 +34,18 @@ videoChannelRouter.get('/channels',
videoChannelsSortValidator, videoChannelsSortValidator,
setVideoChannelsSort, setVideoChannelsSort,
setPagination, setPagination,
listVideoChannels asyncMiddleware(listVideoChannels)
) )
videoChannelRouter.get('/authors/:authorId/channels', videoChannelRouter.get('/authors/:authorId/channels',
listVideoAuthorChannelsValidator, listVideoAuthorChannelsValidator,
listVideoAuthorChannels asyncMiddleware(listVideoAuthorChannels)
) )
videoChannelRouter.post('/channels', videoChannelRouter.post('/channels',
authenticate, authenticate,
videoChannelsAddValidator, videoChannelsAddValidator,
addVideoChannelRetryWrapper asyncMiddleware(addVideoChannelRetryWrapper)
) )
videoChannelRouter.put('/channels/:id', videoChannelRouter.put('/channels/:id',
@ -55,12 +57,12 @@ videoChannelRouter.put('/channels/:id',
videoChannelRouter.delete('/channels/:id', videoChannelRouter.delete('/channels/:id',
authenticate, authenticate,
videoChannelsRemoveValidator, videoChannelsRemoveValidator,
removeVideoChannelRetryWrapper asyncMiddleware(removeVideoChannelRetryWrapper)
) )
videoChannelRouter.get('/channels/:id', videoChannelRouter.get('/channels/:id',
videoChannelGetValidator, videoChannelGetValidator,
getVideoChannel asyncMiddleware(getVideoChannel)
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -71,126 +73,113 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort) const resultList = await 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)) return res.json(getFormattedObjects(resultList.data, resultList.total))
} }
function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) { async function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
db.VideoChannel.listByAuthor(res.locals.author.id) const resultList = await db.VideoChannel.listByAuthor(res.locals.author.id)
.then(result => res.json(getFormattedObjects(result.data, result.total)))
.catch(err => next(err)) 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 // 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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot insert the video video channel with many retries.' errorMessage: 'Cannot insert the video video channel with many retries.'
} }
retryTransactionWrapper(addVideoChannel, options) await retryTransactionWrapper(addVideoChannel, options)
.then(() => {
// TODO : include Location of the new video channel -> 201 // TODO : include Location of the new video channel -> 201
res.type('json').status(204).end() return res.type('json').status(204).end()
})
.catch(err => next(err))
} }
function addVideoChannel (req: express.Request, res: express.Response) { async function addVideoChannel (req: express.Request, res: express.Response) {
const videoChannelInfo: VideoChannelCreate = req.body const videoChannelInfo: VideoChannelCreate = req.body
const author: AuthorInstance = res.locals.oauth.token.User.Author const author: AuthorInstance = res.locals.oauth.token.User.Author
let videoChannelCreated: VideoChannelInstance
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return createVideoChannel(videoChannelInfo, author, t) videoChannelCreated = await 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
}) })
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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot update the video with many retries.' errorMessage: 'Cannot update the video with many retries.'
} }
retryTransactionWrapper(updateVideoChannel, options) await retryTransactionWrapper(updateVideoChannel, options)
.then(() => res.type('json').status(204).end())
.catch(err => next(err)) 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 videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
const videoChannelFieldsSave = videoChannelInstance.toJSON() const videoChannelFieldsSave = videoChannelInstance.toJSON()
const videoChannelInfoToUpdate: VideoChannelUpdate = req.body const videoChannelInfoToUpdate: VideoChannelUpdate = req.body
return db.sequelize.transaction(t => { try {
const options = { await db.sequelize.transaction(async t => {
transaction: t const sequelizeOptions = {
} transaction: t
}
if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name) if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
return videoChannelInstance.save(options) await videoChannelInstance.save(sequelizeOptions)
.then(() => { const json = videoChannelInstance.toUpdateRemoteJSON()
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 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
// If the transaction is retried, sequelize will think the object has not changed } catch (err) {
// So it will skip the SQL request, even if the last one was ROLLBACKed! logger.debug('Cannot update the video channel.', err)
Object.keys(videoChannelFieldsSave).forEach(key => {
const value = videoChannelFieldsSave[key]
videoChannelInstance.set(key, value)
})
throw 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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot remove the video channel with many retries.' errorMessage: 'Cannot remove the video channel with many retries.'
} }
retryTransactionWrapper(removeVideoChannel, options) await retryTransactionWrapper(removeVideoChannel, options)
.then(() => res.type('json').status(204).end())
.catch(err => next(err)) 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 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return videoChannelInstance.destroy({ transaction: t }) await 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
}) })
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) { async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id) const videoChannelWithVideos = await db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id)
.then(videoChannelWithVideos => res.json(videoChannelWithVideos.toFormattedJSON()))
.catch(err => next(err)) return res.json(videoChannelWithVideos.toFormattedJSON())
} }

View File

@ -1,5 +1,4 @@
import * as express from 'express' import * as express from 'express'
import * as Promise from 'bluebird'
import * as multer from 'multer' import * as multer from 'multer'
import { extname, join } from 'path' import { extname, join } from 'path'
@ -30,7 +29,8 @@ import {
videosSearchValidator, videosSearchValidator,
videosAddValidator, videosAddValidator,
videosGetValidator, videosGetValidator,
videosRemoveValidator videosRemoveValidator,
asyncMiddleware
} from '../../../middlewares' } from '../../../middlewares'
import { import {
logger, logger,
@ -38,7 +38,8 @@ import {
generateRandomString, generateRandomString,
getFormattedObjects, getFormattedObjects,
renamePromise, renamePromise,
getVideoFileHeight getVideoFileHeight,
resetSequelizeInstance
} from '../../../helpers' } from '../../../helpers'
import { TagInstance, VideoInstance } from '../../../models' import { TagInstance, VideoInstance } from '../../../models'
import { VideoCreate, VideoUpdate } from '../../../../shared' import { VideoCreate, VideoUpdate } from '../../../../shared'
@ -88,18 +89,18 @@ videosRouter.get('/',
videosSortValidator, videosSortValidator,
setVideosSort, setVideosSort,
setPagination, setPagination,
listVideos asyncMiddleware(listVideos)
) )
videosRouter.put('/:id', videosRouter.put('/:id',
authenticate, authenticate,
videosUpdateValidator, videosUpdateValidator,
updateVideoRetryWrapper asyncMiddleware(updateVideoRetryWrapper)
) )
videosRouter.post('/upload', videosRouter.post('/upload',
authenticate, authenticate,
reqFiles, reqFiles,
videosAddValidator, videosAddValidator,
addVideoRetryWrapper asyncMiddleware(addVideoRetryWrapper)
) )
videosRouter.get('/:id', videosRouter.get('/:id',
videosGetValidator, videosGetValidator,
@ -109,7 +110,7 @@ videosRouter.get('/:id',
videosRouter.delete('/:id', videosRouter.delete('/:id',
authenticate, authenticate,
videosRemoveValidator, videosRemoveValidator,
removeVideoRetryWrapper asyncMiddleware(removeVideoRetryWrapper)
) )
videosRouter.get('/search/:value', videosRouter.get('/search/:value',
@ -119,7 +120,7 @@ videosRouter.get('/search/:value',
setVideosSort, setVideosSort,
setPagination, setPagination,
setVideosSearch, 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 // 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 // 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 = { const options = {
arguments: [ req, res, req.files['videofile'][0] ], arguments: [ req, res, req.files['videofile'][0] ],
errorMessage: 'Cannot insert the video with many retries.' errorMessage: 'Cannot insert the video with many retries.'
} }
retryTransactionWrapper(addVideo, options) await retryTransactionWrapper(addVideo, options)
.then(() => {
// TODO : include Location of the new video -> 201 // TODO : include Location of the new video -> 201
res.type('json').status(204).end() res.type('json').status(204).end()
})
.catch(err => next(err))
} }
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 const videoInfo: VideoCreate = req.body
let videoUUID = '' let videoUUID = ''
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
let p: Promise<TagInstance[]> const sequelizeOptions = { transaction: t }
if (!videoInfo.tags) p = Promise.resolve(undefined) const videoData = {
else p = db.Tag.findOrCreateTags(videoInfo.tags, t) 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)
return p const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
.then(tagInstances => { const videoFileHeight = await getVideoFileHeight(videoFilePath)
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 videoFileData = {
return { tagInstances, video } extname: extname(videoPhysicalFile.filename),
}) resolution: videoFileHeight,
.then(({ tagInstances, video }) => { size: videoPhysicalFile.size
const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) }
return getVideoFileHeight(videoFilePath) const videoFile = db.VideoFile.build(videoFileData)
.then(height => ({ tagInstances, video, videoFileHeight: height })) const videoDir = CONFIG.STORAGE.VIDEOS_DIR
}) const source = join(videoDir, videoPhysicalFile.filename)
.then(({ tagInstances, video, videoFileHeight }) => { const destination = join(videoDir, video.getVideoFilename(videoFile))
const videoFileData = {
extname: extname(videoPhysicalFile.filename),
resolution: videoFileHeight,
size: videoPhysicalFile.size
}
const videoFile = db.VideoFile.build(videoFileData) await renamePromise(source, destination)
return { tagInstances, video, videoFile } // This is important in case if there is another attempt in the retry process
}) videoPhysicalFile.filename = video.getVideoFilename(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) const tasks = []
.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( tasks.push(
video.createTorrentAndSetInfoHash(videoFile), video.createTorrentAndSetInfoHash(videoFile),
video.createThumbnail(videoFile), video.createThumbnail(videoFile),
video.createPreview(videoFile) video.createPreview(videoFile)
) )
if (CONFIG.TRANSCODING.ENABLED === true) { if (CONFIG.TRANSCODING.ENABLED === true) {
// Put uuid because we don't have id auto incremented for now // Put uuid because we don't have id auto incremented for now
const dataInput = { const dataInput = {
videoUUID: video.uuid videoUUID: video.uuid
} }
tasks.push( tasks.push(
JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput) JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput)
) )
} }
await Promise.all(tasks)
return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile })) const videoCreated = await video.save(sequelizeOptions)
}) // Do not forget to add video channel information to the created video
.then(({ tagInstances, video, videoFile }) => { videoCreated.VideoChannel = res.locals.videoChannel
const options = { transaction: t } videoUUID = videoCreated.uuid
return video.save(options) videoFile.videoId = video.id
.then(videoCreated => {
// Do not forget to add video channel information to the created video
videoCreated.VideoChannel = res.locals.videoChannel
videoUUID = videoCreated.uuid
return { tagInstances, video: videoCreated, videoFile } await videoFile.save(sequelizeOptions)
}) video.VideoFiles = [videoFile]
})
.then(({ tagInstances, video, videoFile }) => {
const options = { transaction: t }
videoFile.videoId = video.id
return videoFile.save(options) if (videoInfo.tags) {
.then(() => video.VideoFiles = [ videoFile ]) const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t)
.then(() => ({ tagInstances, video }))
})
.then(({ tagInstances, video }) => {
if (!tagInstances) return video
const options = { transaction: t } await video.setTags(tagInstances, sequelizeOptions)
return video.setTags(tagInstances, options) video.Tags = tagInstances
.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() // Let transcoding job send the video to friends because the video file extension might change
.then(remoteVideo => { if (CONFIG.TRANSCODING.ENABLED === true) return undefined
// Now we'll add the video's meta data to our friends
return addVideoToFriends(remoteVideo, t) const remoteVideo = await video.toAddRemoteJSON()
}) // 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
}) })
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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot update the video with many retries.' errorMessage: 'Cannot update the video with many retries.'
} }
retryTransactionWrapper(updateVideo, options) await retryTransactionWrapper(updateVideo, options)
.then(() => {
return res.type('json').status(204).end() return res.type('json').status(204).end()
})
.catch(err => next(err))
} }
function updateVideo (req: express.Request, res: express.Response) { async function updateVideo (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video const videoInstance = res.locals.video
const videoFieldsSave = videoInstance.toJSON() const videoFieldsSave = videoInstance.toJSON()
const videoInfoToUpdate: VideoUpdate = req.body const videoInfoToUpdate: VideoUpdate = req.body
return db.sequelize.transaction(t => { try {
let tagsPromise: Promise<TagInstance[]> await db.sequelize.transaction(async t => {
if (!videoInfoToUpdate.tags) { const sequelizeOptions = {
tagsPromise = Promise.resolve(null) transaction: t
} else { }
tagsPromise = db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
}
return tagsPromise if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
.then(tagInstances => { if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
const options = { if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
transaction: t 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) await videoInstance.save(sequelizeOptions)
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)
return videoInstance.save(options).then(() => tagInstances) if (videoInfoToUpdate.tags) {
}) const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
.then(tagInstances => {
if (!tagInstances) return
const options = { transaction: t } await videoInstance.setTags(tagInstances, sequelizeOptions)
return videoInstance.setTags(tagInstances, options) videoInstance.Tags = tagInstances
.then(() => { }
videoInstance.Tags = tagInstances
return const json = videoInstance.toUpdateRemoteJSON()
})
}) // Now we'll update the video's meta data to our friends
.then(() => { return updateVideoToFriends(json, t)
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) logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
}) } catch (err) {
.catch(err => {
logger.debug('Cannot update the video.', err)
// Force fields we want to update // Force fields we want to update
// If the transaction is retried, sequelize will think the object has not changed // 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! // So it will skip the SQL request, even if the last one was ROLLBACKed!
Object.keys(videoFieldsSave).forEach(key => { resetSequelizeInstance(videoInstance, videoFieldsSave)
const value = videoFieldsSave[key]
videoInstance.set(key, value)
})
throw err throw err
}) }
} }
function getVideo (req: express.Request, res: express.Response) { function getVideo (req: express.Request, res: express.Response) {
@ -365,17 +303,17 @@ function getVideo (req: express.Request, res: express.Response) {
if (videoInstance.isOwned()) { if (videoInstance.isOwned()) {
// The increment is done directly in the database, not using the instance value // 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') videoInstance.increment('views')
.then(() => { .then(() => {
// FIXME: make a real view system
// For example, only add a view when a user watch a video during 30s etc
const qaduParams = { const qaduParams = {
videoId: videoInstance.id, videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.VIEWS type: REQUEST_VIDEO_QADU_TYPES.VIEWS
} }
return quickAndDirtyUpdateVideoToFriends(qaduParams) 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 { } else {
// Just send the event to our friends // Just send the event to our friends
const eventParams = { const eventParams = {
@ -383,48 +321,48 @@ function getVideo (req: express.Request, res: express.Response) {
type: REQUEST_VIDEO_EVENT_TYPES.VIEWS type: REQUEST_VIDEO_EVENT_TYPES.VIEWS
} }
addEventToRemoteVideo(eventParams) addEventToRemoteVideo(eventParams)
.catch(err => logger.error('Cannot add event to remote video %s.', videoInstance.uuid, err))
} }
// Do not wait the view system // 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) { async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
db.Video.listForApi(req.query.start, req.query.count, req.query.sort) const resultList = await 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)) 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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot remove the video with many retries.' errorMessage: 'Cannot remove the video with many retries.'
} }
retryTransactionWrapper(removeVideo, options) await retryTransactionWrapper(removeVideo, options)
.then(() => {
return res.type('json').status(204).end() return res.type('json').status(204).end()
})
.catch(err => next(err))
} }
function removeVideo (req: express.Request, res: express.Response) { async function removeVideo (req: express.Request, res: express.Response) {
const videoInstance: VideoInstance = res.locals.video const videoInstance: VideoInstance = res.locals.video
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return videoInstance.destroy({ transaction: t }) await 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
}) })
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) { async 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) const resultList = await db.Video.searchAndPopulateAuthorAndPodAndTags(
.then(result => res.json(getFormattedObjects(result.data, result.total))) req.params.value,
.catch(err => next(err)) req.query.field,
req.query.start,
req.query.count,
req.query.sort
)
return res.json(getFormattedObjects(resultList.data, resultList.total))
} }

View File

@ -1,5 +1,4 @@
import * as express from 'express' import * as express from 'express'
import * as Promise from 'bluebird'
import { database as db } from '../../../initializers/database' import { database as db } from '../../../initializers/database'
import { import {
@ -17,7 +16,8 @@ import {
} from '../../../lib' } from '../../../lib'
import { import {
authenticate, authenticate,
videoRateValidator videoRateValidator,
asyncMiddleware
} from '../../../middlewares' } from '../../../middlewares'
import { UserVideoRateUpdate, VideoRateType } from '../../../../shared' import { UserVideoRateUpdate, VideoRateType } from '../../../../shared'
@ -26,7 +26,7 @@ const rateVideoRouter = express.Router()
rateVideoRouter.put('/:id/rate', rateVideoRouter.put('/:id/rate',
authenticate, authenticate,
videoRateValidator, 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 = { const options = {
arguments: [ req, res ], arguments: [ req, res ],
errorMessage: 'Cannot update the user video rate.' errorMessage: 'Cannot update the user video rate.'
} }
retryTransactionWrapper(rateVideo, options) await retryTransactionWrapper(rateVideo, options)
.then(() => res.type('json').status(204).end())
.catch(err => next(err)) 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 body: UserVideoRateUpdate = req.body
const rateType = body.rating const rateType = body.rating
const videoInstance = res.locals.video const videoInstance = res.locals.video
const userInstance = res.locals.oauth.token.User const userInstance = res.locals.oauth.token.User
return db.sequelize.transaction(t => { await db.sequelize.transaction(async t => {
return db.UserVideoRate.load(userInstance.id, videoInstance.id, t) const sequelizeOptions = { transaction: t }
.then(previousRate => { const previousRate = await db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
const options = { transaction: t }
let likesToIncrement = 0 let likesToIncrement = 0
let dislikesToIncrement = 0 let dislikesToIncrement = 0
if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++ if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
let promise: Promise<any> // There was a previous rate, update it
if (previousRate) {
// We will remove the previous rate, so we will need to update the video count attribute
if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
// There was a previous rate, update it if (rateType === 'none') { // Destroy previous rate
if (previousRate) { await previousRate.destroy()
// We will remove the previous rate, so we will need to update the video count attribute } else { // Update previous rate
if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement-- previousRate.type = rateType as VideoRateType
else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
if (rateType === 'none') { // Destroy previous rate await previousRate.save()
promise = previousRate.destroy() }
} else { // Update previous rate } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
previousRate.type = rateType as VideoRateType const query = {
userId: userInstance.id,
videoId: videoInstance.id,
type: rateType
}
promise = previousRate.save() await db.UserVideoRate.create(query, sequelizeOptions)
} }
} 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) const incrementQuery = {
} else { likes: likesToIncrement,
promise = Promise.resolve() dislikes: dislikesToIncrement
} }
return promise.then(() => ({ likesToIncrement, dislikesToIncrement })) // Even if we do not own the video we increment the attributes
}) // It is useful for the user to have a feedback
.then(({ likesToIncrement, dislikesToIncrement }) => { await videoInstance.increment(incrementQuery, sequelizeOptions)
const options = { transaction: t }
const incrementQuery = {
likes: likesToIncrement,
dislikes: dislikesToIncrement
}
// Even if we do not own the video we increment the attributes // Send a event to original pod
// It is usefull for the user to have a feedback if (videoInstance.isOwned() === false) {
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 = [] const eventsParams = []
if (likesToIncrement !== 0) { if (likesToIncrement !== 0) {
eventsParams.push({ eventsParams.push({
videoId: videoInstance.id, videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.LIKES, type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
count: likesToIncrement count: likesToIncrement
}) })
} }
if (dislikesToIncrement !== 0) { if (dislikesToIncrement !== 0) {
eventsParams.push({ eventsParams.push({
videoId: videoInstance.id, videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES, type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
count: dislikesToIncrement count: dislikesToIncrement
}) })
} }
return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, 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
.then(({ likesToIncrement, dislikesToIncrement }) => { const qadusParams = []
// 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 (likesToIncrement !== 0) { if (dislikesToIncrement !== 0) {
qadusParams.push({ qadusParams.push({
videoId: videoInstance.id, videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.LIKES type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
}) })
} }
if (dislikesToIncrement !== 0) { await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
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
}) })
logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username)
} }

View File

@ -1,7 +1,7 @@
import * as express from 'express' import * as express from 'express'
import { join } from 'path' import { join } from 'path'
import * as validator from 'validator' import * as validator from 'validator'
import * as Promise from 'bluebird' import * as Bluebird from 'bluebird'
import { database as db } from '../initializers/database' import { database as db } from '../initializers/database'
import { import {
@ -11,6 +11,7 @@ import {
OPENGRAPH_AND_OEMBED_COMMENT OPENGRAPH_AND_OEMBED_COMMENT
} from '../initializers' } from '../initializers'
import { root, readFileBufferPromise, escapeHTML } from '../helpers' import { root, readFileBufferPromise, escapeHTML } from '../helpers'
import { asyncMiddleware } from '../middlewares'
import { VideoInstance } from '../models' import { VideoInstance } from '../models'
const clientsRouter = express.Router() const clientsRouter = express.Router()
@ -21,7 +22,9 @@ const indexPath = join(distPath, 'index.html')
// Special route that add OpenGraph and oEmbed tags // Special route that add OpenGraph and oEmbed tags
// Do not use a template engine for a so little thing // 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) => { clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => {
res.sendFile(embedPath) res.sendFile(embedPath)
@ -90,9 +93,9 @@ function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance
return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString) 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 const videoId = '' + req.params.id
let videoPromise: Promise<VideoInstance> let videoPromise: Bluebird<VideoInstance>
// Let Angular application handle errors // Let Angular application handle errors
if (validator.isUUID(videoId, 4)) { if (validator.isUUID(videoId, 4)) {
@ -103,21 +106,19 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
return res.sendFile(indexPath) return res.sendFile(indexPath)
} }
Promise.all([ let [ file, video ] = await Promise.all([
readFileBufferPromise(indexPath), readFileBufferPromise(indexPath),
videoPromise 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 const html = file.toString()
if (!video) return res.sendFile(indexPath)
const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video) // Let Angular application handle errors
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) if (!video) return res.sendFile(indexPath)
})
.catch(err => next(err)) const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
} }

View File

@ -7,6 +7,7 @@ import {
STATIC_PATHS STATIC_PATHS
} from '../initializers' } from '../initializers'
import { VideosPreviewCache } from '../lib' import { VideosPreviewCache } from '../lib'
import { asyncMiddleware } from '../middlewares'
const staticRouter = express.Router() const staticRouter = express.Router()
@ -39,7 +40,7 @@ staticRouter.use(
// Video previews path for express // Video previews path for express
staticRouter.use( staticRouter.use(
STATIC_PATHS.PREVIEWS + ':uuid.jpg', STATIC_PATHS.PREVIEWS + ':uuid.jpg',
getPreview asyncMiddleware(getPreview)
) )
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -50,11 +51,9 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) { async function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) {
VideosPreviewCache.Instance.getPreviewPath(req.params.uuid) const path = await VideosPreviewCache.Instance.getPreviewPath(req.params.uuid)
.then(path => { if (!path) return res.sendStatus(404)
if (!path) return res.sendStatus(404)
return res.sendFile(path, { maxAge: STATIC_MAX_AGE }) return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
})
} }

View File

@ -1,6 +1,5 @@
// TODO: import from ES6 when retry typing file will include errorFilter function // TODO: import from ES6 when retry typing file will include errorFilter function
import * as retry from 'async/retry' import * as retry from 'async/retry'
import * as Promise from 'bluebird'
import { logger } from './logger' import { logger } from './logger'

View File

@ -1,4 +1,5 @@
import * as express from 'express' import * as express from 'express'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird' import * as Promise from 'bluebird'
import { pseudoRandomBytesPromise } from './core-utils' import { pseudoRandomBytesPromise } from './core-utils'
@ -69,6 +70,13 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
return resolutionsEnabled return resolutionsEnabled
} }
function resetSequelizeInstance (instance: Sequelize.Instance<any>, savedFields: object) {
Object.keys(savedFields).forEach(key => {
const value = savedFields[key]
instance.set(key, value)
})
}
type SortType = { sortModel: any, sortValue: string } type SortType = { sortModel: any, sortValue: string }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -79,5 +87,6 @@ export {
getFormattedObjects, getFormattedObjects,
isSignupAllowed, isSignupAllowed,
computeResolutionsToTranscode, computeResolutionsToTranscode,
resetSequelizeInstance,
SortType SortType
} }

View File

@ -63,6 +63,7 @@ const sequelize = new Sequelize(dbname, username, password, {
host: CONFIG.DATABASE.HOSTNAME, host: CONFIG.DATABASE.HOSTNAME,
port: CONFIG.DATABASE.PORT, port: CONFIG.DATABASE.PORT,
benchmark: isTestInstance(), benchmark: isTestInstance(),
isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE,
logging: (message: string, benchmark: number) => { logging: (message: string, benchmark: number) => {
let newMessage = message let newMessage = message

View File

@ -2,12 +2,11 @@ import * as Sequelize from 'sequelize'
import { addVideoChannelToFriends } from './friends' import { addVideoChannelToFriends } from './friends'
import { database as db } from '../initializers' import { database as db } from '../initializers'
import { logger } from '../helpers'
import { AuthorInstance } from '../models' import { AuthorInstance } from '../models'
import { VideoChannelCreate } from '../../shared/models' import { VideoChannelCreate } from '../../shared/models'
function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: AuthorInstance, t: Sequelize.Transaction) { async function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: AuthorInstance, t: Sequelize.Transaction) {
let videoChannelUUID = ''
const videoChannelData = { const videoChannelData = {
name: videoChannelInfo.name, name: videoChannelInfo.name,
description: videoChannelInfo.description, description: videoChannelInfo.description,
@ -18,25 +17,34 @@ function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: Autho
const videoChannel = db.VideoChannel.build(videoChannelData) const videoChannel = db.VideoChannel.build(videoChannelData)
const options = { transaction: t } const options = { transaction: t }
return videoChannel.save(options) const videoChannelCreated = await 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 // Do not forget to add Author information to the created video channel
}) videoChannelCreated.Author = author
.then(videoChannel => {
const remoteVideoChannel = videoChannel.toAddRemoteJSON()
// Now we'll add the video channel's meta data to our friends const remoteVideoChannel = videoChannelCreated.toAddRemoteJSON()
return addVideoChannelToFriends(remoteVideoChannel, t)
}) // Now we'll add the video channel's meta data to our friends
.then(() => videoChannelUUID) // Return video channel UUID 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 { export {
createVideoChannel createVideoChannel,
fetchVideoChannelByHostAndUUID
} }

View File

@ -0,0 +1,16 @@
import { Request, Response, NextFunction } from 'express'
// Syntactic sugar to avoid try/catch in express controllers
// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
function asyncMiddleware (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) {
return (req: Request, res: Response, next: NextFunction) => {
return Promise.resolve(fn(req, res, next))
.catch(next)
}
}
// ---------------------------------------------------------------------------
export {
asyncMiddleware
}

View File

@ -1,5 +1,6 @@
export * from './validators' export * from './validators'
export * from './admin' export * from './admin'
export * from './async'
export * from './oauth' export * from './oauth'
export * from './pagination' export * from './pagination'
export * from './pods' export * from './pods'