]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/videos.js
Client: little refractoring
[github/Chocobozzz/PeerTube.git] / server / controllers / api / videos.js
CommitLineData
9f10b292
C
1'use strict'
2
f0f5567b 3const express = require('express')
558d7c23 4const fs = require('fs')
f0f5567b 5const multer = require('multer')
558d7c23 6const path = require('path')
1a42c9e2 7const waterfall = require('async/waterfall')
f0f5567b 8
f253b1c1 9const constants = require('../../initializers/constants')
feb4bdfd 10const db = require('../../initializers/database')
f253b1c1
C
11const logger = require('../../helpers/logger')
12const friends = require('../../lib/friends')
13const middlewares = require('../../middlewares')
55fa55a9 14const admin = middlewares.admin
69b0a27c 15const oAuth = middlewares.oauth
fbf1134e 16const pagination = middlewares.pagination
fc51fde0
C
17const validators = middlewares.validators
18const validatorsPagination = validators.pagination
19const validatorsSort = validators.sort
20const validatorsVideos = validators.videos
46246b5f 21const search = middlewares.search
a877d5ac 22const sort = middlewares.sort
4df023f2 23const databaseUtils = require('../../helpers/database-utils')
f253b1c1 24const utils = require('../../helpers/utils')
f0f5567b
C
25
26const router = express.Router()
9f10b292
C
27
28// multer configuration
f0f5567b 29const storage = multer.diskStorage({
9f10b292 30 destination: function (req, file, cb) {
b3d92510 31 cb(null, constants.CONFIG.STORAGE.VIDEOS_DIR)
9f10b292
C
32 },
33
34 filename: function (req, file, cb) {
f0f5567b 35 let extension = ''
9f10b292
C
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'
bc503c2a
C
39 utils.generateRandomString(16, function (err, randomString) {
40 const fieldname = err ? undefined : randomString
9f10b292
C
41 cb(null, fieldname + '.' + extension)
42 })
43 }
44})
45
8c9c1942 46const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }])
8c308c2b 47
6e07c3de 48router.get('/categories', listVideoCategories)
6f0c39e2 49router.get('/licences', listVideoLicences)
3092476e 50router.get('/languages', listVideoLanguages)
6e07c3de 51
55fa55a9
C
52router.get('/abuse',
53 oAuth.authenticate,
54 admin.ensureIsAdmin,
55 validatorsPagination.pagination,
56 validatorsSort.videoAbusesSort,
57 sort.setVideoAbusesSort,
58 pagination.setPagination,
59 listVideoAbuses
60)
61router.post('/:id/abuse',
62 oAuth.authenticate,
63 validatorsVideos.videoAbuseReport,
bf4ff8fe 64 reportVideoAbuseRetryWrapper
55fa55a9
C
65)
66
d38b8281
C
67router.put('/:id/rate',
68 oAuth.authenticate,
69 validatorsVideos.videoRate,
70 rateVideoRetryWrapper
71)
72
fbf1134e 73router.get('/',
fc51fde0
C
74 validatorsPagination.pagination,
75 validatorsSort.videosSort,
a877d5ac 76 sort.setVideosSort,
fbf1134e
C
77 pagination.setPagination,
78 listVideos
79)
7b1f49de
C
80router.put('/:id',
81 oAuth.authenticate,
82 reqFiles,
83 validatorsVideos.videosUpdate,
ed04d94f 84 updateVideoRetryWrapper
7b1f49de 85)
fbf1134e 86router.post('/',
69b0a27c 87 oAuth.authenticate,
fbf1134e 88 reqFiles,
fc51fde0 89 validatorsVideos.videosAdd,
ed04d94f 90 addVideoRetryWrapper
fbf1134e
C
91)
92router.get('/:id',
fc51fde0 93 validatorsVideos.videosGet,
68ce3ae0 94 getVideo
fbf1134e 95)
198b205c 96
fbf1134e 97router.delete('/:id',
69b0a27c 98 oAuth.authenticate,
fc51fde0 99 validatorsVideos.videosRemove,
fbf1134e
C
100 removeVideo
101)
198b205c 102
46246b5f 103router.get('/search/:value',
fc51fde0
C
104 validatorsVideos.videosSearch,
105 validatorsPagination.pagination,
106 validatorsSort.videosSort,
a877d5ac 107 sort.setVideosSort,
fbf1134e 108 pagination.setPagination,
46246b5f 109 search.setVideosSearch,
fbf1134e
C
110 searchVideos
111)
8c308c2b 112
198b205c
GS
113router.post('/:id/blacklist',
114 oAuth.authenticate,
115 admin.ensureIsAdmin,
116 validatorsVideos.videosBlacklist,
117 addVideoToBlacklist
118)
119
9f10b292 120// ---------------------------------------------------------------------------
c45f7f84 121
9f10b292 122module.exports = router
c45f7f84 123
9f10b292 124// ---------------------------------------------------------------------------
c45f7f84 125
6e07c3de
C
126function listVideoCategories (req, res, next) {
127 res.json(constants.VIDEO_CATEGORIES)
128}
129
6f0c39e2
C
130function listVideoLicences (req, res, next) {
131 res.json(constants.VIDEO_LICENCES)
132}
133
3092476e
C
134function listVideoLanguages (req, res, next) {
135 res.json(constants.VIDEO_LANGUAGES)
136}
137
d38b8281
C
138function rateVideoRetryWrapper (req, res, next) {
139 const options = {
140 arguments: [ req, res ],
141 errorMessage: 'Cannot update the user video rate.'
142 }
143
144 databaseUtils.retryTransactionWrapper(rateVideo, options, function (err) {
145 if (err) return next(err)
146
147 return res.type('json').status(204).end()
148 })
149}
150
151function rateVideo (req, res, finalCallback) {
152 const rateType = req.body.rating
153 const videoInstance = res.locals.video
154 const userInstance = res.locals.oauth.token.User
155
156 waterfall([
157 databaseUtils.startSerializableTransaction,
158
159 function findPreviousRate (t, callback) {
160 db.UserVideoRate.load(userInstance.id, videoInstance.id, t, function (err, previousRate) {
161 return callback(err, t, previousRate)
162 })
163 },
164
165 function insertUserRateIntoDB (t, previousRate, callback) {
166 const options = { transaction: t }
167
168 let likesToIncrement = 0
169 let dislikesToIncrement = 0
170
171 if (rateType === constants.VIDEO_RATE_TYPES.LIKE) likesToIncrement++
172 else if (rateType === constants.VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
173
174 // There was a previous rate, update it
175 if (previousRate) {
176 // We will remove the previous rate, so we will need to remove it from the video attribute
177 if (previousRate.type === constants.VIDEO_RATE_TYPES.LIKE) likesToIncrement--
178 else if (previousRate.type === constants.VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
179
180 previousRate.type = rateType
181
182 previousRate.save(options).asCallback(function (err) {
183 return callback(err, t, likesToIncrement, dislikesToIncrement)
184 })
185 } else { // There was not a previous rate, insert a new one
186 const query = {
187 userId: userInstance.id,
188 videoId: videoInstance.id,
189 type: rateType
190 }
191
192 db.UserVideoRate.create(query, options).asCallback(function (err) {
193 return callback(err, t, likesToIncrement, dislikesToIncrement)
194 })
195 }
196 },
197
198 function updateVideoAttributeDB (t, likesToIncrement, dislikesToIncrement, callback) {
199 const options = { transaction: t }
200 const incrementQuery = {
201 likes: likesToIncrement,
202 dislikes: dislikesToIncrement
203 }
204
205 // Even if we do not own the video we increment the attributes
206 // It is usefull for the user to have a feedback
207 videoInstance.increment(incrementQuery, options).asCallback(function (err) {
208 return callback(err, t, likesToIncrement, dislikesToIncrement)
209 })
210 },
211
212 function sendEventsToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) {
213 // No need for an event type, we own the video
214 if (videoInstance.isOwned()) return callback(null, t, likesToIncrement, dislikesToIncrement)
215
216 const eventsParams = []
217
218 if (likesToIncrement !== 0) {
219 eventsParams.push({
220 videoId: videoInstance.id,
221 type: constants.REQUEST_VIDEO_EVENT_TYPES.LIKES,
222 count: likesToIncrement
223 })
224 }
225
226 if (dislikesToIncrement !== 0) {
227 eventsParams.push({
228 videoId: videoInstance.id,
229 type: constants.REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
230 count: dislikesToIncrement
231 })
232 }
233
234 friends.addEventsToRemoteVideo(eventsParams, t, function (err) {
235 return callback(err, t, likesToIncrement, dislikesToIncrement)
236 })
237 },
238
239 function sendQaduToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) {
240 // We do not own the video, there is no need to send a quick and dirty update to friends
241 // Our rate was already sent by the addEvent function
242 if (videoInstance.isOwned() === false) return callback(null, t)
243
244 const qadusParams = []
245
246 if (likesToIncrement !== 0) {
247 qadusParams.push({
248 videoId: videoInstance.id,
249 type: constants.REQUEST_VIDEO_QADU_TYPES.LIKES
250 })
251 }
252
253 if (dislikesToIncrement !== 0) {
254 qadusParams.push({
255 videoId: videoInstance.id,
256 type: constants.REQUEST_VIDEO_QADU_TYPES.DISLIKES
257 })
258 }
259
260 friends.quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) {
261 return callback(err, t)
262 })
263 },
264
265 databaseUtils.commitTransaction
266
267 ], function (err, t) {
268 if (err) {
269 // This is just a debug because we will retry the insert
270 logger.debug('Cannot add the user video rate.', { error: err })
271 return databaseUtils.rollbackTransaction(err, t, finalCallback)
272 }
273
274 logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username)
275 return finalCallback(null)
276 })
277}
278
ed04d94f
C
279// Wrapper to video add that retry the function if there is a database error
280// We need this because we run the transaction in SERIALIZABLE isolation that can fail
281function addVideoRetryWrapper (req, res, next) {
d6a5b018
C
282 const options = {
283 arguments: [ req, res, req.files.videofile[0] ],
284 errorMessage: 'Cannot insert the video with many retries.'
285 }
ed04d94f 286
4df023f2 287 databaseUtils.retryTransactionWrapper(addVideo, options, function (err) {
d6a5b018
C
288 if (err) return next(err)
289
290 // TODO : include Location of the new video -> 201
291 return res.type('json').status(204).end()
292 })
ed04d94f
C
293}
294
4145c1c6 295function addVideo (req, res, videoFile, finalCallback) {
bc503c2a 296 const videoInfos = req.body
9f10b292 297
1a42c9e2 298 waterfall([
807df9e6 299
4df023f2 300 databaseUtils.startSerializableTransaction,
7920c273 301
4145c1c6 302 function findOrCreateAuthor (t, callback) {
4712081f 303 const user = res.locals.oauth.token.User
feb4bdfd 304
4ff0d862
C
305 const name = user.username
306 // null because it is OUR pod
307 const podId = null
308 const userId = user.id
4712081f 309
4ff0d862 310 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
4145c1c6 311 return callback(err, t, authorInstance)
7920c273
C
312 })
313 },
314
4145c1c6 315 function findOrCreateTags (t, author, callback) {
7920c273 316 const tags = videoInfos.tags
4ff0d862
C
317
318 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
4145c1c6 319 return callback(err, t, author, tagInstances)
feb4bdfd
C
320 })
321 },
322
4145c1c6 323 function createVideoObject (t, author, tagInstances, callback) {
807df9e6
C
324 const videoData = {
325 name: videoInfos.name,
558d7c23
C
326 remoteId: null,
327 extname: path.extname(videoFile.filename),
6e07c3de 328 category: videoInfos.category,
6f0c39e2 329 licence: videoInfos.licence,
3092476e 330 language: videoInfos.language,
31b59b47 331 nsfw: videoInfos.nsfw,
807df9e6 332 description: videoInfos.description,
67100f1f 333 duration: videoFile.duration,
d38b8281 334 authorId: author.id
807df9e6
C
335 }
336
feb4bdfd 337 const video = db.Video.build(videoData)
558d7c23 338
4145c1c6 339 return callback(null, t, author, tagInstances, video)
558d7c23
C
340 },
341
feb4bdfd 342 // Set the videoname the same as the id
4145c1c6 343 function renameVideoFile (t, author, tagInstances, video, callback) {
558d7c23
C
344 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR
345 const source = path.join(videoDir, videoFile.filename)
f285faa0 346 const destination = path.join(videoDir, video.getVideoFilename())
558d7c23
C
347
348 fs.rename(source, destination, function (err) {
4145c1c6 349 if (err) return callback(err)
ed04d94f
C
350
351 // This is important in case if there is another attempt
352 videoFile.filename = video.getVideoFilename()
4145c1c6 353 return callback(null, t, author, tagInstances, video)
558d7c23
C
354 })
355 },
356
4145c1c6 357 function insertVideoIntoDB (t, author, tagInstances, video, callback) {
7920c273
C
358 const options = { transaction: t }
359
360 // Add tags association
361 video.save(options).asCallback(function (err, videoCreated) {
4145c1c6 362 if (err) return callback(err)
7920c273 363
feb4bdfd
C
364 // Do not forget to add Author informations to the created video
365 videoCreated.Author = author
366
4145c1c6 367 return callback(err, t, tagInstances, videoCreated)
3a8a8b51 368 })
807df9e6
C
369 },
370
4145c1c6 371 function associateTagsToVideo (t, tagInstances, video, callback) {
7920c273
C
372 const options = { transaction: t }
373
374 video.setTags(tagInstances, options).asCallback(function (err) {
375 video.Tags = tagInstances
376
4145c1c6 377 return callback(err, t, video)
7920c273
C
378 })
379 },
380
4145c1c6 381 function sendToFriends (t, video, callback) {
62326afb
C
382 // Let transcoding job send the video to friends because the videofile extension might change
383 if (constants.CONFIG.TRANSCODING.ENABLED === true) return callback(null, t)
384
7b1f49de 385 video.toAddRemoteJSON(function (err, remoteVideo) {
4145c1c6 386 if (err) return callback(err)
807df9e6 387
528a9efa 388 // Now we'll add the video's meta data to our friends
ed04d94f 389 friends.addVideoToFriends(remoteVideo, t, function (err) {
4145c1c6 390 return callback(err, t)
ed04d94f 391 })
528a9efa 392 })
4145c1c6
C
393 },
394
395 databaseUtils.commitTransaction
807df9e6 396
7b1f49de
C
397 ], function andFinally (err, t) {
398 if (err) {
ed04d94f
C
399 // This is just a debug because we will retry the insert
400 logger.debug('Cannot insert the video.', { error: err })
4145c1c6 401 return databaseUtils.rollbackTransaction(err, t, finalCallback)
7b1f49de
C
402 }
403
4145c1c6
C
404 logger.info('Video with name %s created.', videoInfos.name)
405 return finalCallback(null)
7b1f49de
C
406 })
407}
408
ed04d94f 409function updateVideoRetryWrapper (req, res, next) {
d6a5b018
C
410 const options = {
411 arguments: [ req, res ],
412 errorMessage: 'Cannot update the video with many retries.'
413 }
ed04d94f 414
4df023f2 415 databaseUtils.retryTransactionWrapper(updateVideo, options, function (err) {
d6a5b018
C
416 if (err) return next(err)
417
418 // TODO : include Location of the new video -> 201
419 return res.type('json').status(204).end()
420 })
ed04d94f
C
421}
422
423function updateVideo (req, res, finalCallback) {
818f7987 424 const videoInstance = res.locals.video
7f4e7c36 425 const videoFieldsSave = videoInstance.toJSON()
7b1f49de
C
426 const videoInfosToUpdate = req.body
427
428 waterfall([
429
4df023f2 430 databaseUtils.startSerializableTransaction,
7b1f49de
C
431
432 function findOrCreateTags (t, callback) {
433 if (videoInfosToUpdate.tags) {
434 db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) {
435 return callback(err, t, tagInstances)
436 })
437 } else {
438 return callback(null, t, null)
439 }
440 },
441
442 function updateVideoIntoDB (t, tagInstances, callback) {
7f4e7c36
C
443 const options = {
444 transaction: t
445 }
7b1f49de 446
c24ac1c1
C
447 if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name)
448 if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category)
449 if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence)
450 if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language)
451 if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw)
452 if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description)
7b1f49de 453
7b1f49de 454 videoInstance.save(options).asCallback(function (err) {
7b1f49de
C
455 return callback(err, t, tagInstances)
456 })
457 },
458
459 function associateTagsToVideo (t, tagInstances, callback) {
460 if (tagInstances) {
461 const options = { transaction: t }
462
463 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
464 videoInstance.Tags = tagInstances
465
466 return callback(err, t)
467 })
468 } else {
469 return callback(null, t)
470 }
471 },
472
473 function sendToFriends (t, callback) {
474 const json = videoInstance.toUpdateRemoteJSON()
475
476 // Now we'll update the video's meta data to our friends
ed04d94f
C
477 friends.updateVideoToFriends(json, t, function (err) {
478 return callback(err, t)
479 })
4145c1c6
C
480 },
481
482 databaseUtils.commitTransaction
7b1f49de 483
7920c273 484 ], function andFinally (err, t) {
807df9e6 485 if (err) {
ed04d94f 486 logger.debug('Cannot update the video.', { error: err })
7920c273 487
7f4e7c36
C
488 // Force fields we want to update
489 // If the transaction is retried, sequelize will think the object has not changed
490 // So it will skip the SQL request, even if the last one was ROLLBACKed!
491 Object.keys(videoFieldsSave).forEach(function (key) {
492 const value = videoFieldsSave[key]
493 videoInstance.set(key, value)
494 })
495
4145c1c6 496 return databaseUtils.rollbackTransaction(err, t, finalCallback)
807df9e6
C
497 }
498
4145c1c6
C
499 logger.info('Video with name %s updated.', videoInfosToUpdate.name)
500 return finalCallback(null)
9f10b292
C
501 })
502}
8c308c2b 503
68ce3ae0 504function getVideo (req, res, next) {
818f7987 505 const videoInstance = res.locals.video
9e167724
C
506
507 if (videoInstance.isOwned()) {
508 // The increment is done directly in the database, not using the instance value
509 videoInstance.increment('views').asCallback(function (err) {
510 if (err) {
511 logger.error('Cannot add view to video %d.', videoInstance.id)
512 return
513 }
514
515 // FIXME: make a real view system
516 // For example, only add a view when a user watch a video during 30s etc
d38b8281
C
517 const qaduParams = {
518 videoId: videoInstance.id,
519 type: constants.REQUEST_VIDEO_QADU_TYPES.VIEWS
520 }
521 friends.quickAndDirtyUpdateVideoToFriends(qaduParams)
9e167724 522 })
e4c87ec2
C
523 } else {
524 // Just send the event to our friends
d38b8281
C
525 const eventParams = {
526 videoId: videoInstance.id,
527 type: constants.REQUEST_VIDEO_EVENT_TYPES.VIEWS
528 }
529 friends.addEventToRemoteVideo(eventParams)
9e167724
C
530 }
531
532 // Do not wait the view system
818f7987 533 res.json(videoInstance.toFormatedJSON())
9f10b292 534}
8c308c2b 535
9f10b292 536function listVideos (req, res, next) {
feb4bdfd 537 db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
9f10b292 538 if (err) return next(err)
c45f7f84 539
55fa55a9 540 res.json(utils.getFormatedObjects(videosList, videosTotal))
9f10b292
C
541 })
542}
c45f7f84 543
9f10b292 544function removeVideo (req, res, next) {
818f7987 545 const videoInstance = res.locals.video
8c308c2b 546
818f7987 547 videoInstance.destroy().asCallback(function (err) {
807df9e6
C
548 if (err) {
549 logger.error('Errors when removed the video.', { error: err })
550 return next(err)
551 }
552
553 return res.type('json').status(204).end()
9f10b292
C
554 })
555}
8c308c2b 556
9f10b292 557function searchVideos (req, res, next) {
7920c273
C
558 db.Video.searchAndPopulateAuthorAndPodAndTags(
559 req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
560 function (err, videosList, videosTotal) {
561 if (err) return next(err)
8c308c2b 562
55fa55a9 563 res.json(utils.getFormatedObjects(videosList, videosTotal))
7920c273
C
564 }
565 )
9f10b292 566}
c173e565 567
55fa55a9
C
568function listVideoAbuses (req, res, next) {
569 db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) {
570 if (err) return next(err)
2df82d42 571
55fa55a9 572 res.json(utils.getFormatedObjects(abusesList, abusesTotal))
2df82d42 573 })
55fa55a9 574}
2df82d42 575
bf4ff8fe 576function reportVideoAbuseRetryWrapper (req, res, next) {
4df023f2
C
577 const options = {
578 arguments: [ req, res ],
579 errorMessage: 'Cannot report abuse to the video with many retries.'
580 }
bf4ff8fe 581
4df023f2
C
582 databaseUtils.retryTransactionWrapper(reportVideoAbuse, options, function (err) {
583 if (err) return next(err)
584
585 return res.type('json').status(204).end()
586 })
bf4ff8fe
C
587}
588
589function reportVideoAbuse (req, res, finalCallback) {
55fa55a9
C
590 const videoInstance = res.locals.video
591 const reporterUsername = res.locals.oauth.token.User.username
592
593 const abuse = {
594 reporterUsername,
595 reason: req.body.reason,
596 videoId: videoInstance.id,
597 reporterPodId: null // This is our pod that reported this abuse
68ce3ae0 598 }
55fa55a9 599
bf4ff8fe
C
600 waterfall([
601
da691c46 602 databaseUtils.startSerializableTransaction,
bf4ff8fe
C
603
604 function createAbuse (t, callback) {
605 db.VideoAbuse.create(abuse).asCallback(function (err, abuse) {
606 return callback(err, t, abuse)
607 })
608 },
609
610 function sendToFriendsIfNeeded (t, abuse, callback) {
611 // We send the information to the destination pod
612 if (videoInstance.isOwned() === false) {
613 const reportData = {
614 reporterUsername,
615 reportReason: abuse.reason,
616 videoRemoteId: videoInstance.remoteId
617 }
55fa55a9 618
bf4ff8fe 619 friends.reportAbuseVideoToFriend(reportData, videoInstance)
55fa55a9
C
620 }
621
bf4ff8fe 622 return callback(null, t)
4145c1c6
C
623 },
624
625 databaseUtils.commitTransaction
55fa55a9 626
bf4ff8fe
C
627 ], function andFinally (err, t) {
628 if (err) {
629 logger.debug('Cannot update the video.', { error: err })
4145c1c6 630 return databaseUtils.rollbackTransaction(err, t, finalCallback)
bf4ff8fe
C
631 }
632
4145c1c6
C
633 logger.info('Abuse report for video %s created.', videoInstance.name)
634 return finalCallback(null)
55fa55a9 635 })
2df82d42 636}
198b205c
GS
637
638function addVideoToBlacklist (req, res, next) {
639 const videoInstance = res.locals.video
640
ab683a8e 641 const toCreate = {
198b205c 642 videoId: videoInstance.id
ab683a8e
C
643 }
644
645 db.BlacklistedVideo.create(toCreate).asCallback(function (err) {
198b205c
GS
646 if (err) {
647 logger.error('Errors when blacklisting video ', { error: err })
648 return next(err)
649 }
650
651 return res.type('json').status(204).end()
652 })
653}