]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/videos/videos.ts
Fix server run
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / videos.ts
CommitLineData
69818c93 1import * as express from 'express'
f6d6e7f8 2import { body, header, param, query, ValidationChain } from 'express-validator'
3import { getResumableUploadPath } from '@server/helpers/upload'
fb719404 4import { isAbleToUploadVideo } from '@server/lib/user'
e6abf95e 5import { getServerActor } from '@server/models/application/application'
ba5a8d89 6import { ExpressPromiseHandler } from '@server/types/express'
71d4af1e 7import { MUserAccountId, MVideoFullLight } from '@server/types/models'
21b5c298 8import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
ba5a8d89 9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
b60e5f38 10import {
37024082 11 exists,
2baea0c7
C
12 isBooleanValid,
13 isDateValid,
f2eb23cd 14 isFileFieldValid,
2baea0c7
C
15 isIdValid,
16 isUUIDValid,
1cd3facc 17 toArray,
c8861d5d 18 toBooleanOrNull,
2baea0c7
C
19 toIntOrNull,
20 toValueOrNull
6e46de09 21} from '../../../helpers/custom-validators/misc'
1fd61899 22import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
2baea0c7
C
23import {
24 isScheduleVideoUpdatePrivacyValid,
ac81d1a0
C
25 isVideoCategoryValid,
26 isVideoDescriptionValid,
f2eb23cd
RK
27 isVideoFileMimeTypeValid,
28 isVideoFileSizeValid,
1cd3facc 29 isVideoFilterValid,
ac81d1a0
C
30 isVideoImage,
31 isVideoLanguageValid,
32 isVideoLicenceValid,
33 isVideoNameValid,
fd8710b8 34 isVideoOriginallyPublishedAtValid,
ac81d1a0 35 isVideoPrivacyValid,
360329cc 36 isVideoSupportValid,
4157cdb1 37 isVideoTagsValid
6e46de09 38} from '../../../helpers/custom-validators/videos'
e6abf95e 39import { cleanUpReqFiles } from '../../../helpers/express-utils'
daf6e480 40import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils'
6e46de09 41import { logger } from '../../../helpers/logger'
f6d6e7f8 42import { deleteFileAndCatch } from '../../../helpers/utils'
0283eaac 43import { getVideoWithAttributes } from '../../../helpers/video'
e6abf95e
C
44import { CONFIG } from '../../../initializers/config'
45import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
46import { isLocalVideoAccepted } from '../../../lib/moderation'
47import { Hooks } from '../../../lib/plugins/hooks'
e6abf95e 48import { VideoModel } from '../../../models/video/video'
f43db2f4 49import { authenticatePromiseIfNeeded } from '../../auth'
10363c74
C
50import {
51 areValidationErrors,
52 checkUserCanManageVideo,
10363c74
C
53 doesVideoChannelOfAccountExist,
54 doesVideoExist,
d4a8e7a6
C
55 doesVideoFileOfVideoExist,
56 isValidVideoIdParam
10363c74 57} from '../shared'
34ca3b52 58
f6d6e7f8 59const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
0c237b19 60 body('videofile')
f2eb23cd
RK
61 .custom((value, { req }) => isFileFieldValid(req.files, 'videofile'))
62 .withMessage('Should have a file'),
63 body('name')
0221f8c9 64 .trim()
7dab0bd6
RK
65 .custom(isVideoNameValid).withMessage(
66 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
67 ),
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 75 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
a2431b7d 76
f6d6e7f8 77 const videoFile: express.VideoUploadFile = req.files['videofile'][0]
a2431b7d 78 const user = res.locals.oauth.token.User
b60e5f38 79
f6d6e7f8 80 if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFile.size, files: req.files })) {
f2eb23cd
RK
81 return cleanUpReqFiles(req)
82 }
83
f6d6e7f8 84 try {
85 if (!videoFile.duration) await addDurationToVideo(videoFile)
86 } catch (err) {
87 logger.error('Invalid input file in videosAddLegacyValidator.', { err })
f2eb23cd 88
76148b27
RK
89 res.fail({
90 status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
91 message: 'Video file unreadable.'
92 })
f2eb23cd
RK
93 return cleanUpReqFiles(req)
94 }
95
f6d6e7f8 96 if (!await isVideoAccepted(req, res, videoFile)) return cleanUpReqFiles(req)
a2431b7d 97
f6d6e7f8 98 return next()
99 }
100])
101
102/**
103 * Gets called after the last PUT request
104 */
105const videosAddResumableValidator = [
106 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
107 const user = res.locals.oauth.token.User
108
109 const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body
110 const file = { ...body, duration: undefined, path: getResumableUploadPath(body.id), filename: body.metadata.filename }
111
112 const cleanup = () => deleteFileAndCatch(file.path)
a2431b7d 113
f6d6e7f8 114 if (!await doesVideoChannelOfAccountExist(file.metadata.channelId, user, res)) return cleanup()
a2431b7d
C
115
116 try {
f6d6e7f8 117 if (!file.duration) await addDurationToVideo(file)
a2431b7d 118 } catch (err) {
f6d6e7f8 119 logger.error('Invalid input file in videosAddResumableValidator.', { err })
a2431b7d 120
76148b27
RK
121 res.fail({
122 status: HttpStatusCode.UNPROCESSABLE_ENTITY_422,
123 message: 'Video file unreadable.'
124 })
f6d6e7f8 125 return cleanup()
a2431b7d
C
126 }
127
f6d6e7f8 128 if (!await isVideoAccepted(req, res, file)) return cleanup()
b4055e1c 129
f6d6e7f8 130 res.locals.videoFileResumable = file
131
132 return next()
133 }
134]
135
136/**
137 * File is created in POST initialisation, and its body is saved as a 'metadata' field is saved by uploadx for later use.
138 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/uploadx.ts
139 *
140 * Uploadx doesn't use next() until the upload completes, so this middleware has to be placed before uploadx
141 * see https://github.com/kukhariev/node-uploadx/blob/dc9fb4a8ac5a6f481902588e93062f591ec6ef03/packages/core/src/handlers/base-handler.ts
142 *
143 */
144const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
145 body('filename')
146 .isString()
147 .exists()
148 .withMessage('Should have a valid filename'),
149 body('name')
150 .trim()
7dab0bd6
RK
151 .custom(isVideoNameValid).withMessage(
152 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
153 ),
f6d6e7f8 154 body('channelId')
155 .customSanitizer(toIntOrNull)
156 .custom(isIdValid).withMessage('Should have correct video channel id'),
157
158 header('x-upload-content-length')
159 .isNumeric()
160 .exists()
161 .withMessage('Should specify the file length'),
162 header('x-upload-content-type')
163 .isString()
164 .exists()
165 .withMessage('Should specify the file mimetype'),
166
167 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
168 const videoFileMetadata = {
169 mimetype: req.headers['x-upload-content-type'] as string,
170 size: +req.headers['x-upload-content-length'],
171 originalname: req.body.name
172 }
173
174 const user = res.locals.oauth.token.User
175 const cleanup = () => cleanUpReqFiles(req)
176
177 logger.debug('Checking videosAddResumableInitValidator parameters and headers', {
178 parameters: req.body,
179 headers: req.headers,
180 files: req.files
181 })
182
183 if (areValidationErrors(req, res)) return cleanup()
184
185 const files = { videofile: [ videoFileMetadata ] }
186 if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup()
187
188 // multer required unsetting the Content-Type, now we can set it for node-uploadx
189 req.headers['content-type'] = 'application/json; charset=utf-8'
190 // place previewfile in metadata so that uploadx saves it in .META
191 if (req.files['previewfile']) req.body.previewfile = req.files['previewfile']
a2431b7d
C
192
193 return next()
b60e5f38 194 }
a920fef1 195])
b60e5f38 196
418d092a 197const videosUpdateValidator = getCommonVideoEditAttributes().concat([
d4a8e7a6
C
198 isValidVideoIdParam('id'),
199
360329cc
C
200 body('name')
201 .optional()
0221f8c9 202 .trim()
7dab0bd6
RK
203 .custom(isVideoNameValid).withMessage(
204 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
205 ),
0f320037
C
206 body('channelId')
207 .optional()
c8861d5d 208 .customSanitizer(toIntOrNull)
0f320037 209 .custom(isIdValid).withMessage('Should have correct video channel id'),
b60e5f38 210
a2431b7d 211 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
212 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
213
cf7a61b5
C
214 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
215 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
0f6acda1 216 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
a2431b7d 217
6221f311 218 // Check if the user who did the request is able to update the video
0f320037 219 const user = res.locals.oauth.token.User
453e83ea 220 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
a2431b7d 221
0f6acda1 222 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
0f320037 223
a2431b7d 224 return next()
b60e5f38 225 }
a920fef1 226])
c173e565 227
8d427346 228async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
0283eaac 229 const video = getVideoWithAttributes(res)
8d427346
C
230
231 // Anybody can watch local videos
232 if (video.isOwned() === true) return next()
233
234 // Logged user
235 if (res.locals.oauth) {
236 // Users can search or watch remote videos
237 if (CONFIG.SEARCH.REMOTE_URI.USERS === true) return next()
238 }
239
240 // Anybody can search or watch remote videos
241 if (CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true) return next()
242
243 // Check our instance follows an actor that shared this video
244 const serverActor = await getServerActor()
245 if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next()
246
76148b27
RK
247 return res.fail({
248 status: HttpStatusCode.FORBIDDEN_403,
81628e50 249 message: 'Cannot get this video regarding follow constraints',
3866ea02 250 type: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS,
76148b27
RK
251 data: {
252 originUrl: video.url
253 }
254 })
8d427346
C
255}
256
7eba5e1f 257const videosCustomGetValidator = (
71d4af1e 258 fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes',
7eba5e1f
C
259 authenticateInQuery = false
260) => {
96f29c0f 261 return [
d4a8e7a6 262 isValidVideoIdParam('id'),
7b1f49de 263
96f29c0f
C
264 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
265 logger.debug('Checking videosGet parameters', { parameters: req.params })
11474c3c 266
96f29c0f 267 if (areValidationErrors(req, res)) return
0f6acda1 268 if (!await doesVideoExist(req.params.id, res, fetchType)) return
191764f3 269
943e5193
C
270 // Controllers does not need to check video rights
271 if (fetchType === 'only-immutable-attributes') return next()
272
71d4af1e 273 const video = getVideoWithAttributes(res) as MVideoFullLight
191764f3 274
96f29c0f 275 // Video private or blacklisted
d7df188f 276 if (video.requiresAuth()) {
eccf70f0 277 await authenticatePromiseIfNeeded(req, res, authenticateInQuery)
8d427346 278
dae86118 279 const user = res.locals.oauth ? res.locals.oauth.token.User : null
191764f3 280
81628e50 281 // Only the owner or a user that have blocklist rights can see the video
d7df188f 282 if (!user || !user.canGetVideo(video)) {
76148b27
RK
283 return res.fail({
284 status: HttpStatusCode.FORBIDDEN_403,
81628e50 285 message: 'Cannot get this private/internal or blocklisted video'
76148b27 286 })
8d427346 287 }
191764f3 288
8d427346 289 return next()
96f29c0f 290 }
11474c3c 291
96f29c0f
C
292 // Video is public, anyone can access it
293 if (video.privacy === VideoPrivacy.PUBLIC) return next()
11474c3c 294
96f29c0f
C
295 // Video is unlisted, check we used the uuid to fetch it
296 if (video.privacy === VideoPrivacy.UNLISTED) {
297 if (isUUIDValid(req.params.id)) return next()
81ebea48 298
96f29c0f 299 // Don't leak this unlisted video
76148b27
RK
300 return res.fail({
301 status: HttpStatusCode.NOT_FOUND_404,
302 message: 'Video not found'
303 })
96f29c0f 304 }
81ebea48 305 }
96f29c0f
C
306 ]
307}
308
309const videosGetValidator = videosCustomGetValidator('all')
eccf70f0 310const videosDownloadValidator = videosCustomGetValidator('all', true)
34ca3b52 311
8319d6ae 312const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
d4a8e7a6
C
313 isValidVideoIdParam('id'),
314
315 param('videoFileId')
316 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'),
8319d6ae
RK
317
318 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
319 logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params })
320
321 if (areValidationErrors(req, res)) return
322 if (!await doesVideoFileOfVideoExist(+req.params.videoFileId, req.params.id, res)) return
323
324 return next()
325 }
326])
327
b60e5f38 328const videosRemoveValidator = [
d4a8e7a6 329 isValidVideoIdParam('id'),
34ca3b52 330
a2431b7d 331 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 332 logger.debug('Checking videosRemove parameters', { parameters: req.params })
34ca3b52 333
a2431b7d 334 if (areValidationErrors(req, res)) return
0f6acda1 335 if (!await doesVideoExist(req.params.id, res)) return
a2431b7d
C
336
337 // Check if the user who did the request is able to delete the video
453e83ea 338 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return
a2431b7d
C
339
340 return next()
b60e5f38
C
341 }
342]
34ca3b52 343
764a9657
C
344const videosOverviewValidator = [
345 query('page')
346 .optional()
347 .isInt({ min: 1, max: OVERVIEWS.VIDEOS.SAMPLES_COUNT })
348 .withMessage('Should have a valid pagination'),
349
350 (req: express.Request, res: express.Response, next: express.NextFunction) => {
351 if (areValidationErrors(req, res)) return
352
353 return next()
354 }
355]
356
418d092a 357function getCommonVideoEditAttributes () {
a920fef1
C
358 return [
359 body('thumbnailfile')
360 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
a1587156
C
361 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
362 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
363 ),
a920fef1
C
364 body('previewfile')
365 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
a1587156
C
366 'This preview file is not supported or too large. Please, make sure it is of the following type: ' +
367 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
368 ),
a920fef1
C
369
370 body('category')
371 .optional()
372 .customSanitizer(toIntOrNull)
373 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
374 body('licence')
375 .optional()
376 .customSanitizer(toIntOrNull)
377 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
378 body('language')
379 .optional()
380 .customSanitizer(toValueOrNull)
381 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
382 body('nsfw')
383 .optional()
c8861d5d 384 .customSanitizer(toBooleanOrNull)
a920fef1
C
385 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
386 body('waitTranscoding')
387 .optional()
c8861d5d 388 .customSanitizer(toBooleanOrNull)
a920fef1
C
389 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
390 body('privacy')
391 .optional()
c8861d5d 392 .customSanitizer(toValueOrNull)
a920fef1
C
393 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
394 body('description')
395 .optional()
396 .customSanitizer(toValueOrNull)
397 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
398 body('support')
399 .optional()
400 .customSanitizer(toValueOrNull)
401 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
402 body('tags')
403 .optional()
404 .customSanitizer(toValueOrNull)
7dab0bd6
RK
405 .custom(isVideoTagsValid)
406 .withMessage(
407 `Should have an array of up to ${CONSTRAINTS_FIELDS.VIDEOS.TAGS.max} tags between ` +
408 `${CONSTRAINTS_FIELDS.VIDEOS.TAG.min} and ${CONSTRAINTS_FIELDS.VIDEOS.TAG.max} characters each`
409 ),
a920fef1
C
410 body('commentsEnabled')
411 .optional()
c8861d5d 412 .customSanitizer(toBooleanOrNull)
a920fef1 413 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
7f2cfe3a 414 body('downloadEnabled')
1e74f19a 415 .optional()
c8861d5d 416 .customSanitizer(toBooleanOrNull)
156c50af 417 .custom(isBooleanValid).withMessage('Should have downloading enabled boolean'),
b718fd22 418 body('originallyPublishedAt')
c8861d5d
C
419 .optional()
420 .customSanitizer(toValueOrNull)
421 .custom(isVideoOriginallyPublishedAtValid).withMessage('Should have a valid original publication date'),
a920fef1
C
422 body('scheduleUpdate')
423 .optional()
424 .customSanitizer(toValueOrNull),
425 body('scheduleUpdate.updateAt')
426 .optional()
70330f63 427 .custom(isDateValid).withMessage('Should have a schedule update date that conforms to ISO 8601'),
a920fef1
C
428 body('scheduleUpdate.privacy')
429 .optional()
2b65c4e5 430 .customSanitizer(toIntOrNull)
a920fef1 431 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
ba5a8d89 432 ] as (ValidationChain | ExpressPromiseHandler)[]
a920fef1 433}
fbad87b0 434
1cd3facc
C
435const commonVideosFiltersValidator = [
436 query('categoryOneOf')
437 .optional()
438 .customSanitizer(toArray)
439 .custom(isNumberArray).withMessage('Should have a valid one of category array'),
440 query('licenceOneOf')
441 .optional()
442 .customSanitizer(toArray)
443 .custom(isNumberArray).withMessage('Should have a valid one of licence array'),
444 query('languageOneOf')
445 .optional()
446 .customSanitizer(toArray)
447 .custom(isStringArray).withMessage('Should have a valid one of language array'),
448 query('tagsOneOf')
449 .optional()
450 .customSanitizer(toArray)
451 .custom(isStringArray).withMessage('Should have a valid one of tags array'),
452 query('tagsAllOf')
453 .optional()
454 .customSanitizer(toArray)
455 .custom(isStringArray).withMessage('Should have a valid all of tags array'),
456 query('nsfw')
457 .optional()
1fd61899
C
458 .custom(isBooleanBothQueryValid).withMessage('Should have a valid NSFW attribute'),
459 query('isLive')
460 .optional()
461 .customSanitizer(toBooleanOrNull)
462 .custom(isBooleanValid).withMessage('Should have a valid live boolean'),
1cd3facc
C
463 query('filter')
464 .optional()
465 .custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
fe987656
C
466 query('skipCount')
467 .optional()
468 .customSanitizer(toBooleanOrNull)
469 .custom(isBooleanValid).withMessage('Should have a valid skip count boolean'),
37024082
RK
470 query('search')
471 .optional()
472 .custom(exists).withMessage('Should have a valid search'),
1cd3facc
C
473
474 (req: express.Request, res: express.Response, next: express.NextFunction) => {
475 logger.debug('Checking commons video filters query', { parameters: req.query })
476
477 if (areValidationErrors(req, res)) return
478
dae86118 479 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
0aa52e17
C
480 if (
481 (req.query.filter === 'all-local' || req.query.filter === 'all') &&
482 (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
483 ) {
76148b27
RK
484 res.fail({
485 status: HttpStatusCode.UNAUTHORIZED_401,
486 message: 'You are not allowed to see all local videos.'
487 })
1cd3facc
C
488 return
489 }
490
491 return next()
492 }
493]
494
fbad87b0
C
495// ---------------------------------------------------------------------------
496
497export {
f6d6e7f8 498 videosAddLegacyValidator,
499 videosAddResumableValidator,
500 videosAddResumableInitValidator,
501
fbad87b0
C
502 videosUpdateValidator,
503 videosGetValidator,
8319d6ae 504 videoFileMetadataGetValidator,
eccf70f0 505 videosDownloadValidator,
8d427346 506 checkVideoFollowConstraints,
96f29c0f 507 videosCustomGetValidator,
fbad87b0 508 videosRemoveValidator,
fbad87b0 509
418d092a 510 getCommonVideoEditAttributes,
1cd3facc 511
764a9657
C
512 commonVideosFiltersValidator,
513
514 videosOverviewValidator
fbad87b0
C
515}
516
517// ---------------------------------------------------------------------------
518
519function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
520 if (req.body.scheduleUpdate) {
521 if (!req.body.scheduleUpdate.updateAt) {
7373507f
C
522 logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.')
523
76148b27 524 res.fail({ message: 'Schedule update at is mandatory.' })
fbad87b0
C
525 return true
526 }
527 }
528
529 return false
530}
b4055e1c 531
f6d6e7f8 532async function commonVideoChecksPass (parameters: {
533 req: express.Request
534 res: express.Response
535 user: MUserAccountId
536 videoFileSize: number
537 files: express.UploadFilesForCheck
538}): Promise<boolean> {
539 const { req, res, user, videoFileSize, files } = parameters
540
541 if (areErrorsInScheduleUpdate(req, res)) return false
542
543 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return false
544
545 if (!isVideoFileMimeTypeValid(files)) {
76148b27
RK
546 res.fail({
547 status: HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415,
548 message: 'This file is not supported. Please, make sure it is of the following type: ' +
549 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
550 })
f6d6e7f8 551 return false
552 }
553
554 if (!isVideoFileSizeValid(videoFileSize.toString())) {
76148b27
RK
555 res.fail({
556 status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
c756bae0
RK
557 message: 'This file is too large. It exceeds the maximum file size authorized.',
558 type: ServerErrorCode.MAX_FILE_SIZE_REACHED
76148b27 559 })
f6d6e7f8 560 return false
561 }
562
563 if (await isAbleToUploadVideo(user.id, videoFileSize) === false) {
76148b27
RK
564 res.fail({
565 status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
c756bae0
RK
566 message: 'The user video quota is exceeded with this video.',
567 type: ServerErrorCode.QUOTA_REACHED
76148b27 568 })
f6d6e7f8 569 return false
570 }
571
572 return true
573}
574
575export async function isVideoAccepted (
576 req: express.Request,
577 res: express.Response,
578 videoFile: express.VideoUploadFile
579) {
b4055e1c
C
580 // Check we accept this video
581 const acceptParameters = {
582 videoBody: req.body,
583 videoFile,
584 user: res.locals.oauth.token.User
585 }
89cd1275
C
586 const acceptedResult = await Hooks.wrapFun(
587 isLocalVideoAccepted,
588 acceptParameters,
b4055e1c
C
589 'filter:api.video.upload.accept.result'
590 )
591
592 if (!acceptedResult || acceptedResult.accepted !== true) {
593 logger.info('Refused local video.', { acceptedResult, acceptParameters })
76148b27
RK
594 res.fail({
595 status: HttpStatusCode.FORBIDDEN_403,
596 message: acceptedResult.errorMessage || 'Refused local video'
597 })
b4055e1c
C
598 return false
599 }
600
601 return true
602}
f6d6e7f8 603
604async function addDurationToVideo (videoFile: { path: string, duration?: number }) {
605 const duration: number = await getDurationFromVideoFile(videoFile.path)
606
607 if (isNaN(duration)) throw new Error(`Couldn't get video duration`)
608
609 videoFile.duration = duration
610}