1 import * as express from 'express'
2 import 'express-validator'
3 import { body, param, query } from 'express-validator/check'
4 import { UserRight, VideoPrivacy } from '../../../shared'
13 } from '../../helpers/custom-validators/misc'
15 isScheduleVideoUpdatePrivacyValid,
16 isVideoAbuseReasonValid,
18 isVideoChannelOfAccountExist,
19 isVideoDescriptionValid,
27 isVideoRatingTypeValid,
30 } from '../../helpers/custom-validators/videos'
31 import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
32 import { logger } from '../../helpers/logger'
33 import { CONSTRAINTS_FIELDS } from '../../initializers'
34 import { UserModel } from '../../models/account/user'
35 import { VideoModel } from '../../models/video/video'
36 import { VideoShareModel } from '../../models/video/video-share'
37 import { authenticate } from '../oauth'
38 import { areValidationErrors } from './utils'
40 const videosAddValidator = [
41 body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
42 'This file is not supported. Please, make sure it is of the following type : '
43 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
45 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
46 'This thumbnail file is not supported. Please, make sure it is of the following type : '
47 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
49 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
50 'This preview file is not supported. Please, make sure it is of the following type : '
51 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
53 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
56 .customSanitizer(toIntOrNull)
57 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
60 .customSanitizer(toIntOrNull)
61 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
64 .customSanitizer(toValueOrNull)
65 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
69 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
70 body('waitTranscoding')
73 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
76 .customSanitizer(toValueOrNull)
77 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
80 .customSanitizer(toValueOrNull)
81 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
84 .customSanitizer(toValueOrNull)
85 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
86 body('commentsEnabled')
89 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
93 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
96 .custom(isIdValid).withMessage('Should have correct video channel id'),
97 body('scheduleUpdate')
99 .customSanitizer(toValueOrNull),
100 body('scheduleUpdate.updateAt')
102 .custom(isDateValid).withMessage('Should have a valid schedule update date'),
103 body('scheduleUpdate.privacy')
106 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy'),
108 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
109 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
111 if (areValidationErrors(req, res)) return
112 if (areErrorsInVideoImageFiles(req, res)) return
113 if (areErrorsInScheduleUpdate(req, res)) return
115 const videoFile: Express.Multer.File = req.files['videofile'][0]
116 const user = res.locals.oauth.token.User
118 if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return
120 const isAble = await user.isAbleToUploadVideo(videoFile)
121 if (isAble === false) {
123 .json({ error: 'The user video quota is exceeded with this video.' })
132 duration = await getDurationFromVideoFile(videoFile.path)
134 logger.error('Invalid input file in videosAddValidator.', { err })
136 .json({ error: 'Invalid input file.' })
142 videoFile['duration'] = duration
148 const videosUpdateValidator = [
149 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
150 body('thumbnailfile').custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
151 'This thumbnail file is not supported. Please, make sure it is of the following type : '
152 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
154 body('previewfile').custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
155 'This preview file is not supported. Please, make sure it is of the following type : '
156 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
160 .custom(isVideoNameValid).withMessage('Should have a valid name'),
163 .customSanitizer(toIntOrNull)
164 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
167 .customSanitizer(toIntOrNull)
168 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
171 .customSanitizer(toValueOrNull)
172 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
176 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
177 body('waitTranscoding')
180 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
184 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
187 .customSanitizer(toValueOrNull)
188 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
191 .customSanitizer(toValueOrNull)
192 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
195 .customSanitizer(toValueOrNull)
196 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
197 body('commentsEnabled')
200 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
204 .custom(isIdValid).withMessage('Should have correct video channel id'),
205 body('scheduleUpdate')
207 .customSanitizer(toValueOrNull),
208 body('scheduleUpdate.updateAt')
210 .custom(isDateValid).withMessage('Should have a valid schedule update date'),
211 body('scheduleUpdate.privacy')
214 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy'),
216 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
217 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
219 if (areValidationErrors(req, res)) return
220 if (areErrorsInVideoImageFiles(req, res)) return
221 if (areErrorsInScheduleUpdate(req, res)) return
222 if (!await isVideoExist(req.params.id, res)) return
224 const video = res.locals.video
226 // Check if the user who did the request is able to update the video
227 const user = res.locals.oauth.token.User
228 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
230 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
231 return res.status(409)
232 .json({ error: 'Cannot set "private" a video that was not private.' })
236 if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return
242 const videosGetValidator = [
243 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
245 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
246 logger.debug('Checking videosGet parameters', { parameters: req.params })
248 if (areValidationErrors(req, res)) return
249 if (!await isVideoExist(req.params.id, res)) return
251 const video = res.locals.video
253 // Video is public, anyone can access it
254 if (video.privacy === VideoPrivacy.PUBLIC) return next()
256 // Video is unlisted, check we used the uuid to fetch it
257 if (video.privacy === VideoPrivacy.UNLISTED) {
258 if (isUUIDValid(req.params.id)) return next()
260 // Don't leak this unlisted video
261 return res.status(404).end()
264 // Video is private, check the user
265 authenticate(req, res, () => {
266 if (video.VideoChannel.Account.userId !== res.locals.oauth.token.User.id) {
267 return res.status(403)
268 .json({ error: 'Cannot get this private video of another user' })
277 const videosRemoveValidator = [
278 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
280 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
281 logger.debug('Checking videosRemove parameters', { parameters: req.params })
283 if (areValidationErrors(req, res)) return
284 if (!await isVideoExist(req.params.id, res)) return
286 // Check if the user who did the request is able to delete the video
287 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
293 const videosSearchValidator = [
294 query('search').not().isEmpty().withMessage('Should have a valid search'),
296 (req: express.Request, res: express.Response, next: express.NextFunction) => {
297 logger.debug('Checking videosSearch parameters', { parameters: req.params })
299 if (areValidationErrors(req, res)) return
305 const videoAbuseReportValidator = [
306 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
307 body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
309 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
310 logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
312 if (areValidationErrors(req, res)) return
313 if (!await isVideoExist(req.params.id, res)) return
319 const videoRateValidator = [
320 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
321 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
323 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
324 logger.debug('Checking videoRate parameters', { parameters: req.body })
326 if (areValidationErrors(req, res)) return
327 if (!await isVideoExist(req.params.id, res)) return
333 const videosShareValidator = [
334 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
335 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
337 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
338 logger.debug('Checking videoShare parameters', { parameters: req.params })
340 if (areValidationErrors(req, res)) return
341 if (!await isVideoExist(req.params.id, res)) return
343 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
345 return res.status(404)
349 res.locals.videoShare = share
354 // ---------------------------------------------------------------------------
358 videosUpdateValidator,
360 videosRemoveValidator,
361 videosSearchValidator,
362 videosShareValidator,
364 videoAbuseReportValidator,
369 // ---------------------------------------------------------------------------
371 function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: express.Response) {
372 // Retrieve the user who did the request
373 if (video.isOwned() === false) {
375 .json({ error: 'Cannot manage a video of another server.' })
380 // Check if the user can delete the video
381 // The user can delete it if he has the right
382 // Or if s/he is the video's account
383 const account = video.VideoChannel.Account
384 if (user.hasRight(right) === false && account.userId !== user.id) {
386 .json({ error: 'Cannot manage a video of another user.' })
394 function areErrorsInVideoImageFiles (req: express.Request, res: express.Response) {
395 // Files are optional
396 if (!req.files) return false
398 for (const imageField of [ 'thumbnail', 'preview' ]) {
399 if (!req.files[ imageField ]) continue
401 const imageFile = req.files[ imageField ][ 0 ] as Express.Multer.File
402 if (imageFile.size > CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max) {
404 .json({ error: `The size of the ${imageField} is too big (>${CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max}).` })
413 function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
414 if (req.body.scheduleUpdate) {
415 if (!req.body.scheduleUpdate.updateAt) {
417 .json({ error: 'Schedule update at is mandatory.' })