3 const express
= require('express')
4 const fs
= require('fs')
5 const multer
= require('multer')
6 const path
= require('path')
7 const waterfall
= require('async/waterfall')
9 const constants
= require('../../initializers/constants')
10 const db
= require('../../initializers/database')
11 const logger
= require('../../helpers/logger')
12 const friends
= require('../../lib/friends')
13 const middlewares
= require('../../middlewares')
14 const admin
= middlewares
.admin
15 const oAuth
= middlewares
.oauth
16 const pagination
= middlewares
.pagination
17 const validators
= middlewares
.validators
18 const validatorsPagination
= validators
.pagination
19 const validatorsSort
= validators
.sort
20 const validatorsVideos
= validators
.videos
21 const search
= middlewares
.search
22 const sort
= middlewares
.sort
23 const databaseUtils
= require('../../helpers/database-utils')
24 const utils
= require('../../helpers/utils')
26 const router
= express
.Router()
28 // multer configuration
29 const storage
= multer
.diskStorage({
30 destination: function (req
, file
, cb
) {
31 cb(null, constants
.CONFIG
.STORAGE
.VIDEOS_DIR
)
34 filename: function (req
, file
, cb
) {
36 if (file
.mimetype
=== 'video/webm') extension
= 'webm'
37 else if (file
.mimetype
=== 'video/mp4') extension
= 'mp4'
38 else if (file
.mimetype
=== 'video/ogg') extension
= 'ogv'
39 utils
.generateRandomString(16, function (err
, randomString
) {
40 const fieldname
= err
? undefined : randomString
41 cb(null, fieldname
+ '.' + extension
)
46 const reqFiles
= multer({ storage: storage
}).fields([{ name: 'videofile', maxCount: 1 }])
51 validatorsPagination
.pagination
,
52 validatorsSort
.videoAbusesSort
,
53 sort
.setVideoAbusesSort
,
54 pagination
.setPagination
,
57 router
.post('/:id/abuse',
59 validatorsVideos
.videoAbuseReport
,
60 reportVideoAbuseRetryWrapper
64 validatorsPagination
.pagination
,
65 validatorsSort
.videosSort
,
67 pagination
.setPagination
,
73 validatorsVideos
.videosUpdate
,
74 updateVideoRetryWrapper
79 validatorsVideos
.videosAdd
,
83 validatorsVideos
.videosGet
,
88 validatorsVideos
.videosRemove
,
91 router
.get('/search/:value',
92 validatorsVideos
.videosSearch
,
93 validatorsPagination
.pagination
,
94 validatorsSort
.videosSort
,
96 pagination
.setPagination
,
97 search
.setVideosSearch
,
101 // ---------------------------------------------------------------------------
103 module
.exports
= router
105 // ---------------------------------------------------------------------------
107 // Wrapper to video add that retry the function if there is a database error
108 // We need this because we run the transaction in SERIALIZABLE isolation that can fail
109 function addVideoRetryWrapper (req
, res
, next
) {
111 arguments: [ req
, res
, req
.files
.videofile
[0] ],
112 errorMessage: 'Cannot insert the video with many retries.'
115 databaseUtils
.retryTransactionWrapper(addVideo
, options
, function (err
) {
116 if (err
) return next(err
)
118 // TODO : include Location of the new video -> 201
119 return res
.type('json').status(204).end()
123 function addVideo (req
, res
, videoFile
, finalCallback
) {
124 const videoInfos
= req
.body
128 databaseUtils
.startSerializableTransaction
,
130 function findOrCreateAuthor (t
, callback
) {
131 const user
= res
.locals
.oauth
.token
.User
133 const name
= user
.username
134 // null because it is OUR pod
136 const userId
= user
.id
138 db
.Author
.findOrCreateAuthor(name
, podId
, userId
, t
, function (err
, authorInstance
) {
139 return callback(err
, t
, authorInstance
)
143 function findOrCreateTags (t
, author
, callback
) {
144 const tags
= videoInfos
.tags
146 db
.Tag
.findOrCreateTags(tags
, t
, function (err
, tagInstances
) {
147 return callback(err
, t
, author
, tagInstances
)
151 function createVideoObject (t
, author
, tagInstances
, callback
) {
153 name: videoInfos
.name
,
155 extname: path
.extname(videoFile
.filename
),
156 description: videoInfos
.description
,
157 duration: videoFile
.duration
,
161 const video
= db
.Video
.build(videoData
)
163 return callback(null, t
, author
, tagInstances
, video
)
166 // Set the videoname the same as the id
167 function renameVideoFile (t
, author
, tagInstances
, video
, callback
) {
168 const videoDir
= constants
.CONFIG
.STORAGE
.VIDEOS_DIR
169 const source
= path
.join(videoDir
, videoFile
.filename
)
170 const destination
= path
.join(videoDir
, video
.getVideoFilename())
172 fs
.rename(source
, destination
, function (err
) {
173 if (err
) return callback(err
)
175 // This is important in case if there is another attempt
176 videoFile
.filename
= video
.getVideoFilename()
177 return callback(null, t
, author
, tagInstances
, video
)
181 function insertVideoIntoDB (t
, author
, tagInstances
, video
, callback
) {
182 const options
= { transaction: t
}
184 // Add tags association
185 video
.save(options
).asCallback(function (err
, videoCreated
) {
186 if (err
) return callback(err
)
188 // Do not forget to add Author informations to the created video
189 videoCreated
.Author
= author
191 return callback(err
, t
, tagInstances
, videoCreated
)
195 function associateTagsToVideo (t
, tagInstances
, video
, callback
) {
196 const options
= { transaction: t
}
198 video
.setTags(tagInstances
, options
).asCallback(function (err
) {
199 video
.Tags
= tagInstances
201 return callback(err
, t
, video
)
205 function sendToFriends (t
, video
, callback
) {
206 video
.toAddRemoteJSON(function (err
, remoteVideo
) {
207 if (err
) return callback(err
)
209 // Now we'll add the video's meta data to our friends
210 friends
.addVideoToFriends(remoteVideo
, t
, function (err
) {
211 return callback(err
, t
)
216 databaseUtils
.commitTransaction
218 ], function andFinally (err
, t
) {
220 // This is just a debug because we will retry the insert
221 logger
.debug('Cannot insert the video.', { error: err
})
222 return databaseUtils
.rollbackTransaction(err
, t
, finalCallback
)
225 logger
.info('Video with name %s created.', videoInfos
.name
)
226 return finalCallback(null)
230 function updateVideoRetryWrapper (req
, res
, next
) {
232 arguments: [ req
, res
],
233 errorMessage: 'Cannot update the video with many retries.'
236 databaseUtils
.retryTransactionWrapper(updateVideo
, options
, function (err
) {
237 if (err
) return next(err
)
239 // TODO : include Location of the new video -> 201
240 return res
.type('json').status(204).end()
244 function updateVideo (req
, res
, finalCallback
) {
245 const videoInstance
= res
.locals
.video
246 const videoFieldsSave
= videoInstance
.toJSON()
247 const videoInfosToUpdate
= req
.body
251 databaseUtils
.startSerializableTransaction
,
253 function findOrCreateTags (t
, callback
) {
254 if (videoInfosToUpdate
.tags
) {
255 db
.Tag
.findOrCreateTags(videoInfosToUpdate
.tags
, t
, function (err
, tagInstances
) {
256 return callback(err
, t
, tagInstances
)
259 return callback(null, t
, null)
263 function updateVideoIntoDB (t
, tagInstances
, callback
) {
268 if (videoInfosToUpdate
.name
) videoInstance
.set('name', videoInfosToUpdate
.name
)
269 if (videoInfosToUpdate
.description
) videoInstance
.set('description', videoInfosToUpdate
.description
)
271 videoInstance
.save(options
).asCallback(function (err
) {
272 return callback(err
, t
, tagInstances
)
276 function associateTagsToVideo (t
, tagInstances
, callback
) {
278 const options
= { transaction: t
}
280 videoInstance
.setTags(tagInstances
, options
).asCallback(function (err
) {
281 videoInstance
.Tags
= tagInstances
283 return callback(err
, t
)
286 return callback(null, t
)
290 function sendToFriends (t
, callback
) {
291 const json
= videoInstance
.toUpdateRemoteJSON()
293 // Now we'll update the video's meta data to our friends
294 friends
.updateVideoToFriends(json
, t
, function (err
) {
295 return callback(err
, t
)
299 databaseUtils
.commitTransaction
301 ], function andFinally (err
, t
) {
303 logger
.debug('Cannot update the video.', { error: err
})
305 // Force fields we want to update
306 // If the transaction is retried, sequelize will think the object has not changed
307 // So it will skip the SQL request, even if the last one was ROLLBACKed!
308 Object
.keys(videoFieldsSave
).forEach(function (key
) {
309 const value
= videoFieldsSave
[key
]
310 videoInstance
.set(key
, value
)
313 return databaseUtils
.rollbackTransaction(err
, t
, finalCallback
)
316 logger
.info('Video with name %s updated.', videoInfosToUpdate
.name
)
317 return finalCallback(null)
321 function getVideo (req
, res
, next
) {
322 const videoInstance
= res
.locals
.video
323 res
.json(videoInstance
.toFormatedJSON())
326 function listVideos (req
, res
, next
) {
327 db
.Video
.listForApi(req
.query
.start
, req
.query
.count
, req
.query
.sort
, function (err
, videosList
, videosTotal
) {
328 if (err
) return next(err
)
330 res
.json(utils
.getFormatedObjects(videosList
, videosTotal
))
334 function removeVideo (req
, res
, next
) {
335 const videoInstance
= res
.locals
.video
337 videoInstance
.destroy().asCallback(function (err
) {
339 logger
.error('Errors when removed the video.', { error: err
})
343 return res
.type('json').status(204).end()
347 function searchVideos (req
, res
, next
) {
348 db
.Video
.searchAndPopulateAuthorAndPodAndTags(
349 req
.params
.value
, req
.query
.field
, req
.query
.start
, req
.query
.count
, req
.query
.sort
,
350 function (err
, videosList
, videosTotal
) {
351 if (err
) return next(err
)
353 res
.json(utils
.getFormatedObjects(videosList
, videosTotal
))
358 function listVideoAbuses (req
, res
, next
) {
359 db
.VideoAbuse
.listForApi(req
.query
.start
, req
.query
.count
, req
.query
.sort
, function (err
, abusesList
, abusesTotal
) {
360 if (err
) return next(err
)
362 res
.json(utils
.getFormatedObjects(abusesList
, abusesTotal
))
366 function reportVideoAbuseRetryWrapper (req
, res
, next
) {
368 arguments: [ req
, res
],
369 errorMessage: 'Cannot report abuse to the video with many retries.'
372 databaseUtils
.retryTransactionWrapper(reportVideoAbuse
, options
, function (err
) {
373 if (err
) return next(err
)
375 return res
.type('json').status(204).end()
379 function reportVideoAbuse (req
, res
, finalCallback
) {
380 const videoInstance
= res
.locals
.video
381 const reporterUsername
= res
.locals
.oauth
.token
.User
.username
385 reason: req
.body
.reason
,
386 videoId: videoInstance
.id
,
387 reporterPodId: null // This is our pod that reported this abuse
392 databaseUtils
.startSerializableTransaction
,
394 function createAbuse (t
, callback
) {
395 db
.VideoAbuse
.create(abuse
).asCallback(function (err
, abuse
) {
396 return callback(err
, t
, abuse
)
400 function sendToFriendsIfNeeded (t
, abuse
, callback
) {
401 // We send the information to the destination pod
402 if (videoInstance
.isOwned() === false) {
405 reportReason: abuse
.reason
,
406 videoRemoteId: videoInstance
.remoteId
409 friends
.reportAbuseVideoToFriend(reportData
, videoInstance
)
412 return callback(null, t
)
415 databaseUtils
.commitTransaction
417 ], function andFinally (err
, t
) {
419 logger
.debug('Cannot update the video.', { error: err
})
420 return databaseUtils
.rollbackTransaction(err
, t
, finalCallback
)
423 logger
.info('Abuse report for video %s created.', videoInstance
.name
)
424 return finalCallback(null)