]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/videos/video-playlists.ts
Add playlist rest tests
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / video-playlists.ts
CommitLineData
418d092a 1import * as express from 'express'
df0b219d
C
2import { body, param, query, ValidationChain } from 'express-validator/check'
3import { UserRight } from '../../../../shared'
418d092a
C
4import { logger } from '../../../helpers/logger'
5import { UserModel } from '../../../models/account/user'
6import { areValidationErrors } from '../utils'
7import { isVideoExist, isVideoImage } from '../../../helpers/custom-validators/videos'
8import { CONSTRAINTS_FIELDS } from '../../../initializers'
07b1a18a 9import { isIdOrUUIDValid, isUUIDValid, toValueOrNull } from '../../../helpers/custom-validators/misc'
418d092a
C
10import {
11 isVideoPlaylistDescriptionValid,
12 isVideoPlaylistExist,
13 isVideoPlaylistNameValid,
df0b219d
C
14 isVideoPlaylistPrivacyValid,
15 isVideoPlaylistTimestampValid,
16 isVideoPlaylistTypeValid
418d092a
C
17} from '../../../helpers/custom-validators/video-playlists'
18import { VideoPlaylistModel } from '../../../models/video/video-playlist'
19import { cleanUpReqFiles } from '../../../helpers/express-utils'
20import { isVideoChannelIdExist } from '../../../helpers/custom-validators/video-channels'
21import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
22import { VideoModel } from '../../../models/video/video'
23import { authenticatePromiseIfNeeded } from '../../oauth'
24import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
df0b219d 25import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
418d092a
C
26
27const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
28 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
29 logger.debug('Checking videoPlaylistsAddValidator parameters', { parameters: req.body })
30
31 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
32
33 if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req)
34
35 return next()
36 }
37])
38
39const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
40 param('playlistId')
41 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
42
43 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
44 logger.debug('Checking videoPlaylistsUpdateValidator parameters', { parameters: req.body })
45
46 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
47
48 if (!await isVideoPlaylistExist(req.params.playlistId, res)) return cleanUpReqFiles(req)
07b1a18a
C
49
50 const videoPlaylist = res.locals.videoPlaylist
51
418d092a
C
52 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
53 return cleanUpReqFiles(req)
54 }
55
07b1a18a
C
56 if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PRIVATE && req.body.privacy === VideoPlaylistPrivacy.PRIVATE) {
57 cleanUpReqFiles(req)
58 return res.status(409)
59 .json({ error: 'Cannot set "private" a video playlist that was not private.' })
60 }
61
df0b219d
C
62 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
63 cleanUpReqFiles(req)
64 return res.status(409)
65 .json({ error: 'Cannot update a watch later playlist.' })
66 }
67
418d092a
C
68 if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req)
69
70 return next()
71 }
72])
73
74const videoPlaylistsDeleteValidator = [
75 param('playlistId')
76 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
77
78 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
79 logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params })
80
81 if (areValidationErrors(req, res)) return
82
83 if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
df0b219d
C
84
85 const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
86 if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
87 return res.status(409)
88 .json({ error: 'Cannot delete a watch later playlist.' })
89 }
90
418d092a
C
91 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
92 return
93 }
94
95 return next()
96 }
97]
98
99const videoPlaylistsGetValidator = [
100 param('playlistId')
101 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
102
103 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
104 logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params })
105
106 if (areValidationErrors(req, res)) return
107
108 if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
109
110 const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
07b1a18a
C
111
112 // Video is unlisted, check we used the uuid to fetch it
113 if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
114 if (isUUIDValid(req.params.playlistId)) return next()
115
116 return res.status(404).end()
117 }
118
418d092a
C
119 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
120 await authenticatePromiseIfNeeded(req, res)
121
122 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : null
123
124 if (
125 !user ||
126 (videoPlaylist.OwnerAccount.userId !== user.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
127 ) {
128 return res.status(403)
129 .json({ error: 'Cannot get this private video playlist.' })
130 }
131
132 return next()
133 }
134
135 return next()
136 }
137]
138
139const videoPlaylistsAddVideoValidator = [
140 param('playlistId')
141 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
142 body('videoId')
143 .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'),
144 body('startTimestamp')
145 .optional()
df0b219d 146 .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'),
418d092a
C
147 body('stopTimestamp')
148 .optional()
df0b219d 149 .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid stop timestamp'),
418d092a
C
150
151 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
152 logger.debug('Checking videoPlaylistsAddVideoValidator parameters', { parameters: req.params })
153
154 if (areValidationErrors(req, res)) return
155
156 if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
07b1a18a 157 if (!await isVideoExist(req.body.videoId, res, 'only-video')) return
418d092a
C
158
159 const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
160 const video: VideoModel = res.locals.video
161
162 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id)
163 if (videoPlaylistElement) {
164 res.status(409)
165 .json({ error: 'This video in this playlist already exists' })
166 .end()
167
168 return
169 }
170
171 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) {
172 return
173 }
174
175 return next()
176 }
177]
178
179const videoPlaylistsUpdateOrRemoveVideoValidator = [
180 param('playlistId')
181 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
182 param('videoId')
183 .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'),
184 body('startTimestamp')
185 .optional()
df0b219d 186 .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'),
418d092a
C
187 body('stopTimestamp')
188 .optional()
df0b219d 189 .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid stop timestamp'),
418d092a
C
190
191 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
192 logger.debug('Checking videoPlaylistsRemoveVideoValidator parameters', { parameters: req.params })
193
194 if (areValidationErrors(req, res)) return
195
196 if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
07b1a18a 197 if (!await isVideoExist(req.params.videoId, res, 'id')) return
418d092a
C
198
199 const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
200 const video: VideoModel = res.locals.video
201
202 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id)
203 if (!videoPlaylistElement) {
204 res.status(404)
205 .json({ error: 'Video playlist element not found' })
206 .end()
207
208 return
209 }
210 res.locals.videoPlaylistElement = videoPlaylistElement
211
212 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
213
214 return next()
215 }
216]
217
218const videoPlaylistElementAPGetValidator = [
219 param('playlistId')
220 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
221 param('videoId')
222 .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'),
223
224 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
225 logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params })
226
227 if (areValidationErrors(req, res)) return
228
229 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideoForAP(req.params.playlistId, req.params.videoId)
230 if (!videoPlaylistElement) {
231 res.status(404)
232 .json({ error: 'Video playlist element not found' })
233 .end()
234
235 return
236 }
237
238 if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
239 return res.status(403).end()
240 }
241
242 res.locals.videoPlaylistElement = videoPlaylistElement
243
244 return next()
245 }
246]
247
248const videoPlaylistsReorderVideosValidator = [
249 param('playlistId')
250 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
251 body('startPosition')
252 .isInt({ min: 1 }).withMessage('Should have a valid start position'),
253 body('insertAfterPosition')
254 .isInt({ min: 0 }).withMessage('Should have a valid insert after position'),
255 body('reorderLength')
256 .optional()
257 .isInt({ min: 1 }).withMessage('Should have a valid range length'),
258
259 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
260 logger.debug('Checking videoPlaylistsReorderVideosValidator parameters', { parameters: req.params })
261
262 if (areValidationErrors(req, res)) return
263
264 if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
265
266 const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
267 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
268
07b1a18a
C
269 const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id)
270 const startPosition: number = req.body.startPosition
271 const insertAfterPosition: number = req.body.insertAfterPosition
272 const reorderLength: number = req.body.reorderLength
273
274 if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) {
275 res.status(400)
276 .json({ error: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` })
277 .end()
278
279 return
280 }
281
282 if (reorderLength && reorderLength + startPosition > nextPosition) {
283 res.status(400)
284 .json({ error: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` })
285 .end()
286
287 return
288 }
289
418d092a
C
290 return next()
291 }
292]
293
df0b219d
C
294const commonVideoPlaylistFiltersValidator = [
295 query('playlistType')
296 .optional()
297 .custom(isVideoPlaylistTypeValid).withMessage('Should have a valid playlist type'),
298
299 (req: express.Request, res: express.Response, next: express.NextFunction) => {
300 logger.debug('Checking commonVideoPlaylistFiltersValidator parameters', { parameters: req.params })
301
302 if (areValidationErrors(req, res)) return
303
304 return next()
305 }
306]
307
418d092a
C
308// ---------------------------------------------------------------------------
309
310export {
311 videoPlaylistsAddValidator,
312 videoPlaylistsUpdateValidator,
313 videoPlaylistsDeleteValidator,
314 videoPlaylistsGetValidator,
315
316 videoPlaylistsAddVideoValidator,
317 videoPlaylistsUpdateOrRemoveVideoValidator,
318 videoPlaylistsReorderVideosValidator,
319
df0b219d
C
320 videoPlaylistElementAPGetValidator,
321
322 commonVideoPlaylistFiltersValidator
418d092a
C
323}
324
325// ---------------------------------------------------------------------------
326
327function getCommonPlaylistEditAttributes () {
328 return [
329 body('thumbnailfile')
330 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
331 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: '
332 + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ')
333 ),
334
335 body('displayName')
336 .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'),
337 body('description')
338 .optional()
339 .customSanitizer(toValueOrNull)
340 .custom(isVideoPlaylistDescriptionValid).withMessage('Should have a valid description'),
341 body('privacy')
342 .optional()
343 .toInt()
344 .custom(isVideoPlaylistPrivacyValid).withMessage('Should have correct playlist privacy'),
345 body('videoChannelId')
346 .optional()
347 .toInt()
348 ] as (ValidationChain | express.Handler)[]
349}
350
351function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoPlaylistModel, right: UserRight, res: express.Response) {
352 if (videoPlaylist.isOwned() === false) {
353 res.status(403)
354 .json({ error: 'Cannot manage video playlist of another server.' })
355 .end()
356
357 return false
358 }
359
360 // Check if the user can manage the video playlist
361 // The user can delete it if s/he is an admin
362 // Or if s/he is the video playlist's owner
363 if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) {
364 res.status(403)
365 .json({ error: 'Cannot manage video playlist of another user' })
366 .end()
367
368 return false
369 }
370
371 return true
372}