|
- import express from 'express'
- import { body, param, query, ValidationChain } from 'express-validator'
- import { ExpressPromiseHandler } from '@server/types/express-handler'
- import { MUserAccountId } from '@server/types/models'
- import {
- HttpStatusCode,
- UserRight,
- VideoPlaylistCreate,
- VideoPlaylistPrivacy,
- VideoPlaylistType,
- VideoPlaylistUpdate
- } from '@shared/models'
- import {
- isArrayOf,
- isIdOrUUIDValid,
- isIdValid,
- isUUIDValid,
- toCompleteUUID,
- toIntArray,
- toIntOrNull,
- toValueOrNull
- } from '../../../helpers/custom-validators/misc'
- import {
- isVideoPlaylistDescriptionValid,
- isVideoPlaylistNameValid,
- isVideoPlaylistPrivacyValid,
- isVideoPlaylistTimestampValid,
- isVideoPlaylistTypeValid
- } from '../../../helpers/custom-validators/video-playlists'
- import { isVideoImage } from '../../../helpers/custom-validators/videos'
- import { cleanUpReqFiles } from '../../../helpers/express-utils'
- import { logger } from '../../../helpers/logger'
- import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
- import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
- import { MVideoPlaylist } from '../../../types/models/video/video-playlist'
- import { authenticatePromiseIfNeeded } from '../../auth'
- import {
- areValidationErrors,
- doesVideoChannelIdExist,
- doesVideoExist,
- doesVideoPlaylistExist,
- isValidPlaylistIdParam,
- VideoPlaylistFetchType
- } from '../shared'
-
- const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
- body('displayName')
- .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'),
-
- 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)
-
- const body: VideoPlaylistCreate = req.body
- if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req)
-
- if (
- !body.videoChannelId &&
- (body.privacy === VideoPlaylistPrivacy.PUBLIC || body.privacy === VideoPlaylistPrivacy.UNLISTED)
- ) {
- cleanUpReqFiles(req)
-
- return res.fail({ message: 'Cannot set "public" or "unlisted" a playlist that is not assigned to a channel.' })
- }
-
- return next()
- }
- ])
-
- const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
- isValidPlaylistIdParam('playlistId'),
-
- body('displayName')
- .optional()
- .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'),
-
- 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 doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req)
-
- const videoPlaylist = getPlaylist(res)
-
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
- return cleanUpReqFiles(req)
- }
-
- const body: VideoPlaylistUpdate = req.body
-
- const newPrivacy = body.privacy || videoPlaylist.privacy
- if (newPrivacy === VideoPlaylistPrivacy.PUBLIC &&
- (
- (!videoPlaylist.videoChannelId && !body.videoChannelId) ||
- body.videoChannelId === null
- )
- ) {
- cleanUpReqFiles(req)
-
- return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' })
- }
-
- if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
- cleanUpReqFiles(req)
-
- return res.fail({ message: 'Cannot update a watch later playlist.' })
- }
-
- if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req)
-
- return next()
- }
- ])
-
- const videoPlaylistsDeleteValidator = [
- isValidPlaylistIdParam('playlistId'),
-
- 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 doesVideoPlaylistExist(req.params.playlistId, res)) return
-
- const videoPlaylist = getPlaylist(res)
- if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
- return res.fail({ message: 'Cannot delete a watch later playlist.' })
- }
-
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
- return
- }
-
- return next()
- }
- ]
-
- const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
- return [
- isValidPlaylistIdParam('playlistId'),
-
- 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 doesVideoPlaylistExist(req.params.playlistId, res, fetchType)) return
-
- const videoPlaylist = res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
-
- // Video is unlisted, check we used the uuid to fetch it
- if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
- if (isUUIDValid(req.params.playlistId)) return next()
-
- return res.fail({
- status: HttpStatusCode.NOT_FOUND_404,
- message: 'Playlist not found'
- })
- }
-
- if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
- await authenticatePromiseIfNeeded(req, res)
-
- const user = res.locals.oauth ? res.locals.oauth.token.User : null
-
- if (
- !user ||
- (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
- ) {
- return res.fail({
- status: HttpStatusCode.FORBIDDEN_403,
- message: 'Cannot get this private video playlist.'
- })
- }
-
- return next()
- }
-
- return next()
- }
- ]
- }
-
- const videoPlaylistsSearchValidator = [
- query('search').optional().not().isEmpty().withMessage('Should have a valid search'),
-
- (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking videoPlaylists search query', { parameters: req.query })
-
- if (areValidationErrors(req, res)) return
-
- return next()
- }
- ]
-
- const videoPlaylistsAddVideoValidator = [
- isValidPlaylistIdParam('playlistId'),
-
- body('videoId')
- .customSanitizer(toCompleteUUID)
- .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'),
- body('startTimestamp')
- .optional()
- .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'),
- body('stopTimestamp')
- .optional()
- .custom(isVideoPlaylistTimestampValid).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 doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
- if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
-
- const videoPlaylist = getPlaylist(res)
-
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) {
- return
- }
-
- return next()
- }
- ]
-
- const videoPlaylistsUpdateOrRemoveVideoValidator = [
- isValidPlaylistIdParam('playlistId'),
- param('playlistElementId')
- .customSanitizer(toCompleteUUID)
- .custom(isIdValid).withMessage('Should have an element id/uuid'),
- body('startTimestamp')
- .optional()
- .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'),
- body('stopTimestamp')
- .optional()
- .custom(isVideoPlaylistTimestampValid).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 doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
-
- const videoPlaylist = getPlaylist(res)
-
- const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId)
- if (!videoPlaylistElement) {
- res.fail({
- status: HttpStatusCode.NOT_FOUND_404,
- message: 'Video playlist element not found'
- })
- return
- }
- res.locals.videoPlaylistElement = videoPlaylistElement
-
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
-
- return next()
- }
- ]
-
- const videoPlaylistElementAPGetValidator = [
- isValidPlaylistIdParam('playlistId'),
- param('playlistElementId')
- .custom(isIdValid).withMessage('Should have an playlist element id'),
-
- async (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params })
-
- if (areValidationErrors(req, res)) return
-
- const playlistElementId = parseInt(req.params.playlistElementId + '', 10)
- const playlistId = req.params.playlistId
-
- const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId)
- if (!videoPlaylistElement) {
- res.fail({
- status: HttpStatusCode.NOT_FOUND_404,
- message: 'Video playlist element not found'
- })
- return
- }
-
- if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
- return res.fail({
- status: HttpStatusCode.FORBIDDEN_403,
- message: 'Cannot get this private video playlist.'
- })
- }
-
- res.locals.videoPlaylistElementAP = videoPlaylistElement
-
- return next()
- }
- ]
-
- const videoPlaylistsReorderVideosValidator = [
- isValidPlaylistIdParam('playlistId'),
- 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 doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
-
- const videoPlaylist = getPlaylist(res)
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
-
- const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id)
- const startPosition: number = req.body.startPosition
- const insertAfterPosition: number = req.body.insertAfterPosition
- const reorderLength: number = req.body.reorderLength
-
- if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) {
- res.fail({ message: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` })
- return
- }
-
- if (reorderLength && reorderLength + startPosition > nextPosition) {
- res.fail({ message: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` })
- return
- }
-
- return next()
- }
- ]
-
- const commonVideoPlaylistFiltersValidator = [
- query('playlistType')
- .optional()
- .custom(isVideoPlaylistTypeValid).withMessage('Should have a valid playlist type'),
-
- (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking commonVideoPlaylistFiltersValidator parameters', { parameters: req.params })
-
- if (areValidationErrors(req, res)) return
-
- return next()
- }
- ]
-
- const doVideosInPlaylistExistValidator = [
- query('videoIds')
- .customSanitizer(toIntArray)
- .custom(v => isArrayOf(v, isIdValid)).withMessage('Should have a valid video ids array'),
-
- (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking areVideosInPlaylistExistValidator parameters', { parameters: req.query })
-
- if (areValidationErrors(req, res)) return
-
- return next()
- }
- ]
-
- // ---------------------------------------------------------------------------
-
- export {
- videoPlaylistsAddValidator,
- videoPlaylistsUpdateValidator,
- videoPlaylistsDeleteValidator,
- videoPlaylistsGetValidator,
- videoPlaylistsSearchValidator,
-
- videoPlaylistsAddVideoValidator,
- videoPlaylistsUpdateOrRemoveVideoValidator,
- videoPlaylistsReorderVideosValidator,
-
- videoPlaylistElementAPGetValidator,
-
- commonVideoPlaylistFiltersValidator,
-
- doVideosInPlaylistExistValidator
- }
-
- // ---------------------------------------------------------------------------
-
- 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('description')
- .optional()
- .customSanitizer(toValueOrNull)
- .custom(isVideoPlaylistDescriptionValid).withMessage('Should have a valid description'),
- body('privacy')
- .optional()
- .customSanitizer(toIntOrNull)
- .custom(isVideoPlaylistPrivacyValid).withMessage('Should have correct playlist privacy'),
- body('videoChannelId')
- .optional()
- .customSanitizer(toIntOrNull)
- ] as (ValidationChain | ExpressPromiseHandler)[]
- }
-
- function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) {
- if (videoPlaylist.isOwned() === false) {
- res.fail({
- status: HttpStatusCode.FORBIDDEN_403,
- message: 'Cannot manage video playlist of another server.'
- })
- 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.fail({
- status: HttpStatusCode.FORBIDDEN_403,
- message: 'Cannot manage video playlist of another user'
- })
- return false
- }
-
- return true
- }
-
- function getPlaylist (res: express.Response) {
- return res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
- }
|