From 418d092afa81e2c8fe8ac6838fc4b5eb0af6a782 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 26 Feb 2019 10:55:40 +0100 Subject: Playlist server API --- .../validators/videos/video-channels.ts | 14 - .../middlewares/validators/videos/video-imports.ts | 4 +- .../validators/videos/video-playlists.ts | 302 +++++++++++++++++++++ server/middlewares/validators/videos/videos.ts | 8 +- 4 files changed, 308 insertions(+), 20 deletions(-) create mode 100644 server/middlewares/validators/videos/video-playlists.ts (limited to 'server/middlewares/validators/videos') diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index f039794e0..c2763ce51 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts @@ -16,19 +16,6 @@ import { areValidationErrors } from '../utils' import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor' -const listVideoAccountChannelsValidator = [ - param('accountName').exists().withMessage('Should have a valid account name'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isAccountNameWithHostExist(req.params.accountName, res)) return - - return next() - } -] - const videoChannelsAddValidator = [ body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), @@ -127,7 +114,6 @@ const localVideoChannelValidator = [ // --------------------------------------------------------------------------- export { - listVideoAccountChannelsValidator, videoChannelsAddValidator, videoChannelsUpdateValidator, videoChannelsRemoveValidator, diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts index 48d20f904..121df36b6 100644 --- a/server/middlewares/validators/videos/video-imports.ts +++ b/server/middlewares/validators/videos/video-imports.ts @@ -3,14 +3,14 @@ import { body } from 'express-validator/check' import { isIdValid } from '../../../helpers/custom-validators/misc' import { logger } from '../../../helpers/logger' import { areValidationErrors } from '../utils' -import { getCommonVideoAttributes } from './videos' +import { getCommonVideoEditAttributes } from './videos' import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' import { cleanUpReqFiles } from '../../../helpers/express-utils' import { isVideoChannelOfAccountExist, isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' import { CONFIG } from '../../../initializers/constants' import { CONSTRAINTS_FIELDS } from '../../../initializers' -const videoImportAddValidator = getCommonVideoAttributes().concat([ +const videoImportAddValidator = getCommonVideoEditAttributes().concat([ body('channelId') .toInt() .custom(isIdValid).withMessage('Should have correct video channel id'), diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts new file mode 100644 index 000000000..ef8d0b851 --- /dev/null +++ b/server/middlewares/validators/videos/video-playlists.ts @@ -0,0 +1,302 @@ +import * as express from 'express' +import { body, param, ValidationChain } from 'express-validator/check' +import { UserRight, VideoPrivacy } from '../../../../shared' +import { logger } from '../../../helpers/logger' +import { UserModel } from '../../../models/account/user' +import { areValidationErrors } from '../utils' +import { isVideoExist, isVideoImage } from '../../../helpers/custom-validators/videos' +import { CONSTRAINTS_FIELDS } from '../../../initializers' +import { isIdOrUUIDValid, toValueOrNull } from '../../../helpers/custom-validators/misc' +import { + isVideoPlaylistDescriptionValid, + isVideoPlaylistExist, + isVideoPlaylistNameValid, + isVideoPlaylistPrivacyValid +} from '../../../helpers/custom-validators/video-playlists' +import { VideoPlaylistModel } from '../../../models/video/video-playlist' +import { cleanUpReqFiles } from '../../../helpers/express-utils' +import { isVideoChannelIdExist } from '../../../helpers/custom-validators/video-channels' +import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' +import { VideoModel } from '../../../models/video/video' +import { authenticatePromiseIfNeeded } from '../../oauth' +import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' + +const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistsAddValidator parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return cleanUpReqFiles(req) + + if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req) + + return next() + } +]) + +const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ + param('playlistId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistsUpdateValidator parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return cleanUpReqFiles(req) + + if (!await isVideoPlaylistExist(req.params.playlistId, res)) return cleanUpReqFiles(req) + if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { + return cleanUpReqFiles(req) + } + + if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req) + + return next() + } +]) + +const videoPlaylistsDeleteValidator = [ + param('playlistId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + if (!await isVideoPlaylistExist(req.params.playlistId, res)) return + if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { + return + } + + return next() + } +] + +const videoPlaylistsGetValidator = [ + param('playlistId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + if (!await isVideoPlaylistExist(req.params.playlistId, res)) return + + const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist + if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { + await authenticatePromiseIfNeeded(req, res) + + const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : null + + if ( + !user || + (videoPlaylist.OwnerAccount.userId !== user.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) + ) { + return res.status(403) + .json({ error: 'Cannot get this private video playlist.' }) + } + + return next() + } + + return next() + } +] + +const videoPlaylistsAddVideoValidator = [ + param('playlistId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), + body('videoId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'), + body('startTimestamp') + .optional() + .isInt({ min: 0 }).withMessage('Should have a valid start timestamp'), + body('stopTimestamp') + .optional() + .isInt({ min: 0 }).withMessage('Should have a valid stop timestamp'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistsAddVideoValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + if (!await isVideoPlaylistExist(req.params.playlistId, res)) return + if (!await isVideoExist(req.body.videoId, res, 'id')) return + + const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist + const video: VideoModel = res.locals.video + + const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) + if (videoPlaylistElement) { + res.status(409) + .json({ error: 'This video in this playlist already exists' }) + .end() + + return + } + + if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { + return + } + + return next() + } +] + +const videoPlaylistsUpdateOrRemoveVideoValidator = [ + param('playlistId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), + param('videoId') + .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'), + body('startTimestamp') + .optional() + .isInt({ min: 0 }).withMessage('Should have a valid start timestamp'), + body('stopTimestamp') + .optional() + .isInt({ min: 0 }).withMessage('Should have a valid stop timestamp'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistsRemoveVideoValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + if (!await isVideoPlaylistExist(req.params.playlistId, res)) return + if (!await isVideoExist(req.params.playlistId, res, 'id')) return + + const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist + const video: VideoModel = res.locals.video + + const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) + if (!videoPlaylistElement) { + res.status(404) + .json({ error: 'Video playlist element not found' }) + .end() + + return + } + res.locals.videoPlaylistElement = videoPlaylistElement + + if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return + + return next() + } +] + +const videoPlaylistElementAPGetValidator = [ + param('playlistId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), + param('videoId') + .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideoForAP(req.params.playlistId, req.params.videoId) + if (!videoPlaylistElement) { + res.status(404) + .json({ error: 'Video playlist element not found' }) + .end() + + return + } + + if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { + return res.status(403).end() + } + + res.locals.videoPlaylistElement = videoPlaylistElement + + return next() + } +] + +const videoPlaylistsReorderVideosValidator = [ + param('playlistId') + .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), + body('startPosition') + .isInt({ min: 1 }).withMessage('Should have a valid start position'), + body('insertAfterPosition') + .isInt({ min: 0 }).withMessage('Should have a valid insert after position'), + body('reorderLength') + .optional() + .isInt({ min: 1 }).withMessage('Should have a valid range length'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoPlaylistsReorderVideosValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + if (!await isVideoPlaylistExist(req.params.playlistId, res)) return + + const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist + if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return + + return next() + } +] + +// --------------------------------------------------------------------------- + +export { + videoPlaylistsAddValidator, + videoPlaylistsUpdateValidator, + videoPlaylistsDeleteValidator, + videoPlaylistsGetValidator, + + videoPlaylistsAddVideoValidator, + videoPlaylistsUpdateOrRemoveVideoValidator, + videoPlaylistsReorderVideosValidator, + + videoPlaylistElementAPGetValidator +} + +// --------------------------------------------------------------------------- + +function getCommonPlaylistEditAttributes () { + return [ + body('thumbnailfile') + .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( + 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' + + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') + ), + + body('displayName') + .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'), + body('description') + .optional() + .customSanitizer(toValueOrNull) + .custom(isVideoPlaylistDescriptionValid).withMessage('Should have a valid description'), + body('privacy') + .optional() + .toInt() + .custom(isVideoPlaylistPrivacyValid).withMessage('Should have correct playlist privacy'), + body('videoChannelId') + .optional() + .toInt() + ] as (ValidationChain | express.Handler)[] +} + +function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoPlaylistModel, right: UserRight, res: express.Response) { + if (videoPlaylist.isOwned() === false) { + res.status(403) + .json({ error: 'Cannot manage video playlist of another server.' }) + .end() + + return false + } + + // Check if the user can manage the video playlist + // The user can delete it if s/he is an admin + // Or if s/he is the video playlist's owner + if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) { + res.status(403) + .json({ error: 'Cannot manage video playlist of another user' }) + .end() + + return false + } + + return true +} diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 159727e28..a5e3ed0dc 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts @@ -46,7 +46,7 @@ import { VideoFetchType } from '../../../helpers/video' import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' import { getServerActor } from '../../../helpers/utils' -const videosAddValidator = getCommonVideoAttributes().concat([ +const videosAddValidator = getCommonVideoEditAttributes().concat([ body('videofile') .custom((value, { req }) => isVideoFile(req.files)).withMessage( 'This file is not supported or too large. Please, make sure it is of the following type: ' @@ -94,7 +94,7 @@ const videosAddValidator = getCommonVideoAttributes().concat([ } ]) -const videosUpdateValidator = getCommonVideoAttributes().concat([ +const videosUpdateValidator = getCommonVideoEditAttributes().concat([ param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), body('name') .optional() @@ -288,7 +288,7 @@ const videosAcceptChangeOwnershipValidator = [ } ] -function getCommonVideoAttributes () { +function getCommonVideoEditAttributes () { return [ body('thumbnailfile') .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( @@ -421,7 +421,7 @@ export { videosTerminateChangeOwnershipValidator, videosAcceptChangeOwnershipValidator, - getCommonVideoAttributes, + getCommonVideoEditAttributes, commonVideosFiltersValidator } -- cgit v1.2.3