X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fcontrollers%2Fapi%2Fvideos.js;h=6573b12103efa9c30870b8905e83b6a37434601e;hb=bf4ff8fe0be63422c05d42e12f25b454bb95d1a5;hp=1b306d1cf038644f09f77adcacb37c35e14f9f13;hpb=7b1f49de22c40ae121ddb3c399b2540ba56fd414;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js index 1b306d1cf..6573b1210 100644 --- a/server/controllers/api/videos.js +++ b/server/controllers/api/videos.js @@ -11,6 +11,7 @@ const db = require('../../initializers/database') const logger = require('../../helpers/logger') const friends = require('../../lib/friends') const middlewares = require('../../middlewares') +const admin = middlewares.admin const oAuth = middlewares.oauth const pagination = middlewares.pagination const validators = middlewares.validators @@ -43,6 +44,21 @@ const storage = multer.diskStorage({ const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) +router.get('/abuse', + oAuth.authenticate, + admin.ensureIsAdmin, + validatorsPagination.pagination, + validatorsSort.videoAbusesSort, + sort.setVideoAbusesSort, + pagination.setPagination, + listVideoAbuses +) +router.post('/:id/abuse', + oAuth.authenticate, + validatorsVideos.videoAbuseReport, + reportVideoAbuseRetryWrapper +) + router.get('/', validatorsPagination.pagination, validatorsSort.videosSort, @@ -54,13 +70,13 @@ router.put('/:id', oAuth.authenticate, reqFiles, validatorsVideos.videosUpdate, - updateVideo + updateVideoRetryWrapper ) router.post('/', oAuth.authenticate, reqFiles, validatorsVideos.videosAdd, - addVideo + addVideoRetryWrapper ) router.get('/:id', validatorsVideos.videosGet, @@ -87,19 +103,37 @@ module.exports = router // --------------------------------------------------------------------------- -function addVideo (req, res, next) { - const videoFile = req.files.videofile[0] +// Wrapper to video add that retry the function if there is a database error +// We need this because we run the transaction in SERIALIZABLE isolation that can fail +function addVideoRetryWrapper (req, res, next) { + utils.transactionRetryer( + function (callback) { + return addVideo(req, res, req.files.videofile[0], callback) + }, + function (err) { + if (err) { + logger.error('Cannot insert the video with many retries.', { error: err }) + return next(err) + } + + // TODO : include Location of the new video -> 201 + return res.type('json').status(204).end() + } + ) +} + +function addVideo (req, res, videoFile, callback) { const videoInfos = req.body waterfall([ - function startTransaction (callback) { - db.sequelize.transaction().asCallback(function (err, t) { - return callback(err, t) + function startTransaction (callbackWaterfall) { + db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { + return callbackWaterfall(err, t) }) }, - function findOrCreateAuthor (t, callback) { + function findOrCreateAuthor (t, callbackWaterfall) { const user = res.locals.oauth.token.User const name = user.username @@ -108,19 +142,19 @@ function addVideo (req, res, next) { const userId = user.id db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { - return callback(err, t, authorInstance) + return callbackWaterfall(err, t, authorInstance) }) }, - function findOrCreateTags (t, author, callback) { + function findOrCreateTags (t, author, callbackWaterfall) { const tags = videoInfos.tags db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { - return callback(err, t, author, tagInstances) + return callbackWaterfall(err, t, author, tagInstances) }) }, - function createVideoObject (t, author, tagInstances, callback) { + function createVideoObject (t, author, tagInstances, callbackWaterfall) { const videoData = { name: videoInfos.name, remoteId: null, @@ -132,75 +166,98 @@ function addVideo (req, res, next) { const video = db.Video.build(videoData) - return callback(null, t, author, tagInstances, video) + return callbackWaterfall(null, t, author, tagInstances, video) }, // Set the videoname the same as the id - function renameVideoFile (t, author, tagInstances, video, callback) { + function renameVideoFile (t, author, tagInstances, video, callbackWaterfall) { const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR const source = path.join(videoDir, videoFile.filename) const destination = path.join(videoDir, video.getVideoFilename()) fs.rename(source, destination, function (err) { - return callback(err, t, author, tagInstances, video) + if (err) return callbackWaterfall(err) + + // This is important in case if there is another attempt + videoFile.filename = video.getVideoFilename() + return callbackWaterfall(null, t, author, tagInstances, video) }) }, - function insertVideoIntoDB (t, author, tagInstances, video, callback) { + function insertVideoIntoDB (t, author, tagInstances, video, callbackWaterfall) { const options = { transaction: t } // Add tags association video.save(options).asCallback(function (err, videoCreated) { - if (err) return callback(err) + if (err) return callbackWaterfall(err) // Do not forget to add Author informations to the created video videoCreated.Author = author - return callback(err, t, tagInstances, videoCreated) + return callbackWaterfall(err, t, tagInstances, videoCreated) }) }, - function associateTagsToVideo (t, tagInstances, video, callback) { + function associateTagsToVideo (t, tagInstances, video, callbackWaterfall) { const options = { transaction: t } video.setTags(tagInstances, options).asCallback(function (err) { video.Tags = tagInstances - return callback(err, t, video) + return callbackWaterfall(err, t, video) }) }, - function sendToFriends (t, video, callback) { + function sendToFriends (t, video, callbackWaterfall) { video.toAddRemoteJSON(function (err, remoteVideo) { - if (err) return callback(err) + if (err) return callbackWaterfall(err) // Now we'll add the video's meta data to our friends - friends.addVideoToFriends(remoteVideo) - - return callback(null, t) + friends.addVideoToFriends(remoteVideo, t, function (err) { + return callbackWaterfall(err, t) + }) }) } ], function andFinally (err, t) { if (err) { - logger.error('Cannot insert the video.') + // This is just a debug because we will retry the insert + logger.debug('Cannot insert the video.', { error: err }) // Abort transaction? if (t) t.rollback() - return next(err) + return callback(err) } // Commit transaction t.commit() - // TODO : include Location of the new video -> 201 - return res.type('json').status(204).end() + logger.info('Video with name %s created.', videoInfos.name) + + return callback(null) }) } -function updateVideo (req, res, next) { - let videoInstance = res.locals.video +function updateVideoRetryWrapper (req, res, next) { + utils.transactionRetryer( + function (callback) { + return updateVideo(req, res, callback) + }, + function (err) { + if (err) { + logger.error('Cannot update the video with many retries.', { error: err }) + return next(err) + } + + // TODO : include Location of the new video -> 201 + return res.type('json').status(204).end() + } + ) +} + +function updateVideo (req, res, finalCallback) { + const videoInstance = res.locals.video const videoInfosToUpdate = req.body waterfall([ @@ -229,8 +286,6 @@ function updateVideo (req, res, next) { // Add tags association videoInstance.save(options).asCallback(function (err) { - if (err) return callback(err) - return callback(err, t, tagInstances) }) }, @@ -253,64 +308,45 @@ function updateVideo (req, res, next) { const json = videoInstance.toUpdateRemoteJSON() // Now we'll update the video's meta data to our friends - friends.updateVideoToFriends(json) - - return callback(null, t) + friends.updateVideoToFriends(json, t, function (err) { + return callback(err, t) + }) } ], function andFinally (err, t) { if (err) { - logger.error('Cannot insert the video.') + logger.debug('Cannot update the video.', { error: err }) // Abort transaction? if (t) t.rollback() - return next(err) + return finalCallback(err) } // Commit transaction t.commit() - // TODO : include Location of the new video -> 201 - return res.type('json').status(204).end() + return finalCallback(null) }) } function getVideo (req, res, next) { - db.Video.loadAndPopulateAuthorAndPodAndTags(req.params.id, function (err, video) { - if (err) return next(err) - - if (!video) { - return res.type('json').status(204).end() - } - - res.json(video.toFormatedJSON()) - }) + const videoInstance = res.locals.video + res.json(videoInstance.toFormatedJSON()) } function listVideos (req, res, next) { db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { if (err) return next(err) - res.json(getFormatedVideos(videosList, videosTotal)) + res.json(utils.getFormatedObjects(videosList, videosTotal)) }) } function removeVideo (req, res, next) { - const videoId = req.params.id + const videoInstance = res.locals.video - waterfall([ - function loadVideo (callback) { - db.Video.load(videoId, function (err, video) { - return callback(err, video) - }) - }, - - function deleteVideo (video, callback) { - // Informations to other pods will be sent by the afterDestroy video hook - video.destroy().asCallback(callback) - } - ], function andFinally (err) { + videoInstance.destroy().asCallback(function (err) { if (err) { logger.error('Errors when removed the video.', { error: err }) return next(err) @@ -326,22 +362,89 @@ function searchVideos (req, res, next) { function (err, videosList, videosTotal) { if (err) return next(err) - res.json(getFormatedVideos(videosList, videosTotal)) + res.json(utils.getFormatedObjects(videosList, videosTotal)) } ) } -// --------------------------------------------------------------------------- - -function getFormatedVideos (videos, videosTotal) { - const formatedVideos = [] +function listVideoAbuses (req, res, next) { + db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) { + if (err) return next(err) - videos.forEach(function (video) { - formatedVideos.push(video.toFormatedJSON()) + res.json(utils.getFormatedObjects(abusesList, abusesTotal)) }) +} + +function reportVideoAbuseRetryWrapper (req, res, next) { + utils.transactionRetryer( + function (callback) { + return reportVideoAbuse(req, res, callback) + }, + function (err) { + if (err) { + logger.error('Cannot report abuse to the video with many retries.', { error: err }) + return next(err) + } + + return res.type('json').status(204).end() + } + ) +} - return { - total: videosTotal, - data: formatedVideos +function reportVideoAbuse (req, res, finalCallback) { + const videoInstance = res.locals.video + const reporterUsername = res.locals.oauth.token.User.username + + const abuse = { + reporterUsername, + reason: req.body.reason, + videoId: videoInstance.id, + reporterPodId: null // This is our pod that reported this abuse } + + waterfall([ + + function startTransaction (callback) { + db.sequelize.transaction().asCallback(function (err, t) { + return callback(err, t) + }) + }, + + function createAbuse (t, callback) { + db.VideoAbuse.create(abuse).asCallback(function (err, abuse) { + return callback(err, t, abuse) + }) + }, + + function sendToFriendsIfNeeded (t, abuse, callback) { + // We send the information to the destination pod + if (videoInstance.isOwned() === false) { + const reportData = { + reporterUsername, + reportReason: abuse.reason, + videoRemoteId: videoInstance.remoteId + } + + friends.reportAbuseVideoToFriend(reportData, videoInstance) + } + + return callback(null, t) + } + + ], function andFinally (err, t) { + if (err) { + logger.debug('Cannot update the video.', { error: err }) + + // Abort transaction? + if (t) t.rollback() + + return finalCallback(err) + } + + // Commit transaction + t.commit() + + return finalCallback(null) + }) } +