]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/videos/video-playlists.ts
Cleanup useless express validator messages
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / video-playlists.ts
CommitLineData
41fb13c3 1import express from 'express'
c8861d5d 2import { body, param, query, ValidationChain } from 'express-validator'
f8360396 3import { ExpressPromiseHandler } from '@server/types/express-handler'
ba5a8d89 4import { MUserAccountId } from '@server/types/models'
d17c7b4e
C
5import {
6 HttpStatusCode,
7 UserRight,
8 VideoPlaylistCreate,
9 VideoPlaylistPrivacy,
10 VideoPlaylistType,
11 VideoPlaylistUpdate
12} from '@shared/models'
c8861d5d
C
13import {
14 isArrayOf,
15 isIdOrUUIDValid,
16 isIdValid,
17 isUUIDValid,
d4a8e7a6 18 toCompleteUUID,
c8861d5d
C
19 toIntArray,
20 toIntOrNull,
21 toValueOrNull
22} from '../../../helpers/custom-validators/misc'
418d092a 23import {
9f79ade6 24 isVideoPlaylistDescriptionValid,
418d092a 25 isVideoPlaylistNameValid,
df0b219d
C
26 isVideoPlaylistPrivacyValid,
27 isVideoPlaylistTimestampValid,
28 isVideoPlaylistTypeValid
418d092a 29} from '../../../helpers/custom-validators/video-playlists'
c729caf6 30import { isVideoImageValid } from '../../../helpers/custom-validators/videos'
418d092a 31import { cleanUpReqFiles } from '../../../helpers/express-utils'
ba5a8d89 32import { logger } from '../../../helpers/logger'
ba5a8d89
C
33import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
34import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
26d6bf65 35import { MVideoPlaylist } from '../../../types/models/video/video-playlist'
2c2befaa 36import { authenticatePromise } from '../../auth'
d4a8e7a6
C
37import {
38 areValidationErrors,
39 doesVideoChannelIdExist,
40 doesVideoExist,
41 doesVideoPlaylistExist,
42 isValidPlaylistIdParam,
43 VideoPlaylistFetchType
44} from '../shared'
418d092a
C
45
46const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
1b319b7a 47 body('displayName')
396f6f01 48 .custom(isVideoPlaylistNameValid),
1b319b7a 49
418d092a
C
50 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
51 logger.debug('Checking videoPlaylistsAddValidator parameters', { parameters: req.body })
52
53 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
54
c5e4e36d 55 const body: VideoPlaylistCreate = req.body
0f6acda1 56 if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req)
c5e4e36d 57
d4a8e7a6
C
58 if (
59 !body.videoChannelId &&
60 (body.privacy === VideoPlaylistPrivacy.PUBLIC || body.privacy === VideoPlaylistPrivacy.UNLISTED)
61 ) {
c5e4e36d 62 cleanUpReqFiles(req)
76148b27 63
d4a8e7a6 64 return res.fail({ message: 'Cannot set "public" or "unlisted" a playlist that is not assigned to a channel.' })
c5e4e36d 65 }
418d092a
C
66
67 return next()
68 }
69])
70
71const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
d4a8e7a6 72 isValidPlaylistIdParam('playlistId'),
418d092a 73
1b319b7a
C
74 body('displayName')
75 .optional()
396f6f01 76 .custom(isVideoPlaylistNameValid),
1b319b7a 77
418d092a
C
78 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
79 logger.debug('Checking videoPlaylistsUpdateValidator parameters', { parameters: req.body })
80
81 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
82
0f6acda1 83 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req)
07b1a18a 84
453e83ea 85 const videoPlaylist = getPlaylist(res)
07b1a18a 86
453e83ea 87 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
418d092a
C
88 return cleanUpReqFiles(req)
89 }
90
c5e4e36d
C
91 const body: VideoPlaylistUpdate = req.body
92
c5e4e36d
C
93 const newPrivacy = body.privacy || videoPlaylist.privacy
94 if (newPrivacy === VideoPlaylistPrivacy.PUBLIC &&
95 (
96 (!videoPlaylist.videoChannelId && !body.videoChannelId) ||
97 body.videoChannelId === null
98 )
99 ) {
100 cleanUpReqFiles(req)
76148b27
RK
101
102 return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' })
c5e4e36d
C
103 }
104
df0b219d
C
105 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
106 cleanUpReqFiles(req)
76148b27
RK
107
108 return res.fail({ message: 'Cannot update a watch later playlist.' })
df0b219d
C
109 }
110
0f6acda1 111 if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req)
418d092a
C
112
113 return next()
114 }
115])
116
117const videoPlaylistsDeleteValidator = [
d4a8e7a6 118 isValidPlaylistIdParam('playlistId'),
418d092a
C
119
120 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
121 logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params })
122
123 if (areValidationErrors(req, res)) return
124
0f6acda1 125 if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return
df0b219d 126
453e83ea 127 const videoPlaylist = getPlaylist(res)
df0b219d 128 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
76148b27 129 return res.fail({ message: 'Cannot delete a watch later playlist.' })
df0b219d
C
130 }
131
453e83ea 132 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
418d092a
C
133 return
134 }
135
136 return next()
137 }
138]
139
453e83ea
C
140const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
141 return [
d4a8e7a6 142 isValidPlaylistIdParam('playlistId'),
418d092a 143
453e83ea
C
144 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
145 logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params })
418d092a 146
453e83ea 147 if (areValidationErrors(req, res)) return
418d092a 148
453e83ea 149 if (!await doesVideoPlaylistExist(req.params.playlistId, res, fetchType)) return
418d092a 150
453e83ea 151 const videoPlaylist = res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
07b1a18a 152
453e83ea
C
153 // Video is unlisted, check we used the uuid to fetch it
154 if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
155 if (isUUIDValid(req.params.playlistId)) return next()
07b1a18a 156
76148b27
RK
157 return res.fail({
158 status: HttpStatusCode.NOT_FOUND_404,
159 message: 'Playlist not found'
160 })
453e83ea 161 }
07b1a18a 162
453e83ea 163 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
2c2befaa 164 await authenticatePromise(req, res)
418d092a 165
453e83ea 166 const user = res.locals.oauth ? res.locals.oauth.token.User : null
4d09cfba 167
453e83ea
C
168 if (
169 !user ||
170 (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
171 ) {
76148b27
RK
172 return res.fail({
173 status: HttpStatusCode.FORBIDDEN_403,
174 message: 'Cannot get this private video playlist.'
175 })
453e83ea
C
176 }
177
178 return next()
418d092a
C
179 }
180
181 return next()
182 }
453e83ea
C
183 ]
184}
418d092a 185
c06af501 186const videoPlaylistsSearchValidator = [
396f6f01
C
187 query('search')
188 .optional()
189 .not().isEmpty(),
c06af501
RK
190
191 (req: express.Request, res: express.Response, next: express.NextFunction) => {
192 logger.debug('Checking videoPlaylists search query', { parameters: req.query })
193
194 if (areValidationErrors(req, res)) return
195
196 return next()
197 }
198]
199
418d092a 200const videoPlaylistsAddVideoValidator = [
d4a8e7a6
C
201 isValidPlaylistIdParam('playlistId'),
202
418d092a 203 body('videoId')
d4a8e7a6 204 .customSanitizer(toCompleteUUID)
396f6f01 205 .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid/short uuid'),
418d092a
C
206 body('startTimestamp')
207 .optional()
396f6f01 208 .custom(isVideoPlaylistTimestampValid),
418d092a
C
209 body('stopTimestamp')
210 .optional()
396f6f01 211 .custom(isVideoPlaylistTimestampValid),
418d092a
C
212
213 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
214 logger.debug('Checking videoPlaylistsAddVideoValidator parameters', { parameters: req.params })
215
216 if (areValidationErrors(req, res)) return
217
0f6acda1
C
218 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
219 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
418d092a 220
453e83ea 221 const videoPlaylist = getPlaylist(res)
418d092a 222
453e83ea 223 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) {
418d092a
C
224 return
225 }
226
227 return next()
228 }
229]
230
231const videoPlaylistsUpdateOrRemoveVideoValidator = [
d4a8e7a6 232 isValidPlaylistIdParam('playlistId'),
bfbd9128 233 param('playlistElementId')
d4a8e7a6 234 .customSanitizer(toCompleteUUID)
396f6f01 235 .custom(isIdValid).withMessage('Should have an element id/uuid/short uuid'),
418d092a
C
236 body('startTimestamp')
237 .optional()
396f6f01 238 .custom(isVideoPlaylistTimestampValid),
418d092a
C
239 body('stopTimestamp')
240 .optional()
396f6f01 241 .custom(isVideoPlaylistTimestampValid),
418d092a
C
242
243 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
244 logger.debug('Checking videoPlaylistsRemoveVideoValidator parameters', { parameters: req.params })
245
246 if (areValidationErrors(req, res)) return
247
0f6acda1 248 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
418d092a 249
453e83ea 250 const videoPlaylist = getPlaylist(res)
418d092a 251
bfbd9128 252 const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId)
418d092a 253 if (!videoPlaylistElement) {
76148b27
RK
254 res.fail({
255 status: HttpStatusCode.NOT_FOUND_404,
256 message: 'Video playlist element not found'
257 })
418d092a
C
258 return
259 }
260 res.locals.videoPlaylistElement = videoPlaylistElement
261
262 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
263
264 return next()
265 }
266]
267
268const videoPlaylistElementAPGetValidator = [
d4a8e7a6 269 isValidPlaylistIdParam('playlistId'),
37190663 270 param('playlistElementId')
396f6f01 271 .custom(isIdValid),
418d092a
C
272
273 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
274 logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params })
275
276 if (areValidationErrors(req, res)) return
277
37190663
C
278 const playlistElementId = parseInt(req.params.playlistElementId + '', 10)
279 const playlistId = req.params.playlistId
280
281 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId)
418d092a 282 if (!videoPlaylistElement) {
76148b27
RK
283 res.fail({
284 status: HttpStatusCode.NOT_FOUND_404,
285 message: 'Video playlist element not found'
286 })
418d092a
C
287 return
288 }
289
290 if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
76148b27
RK
291 return res.fail({
292 status: HttpStatusCode.FORBIDDEN_403,
293 message: 'Cannot get this private video playlist.'
294 })
418d092a
C
295 }
296
b5fecbf4 297 res.locals.videoPlaylistElementAP = videoPlaylistElement
418d092a
C
298
299 return next()
300 }
301]
302
303const videoPlaylistsReorderVideosValidator = [
d4a8e7a6 304 isValidPlaylistIdParam('playlistId'),
396f6f01 305
418d092a 306 body('startPosition')
396f6f01 307 .isInt({ min: 1 }),
418d092a 308 body('insertAfterPosition')
396f6f01 309 .isInt({ min: 0 }),
418d092a
C
310 body('reorderLength')
311 .optional()
396f6f01 312 .isInt({ min: 1 }),
418d092a
C
313
314 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
315 logger.debug('Checking videoPlaylistsReorderVideosValidator parameters', { parameters: req.params })
316
317 if (areValidationErrors(req, res)) return
318
0f6acda1 319 if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
418d092a 320
453e83ea 321 const videoPlaylist = getPlaylist(res)
418d092a
C
322 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
323
07b1a18a
C
324 const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id)
325 const startPosition: number = req.body.startPosition
326 const insertAfterPosition: number = req.body.insertAfterPosition
327 const reorderLength: number = req.body.reorderLength
328
329 if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) {
76148b27 330 res.fail({ message: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` })
07b1a18a
C
331 return
332 }
333
334 if (reorderLength && reorderLength + startPosition > nextPosition) {
76148b27 335 res.fail({ message: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` })
07b1a18a
C
336 return
337 }
338
418d092a
C
339 return next()
340 }
341]
342
df0b219d
C
343const commonVideoPlaylistFiltersValidator = [
344 query('playlistType')
345 .optional()
396f6f01 346 .custom(isVideoPlaylistTypeValid),
df0b219d
C
347
348 (req: express.Request, res: express.Response, next: express.NextFunction) => {
349 logger.debug('Checking commonVideoPlaylistFiltersValidator parameters', { parameters: req.params })
350
351 if (areValidationErrors(req, res)) return
352
353 return next()
354 }
355]
356
f0a39880
C
357const doVideosInPlaylistExistValidator = [
358 query('videoIds')
359 .customSanitizer(toIntArray)
360 .custom(v => isArrayOf(v, isIdValid)).withMessage('Should have a valid video ids array'),
361
362 (req: express.Request, res: express.Response, next: express.NextFunction) => {
363 logger.debug('Checking areVideosInPlaylistExistValidator parameters', { parameters: req.query })
364
365 if (areValidationErrors(req, res)) return
366
367 return next()
368 }
369]
370
418d092a
C
371// ---------------------------------------------------------------------------
372
373export {
374 videoPlaylistsAddValidator,
375 videoPlaylistsUpdateValidator,
376 videoPlaylistsDeleteValidator,
377 videoPlaylistsGetValidator,
c06af501 378 videoPlaylistsSearchValidator,
418d092a
C
379
380 videoPlaylistsAddVideoValidator,
381 videoPlaylistsUpdateOrRemoveVideoValidator,
382 videoPlaylistsReorderVideosValidator,
383
df0b219d
C
384 videoPlaylistElementAPGetValidator,
385
f0a39880
C
386 commonVideoPlaylistFiltersValidator,
387
388 doVideosInPlaylistExistValidator
418d092a
C
389}
390
391// ---------------------------------------------------------------------------
392
393function getCommonPlaylistEditAttributes () {
394 return [
395 body('thumbnailfile')
c729caf6 396 .custom((value, { req }) => isVideoImageValid(req.files, 'thumbnailfile'))
a1587156
C
397 .withMessage(
398 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
399 CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ')
400 ),
418d092a 401
418d092a
C
402 body('description')
403 .optional()
404 .customSanitizer(toValueOrNull)
396f6f01 405 .custom(isVideoPlaylistDescriptionValid),
418d092a
C
406 body('privacy')
407 .optional()
c8861d5d 408 .customSanitizer(toIntOrNull)
396f6f01 409 .custom(isVideoPlaylistPrivacyValid),
418d092a
C
410 body('videoChannelId')
411 .optional()
c8861d5d 412 .customSanitizer(toIntOrNull)
ba5a8d89 413 ] as (ValidationChain | ExpressPromiseHandler)[]
418d092a
C
414}
415
453e83ea 416function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) {
418d092a 417 if (videoPlaylist.isOwned() === false) {
76148b27
RK
418 res.fail({
419 status: HttpStatusCode.FORBIDDEN_403,
420 message: 'Cannot manage video playlist of another server.'
421 })
418d092a
C
422 return false
423 }
424
425 // Check if the user can manage the video playlist
426 // The user can delete it if s/he is an admin
427 // Or if s/he is the video playlist's owner
428 if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) {
76148b27
RK
429 res.fail({
430 status: HttpStatusCode.FORBIDDEN_403,
431 message: 'Cannot manage video playlist of another user'
432 })
418d092a
C
433 return false
434 }
435
436 return true
437}
453e83ea
C
438
439function getPlaylist (res: express.Response) {
440 return res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
441}