]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/videos/videos.ts
Move engines outside package.json
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / videos.ts
CommitLineData
69818c93 1import * as express from 'express'
c8861d5d 2import { body, param, query, ValidationChain } from 'express-validator'
fb719404 3import { isAbleToUploadVideo } from '@server/lib/user'
e6abf95e 4import { getServerActor } from '@server/models/application/application'
ba5a8d89 5import { ExpressPromiseHandler } from '@server/types/express'
031ea8ef 6import { MVideoWithRights } from '@server/types/models'
e6abf95e 7import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
ba5a8d89 8import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
e6abf95e 9import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
b60e5f38 10import {
37024082 11 exists,
2baea0c7
C
12 isBooleanValid,
13 isDateValid,
f2eb23cd 14 isFileFieldValid,
2baea0c7
C
15 isIdOrUUIDValid,
16 isIdValid,
17 isUUIDValid,
1cd3facc 18 toArray,
c8861d5d 19 toBooleanOrNull,
2baea0c7
C
20 toIntOrNull,
21 toValueOrNull
6e46de09 22} from '../../../helpers/custom-validators/misc'
1fd61899 23import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
e6abf95e 24import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
2baea0c7
C
25import {
26 isScheduleVideoUpdatePrivacyValid,
ac81d1a0
C
27 isVideoCategoryValid,
28 isVideoDescriptionValid,
f2eb23cd
RK
29 isVideoFileMimeTypeValid,
30 isVideoFileSizeValid,
1cd3facc 31 isVideoFilterValid,
ac81d1a0
C
32 isVideoImage,
33 isVideoLanguageValid,
34 isVideoLicenceValid,
35 isVideoNameValid,
fd8710b8 36 isVideoOriginallyPublishedAtValid,
ac81d1a0 37 isVideoPrivacyValid,
360329cc 38 isVideoSupportValid,
4157cdb1 39 isVideoTagsValid
6e46de09 40} from '../../../helpers/custom-validators/videos'
e6abf95e 41import { cleanUpReqFiles } from '../../../helpers/express-utils'
daf6e480 42import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils'
6e46de09 43import { logger } from '../../../helpers/logger'
8319d6ae
RK
44import {
45 checkUserCanManageVideo,
46 doesVideoChannelOfAccountExist,
47 doesVideoExist,
48 doesVideoFileOfVideoExist
49} from '../../../helpers/middlewares'
0283eaac 50import { getVideoWithAttributes } from '../../../helpers/video'
e6abf95e
C
51import { CONFIG } from '../../../initializers/config'
52import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
53import { isLocalVideoAccepted } from '../../../lib/moderation'
54import { Hooks } from '../../../lib/plugins/hooks'
55import { AccountModel } from '../../../models/account/account'
56import { VideoModel } from '../../../models/video/video'
f43db2f4 57import { authenticatePromiseIfNeeded } from '../../auth'
e6abf95e 58import { areValidationErrors } from '../utils'
34ca3b52 59
418d092a 60const videosAddValidator = getCommonVideoEditAttributes().concat([
0c237b19 61 body('videofile')
f2eb23cd
RK
62 .custom((value, { req }) => isFileFieldValid(req.files, 'videofile'))
63 .withMessage('Should have a file'),
64 body('name')
0221f8c9 65 .trim()
f2eb23cd
RK
66 .custom(isVideoNameValid)
67 .withMessage('Should have a valid name'),
0f320037 68 body('channelId')
c8861d5d 69 .customSanitizer(toIntOrNull)
2baea0c7 70 .custom(isIdValid).withMessage('Should have correct video channel id'),
b60e5f38 71
a2431b7d 72 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
73 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
74
cf7a61b5
C
75 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
76 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
a2431b7d 77
b4055e1c 78 const videoFile: Express.Multer.File & { duration?: number } = req.files['videofile'][0]
a2431b7d 79 const user = res.locals.oauth.token.User
b60e5f38 80
0f6acda1 81 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
a2431b7d 82
f2eb23cd
RK
83 if (!isVideoFileMimeTypeValid(req.files)) {
84 res.status(HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415)
85 .json({
86 error: 'This file is not supported. Please, make sure it is of the following type: ' +
87 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
88 })
89
90 return cleanUpReqFiles(req)
91 }
92
93 if (!isVideoFileSizeValid(videoFile.size.toString())) {
94 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
95 .json({
96 error: 'This file is too large.'
97 })
98
99 return cleanUpReqFiles(req)
100 }
101
fb719404 102 if (await isAbleToUploadVideo(user.id, videoFile.size) === false) {
f2eb23cd 103 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
a2431b7d 104 .json({ error: 'The user video quota is exceeded with this video.' })
a2431b7d 105
cf7a61b5 106 return cleanUpReqFiles(req)
a2431b7d
C
107 }
108
109 let duration: number
110
111 try {
112 duration = await getDurationFromVideoFile(videoFile.path)
113 } catch (err) {
d5b7d911 114 logger.error('Invalid input file in videosAddValidator.', { err })
f2eb23cd
RK
115 res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422)
116 .json({ error: 'Video file unreadable.' })
a2431b7d 117
cf7a61b5 118 return cleanUpReqFiles(req)
a2431b7d
C
119 }
120
b4055e1c
C
121 videoFile.duration = duration
122
123 if (!await isVideoAccepted(req, res, videoFile)) return cleanUpReqFiles(req)
a2431b7d
C
124
125 return next()
b60e5f38 126 }
a920fef1 127])
b60e5f38 128
418d092a 129const videosUpdateValidator = getCommonVideoEditAttributes().concat([
72c7248b 130 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
360329cc
C
131 body('name')
132 .optional()
0221f8c9 133 .trim()
360329cc 134 .custom(isVideoNameValid).withMessage('Should have a valid name'),
0f320037
C
135 body('channelId')
136 .optional()
c8861d5d 137 .customSanitizer(toIntOrNull)
0f320037 138 .custom(isIdValid).withMessage('Should have correct video channel id'),
b60e5f38 139
a2431b7d 140 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
141 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
142
cf7a61b5
C
143 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
144 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
0f6acda1 145 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
a2431b7d 146
6221f311 147 // Check if the user who did the request is able to update the video
0f320037 148 const user = res.locals.oauth.token.User
453e83ea 149 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
a2431b7d 150
0f6acda1 151 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
0f320037 152
a2431b7d 153 return next()
b60e5f38 154 }
a920fef1 155])
c173e565 156
8d427346 157async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
0283eaac 158 const video = getVideoWithAttributes(res)
8d427346
C
159
160 // Anybody can watch local videos
161 if (video.isOwned() === true) return next()
162
163 // Logged user
164 if (res.locals.oauth) {
165 // Users can search or watch remote videos
166 if (CONFIG.SEARCH.REMOTE_URI.USERS === true) return next()
167 }
168
169 // Anybody can search or watch remote videos
170 if (CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true) return next()
171
172 // Check our instance follows an actor that shared this video
173 const serverActor = await getServerActor()
174 if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next()
175
2d53be02 176 return res.status(HttpStatusCode.FORBIDDEN_403)
8d427346 177 .json({
e6abf95e
C
178 errorCode: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS,
179 error: 'Cannot get this video regarding follow constraints.',
180 originUrl: video.url
8d427346
C
181 })
182}
183
7eba5e1f
C
184const videosCustomGetValidator = (
185 fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes',
186 authenticateInQuery = false
187) => {
96f29c0f
C
188 return [
189 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
7b1f49de 190
96f29c0f
C
191 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
192 logger.debug('Checking videosGet parameters', { parameters: req.params })
11474c3c 193
96f29c0f 194 if (areValidationErrors(req, res)) return
0f6acda1 195 if (!await doesVideoExist(req.params.id, res, fetchType)) return
191764f3 196
943e5193
C
197 // Controllers does not need to check video rights
198 if (fetchType === 'only-immutable-attributes') return next()
199
d7df188f 200 const video = getVideoWithAttributes(res) as MVideoWithRights
191764f3 201
96f29c0f 202 // Video private or blacklisted
d7df188f 203 if (video.requiresAuth()) {
eccf70f0 204 await authenticatePromiseIfNeeded(req, res, authenticateInQuery)
8d427346 205
dae86118 206 const user = res.locals.oauth ? res.locals.oauth.token.User : null
191764f3 207
8d427346 208 // Only the owner or a user that have blacklist rights can see the video
d7df188f 209 if (!user || !user.canGetVideo(video)) {
2d53be02 210 return res.status(HttpStatusCode.FORBIDDEN_403)
22a73cb8 211 .json({ error: 'Cannot get this private/internal or blacklisted video.' })
8d427346 212 }
191764f3 213
8d427346 214 return next()
96f29c0f 215 }
11474c3c 216
96f29c0f
C
217 // Video is public, anyone can access it
218 if (video.privacy === VideoPrivacy.PUBLIC) return next()
11474c3c 219
96f29c0f
C
220 // Video is unlisted, check we used the uuid to fetch it
221 if (video.privacy === VideoPrivacy.UNLISTED) {
222 if (isUUIDValid(req.params.id)) return next()
81ebea48 223
96f29c0f 224 // Don't leak this unlisted video
2d53be02 225 return res.status(HttpStatusCode.NOT_FOUND_404).end()
96f29c0f 226 }
81ebea48 227 }
96f29c0f
C
228 ]
229}
230
231const videosGetValidator = videosCustomGetValidator('all')
eccf70f0 232const videosDownloadValidator = videosCustomGetValidator('all', true)
34ca3b52 233
8319d6ae
RK
234const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
235 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
236 param('videoFileId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'),
237
238 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
239 logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params })
240
241 if (areValidationErrors(req, res)) return
242 if (!await doesVideoFileOfVideoExist(+req.params.videoFileId, req.params.id, res)) return
243
244 return next()
245 }
246])
247
b60e5f38 248const videosRemoveValidator = [
72c7248b 249 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
34ca3b52 250
a2431b7d 251 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 252 logger.debug('Checking videosRemove parameters', { parameters: req.params })
34ca3b52 253
a2431b7d 254 if (areValidationErrors(req, res)) return
0f6acda1 255 if (!await doesVideoExist(req.params.id, res)) return
a2431b7d
C
256
257 // Check if the user who did the request is able to delete the video
453e83ea 258 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return
a2431b7d
C
259
260 return next()
b60e5f38
C
261 }
262]
34ca3b52 263
74d63469
GR
264const videosChangeOwnershipValidator = [
265 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
266
267 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
268 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
269
270 if (areValidationErrors(req, res)) return
0f6acda1 271 if (!await doesVideoExist(req.params.videoId, res)) return
74d63469
GR
272
273 // Check if the user who did the request is able to change the ownership of the video
453e83ea 274 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
74d63469
GR
275
276 const nextOwner = await AccountModel.loadLocalByName(req.body.username)
277 if (!nextOwner) {
2d53be02 278 res.status(HttpStatusCode.BAD_REQUEST_400)
9ccff238
LD
279 .json({ error: 'Changing video ownership to a remote account is not supported yet' })
280
74d63469
GR
281 return
282 }
283 res.locals.nextOwner = nextOwner
284
285 return next()
286 }
287]
288
289const videosTerminateChangeOwnershipValidator = [
290 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
291
292 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
293 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
294
295 if (areValidationErrors(req, res)) return
296 if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return
297
298 // Check if the user who did the request is able to change the ownership of the video
299 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
300
dae86118 301 const videoChangeOwnership = res.locals.videoChangeOwnership
74d63469 302
a1587156 303 if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
2d53be02 304 res.status(HttpStatusCode.FORBIDDEN_403)
a1587156 305 .json({ error: 'Ownership already accepted or refused' })
74d63469
GR
306 return
307 }
a1587156
C
308
309 return next()
74d63469
GR
310 }
311]
312
313const videosAcceptChangeOwnershipValidator = [
314 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
315 const body = req.body as VideoChangeOwnershipAccept
0f6acda1 316 if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
74d63469
GR
317
318 const user = res.locals.oauth.token.User
dae86118 319 const videoChangeOwnership = res.locals.videoChangeOwnership
fb719404 320 const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size)
74d63469 321 if (isAble === false) {
f2eb23cd 322 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
74d63469 323 .json({ error: 'The user video quota is exceeded with this video.' })
9ccff238 324
74d63469
GR
325 return
326 }
327
328 return next()
329 }
330]
331
764a9657
C
332const videosOverviewValidator = [
333 query('page')
334 .optional()
335 .isInt({ min: 1, max: OVERVIEWS.VIDEOS.SAMPLES_COUNT })
336 .withMessage('Should have a valid pagination'),
337
338 (req: express.Request, res: express.Response, next: express.NextFunction) => {
339 if (areValidationErrors(req, res)) return
340
341 return next()
342 }
343]
344
418d092a 345function getCommonVideoEditAttributes () {
a920fef1
C
346 return [
347 body('thumbnailfile')
348 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
a1587156
C
349 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
350 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
351 ),
a920fef1
C
352 body('previewfile')
353 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
a1587156
C
354 'This preview file is not supported or too large. Please, make sure it is of the following type: ' +
355 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
356 ),
a920fef1
C
357
358 body('category')
359 .optional()
360 .customSanitizer(toIntOrNull)
361 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
362 body('licence')
363 .optional()
364 .customSanitizer(toIntOrNull)
365 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
366 body('language')
367 .optional()
368 .customSanitizer(toValueOrNull)
369 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
370 body('nsfw')
371 .optional()
c8861d5d 372 .customSanitizer(toBooleanOrNull)
a920fef1
C
373 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
374 body('waitTranscoding')
375 .optional()
c8861d5d 376 .customSanitizer(toBooleanOrNull)
a920fef1
C
377 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
378 body('privacy')
379 .optional()
c8861d5d 380 .customSanitizer(toValueOrNull)
a920fef1
C
381 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
382 body('description')
383 .optional()
384 .customSanitizer(toValueOrNull)
385 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
386 body('support')
387 .optional()
388 .customSanitizer(toValueOrNull)
389 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
390 body('tags')
391 .optional()
392 .customSanitizer(toValueOrNull)
393 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
394 body('commentsEnabled')
395 .optional()
c8861d5d 396 .customSanitizer(toBooleanOrNull)
a920fef1 397 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
7f2cfe3a 398 body('downloadEnabled')
1e74f19a 399 .optional()
c8861d5d 400 .customSanitizer(toBooleanOrNull)
156c50af 401 .custom(isBooleanValid).withMessage('Should have downloading enabled boolean'),
b718fd22 402 body('originallyPublishedAt')
c8861d5d
C
403 .optional()
404 .customSanitizer(toValueOrNull)
405 .custom(isVideoOriginallyPublishedAtValid).withMessage('Should have a valid original publication date'),
a920fef1
C
406 body('scheduleUpdate')
407 .optional()
408 .customSanitizer(toValueOrNull),
409 body('scheduleUpdate.updateAt')
410 .optional()
411 .custom(isDateValid).withMessage('Should have a valid schedule update date'),
412 body('scheduleUpdate.privacy')
413 .optional()
2b65c4e5 414 .customSanitizer(toIntOrNull)
a920fef1 415 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
ba5a8d89 416 ] as (ValidationChain | ExpressPromiseHandler)[]
a920fef1 417}
fbad87b0 418
1cd3facc
C
419const commonVideosFiltersValidator = [
420 query('categoryOneOf')
421 .optional()
422 .customSanitizer(toArray)
423 .custom(isNumberArray).withMessage('Should have a valid one of category array'),
424 query('licenceOneOf')
425 .optional()
426 .customSanitizer(toArray)
427 .custom(isNumberArray).withMessage('Should have a valid one of licence array'),
428 query('languageOneOf')
429 .optional()
430 .customSanitizer(toArray)
431 .custom(isStringArray).withMessage('Should have a valid one of language array'),
432 query('tagsOneOf')
433 .optional()
434 .customSanitizer(toArray)
435 .custom(isStringArray).withMessage('Should have a valid one of tags array'),
436 query('tagsAllOf')
437 .optional()
438 .customSanitizer(toArray)
439 .custom(isStringArray).withMessage('Should have a valid all of tags array'),
440 query('nsfw')
441 .optional()
1fd61899
C
442 .custom(isBooleanBothQueryValid).withMessage('Should have a valid NSFW attribute'),
443 query('isLive')
444 .optional()
445 .customSanitizer(toBooleanOrNull)
446 .custom(isBooleanValid).withMessage('Should have a valid live boolean'),
1cd3facc
C
447 query('filter')
448 .optional()
449 .custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
fe987656
C
450 query('skipCount')
451 .optional()
452 .customSanitizer(toBooleanOrNull)
453 .custom(isBooleanValid).withMessage('Should have a valid skip count boolean'),
37024082
RK
454 query('search')
455 .optional()
456 .custom(exists).withMessage('Should have a valid search'),
1cd3facc
C
457
458 (req: express.Request, res: express.Response, next: express.NextFunction) => {
459 logger.debug('Checking commons video filters query', { parameters: req.query })
460
461 if (areValidationErrors(req, res)) return
462
dae86118 463 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
0aa52e17
C
464 if (
465 (req.query.filter === 'all-local' || req.query.filter === 'all') &&
466 (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
467 ) {
2d53be02 468 res.status(HttpStatusCode.UNAUTHORIZED_401)
1cd3facc
C
469 .json({ error: 'You are not allowed to see all local videos.' })
470
471 return
472 }
473
474 return next()
475 }
476]
477
fbad87b0
C
478// ---------------------------------------------------------------------------
479
480export {
481 videosAddValidator,
482 videosUpdateValidator,
483 videosGetValidator,
8319d6ae 484 videoFileMetadataGetValidator,
eccf70f0 485 videosDownloadValidator,
8d427346 486 checkVideoFollowConstraints,
96f29c0f 487 videosCustomGetValidator,
fbad87b0 488 videosRemoveValidator,
fbad87b0 489
74d63469
GR
490 videosChangeOwnershipValidator,
491 videosTerminateChangeOwnershipValidator,
492 videosAcceptChangeOwnershipValidator,
493
418d092a 494 getCommonVideoEditAttributes,
1cd3facc 495
764a9657
C
496 commonVideosFiltersValidator,
497
498 videosOverviewValidator
fbad87b0
C
499}
500
501// ---------------------------------------------------------------------------
502
503function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
504 if (req.body.scheduleUpdate) {
505 if (!req.body.scheduleUpdate.updateAt) {
7373507f
C
506 logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.')
507
2d53be02 508 res.status(HttpStatusCode.BAD_REQUEST_400)
fbad87b0 509 .json({ error: 'Schedule update at is mandatory.' })
fbad87b0
C
510
511 return true
512 }
513 }
514
515 return false
516}
b4055e1c
C
517
518async function isVideoAccepted (req: express.Request, res: express.Response, videoFile: Express.Multer.File & { duration?: number }) {
519 // Check we accept this video
520 const acceptParameters = {
521 videoBody: req.body,
522 videoFile,
523 user: res.locals.oauth.token.User
524 }
89cd1275
C
525 const acceptedResult = await Hooks.wrapFun(
526 isLocalVideoAccepted,
527 acceptParameters,
b4055e1c
C
528 'filter:api.video.upload.accept.result'
529 )
530
531 if (!acceptedResult || acceptedResult.accepted !== true) {
532 logger.info('Refused local video.', { acceptedResult, acceptParameters })
2d53be02 533 res.status(HttpStatusCode.FORBIDDEN_403)
b4055e1c
C
534 .json({ error: acceptedResult.errorMessage || 'Refused local video' })
535
536 return false
537 }
538
539 return true
540}