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