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 utils
= require('../../helpers/utils')
25 const router
= express
.Router()
27 // multer configuration
28 const storage
= multer
.diskStorage({
29 destination: function (req
, file
, cb
) {
30 cb(null, constants
.CONFIG
.STORAGE
.VIDEOS_DIR
)
33 filename: function (req
, file
, cb
) {
35 if (file
.mimetype
=== 'video/webm') extension
= 'webm'
36 else if (file
.mimetype
=== 'video/mp4') extension
= 'mp4'
37 else if (file
.mimetype
=== 'video/ogg') extension
= 'ogv'
38 utils
.generateRandomString(16, function (err
, randomString
) {
39 const fieldname
= err
? undefined : randomString
40 cb(null, fieldname
+ '.' + extension
)
45 const reqFiles
= multer({ storage: storage
}).fields([{ name: 'videofile', maxCount: 1 }])
50 validatorsPagination
.pagination
,
51 validatorsSort
.videoAbusesSort
,
52 sort
.setVideoAbusesSort
,
53 pagination
.setPagination
,
56 router
.post('/:id/abuse',
58 validatorsVideos
.videoAbuseReport
,
59 reportVideoAbuseRetryWrapper
63 validatorsPagination
.pagination
,
64 validatorsSort
.videosSort
,
66 pagination
.setPagination
,
72 validatorsVideos
.videosUpdate
,
73 updateVideoRetryWrapper
78 validatorsVideos
.videosAdd
,
82 validatorsVideos
.videosGet
,
87 validatorsVideos
.videosRemove
,
90 router
.get('/search/:value',
91 validatorsVideos
.videosSearch
,
92 validatorsPagination
.pagination
,
93 validatorsSort
.videosSort
,
95 pagination
.setPagination
,
96 search
.setVideosSearch
,
100 // ---------------------------------------------------------------------------
102 module
.exports
= router
104 // ---------------------------------------------------------------------------
106 // Wrapper to video add that retry the function if there is a database error
107 // We need this because we run the transaction in SERIALIZABLE isolation that can fail
108 function addVideoRetryWrapper (req
, res
, next
) {
109 utils
.transactionRetryer(
110 function (callback
) {
111 return addVideo(req
, res
, req
.files
.videofile
[0], callback
)
115 logger
.error('Cannot insert the video with many retries.', { error: err
})
119 // TODO : include Location of the new video -> 201
120 return res
.type('json').status(204).end()
125 function addVideo (req
, res
, videoFile
, callback
) {
126 const videoInfos
= req
.body
130 function startTransaction (callbackWaterfall
) {
131 db
.sequelize
.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err
, t
) {
132 return callbackWaterfall(err
, t
)
136 function findOrCreateAuthor (t
, callbackWaterfall
) {
137 const user
= res
.locals
.oauth
.token
.User
139 const name
= user
.username
140 // null because it is OUR pod
142 const userId
= user
.id
144 db
.Author
.findOrCreateAuthor(name
, podId
, userId
, t
, function (err
, authorInstance
) {
145 return callbackWaterfall(err
, t
, authorInstance
)
149 function findOrCreateTags (t
, author
, callbackWaterfall
) {
150 const tags
= videoInfos
.tags
152 db
.Tag
.findOrCreateTags(tags
, t
, function (err
, tagInstances
) {
153 return callbackWaterfall(err
, t
, author
, tagInstances
)
157 function createVideoObject (t
, author
, tagInstances
, callbackWaterfall
) {
159 name: videoInfos
.name
,
161 extname: path
.extname(videoFile
.filename
),
162 description: videoInfos
.description
,
163 duration: videoFile
.duration
,
167 const video
= db
.Video
.build(videoData
)
169 return callbackWaterfall(null, t
, author
, tagInstances
, video
)
172 // Set the videoname the same as the id
173 function renameVideoFile (t
, author
, tagInstances
, video
, callbackWaterfall
) {
174 const videoDir
= constants
.CONFIG
.STORAGE
.VIDEOS_DIR
175 const source
= path
.join(videoDir
, videoFile
.filename
)
176 const destination
= path
.join(videoDir
, video
.getVideoFilename())
178 fs
.rename(source
, destination
, function (err
) {
179 if (err
) return callbackWaterfall(err
)
181 // This is important in case if there is another attempt
182 videoFile
.filename
= video
.getVideoFilename()
183 return callbackWaterfall(null, t
, author
, tagInstances
, video
)
187 function insertVideoIntoDB (t
, author
, tagInstances
, video
, callbackWaterfall
) {
188 const options
= { transaction: t
}
190 // Add tags association
191 video
.save(options
).asCallback(function (err
, videoCreated
) {
192 if (err
) return callbackWaterfall(err
)
194 // Do not forget to add Author informations to the created video
195 videoCreated
.Author
= author
197 return callbackWaterfall(err
, t
, tagInstances
, videoCreated
)
201 function associateTagsToVideo (t
, tagInstances
, video
, callbackWaterfall
) {
202 const options
= { transaction: t
}
204 video
.setTags(tagInstances
, options
).asCallback(function (err
) {
205 video
.Tags
= tagInstances
207 return callbackWaterfall(err
, t
, video
)
211 function sendToFriends (t
, video
, callbackWaterfall
) {
212 video
.toAddRemoteJSON(function (err
, remoteVideo
) {
213 if (err
) return callbackWaterfall(err
)
215 // Now we'll add the video's meta data to our friends
216 friends
.addVideoToFriends(remoteVideo
, t
, function (err
) {
217 return callbackWaterfall(err
, t
)
222 ], function andFinally (err
, t
) {
224 // This is just a debug because we will retry the insert
225 logger
.debug('Cannot insert the video.', { error: err
})
227 // Abort transaction?
233 // Commit transaction
234 t
.commit().asCallback(function (err
) {
235 if (err
) return callback(err
)
237 logger
.info('Video with name %s created.', videoInfos
.name
)
238 return callback(null)
243 function updateVideoRetryWrapper (req
, res
, next
) {
244 utils
.transactionRetryer(
245 function (callback
) {
246 return updateVideo(req
, res
, callback
)
250 logger
.error('Cannot update the video with many retries.', { error: err
})
254 // TODO : include Location of the new video -> 201
255 return res
.type('json').status(204).end()
260 function updateVideo (req
, res
, finalCallback
) {
261 const videoInstance
= res
.locals
.video
262 const videoInfosToUpdate
= req
.body
266 function startTransaction (callback
) {
267 db
.sequelize
.transaction().asCallback(function (err
, t
) {
268 return callback(err
, t
)
272 function findOrCreateTags (t
, callback
) {
273 if (videoInfosToUpdate
.tags
) {
274 db
.Tag
.findOrCreateTags(videoInfosToUpdate
.tags
, t
, function (err
, tagInstances
) {
275 return callback(err
, t
, tagInstances
)
278 return callback(null, t
, null)
282 function updateVideoIntoDB (t
, tagInstances
, callback
) {
283 const options
= { transaction: t
}
285 if (videoInfosToUpdate
.name
) videoInstance
.set('name', videoInfosToUpdate
.name
)
286 if (videoInfosToUpdate
.description
) videoInstance
.set('description', videoInfosToUpdate
.description
)
288 // Add tags association
289 videoInstance
.save(options
).asCallback(function (err
) {
290 return callback(err
, t
, tagInstances
)
294 function associateTagsToVideo (t
, tagInstances
, callback
) {
296 const options
= { transaction: t
}
298 videoInstance
.setTags(tagInstances
, options
).asCallback(function (err
) {
299 videoInstance
.Tags
= tagInstances
301 return callback(err
, t
)
304 return callback(null, t
)
308 function sendToFriends (t
, callback
) {
309 const json
= videoInstance
.toUpdateRemoteJSON()
311 // Now we'll update the video's meta data to our friends
312 friends
.updateVideoToFriends(json
, t
, function (err
) {
313 return callback(err
, t
)
317 ], function andFinally (err
, t
) {
319 logger
.debug('Cannot update the video.', { error: err
})
321 // Abort transaction?
324 return finalCallback(err
)
327 // Commit transaction
328 t
.commit().asCallback(function (err
) {
329 if (err
) return finalCallback(err
)
331 logger
.info('Video with name %s updated.', videoInfosToUpdate
.name
)
332 return finalCallback(null)
337 function getVideo (req
, res
, next
) {
338 const videoInstance
= res
.locals
.video
339 res
.json(videoInstance
.toFormatedJSON())
342 function listVideos (req
, res
, next
) {
343 db
.Video
.listForApi(req
.query
.start
, req
.query
.count
, req
.query
.sort
, function (err
, videosList
, videosTotal
) {
344 if (err
) return next(err
)
346 res
.json(utils
.getFormatedObjects(videosList
, videosTotal
))
350 function removeVideo (req
, res
, next
) {
351 const videoInstance
= res
.locals
.video
353 videoInstance
.destroy().asCallback(function (err
) {
355 logger
.error('Errors when removed the video.', { error: err
})
359 return res
.type('json').status(204).end()
363 function searchVideos (req
, res
, next
) {
364 db
.Video
.searchAndPopulateAuthorAndPodAndTags(
365 req
.params
.value
, req
.query
.field
, req
.query
.start
, req
.query
.count
, req
.query
.sort
,
366 function (err
, videosList
, videosTotal
) {
367 if (err
) return next(err
)
369 res
.json(utils
.getFormatedObjects(videosList
, videosTotal
))
374 function listVideoAbuses (req
, res
, next
) {
375 db
.VideoAbuse
.listForApi(req
.query
.start
, req
.query
.count
, req
.query
.sort
, function (err
, abusesList
, abusesTotal
) {
376 if (err
) return next(err
)
378 res
.json(utils
.getFormatedObjects(abusesList
, abusesTotal
))
382 function reportVideoAbuseRetryWrapper (req
, res
, next
) {
383 utils
.transactionRetryer(
384 function (callback
) {
385 return reportVideoAbuse(req
, res
, callback
)
389 logger
.error('Cannot report abuse to the video with many retries.', { error: err
})
393 return res
.type('json').status(204).end()
398 function reportVideoAbuse (req
, res
, finalCallback
) {
399 const videoInstance
= res
.locals
.video
400 const reporterUsername
= res
.locals
.oauth
.token
.User
.username
404 reason: req
.body
.reason
,
405 videoId: videoInstance
.id
,
406 reporterPodId: null // This is our pod that reported this abuse
411 function startTransaction (callback
) {
412 db
.sequelize
.transaction().asCallback(function (err
, t
) {
413 return callback(err
, t
)
417 function createAbuse (t
, callback
) {
418 db
.VideoAbuse
.create(abuse
).asCallback(function (err
, abuse
) {
419 return callback(err
, t
, abuse
)
423 function sendToFriendsIfNeeded (t
, abuse
, callback
) {
424 // We send the information to the destination pod
425 if (videoInstance
.isOwned() === false) {
428 reportReason: abuse
.reason
,
429 videoRemoteId: videoInstance
.remoteId
432 friends
.reportAbuseVideoToFriend(reportData
, videoInstance
)
435 return callback(null, t
)
438 ], function andFinally (err
, t
) {
440 logger
.debug('Cannot update the video.', { error: err
})
442 // Abort transaction?
445 return finalCallback(err
)
448 // Commit transaction
449 t
.commit().asCallback(function (err
) {
450 if (err
) return finalCallback(err
)
452 logger
.info('Abuse report for video %s created.', videoInstance
.name
)
453 return finalCallback(null)