Add playlist rest tests
This commit is contained in:
parent
07b1a18aa6
commit
df0b219d36
35 changed files with 1485 additions and 756 deletions
|
@ -320,7 +320,10 @@ async function videoRedundancyController (req: express.Request, res: express.Res
|
|||
async function videoPlaylistController (req: express.Request, res: express.Response) {
|
||||
const playlist: VideoPlaylistModel = res.locals.videoPlaylist
|
||||
|
||||
const json = await playlist.toActivityPubObject()
|
||||
// We need more attributes
|
||||
playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId)
|
||||
|
||||
const json = await playlist.toActivityPubObject(req.query.page, null)
|
||||
const audience = getAudience(playlist.OwnerAccount.Actor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
|
||||
const object = audiencify(json, audience)
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import { JobQueue } from '../../lib/job-queue'
|
|||
import { logger } from '../../helpers/logger'
|
||||
import { VideoPlaylistModel } from '../../models/video/video-playlist'
|
||||
import { UserModel } from '../../models/account/user'
|
||||
import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
|
||||
|
||||
const accountsRouter = express.Router()
|
||||
|
||||
|
@ -57,6 +58,7 @@ accountsRouter.get('/:accountName/video-playlists',
|
|||
videoPlaylistsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
commonVideoPlaylistFiltersValidator,
|
||||
asyncMiddleware(listAccountPlaylists)
|
||||
)
|
||||
|
||||
|
@ -106,7 +108,8 @@ async function listAccountPlaylists (req: express.Request, res: express.Response
|
|||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
accountId: res.locals.account.id,
|
||||
privateAndUnlisted
|
||||
privateAndUnlisted,
|
||||
type: req.query.playlistType
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
|
|
|
@ -6,7 +6,7 @@ import { getFormattedObjects } from '../../../helpers/utils'
|
|||
import { CONFIG, RATES_LIMIT, sequelizeTypescript } from '../../../initializers'
|
||||
import { Emailer } from '../../../lib/emailer'
|
||||
import { Redis } from '../../../lib/redis'
|
||||
import { createUserAccountAndChannel } from '../../../lib/user'
|
||||
import { createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
|
@ -174,7 +174,7 @@ async function createUser (req: express.Request, res: express.Response) {
|
|||
videoQuotaDaily: body.videoQuotaDaily
|
||||
})
|
||||
|
||||
const { user, account } = await createUserAccountAndChannel(userToCreate)
|
||||
const { user, account } = await createUserAccountAndChannelAndPlaylist(userToCreate)
|
||||
|
||||
auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
|
||||
logger.info('User %s with its channel and account created.', body.username)
|
||||
|
@ -205,7 +205,7 @@ async function registerUser (req: express.Request, res: express.Response) {
|
|||
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
|
||||
})
|
||||
|
||||
const { user } = await createUserAccountAndChannel(userToCreate)
|
||||
const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate)
|
||||
|
||||
auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
|
||||
logger.info('User %s with its channel and account registered.', body.username)
|
||||
|
|
|
@ -33,6 +33,7 @@ import { resetSequelizeInstance } from '../../helpers/database-utils'
|
|||
import { UserModel } from '../../models/account/user'
|
||||
import { JobQueue } from '../../lib/job-queue'
|
||||
import { VideoPlaylistModel } from '../../models/video/video-playlist'
|
||||
import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
|
||||
|
||||
const auditLogger = auditLoggerFactory('channels')
|
||||
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
|
||||
|
@ -85,6 +86,7 @@ videoChannelRouter.get('/:nameWithHost/video-playlists',
|
|||
videoPlaylistsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
commonVideoPlaylistFiltersValidator,
|
||||
asyncMiddleware(listVideoChannelPlaylists)
|
||||
)
|
||||
|
||||
|
@ -197,6 +199,8 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
|
|||
const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
await VideoPlaylistModel.resetPlaylistsOfChannel(videoChannelInstance.id, t)
|
||||
|
||||
await videoChannelInstance.destroy({ transaction: t })
|
||||
|
||||
auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()))
|
||||
|
@ -225,7 +229,8 @@ async function listVideoChannelPlaylists (req: express.Request, res: express.Res
|
|||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
videoChannelId: res.locals.videoChannel.id
|
||||
videoChannelId: res.locals.videoChannel.id,
|
||||
type: req.query.playlistType
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
|
|
|
@ -17,6 +17,7 @@ import { logger } from '../../helpers/logger'
|
|||
import { resetSequelizeInstance } from '../../helpers/database-utils'
|
||||
import { VideoPlaylistModel } from '../../models/video/video-playlist'
|
||||
import {
|
||||
commonVideoPlaylistFiltersValidator,
|
||||
videoPlaylistsAddValidator,
|
||||
videoPlaylistsAddVideoValidator,
|
||||
videoPlaylistsDeleteValidator,
|
||||
|
@ -45,6 +46,7 @@ import { VideoPlaylistElementModel } from '../../models/video/video-playlist-ele
|
|||
import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model'
|
||||
import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model'
|
||||
import { copy, pathExists } from 'fs-extra'
|
||||
import { AccountModel } from '../../models/account/account'
|
||||
|
||||
const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
|
||||
|
||||
|
@ -55,6 +57,7 @@ videoPlaylistRouter.get('/',
|
|||
videoPlaylistsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
commonVideoPlaylistFiltersValidator,
|
||||
asyncMiddleware(listVideoPlaylists)
|
||||
)
|
||||
|
||||
|
@ -130,7 +133,8 @@ async function listVideoPlaylists (req: express.Request, res: express.Response)
|
|||
followerActorId: serverActor.id,
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort
|
||||
sort: req.query.sort,
|
||||
type: req.query.type
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
|
@ -171,7 +175,8 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
|
|||
const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
|
||||
const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
|
||||
|
||||
videoPlaylistCreated.OwnerAccount = user.Account
|
||||
// We need more attributes for the federation
|
||||
videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t)
|
||||
await sendCreateVideoPlaylist(videoPlaylistCreated, t)
|
||||
|
||||
return videoPlaylistCreated
|
||||
|
@ -216,6 +221,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
|
|||
const videoChannel = res.locals.videoChannel as VideoChannelModel
|
||||
|
||||
videoPlaylistInstance.videoChannelId = videoChannel.id
|
||||
videoPlaylistInstance.VideoChannel = videoChannel
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,6 +233,8 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
|
|||
}
|
||||
|
||||
const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
|
||||
// We need more attributes for the federation
|
||||
playlistUpdated.OwnerAccount = await AccountModel.load(playlistUpdated.OwnerAccount.id, t)
|
||||
|
||||
const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE
|
||||
|
||||
|
@ -290,11 +298,15 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
|
|||
const playlistThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoPlaylist.getThumbnailName())
|
||||
|
||||
if (await pathExists(playlistThumbnailPath) === false) {
|
||||
logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
|
||||
|
||||
const videoThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
|
||||
await copy(videoThumbnailPath, playlistThumbnailPath)
|
||||
}
|
||||
}
|
||||
|
||||
// We need more attributes for the federation
|
||||
videoPlaylist.OwnerAccount = await AccountModel.load(videoPlaylist.OwnerAccount.id, t)
|
||||
await sendUpdateVideoPlaylist(videoPlaylist, t)
|
||||
|
||||
return playlistElement
|
||||
|
@ -320,6 +332,8 @@ async function updateVideoPlaylistElement (req: express.Request, res: express.Re
|
|||
|
||||
const element = await videoPlaylistElement.save({ transaction: t })
|
||||
|
||||
// We need more attributes for the federation
|
||||
videoPlaylist.OwnerAccount = await AccountModel.load(videoPlaylist.OwnerAccount.id, t)
|
||||
await sendUpdateVideoPlaylist(videoPlaylist, t)
|
||||
|
||||
return element
|
||||
|
@ -341,6 +355,8 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo
|
|||
// Decrease position of the next elements
|
||||
await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, positionToDelete, null, -1, t)
|
||||
|
||||
// We need more attributes for the federation
|
||||
videoPlaylist.OwnerAccount = await AccountModel.load(videoPlaylist.OwnerAccount.id, t)
|
||||
await sendUpdateVideoPlaylist(videoPlaylist, t)
|
||||
|
||||
logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid)
|
||||
|
@ -382,6 +398,8 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons
|
|||
// Decrease positions of elements after the old position of our ordered elements (decrease)
|
||||
await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, null, -reorderLength, t)
|
||||
|
||||
// We need more attributes for the federation
|
||||
videoPlaylist.OwnerAccount = await AccountModel.load(videoPlaylist.OwnerAccount.id, t)
|
||||
await sendUpdateVideoPlaylist(videoPlaylist, t)
|
||||
})
|
||||
|
||||
|
@ -415,5 +433,6 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon
|
|||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
const additionalAttributes = { playlistInfo: true }
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { exists } from '../misc'
|
||||
import { exists, isDateValid } from '../misc'
|
||||
import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
|
||||
import * as validator from 'validator'
|
||||
import { PlaylistElementObject } from '../../../../shared/models/activitypub/objects/playlist-element-object'
|
||||
|
@ -7,7 +7,9 @@ import { isActivityPubUrlValid } from './misc'
|
|||
function isPlaylistObjectValid (object: PlaylistObject) {
|
||||
return exists(object) &&
|
||||
object.type === 'Playlist' &&
|
||||
validator.isInt(object.totalItems + '')
|
||||
validator.isInt(object.totalItems + '') &&
|
||||
isDateValid(object.published) &&
|
||||
isDateValid(object.updated)
|
||||
}
|
||||
|
||||
function isPlaylistElementObjectValid (object: PlaylistElementObject) {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { exists } from './misc'
|
||||
import * as validator from 'validator'
|
||||
import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers'
|
||||
import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers'
|
||||
import * as express from 'express'
|
||||
import { VideoPlaylistModel } from '../../models/video/video-playlist'
|
||||
import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
|
||||
|
||||
const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS
|
||||
|
||||
|
@ -19,8 +18,16 @@ function isVideoPlaylistPrivacyValid (value: number) {
|
|||
return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[ value ] !== undefined
|
||||
}
|
||||
|
||||
function isVideoPlaylistTimestampValid (value: any) {
|
||||
return value === null || (exists(value) && validator.isInt('' + value, { min: 0 }))
|
||||
}
|
||||
|
||||
function isVideoPlaylistTypeValid (value: any) {
|
||||
return exists(value) && VIDEO_PLAYLIST_TYPES[ value ] !== undefined
|
||||
}
|
||||
|
||||
async function isVideoPlaylistExist (id: number | string, res: express.Response) {
|
||||
const videoPlaylist = await VideoPlaylistModel.load(id, undefined)
|
||||
const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined)
|
||||
|
||||
if (!videoPlaylist) {
|
||||
res.status(404)
|
||||
|
@ -40,5 +47,7 @@ export {
|
|||
isVideoPlaylistExist,
|
||||
isVideoPlaylistNameValid,
|
||||
isVideoPlaylistDescriptionValid,
|
||||
isVideoPlaylistPrivacyValid
|
||||
isVideoPlaylistPrivacyValid,
|
||||
isVideoPlaylistTimestampValid,
|
||||
isVideoPlaylistTypeValid
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { invert } from 'lodash'
|
|||
import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
|
||||
import * as bytes from 'bytes'
|
||||
import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||
import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
|
||||
|
||||
// Use a variable to reload the configuration if we need
|
||||
let config: IConfig = require('config')
|
||||
|
@ -522,6 +523,11 @@ const VIDEO_PLAYLIST_PRIVACIES = {
|
|||
[VideoPlaylistPrivacy.PRIVATE]: 'Private'
|
||||
}
|
||||
|
||||
const VIDEO_PLAYLIST_TYPES = {
|
||||
[VideoPlaylistType.REGULAR]: 'Regular',
|
||||
[VideoPlaylistType.WATCH_LATER]: 'Watch later'
|
||||
}
|
||||
|
||||
const MIMETYPES = {
|
||||
VIDEO: {
|
||||
MIMETYPE_EXT: buildVideoMimetypeExt(),
|
||||
|
@ -778,6 +784,7 @@ export {
|
|||
STATIC_MAX_AGE,
|
||||
STATIC_PATHS,
|
||||
VIDEO_IMPORT_TIMEOUT,
|
||||
VIDEO_PLAYLIST_TYPES,
|
||||
ACTIVITY_PUB,
|
||||
ACTIVITY_PUB_ACTOR_TYPES,
|
||||
THUMBNAILS_SIZE,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as passwordGenerator from 'password-generator'
|
||||
import { UserRole } from '../../shared'
|
||||
import { logger } from '../helpers/logger'
|
||||
import { createApplicationActor, createUserAccountAndChannel } from '../lib/user'
|
||||
import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user'
|
||||
import { UserModel } from '../models/account/user'
|
||||
import { ApplicationModel } from '../models/application/application'
|
||||
import { OAuthClientModel } from '../models/oauth/oauth-client'
|
||||
|
@ -141,7 +141,7 @@ async function createOAuthAdminIfNotExist () {
|
|||
}
|
||||
const user = new UserModel(userData)
|
||||
|
||||
await createUserAccountAndChannel(user, validatePassword)
|
||||
await createUserAccountAndChannelAndPlaylist(user, validatePassword)
|
||||
logger.info('Username: ' + username)
|
||||
logger.info('User password: ' + password)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,9 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount
|
|||
url: playlistObject.id,
|
||||
uuid: playlistObject.uuid,
|
||||
ownerAccountId: byAccount.id,
|
||||
videoChannelId: null
|
||||
videoChannelId: null,
|
||||
createdAt: new Date(playlistObject.published),
|
||||
updatedAt: new Date(playlistObject.updated)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { VideoModel } from '../../../models/video/video'
|
|||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||
import { VideoCommentModel } from '../../../models/video/video-comment'
|
||||
import { forwardVideoRelatedActivity } from '../send/utils'
|
||||
import { VideoPlaylistModel } from '../../../models/video/video-playlist'
|
||||
|
||||
async function processDeleteActivity (activity: ActivityDelete, byActor: ActorModel) {
|
||||
const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
|
||||
|
@ -45,6 +46,15 @@ async function processDeleteActivity (activity: ActivityDelete, byActor: ActorMo
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
const videoPlaylist = await VideoPlaylistModel.loadByUrlAndPopulateAccount(objectUrl)
|
||||
if (videoPlaylist) {
|
||||
if (videoPlaylist.isOwned()) throw new Error(`Remote instance cannot delete owned playlist ${videoPlaylist.url}.`)
|
||||
|
||||
return retryTransactionWrapper(processDeleteVideoPlaylist, byActor, videoPlaylist)
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
@ -70,6 +80,20 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel)
|
|||
logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
|
||||
}
|
||||
|
||||
async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) {
|
||||
logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
if (playlistToDelete.OwnerAccount.Actor.id !== actor.id) {
|
||||
throw new Error('Account ' + actor.url + ' does not own video playlist ' + playlistToDelete.url)
|
||||
}
|
||||
|
||||
await playlistToDelete.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid)
|
||||
}
|
||||
|
||||
async function processDeleteAccount (accountToRemove: AccountModel) {
|
||||
logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid)
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transac
|
|||
const byActor = playlist.OwnerAccount.Actor
|
||||
const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
|
||||
|
||||
const object = await playlist.toActivityPubObject()
|
||||
const object = await playlist.toActivityPubObject(null, t)
|
||||
const createActivity = buildCreateActivity(playlist.url, byActor, object, audience)
|
||||
|
||||
const serverActor = await getServerActor()
|
||||
|
|
|
@ -31,7 +31,12 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
|
|||
const url = getDeleteActivityPubUrl(byActor.url)
|
||||
const activity = buildDeleteActivity(url, byActor.url, byActor)
|
||||
|
||||
const actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t)
|
||||
const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
|
||||
|
||||
// In case the actor did not have any videos
|
||||
const serverActor = await getServerActor()
|
||||
actorsInvolved.push(serverActor)
|
||||
|
||||
actorsInvolved.push(byActor)
|
||||
|
||||
return broadcastToFollowers(activity, byActor, actorsInvolved, t)
|
||||
|
|
|
@ -52,7 +52,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
|
|||
let actorsInvolved: ActorModel[]
|
||||
if (accountOrChannel instanceof AccountModel) {
|
||||
// Actors that shared my videos are involved too
|
||||
actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t)
|
||||
actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
|
||||
} else {
|
||||
// Actors that shared videos of my channel are involved too
|
||||
actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t)
|
||||
|
@ -87,7 +87,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Tr
|
|||
|
||||
const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString())
|
||||
|
||||
const object = await videoPlaylist.toActivityPubObject()
|
||||
const object = await videoPlaylist.toActivityPubObject(null, t)
|
||||
const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC)
|
||||
|
||||
const updateActivity = buildUpdateActivity(url, byActor, object, audience)
|
||||
|
|
|
@ -11,8 +11,9 @@ import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
|
|||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
|
||||
import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
|
||||
import { createWatchLaterPlaylist } from './video-playlist'
|
||||
|
||||
async function createUserAccountAndChannel (userToCreate: UserModel, validateUser = true) {
|
||||
async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, validateUser = true) {
|
||||
const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
|
||||
const userOptions = {
|
||||
transaction: t,
|
||||
|
@ -38,7 +39,9 @@ async function createUserAccountAndChannel (userToCreate: UserModel, validateUse
|
|||
}
|
||||
const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t)
|
||||
|
||||
return { user: userCreated, account: accountCreated, videoChannel }
|
||||
const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
|
||||
|
||||
return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist }
|
||||
})
|
||||
|
||||
const [ accountKeys, channelKeys ] = await Promise.all([
|
||||
|
@ -89,7 +92,7 @@ async function createApplicationActor (applicationId: number) {
|
|||
|
||||
export {
|
||||
createApplicationActor,
|
||||
createUserAccountAndChannel,
|
||||
createUserAccountAndChannelAndPlaylist,
|
||||
createLocalAccountWithoutKeys
|
||||
}
|
||||
|
||||
|
|
29
server/lib/video-playlist.ts
Normal file
29
server/lib/video-playlist.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { VideoPlaylistModel } from '../models/video/video-playlist'
|
||||
import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||
import { getVideoPlaylistActivityPubUrl } from './activitypub'
|
||||
import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
|
||||
|
||||
async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) {
|
||||
const videoPlaylist = new VideoPlaylistModel({
|
||||
name: 'Watch later',
|
||||
privacy: VideoPlaylistPrivacy.PRIVATE,
|
||||
type: VideoPlaylistType.WATCH_LATER,
|
||||
ownerAccountId: account.id
|
||||
})
|
||||
|
||||
videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object
|
||||
|
||||
await videoPlaylist.save({ transaction: t })
|
||||
|
||||
videoPlaylist.OwnerAccount = account
|
||||
|
||||
return videoPlaylist
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
createWatchLaterPlaylist
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import * as express from 'express'
|
||||
import { body, param, ValidationChain } from 'express-validator/check'
|
||||
import { UserRight, VideoPrivacy } from '../../../../shared'
|
||||
import { body, param, query, ValidationChain } from 'express-validator/check'
|
||||
import { UserRight } from '../../../../shared'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
import { UserModel } from '../../../models/account/user'
|
||||
import { areValidationErrors } from '../utils'
|
||||
|
@ -11,7 +11,9 @@ import {
|
|||
isVideoPlaylistDescriptionValid,
|
||||
isVideoPlaylistExist,
|
||||
isVideoPlaylistNameValid,
|
||||
isVideoPlaylistPrivacyValid
|
||||
isVideoPlaylistPrivacyValid,
|
||||
isVideoPlaylistTimestampValid,
|
||||
isVideoPlaylistTypeValid
|
||||
} from '../../../helpers/custom-validators/video-playlists'
|
||||
import { VideoPlaylistModel } from '../../../models/video/video-playlist'
|
||||
import { cleanUpReqFiles } from '../../../helpers/express-utils'
|
||||
|
@ -20,6 +22,7 @@ import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-
|
|||
import { VideoModel } from '../../../models/video/video'
|
||||
import { authenticatePromiseIfNeeded } from '../../oauth'
|
||||
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||
import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
|
||||
|
||||
const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -56,6 +59,12 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
|
|||
.json({ error: 'Cannot set "private" a video playlist that was not private.' })
|
||||
}
|
||||
|
||||
if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
|
||||
cleanUpReqFiles(req)
|
||||
return res.status(409)
|
||||
.json({ error: 'Cannot update a watch later playlist.' })
|
||||
}
|
||||
|
||||
if (req.body.videoChannelId && !await isVideoChannelIdExist(req.body.videoChannelId, res)) return cleanUpReqFiles(req)
|
||||
|
||||
return next()
|
||||
|
@ -72,6 +81,13 @@ const videoPlaylistsDeleteValidator = [
|
|||
if (areValidationErrors(req, res)) return
|
||||
|
||||
if (!await isVideoPlaylistExist(req.params.playlistId, res)) return
|
||||
|
||||
const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist
|
||||
if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
|
||||
return res.status(409)
|
||||
.json({ error: 'Cannot delete a watch later playlist.' })
|
||||
}
|
||||
|
||||
if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
|
||||
return
|
||||
}
|
||||
|
@ -127,10 +143,10 @@ const videoPlaylistsAddVideoValidator = [
|
|||
.custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'),
|
||||
body('startTimestamp')
|
||||
.optional()
|
||||
.isInt({ min: 0 }).withMessage('Should have a valid start timestamp'),
|
||||
.custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'),
|
||||
body('stopTimestamp')
|
||||
.optional()
|
||||
.isInt({ min: 0 }).withMessage('Should have a valid stop timestamp'),
|
||||
.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 })
|
||||
|
@ -167,10 +183,10 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [
|
|||
.custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'),
|
||||
body('startTimestamp')
|
||||
.optional()
|
||||
.isInt({ min: 0 }).withMessage('Should have a valid start timestamp'),
|
||||
.custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'),
|
||||
body('stopTimestamp')
|
||||
.optional()
|
||||
.isInt({ min: 0 }).withMessage('Should have a valid stop timestamp'),
|
||||
.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 })
|
||||
|
@ -275,6 +291,20 @@ const videoPlaylistsReorderVideosValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
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()
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -287,7 +317,9 @@ export {
|
|||
videoPlaylistsUpdateOrRemoveVideoValidator,
|
||||
videoPlaylistsReorderVideosValidator,
|
||||
|
||||
videoPlaylistElementAPGetValidator
|
||||
videoPlaylistElementAPGetValidator,
|
||||
|
||||
commonVideoPlaylistFiltersValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -67,9 +67,9 @@ type AvailableForListOptions = {
|
|||
]
|
||||
})
|
||||
@Scopes({
|
||||
[ScopeNames.SUMMARY]: (required: boolean, withAccount: boolean) => {
|
||||
[ScopeNames.SUMMARY]: (withAccount = false) => {
|
||||
const base: IFindOptions<VideoChannelModel> = {
|
||||
attributes: [ 'name', 'description', 'id' ],
|
||||
attributes: [ 'name', 'description', 'id', 'actorId' ],
|
||||
include: [
|
||||
{
|
||||
attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
|
||||
|
@ -225,7 +225,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
|
|||
foreignKey: {
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade',
|
||||
onDelete: 'CASCADE',
|
||||
hooks: true
|
||||
})
|
||||
VideoPlaylists: VideoPlaylistModel[]
|
||||
|
|
|
@ -20,6 +20,7 @@ import { getSort, throwIfNotValid } from '../utils'
|
|||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
|
||||
import * as validator from 'validator'
|
||||
|
||||
@Table({
|
||||
tableName: 'videoPlaylistElement',
|
||||
|
@ -34,10 +35,6 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object
|
|||
fields: [ 'videoPlaylistId', 'videoId' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'videoPlaylistId', 'position' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'url' ],
|
||||
unique: true
|
||||
|
@ -143,7 +140,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
|
|||
return VideoPlaylistElementModel.findOne(query)
|
||||
}
|
||||
|
||||
static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number) {
|
||||
static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number, t?: Sequelize.Transaction) {
|
||||
const query = {
|
||||
attributes: [ 'url' ],
|
||||
offset: start,
|
||||
|
@ -151,7 +148,8 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
|
|||
order: getSort('position'),
|
||||
where: {
|
||||
videoPlaylistId
|
||||
}
|
||||
},
|
||||
transaction: t
|
||||
}
|
||||
|
||||
return VideoPlaylistElementModel
|
||||
|
|
|
@ -24,7 +24,14 @@ import {
|
|||
isVideoPlaylistPrivacyValid
|
||||
} from '../../helpers/custom-validators/video-playlists'
|
||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||
import { CONFIG, CONSTRAINTS_FIELDS, STATIC_PATHS, THUMBNAILS_SIZE, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers'
|
||||
import {
|
||||
CONFIG,
|
||||
CONSTRAINTS_FIELDS,
|
||||
STATIC_PATHS,
|
||||
THUMBNAILS_SIZE,
|
||||
VIDEO_PLAYLIST_PRIVACIES,
|
||||
VIDEO_PLAYLIST_TYPES
|
||||
} from '../../initializers'
|
||||
import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
|
||||
import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
|
||||
import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
|
||||
|
@ -34,22 +41,25 @@ import { PlaylistObject } from '../../../shared/models/activitypub/objects/playl
|
|||
import { activityPubCollectionPagination } from '../../helpers/activitypub'
|
||||
import { remove } from 'fs-extra'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
|
||||
|
||||
enum ScopeNames {
|
||||
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
|
||||
WITH_VIDEOS_LENGTH = 'WITH_VIDEOS_LENGTH',
|
||||
WITH_ACCOUNT_AND_CHANNEL = 'WITH_ACCOUNT_AND_CHANNEL'
|
||||
WITH_ACCOUNT_AND_CHANNEL_SUMMARY = 'WITH_ACCOUNT_AND_CHANNEL_SUMMARY',
|
||||
WITH_ACCOUNT = 'WITH_ACCOUNT'
|
||||
}
|
||||
|
||||
type AvailableForListOptions = {
|
||||
followerActorId: number
|
||||
accountId?: number,
|
||||
type?: VideoPlaylistType
|
||||
accountId?: number
|
||||
videoChannelId?: number
|
||||
privateAndUnlisted?: boolean
|
||||
}
|
||||
|
||||
@Scopes({
|
||||
[ScopeNames.WITH_VIDEOS_LENGTH]: {
|
||||
[ ScopeNames.WITH_VIDEOS_LENGTH ]: {
|
||||
attributes: {
|
||||
include: [
|
||||
[
|
||||
|
@ -59,7 +69,15 @@ type AvailableForListOptions = {
|
|||
]
|
||||
}
|
||||
},
|
||||
[ScopeNames.WITH_ACCOUNT_AND_CHANNEL]: {
|
||||
[ ScopeNames.WITH_ACCOUNT ]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
[ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: {
|
||||
include: [
|
||||
{
|
||||
model: () => AccountModel.scope(AccountScopeNames.SUMMARY),
|
||||
|
@ -71,7 +89,7 @@ type AvailableForListOptions = {
|
|||
}
|
||||
]
|
||||
},
|
||||
[ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
|
||||
[ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => {
|
||||
// Only list local playlists OR playlists that are on an instance followed by actorId
|
||||
const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId)
|
||||
const actorWhere = {
|
||||
|
@ -107,6 +125,12 @@ type AvailableForListOptions = {
|
|||
})
|
||||
}
|
||||
|
||||
if (options.type) {
|
||||
whereAnd.push({
|
||||
type: options.type
|
||||
})
|
||||
}
|
||||
|
||||
const where = {
|
||||
[Sequelize.Op.and]: whereAnd
|
||||
}
|
||||
|
@ -179,6 +203,11 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
@Column(DataType.UUID)
|
||||
uuid: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(VideoPlaylistType.REGULAR)
|
||||
@Column
|
||||
type: VideoPlaylistType
|
||||
|
||||
@ForeignKey(() => AccountModel)
|
||||
@Column
|
||||
ownerAccountId: number
|
||||
|
@ -208,13 +237,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
name: 'videoPlaylistId',
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
VideoPlaylistElements: VideoPlaylistElementModel[]
|
||||
|
||||
// Calculated field
|
||||
videosLength?: number
|
||||
|
||||
@BeforeDestroy
|
||||
static async removeFiles (instance: VideoPlaylistModel) {
|
||||
logger.info('Removing files of video playlist %s.', instance.url)
|
||||
|
@ -227,6 +253,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
start: number,
|
||||
count: number,
|
||||
sort: string,
|
||||
type?: VideoPlaylistType,
|
||||
accountId?: number,
|
||||
videoChannelId?: number,
|
||||
privateAndUnlisted?: boolean
|
||||
|
@ -242,6 +269,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
method: [
|
||||
ScopeNames.AVAILABLE_FOR_LIST,
|
||||
{
|
||||
type: options.type,
|
||||
followerActorId: options.followerActorId,
|
||||
accountId: options.accountId,
|
||||
videoChannelId: options.videoChannelId,
|
||||
|
@ -289,7 +317,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
.then(e => !!e)
|
||||
}
|
||||
|
||||
static load (id: number | string, transaction: Sequelize.Transaction) {
|
||||
static loadWithAccountAndChannel (id: number | string, transaction: Sequelize.Transaction) {
|
||||
const where = buildWhereIdOrUUID(id)
|
||||
|
||||
const query = {
|
||||
|
@ -298,14 +326,39 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
}
|
||||
|
||||
return VideoPlaylistModel
|
||||
.scope([ ScopeNames.WITH_ACCOUNT_AND_CHANNEL, ScopeNames.WITH_VIDEOS_LENGTH ])
|
||||
.scope([ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY, ScopeNames.WITH_VIDEOS_LENGTH ])
|
||||
.findOne(query)
|
||||
}
|
||||
|
||||
static loadByUrlAndPopulateAccount (url: string) {
|
||||
const query = {
|
||||
where: {
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
return VideoPlaylistModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query)
|
||||
}
|
||||
|
||||
static getPrivacyLabel (privacy: VideoPlaylistPrivacy) {
|
||||
return VIDEO_PLAYLIST_PRIVACIES[privacy] || 'Unknown'
|
||||
}
|
||||
|
||||
static getTypeLabel (type: VideoPlaylistType) {
|
||||
return VIDEO_PLAYLIST_TYPES[type] || 'Unknown'
|
||||
}
|
||||
|
||||
static resetPlaylistsOfChannel (videoChannelId: number, transaction: Sequelize.Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
videoChannelId
|
||||
},
|
||||
transaction
|
||||
}
|
||||
|
||||
return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
|
||||
}
|
||||
|
||||
getThumbnailName () {
|
||||
const extension = '.jpg'
|
||||
|
||||
|
@ -345,7 +398,12 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
|
||||
thumbnailPath: this.getThumbnailStaticPath(),
|
||||
|
||||
videosLength: this.videosLength,
|
||||
type: {
|
||||
id: this.type,
|
||||
label: VideoPlaylistModel.getTypeLabel(this.type)
|
||||
},
|
||||
|
||||
videosLength: this.get('videosLength'),
|
||||
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
|
@ -355,18 +413,20 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
}
|
||||
}
|
||||
|
||||
toActivityPubObject (): Promise<PlaylistObject> {
|
||||
toActivityPubObject (page: number, t: Sequelize.Transaction): Promise<PlaylistObject> {
|
||||
const handler = (start: number, count: number) => {
|
||||
return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count)
|
||||
return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t)
|
||||
}
|
||||
|
||||
return activityPubCollectionPagination(this.url, handler, null)
|
||||
return activityPubCollectionPagination(this.url, handler, page)
|
||||
.then(o => {
|
||||
return Object.assign(o, {
|
||||
type: 'Playlist' as 'Playlist',
|
||||
name: this.name,
|
||||
content: this.description,
|
||||
uuid: this.uuid,
|
||||
published: this.createdAt.toISOString(),
|
||||
updated: this.updatedAt.toISOString(),
|
||||
attributedTo: this.VideoChannel ? [ this.VideoChannel.Actor.url ] : [],
|
||||
icon: {
|
||||
type: 'Image' as 'Image',
|
||||
|
|
|
@ -125,7 +125,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
|||
.then(res => res.map(r => r.Actor))
|
||||
}
|
||||
|
||||
static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> {
|
||||
static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> {
|
||||
const query = {
|
||||
attributes: [],
|
||||
include: [
|
||||
|
|
|
@ -225,7 +225,7 @@ type AvailableForListIDsOptions = {
|
|||
},
|
||||
include: [
|
||||
{
|
||||
model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY)
|
||||
model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] })
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1535,18 +1535,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
|
||||
if (ids.length === 0) return { data: [], total: count }
|
||||
|
||||
// FIXME: typings
|
||||
const apiScope: any[] = [
|
||||
{
|
||||
method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ]
|
||||
}
|
||||
]
|
||||
|
||||
if (options.user) {
|
||||
apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
|
||||
}
|
||||
|
||||
const secondQuery = {
|
||||
const secondQuery: IFindOptions<VideoModel> = {
|
||||
offset: 0,
|
||||
limit: query.limit,
|
||||
attributes: query.attributes,
|
||||
|
@ -1556,6 +1545,29 @@ export class VideoModel extends Model<VideoModel> {
|
|||
)
|
||||
]
|
||||
}
|
||||
|
||||
// FIXME: typing
|
||||
const apiScope: any[] = []
|
||||
|
||||
if (options.user) {
|
||||
apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
|
||||
|
||||
// Even if the relation is n:m, we know that a user only have 0..1 video history
|
||||
// So we won't have multiple rows for the same video
|
||||
// A subquery adds some bugs in our query so disable it
|
||||
secondQuery.subQuery = false
|
||||
}
|
||||
|
||||
apiScope.push({
|
||||
method: [
|
||||
ScopeNames.FOR_API, {
|
||||
ids, withFiles:
|
||||
options.withFiles,
|
||||
videoPlaylistId: options.videoPlaylistId
|
||||
} as ForAPIOptions
|
||||
]
|
||||
})
|
||||
|
||||
const rows = await VideoModel.scope(apiScope).findAll(secondQuery)
|
||||
|
||||
return {
|
||||
|
|
|
@ -16,6 +16,7 @@ import './video-captions'
|
|||
import './video-channels'
|
||||
import './video-comments'
|
||||
import './video-imports'
|
||||
import './video-playlists'
|
||||
import './videos'
|
||||
import './videos-filter'
|
||||
import './videos-history'
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,152 +2,768 @@
|
|||
|
||||
import * as chai from 'chai'
|
||||
import 'mocha'
|
||||
import { join } from 'path'
|
||||
import * as request from 'supertest'
|
||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||
import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
|
||||
import {
|
||||
addVideoChannel,
|
||||
checkTmpIsEmpty,
|
||||
checkVideoFilesWereRemoved,
|
||||
completeVideoCheck,
|
||||
addVideoInPlaylist,
|
||||
checkPlaylistFilesWereRemoved,
|
||||
createUser,
|
||||
dateIsValid,
|
||||
createVideoPlaylist,
|
||||
deleteVideoChannel,
|
||||
deleteVideoPlaylist,
|
||||
doubleFollow,
|
||||
flushAndRunMultipleServers,
|
||||
flushTests,
|
||||
getLocalVideos,
|
||||
getVideo,
|
||||
getVideoChannelsList,
|
||||
getVideosList,
|
||||
getAccountPlaylistsList,
|
||||
getAccountPlaylistsListWithToken,
|
||||
getPlaylistVideos,
|
||||
getVideoChannelPlaylistsList,
|
||||
getVideoPlaylist,
|
||||
getVideoPlaylistsList,
|
||||
getVideoPlaylistWithToken,
|
||||
killallServers,
|
||||
rateVideo,
|
||||
removeVideo,
|
||||
removeUser,
|
||||
removeVideoFromPlaylist,
|
||||
reorderVideosPlaylist,
|
||||
ServerInfo,
|
||||
setAccessTokensToServers,
|
||||
setDefaultVideoChannel,
|
||||
testImage,
|
||||
updateVideo,
|
||||
unfollow,
|
||||
updateVideoPlaylist,
|
||||
updateVideoPlaylistElement,
|
||||
uploadVideo,
|
||||
uploadVideoAndGetId,
|
||||
userLogin,
|
||||
viewVideo,
|
||||
wait,
|
||||
webtorrentAdd
|
||||
waitJobs
|
||||
} from '../../../../shared/utils'
|
||||
import {
|
||||
addVideoCommentReply,
|
||||
addVideoCommentThread,
|
||||
deleteVideoComment,
|
||||
getVideoCommentThreads,
|
||||
getVideoThreadComments
|
||||
} from '../../../../shared/utils/videos/video-comments'
|
||||
import { waitJobs } from '../../../../shared/utils/server/jobs'
|
||||
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||
import { VideoPlaylist } from '../../../../shared/models/videos/playlist/video-playlist.model'
|
||||
import { Video } from '../../../../shared/models/videos'
|
||||
import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
describe('Test video playlists', function () {
|
||||
let servers: ServerInfo[] = []
|
||||
|
||||
let playlistServer2Id1: number
|
||||
let playlistServer2Id2: number
|
||||
let playlistServer2UUID2: number
|
||||
|
||||
let playlistServer1Id: number
|
||||
let playlistServer1UUID: string
|
||||
|
||||
let nsfwVideoServer1: number
|
||||
|
||||
before(async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
servers = await flushAndRunMultipleServers(3)
|
||||
servers = await flushAndRunMultipleServers(3, { transcoding: { enabled: false } })
|
||||
|
||||
// Get the access tokens
|
||||
await setAccessTokensToServers(servers)
|
||||
await setDefaultVideoChannel(servers)
|
||||
|
||||
// Server 1 and server 2 follow each other
|
||||
await doubleFollow(servers[0], servers[1])
|
||||
// Server 1 and server 3 follow each other
|
||||
await doubleFollow(servers[0], servers[2])
|
||||
|
||||
{
|
||||
const serverPromises: Promise<any>[][] = []
|
||||
|
||||
for (const server of servers) {
|
||||
const videoPromises: Promise<any>[] = []
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
videoPromises.push(
|
||||
uploadVideo(server.url, server.accessToken, { name: `video ${i} server ${server.serverNumber}`, nsfw: false })
|
||||
.then(res => res.body.video)
|
||||
)
|
||||
}
|
||||
|
||||
serverPromises.push(videoPromises)
|
||||
}
|
||||
|
||||
servers[0].videos = await Promise.all(serverPromises[0])
|
||||
servers[1].videos = await Promise.all(serverPromises[1])
|
||||
servers[2].videos = await Promise.all(serverPromises[2])
|
||||
}
|
||||
|
||||
nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'NSFW video', nsfw: true })).id
|
||||
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Should list watch later playlist', async function () {
|
||||
const url = servers[ 0 ].url
|
||||
const accessToken = servers[ 0 ].accessToken
|
||||
|
||||
{
|
||||
const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER)
|
||||
|
||||
expect(res.body.total).to.equal(1)
|
||||
expect(res.body.data).to.have.lengthOf(1)
|
||||
|
||||
const playlist: VideoPlaylist = res.body.data[ 0 ]
|
||||
expect(playlist.displayName).to.equal('Watch later')
|
||||
expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
|
||||
expect(playlist.type.label).to.equal('Watch later')
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.REGULAR)
|
||||
|
||||
expect(res.body.total).to.equal(0)
|
||||
expect(res.body.data).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getAccountPlaylistsList(url, 'root', 0, 5)
|
||||
expect(res.body.total).to.equal(0)
|
||||
expect(res.body.data).to.have.lengthOf(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await createVideoPlaylist({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'my super playlist',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||
description: 'my super description',
|
||||
thumbnailfile: 'thumbnail.jpg',
|
||||
videoChannelId: servers[0].videoChannel.id
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getVideoPlaylistsList(server.url, 0, 5)
|
||||
expect(res.body.total).to.equal(1)
|
||||
expect(res.body.data).to.have.lengthOf(1)
|
||||
|
||||
const playlistFromList = res.body.data[0] as VideoPlaylist
|
||||
|
||||
const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid)
|
||||
const playlistFromGet = res2.body
|
||||
|
||||
for (const playlist of [ playlistFromGet, playlistFromList ]) {
|
||||
expect(playlist.id).to.be.a('number')
|
||||
expect(playlist.uuid).to.be.a('string')
|
||||
|
||||
expect(playlist.isLocal).to.equal(server.serverNumber === 1)
|
||||
|
||||
expect(playlist.displayName).to.equal('my super playlist')
|
||||
expect(playlist.description).to.equal('my super description')
|
||||
expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC)
|
||||
expect(playlist.privacy.label).to.equal('Public')
|
||||
expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
|
||||
expect(playlist.type.label).to.equal('Regular')
|
||||
|
||||
expect(playlist.videosLength).to.equal(0)
|
||||
|
||||
expect(playlist.ownerAccount.name).to.equal('root')
|
||||
expect(playlist.ownerAccount.displayName).to.equal('root')
|
||||
expect(playlist.videoChannel.name).to.equal('root_channel')
|
||||
expect(playlist.videoChannel.displayName).to.equal('Main root channel')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () {
|
||||
// create 2 playlists (with videos and no videos)
|
||||
// With thumbnail and no thumbnail
|
||||
this.timeout(30000)
|
||||
|
||||
{
|
||||
const res = await createVideoPlaylist({
|
||||
url: servers[1].url,
|
||||
token: servers[1].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist 2',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC
|
||||
}
|
||||
})
|
||||
playlistServer2Id1 = res.body.videoPlaylist.id
|
||||
}
|
||||
|
||||
{
|
||||
const res = await createVideoPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist 3',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||
thumbnailfile: 'thumbnail.jpg'
|
||||
}
|
||||
})
|
||||
|
||||
playlistServer2Id2 = res.body.videoPlaylist.id
|
||||
playlistServer2UUID2 = res.body.videoPlaylist.uuid
|
||||
}
|
||||
|
||||
for (let id of [ playlistServer2Id1, playlistServer2Id2 ]) {
|
||||
await addVideoInPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: id,
|
||||
elementAttrs: { videoId: servers[ 1 ].videos[ 0 ].id, startTimestamp: 1, stopTimestamp: 2 }
|
||||
})
|
||||
await addVideoInPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: id,
|
||||
elementAttrs: { videoId: servers[ 1 ].videos[ 1 ].id }
|
||||
})
|
||||
}
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of [ servers[0], servers[1] ]) {
|
||||
const res = await getVideoPlaylistsList(server.url, 0, 5)
|
||||
|
||||
const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
|
||||
expect(playlist2).to.not.be.undefined
|
||||
await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath)
|
||||
|
||||
const playlist3 = res.body.data.find(p => p.displayName === 'playlist 3')
|
||||
expect(playlist3).to.not.be.undefined
|
||||
await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
|
||||
}
|
||||
|
||||
const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
|
||||
expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
|
||||
expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
|
||||
})
|
||||
|
||||
it('Should have the playlist on server 3 after a new follow', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
// Server 2 and server 3 follow each other
|
||||
await doubleFollow(servers[1], servers[2])
|
||||
|
||||
const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
|
||||
|
||||
const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
|
||||
expect(playlist2).to.not.be.undefined
|
||||
await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
|
||||
|
||||
expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
|
||||
})
|
||||
|
||||
it('Should create some playlists and list them correctly', async function () {
|
||||
// create 3 playlists with some videos in it
|
||||
// check pagination
|
||||
// check sort
|
||||
// check empty
|
||||
it('Should correctly list the playlists', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
{
|
||||
const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, 'createdAt')
|
||||
|
||||
expect(res.body.total).to.equal(3)
|
||||
|
||||
const data: VideoPlaylist[] = res.body.data
|
||||
expect(data).to.have.lengthOf(2)
|
||||
expect(data[ 0 ].displayName).to.equal('playlist 2')
|
||||
expect(data[ 1 ].displayName).to.equal('playlist 3')
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, '-createdAt')
|
||||
|
||||
expect(res.body.total).to.equal(3)
|
||||
|
||||
const data: VideoPlaylist[] = res.body.data
|
||||
expect(data).to.have.lengthOf(2)
|
||||
expect(data[ 0 ].displayName).to.equal('playlist 2')
|
||||
expect(data[ 1 ].displayName).to.equal('my super playlist')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should list video channel playlists', async function () {
|
||||
// check pagination
|
||||
// check sort
|
||||
// check empty
|
||||
this.timeout(30000)
|
||||
|
||||
{
|
||||
const res = await getVideoChannelPlaylistsList(servers[ 0 ].url, 'root_channel', 0, 2, '-createdAt')
|
||||
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
const data: VideoPlaylist[] = res.body.data
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(data[ 0 ].displayName).to.equal('my super playlist')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should list account playlists', async function () {
|
||||
// check pagination
|
||||
// check sort
|
||||
// check empty
|
||||
this.timeout(30000)
|
||||
|
||||
{
|
||||
const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, '-createdAt')
|
||||
|
||||
expect(res.body.total).to.equal(2)
|
||||
|
||||
const data: VideoPlaylist[] = res.body.data
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(data[ 0 ].displayName).to.equal('playlist 2')
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, 'createdAt')
|
||||
|
||||
expect(res.body.total).to.equal(2)
|
||||
|
||||
const data: VideoPlaylist[] = res.body.data
|
||||
expect(data).to.have.lengthOf(1)
|
||||
expect(data[ 0 ].displayName).to.equal('playlist 3')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should get a playlist', async function () {
|
||||
// get empty playlist
|
||||
// get non empty playlist
|
||||
it('Should not list unlisted or private playlists', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await createVideoPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist unlisted',
|
||||
privacy: VideoPlaylistPrivacy.UNLISTED
|
||||
}
|
||||
})
|
||||
|
||||
await createVideoPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist private',
|
||||
privacy: VideoPlaylistPrivacy.PRIVATE
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const results = [
|
||||
await getAccountPlaylistsList(server.url, 'root@localhost:9002', 0, 5, '-createdAt'),
|
||||
await getVideoPlaylistsList(server.url, 0, 2, '-createdAt')
|
||||
]
|
||||
|
||||
expect(results[0].body.total).to.equal(2)
|
||||
expect(results[1].body.total).to.equal(3)
|
||||
|
||||
for (const res of results) {
|
||||
const data: VideoPlaylist[] = res.body.data
|
||||
expect(data).to.have.lengthOf(2)
|
||||
expect(data[ 0 ].displayName).to.equal('playlist 3')
|
||||
expect(data[ 1 ].displayName).to.equal('playlist 2')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('Should update a playlist', async function () {
|
||||
// update thumbnail
|
||||
this.timeout(30000)
|
||||
|
||||
// update other details
|
||||
await updateVideoPlaylist({
|
||||
url: servers[1].url,
|
||||
token: servers[1].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist 3 updated',
|
||||
description: 'description updated',
|
||||
privacy: VideoPlaylistPrivacy.UNLISTED,
|
||||
thumbnailfile: 'thumbnail.jpg',
|
||||
videoChannelId: servers[1].videoChannel.id
|
||||
},
|
||||
playlistId: playlistServer2Id2
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getVideoPlaylist(server.url, playlistServer2UUID2)
|
||||
const playlist: VideoPlaylist = res.body
|
||||
|
||||
expect(playlist.displayName).to.equal('playlist 3 updated')
|
||||
expect(playlist.description).to.equal('description updated')
|
||||
|
||||
expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED)
|
||||
expect(playlist.privacy.label).to.equal('Unlisted')
|
||||
|
||||
expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
|
||||
expect(playlist.type.label).to.equal('Regular')
|
||||
|
||||
expect(playlist.videosLength).to.equal(2)
|
||||
|
||||
expect(playlist.ownerAccount.name).to.equal('root')
|
||||
expect(playlist.ownerAccount.displayName).to.equal('root')
|
||||
expect(playlist.videoChannel.name).to.equal('root_channel')
|
||||
expect(playlist.videoChannel.displayName).to.equal('Main root channel')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const addVideo = (elementAttrs: any) => {
|
||||
return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs })
|
||||
}
|
||||
|
||||
const res = await createVideoPlaylist({
|
||||
url: servers[ 0 ].url,
|
||||
token: servers[ 0 ].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist 4',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC
|
||||
}
|
||||
})
|
||||
|
||||
playlistServer1Id = res.body.videoPlaylist.id
|
||||
playlistServer1UUID = res.body.videoPlaylist.uuid
|
||||
|
||||
await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
|
||||
await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 })
|
||||
await addVideo({ videoId: servers[2].videos[2].uuid })
|
||||
await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 })
|
||||
await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
|
||||
await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
|
||||
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Should correctly list playlist videos', async function () {
|
||||
// empty
|
||||
// some filters?
|
||||
this.timeout(30000)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
|
||||
|
||||
expect(res.body.total).to.equal(6)
|
||||
|
||||
const videos: Video[] = res.body.data
|
||||
expect(videos).to.have.lengthOf(6)
|
||||
|
||||
expect(videos[0].name).to.equal('video 0 server 1')
|
||||
expect(videos[0].playlistElement.position).to.equal(1)
|
||||
expect(videos[0].playlistElement.startTimestamp).to.equal(15)
|
||||
expect(videos[0].playlistElement.stopTimestamp).to.equal(28)
|
||||
|
||||
expect(videos[1].name).to.equal('video 1 server 3')
|
||||
expect(videos[1].playlistElement.position).to.equal(2)
|
||||
expect(videos[1].playlistElement.startTimestamp).to.equal(35)
|
||||
expect(videos[1].playlistElement.stopTimestamp).to.be.null
|
||||
|
||||
expect(videos[2].name).to.equal('video 2 server 3')
|
||||
expect(videos[2].playlistElement.position).to.equal(3)
|
||||
expect(videos[2].playlistElement.startTimestamp).to.be.null
|
||||
expect(videos[2].playlistElement.stopTimestamp).to.be.null
|
||||
|
||||
expect(videos[3].name).to.equal('video 3 server 1')
|
||||
expect(videos[3].playlistElement.position).to.equal(4)
|
||||
expect(videos[3].playlistElement.startTimestamp).to.be.null
|
||||
expect(videos[3].playlistElement.stopTimestamp).to.equal(35)
|
||||
|
||||
expect(videos[4].name).to.equal('video 4 server 1')
|
||||
expect(videos[4].playlistElement.position).to.equal(5)
|
||||
expect(videos[4].playlistElement.startTimestamp).to.equal(45)
|
||||
expect(videos[4].playlistElement.stopTimestamp).to.equal(60)
|
||||
|
||||
expect(videos[5].name).to.equal('NSFW video')
|
||||
expect(videos[5].playlistElement.position).to.equal(6)
|
||||
expect(videos[5].playlistElement.startTimestamp).to.equal(5)
|
||||
expect(videos[5].playlistElement.stopTimestamp).to.be.null
|
||||
|
||||
const res2 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10, { nsfw: false })
|
||||
expect(res2.body.total).to.equal(5)
|
||||
expect(res2.body.data.find(v => v.name === 'NSFW video')).to.be.undefined
|
||||
|
||||
const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2)
|
||||
expect(res3.body.data).to.have.lengthOf(2)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should reorder the playlist', async function () {
|
||||
// reorder 1 element
|
||||
// reorder 3 elements
|
||||
// reorder at the beginning
|
||||
// reorder at the end
|
||||
// reorder before/after
|
||||
this.timeout(30000)
|
||||
|
||||
{
|
||||
await reorderVideosPlaylist({
|
||||
url: servers[ 0 ].url,
|
||||
token: servers[ 0 ].accessToken,
|
||||
playlistId: playlistServer1Id,
|
||||
elementAttrs: {
|
||||
startPosition: 2,
|
||||
insertAfterPosition: 3
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
|
||||
const names = res.body.data.map(v => v.name)
|
||||
|
||||
expect(names).to.deep.equal([
|
||||
'video 0 server 1',
|
||||
'video 2 server 3',
|
||||
'video 1 server 3',
|
||||
'video 3 server 1',
|
||||
'video 4 server 1',
|
||||
'NSFW video'
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await reorderVideosPlaylist({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistId: playlistServer1Id,
|
||||
elementAttrs: {
|
||||
startPosition: 1,
|
||||
reorderLength: 3,
|
||||
insertAfterPosition: 4
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
|
||||
const names = res.body.data.map(v => v.name)
|
||||
|
||||
expect(names).to.deep.equal([
|
||||
'video 3 server 1',
|
||||
'video 0 server 1',
|
||||
'video 2 server 3',
|
||||
'video 1 server 3',
|
||||
'video 4 server 1',
|
||||
'NSFW video'
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await reorderVideosPlaylist({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistId: playlistServer1Id,
|
||||
elementAttrs: {
|
||||
startPosition: 6,
|
||||
insertAfterPosition: 3
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
|
||||
const videos: Video[] = res.body.data
|
||||
|
||||
const names = videos.map(v => v.name)
|
||||
|
||||
expect(names).to.deep.equal([
|
||||
'video 3 server 1',
|
||||
'video 0 server 1',
|
||||
'video 2 server 3',
|
||||
'NSFW video',
|
||||
'video 1 server 3',
|
||||
'video 4 server 1'
|
||||
])
|
||||
|
||||
for (let i = 1; i <= videos.length; i++) {
|
||||
expect(videos[i - 1].playlistElement.position).to.equal(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('Should update startTimestamp/endTimestamp of some elements', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await updateVideoPlaylistElement({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistId: playlistServer1Id,
|
||||
videoId: servers[0].videos[3].uuid,
|
||||
elementAttrs: {
|
||||
startTimestamp: 1
|
||||
}
|
||||
})
|
||||
|
||||
await updateVideoPlaylistElement({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistId: playlistServer1Id,
|
||||
videoId: servers[0].videos[4].uuid,
|
||||
elementAttrs: {
|
||||
stopTimestamp: null
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
|
||||
const videos: Video[] = res.body.data
|
||||
|
||||
expect(videos[0].name).to.equal('video 3 server 1')
|
||||
expect(videos[0].playlistElement.position).to.equal(1)
|
||||
expect(videos[0].playlistElement.startTimestamp).to.equal(1)
|
||||
expect(videos[0].playlistElement.stopTimestamp).to.equal(35)
|
||||
|
||||
expect(videos[5].name).to.equal('video 4 server 1')
|
||||
expect(videos[5].playlistElement.position).to.equal(6)
|
||||
expect(videos[5].playlistElement.startTimestamp).to.equal(45)
|
||||
expect(videos[5].playlistElement.stopTimestamp).to.be.null
|
||||
}
|
||||
})
|
||||
|
||||
it('Should delete some elements', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await removeVideoFromPlaylist({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistId: playlistServer1Id,
|
||||
videoId: servers[0].videos[3].uuid
|
||||
})
|
||||
|
||||
await removeVideoFromPlaylist({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistId: playlistServer1Id,
|
||||
videoId: nsfwVideoServer1
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
|
||||
|
||||
expect(res.body.total).to.equal(4)
|
||||
|
||||
const videos: Video[] = res.body.data
|
||||
expect(videos).to.have.lengthOf(4)
|
||||
|
||||
expect(videos[ 0 ].name).to.equal('video 0 server 1')
|
||||
expect(videos[ 0 ].playlistElement.position).to.equal(1)
|
||||
|
||||
expect(videos[ 1 ].name).to.equal('video 2 server 3')
|
||||
expect(videos[ 1 ].playlistElement.position).to.equal(2)
|
||||
|
||||
expect(videos[ 2 ].name).to.equal('video 1 server 3')
|
||||
expect(videos[ 2 ].playlistElement.position).to.equal(3)
|
||||
|
||||
expect(videos[ 3 ].name).to.equal('video 4 server 1')
|
||||
expect(videos[ 3 ].playlistElement.position).to.equal(4)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id)
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
await getVideoPlaylist(server.url, playlistServer1UUID, 404)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have deleted the thumbnail on server 1, 2 and 3', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
for (const server of servers) {
|
||||
await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.serverNumber)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should unfollow servers 1 and 2 and hide their playlists', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const finder = data => data.find(p => p.displayName === 'my super playlist')
|
||||
|
||||
{
|
||||
const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5)
|
||||
expect(res.body.total).to.equal(2)
|
||||
expect(finder(res.body.data)).to.not.be.undefined
|
||||
}
|
||||
|
||||
await unfollow(servers[2].url, servers[2].accessToken, servers[0])
|
||||
|
||||
{
|
||||
const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5)
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
expect(finder(res.body.data)).to.be.undefined
|
||||
}
|
||||
})
|
||||
|
||||
it('Should delete a channel and remove the associated playlist', async function () {
|
||||
it('Should delete a channel and put the associated playlist in private mode', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' })
|
||||
const videoChannelId = res.body.videoChannel.id
|
||||
|
||||
const res2 = await createVideoPlaylist({
|
||||
url: servers[0].url,
|
||||
token: servers[0].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'channel playlist',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||
videoChannelId
|
||||
}
|
||||
})
|
||||
const videoPlaylistUUID = res2.body.videoPlaylist.uuid
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel')
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID)
|
||||
expect(res3.body.displayName).to.equal('channel playlist')
|
||||
expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
|
||||
|
||||
await getVideoPlaylist(servers[1].url, videoPlaylistUUID, 404)
|
||||
})
|
||||
|
||||
it('Should delete an account and delete its playlists', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const user = { username: 'user_1', password: 'password' }
|
||||
const res = await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
|
||||
|
||||
const userId = res.body.user.id
|
||||
const userAccessToken = await userLogin(servers[0], user)
|
||||
|
||||
await createVideoPlaylist({
|
||||
url: servers[0].url,
|
||||
token: userAccessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist to be deleted',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
const finder = data => data.find(p => p.displayName === 'playlist to be deleted')
|
||||
|
||||
{
|
||||
for (const server of [ servers[0], servers[1] ]) {
|
||||
const res = await getVideoPlaylistsList(server.url, 0, 15)
|
||||
expect(finder(res.body.data)).to.not.be.undefined
|
||||
}
|
||||
}
|
||||
|
||||
await removeUser(servers[0].url, userId, servers[0].accessToken)
|
||||
await waitJobs(servers)
|
||||
|
||||
{
|
||||
for (const server of [ servers[0], servers[1] ]) {
|
||||
const res = await getVideoPlaylistsList(server.url, 0, 15)
|
||||
expect(finder(res.body.data)).to.be.undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
|
|
BIN
server/tests/fixtures/thumbnail-playlist.jpg
vendored
Normal file
BIN
server/tests/fixtures/thumbnail-playlist.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -13,6 +13,9 @@ export interface PlaylistObject {
|
|||
|
||||
icon: ActivityIconObject
|
||||
|
||||
published: string
|
||||
updated: string
|
||||
|
||||
orderedItems?: string[]
|
||||
|
||||
partOf?: string
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export enum VideoPlaylistType {
|
||||
REGULAR = 1,
|
||||
WATCH_LATER = 2
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { AccountSummary } from '../../actors/index'
|
||||
import { VideoChannelSummary, VideoConstant } from '..'
|
||||
import { VideoPlaylistPrivacy } from './video-playlist-privacy.model'
|
||||
import { VideoPlaylistType } from './video-playlist-type.model'
|
||||
|
||||
export interface VideoPlaylist {
|
||||
id: number
|
||||
|
@ -15,6 +16,8 @@ export interface VideoPlaylist {
|
|||
|
||||
videosLength: number
|
||||
|
||||
type: VideoConstant<VideoPlaylistType>
|
||||
|
||||
createdAt: Date | string
|
||||
updatedAt: Date | string
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ function makeUploadRequest (options: {
|
|||
Object.keys(options.fields).forEach(field => {
|
||||
const value = options.fields[field]
|
||||
|
||||
if (value === undefined) return
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
req.field(field + '[' + i + ']', value[i])
|
||||
|
|
|
@ -6,6 +6,7 @@ import { root, wait } from '../miscs/miscs'
|
|||
import { readdir, readFile } from 'fs-extra'
|
||||
import { existsSync } from 'fs'
|
||||
import { expect } from 'chai'
|
||||
import { VideoChannel } from '../../models/videos'
|
||||
|
||||
interface ServerInfo {
|
||||
app: ChildProcess,
|
||||
|
@ -25,6 +26,7 @@ interface ServerInfo {
|
|||
}
|
||||
|
||||
accessToken?: string
|
||||
videoChannel?: VideoChannel
|
||||
|
||||
video?: {
|
||||
id: number
|
||||
|
@ -39,6 +41,8 @@ interface ServerInfo {
|
|||
id: number
|
||||
uuid: string
|
||||
}
|
||||
|
||||
videos?: { id: number, uuid: string }[]
|
||||
}
|
||||
|
||||
function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '..
|
|||
|
||||
import { UserRole } from '../../index'
|
||||
import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
|
||||
import { ServerInfo, userLogin } from '..'
|
||||
|
||||
function createUser (
|
||||
url: string,
|
||||
|
@ -32,6 +33,13 @@ function createUser (
|
|||
.expect(specialStatus)
|
||||
}
|
||||
|
||||
async function generateUserAccessToken (server: ServerInfo, username: string) {
|
||||
const password = 'my super password'
|
||||
await createUser(server.url, server.accessToken, username, password)
|
||||
|
||||
return userLogin(server, { username, password })
|
||||
}
|
||||
|
||||
function registerUser (url: string, username: string, password: string, specialStatus = 204) {
|
||||
const path = '/api/v1/users/register'
|
||||
const body = {
|
||||
|
@ -300,5 +308,6 @@ export {
|
|||
resetPassword,
|
||||
updateMyAvatar,
|
||||
askSendVerifyEmail,
|
||||
generateUserAccessToken,
|
||||
verifyEmail
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import * as request from 'supertest'
|
||||
import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
|
||||
import { updateAvatarRequest } from '../requests/requests'
|
||||
import { getMyUserInformation, ServerInfo } from '..'
|
||||
import { User } from '../..'
|
||||
|
||||
function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
|
||||
const path = '/api/v1/video-channels'
|
||||
|
@ -105,6 +107,19 @@ function updateVideoChannelAvatar (options: {
|
|||
return updateAvatarRequest(Object.assign(options, { path }))
|
||||
}
|
||||
|
||||
function setDefaultVideoChannel (servers: ServerInfo[]) {
|
||||
const tasks: Promise<any>[] = []
|
||||
|
||||
for (const server of servers) {
|
||||
const p = getMyUserInformation(server.url, server.accessToken)
|
||||
.then(res => server.videoChannel = (res.body as User).videoChannels[0])
|
||||
|
||||
tasks.push(p)
|
||||
}
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -114,5 +129,6 @@ export {
|
|||
addVideoChannel,
|
||||
updateVideoChannel,
|
||||
deleteVideoChannel,
|
||||
getVideoChannel
|
||||
getVideoChannel,
|
||||
setDefaultVideoChannel
|
||||
}
|
||||
|
|
|
@ -4,6 +4,12 @@ import { omit } from 'lodash'
|
|||
import { VideoPlaylistUpdate } from '../../models/videos/playlist/video-playlist-update.model'
|
||||
import { VideoPlaylistElementCreate } from '../../models/videos/playlist/video-playlist-element-create.model'
|
||||
import { VideoPlaylistElementUpdate } from '../../models/videos/playlist/video-playlist-element-update.model'
|
||||
import { videoUUIDToId } from './videos'
|
||||
import { join } from 'path'
|
||||
import { root } from '..'
|
||||
import { readdir } from 'fs-extra'
|
||||
import { expect } from 'chai'
|
||||
import { VideoPlaylistType } from '../../models/videos/playlist/video-playlist-type.model'
|
||||
|
||||
function getVideoPlaylistsList (url: string, start: number, count: number, sort?: string) {
|
||||
const path = '/api/v1/video-playlists'
|
||||
|
@ -17,7 +23,67 @@ function getVideoPlaylistsList (url: string, start: number, count: number, sort?
|
|||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
query
|
||||
query,
|
||||
statusCodeExpected: 200
|
||||
})
|
||||
}
|
||||
|
||||
function getVideoChannelPlaylistsList (url: string, videoChannelName: string, start: number, count: number, sort?: string) {
|
||||
const path = '/api/v1/video-channels/' + videoChannelName + '/video-playlists'
|
||||
|
||||
const query = {
|
||||
start,
|
||||
count,
|
||||
sort
|
||||
}
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
query,
|
||||
statusCodeExpected: 200
|
||||
})
|
||||
}
|
||||
|
||||
function getAccountPlaylistsList (url: string, accountName: string, start: number, count: number, sort?: string) {
|
||||
const path = '/api/v1/accounts/' + accountName + '/video-playlists'
|
||||
|
||||
const query = {
|
||||
start,
|
||||
count,
|
||||
sort
|
||||
}
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
query,
|
||||
statusCodeExpected: 200
|
||||
})
|
||||
}
|
||||
|
||||
function getAccountPlaylistsListWithToken (
|
||||
url: string,
|
||||
token: string,
|
||||
accountName: string,
|
||||
start: number,
|
||||
count: number,
|
||||
playlistType?: VideoPlaylistType
|
||||
) {
|
||||
const path = '/api/v1/accounts/' + accountName + '/video-playlists'
|
||||
|
||||
const query = {
|
||||
start,
|
||||
count,
|
||||
playlistType
|
||||
}
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
token,
|
||||
path,
|
||||
query,
|
||||
statusCodeExpected: 200
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -31,6 +97,17 @@ function getVideoPlaylist (url: string, playlistId: number | string, statusCodeE
|
|||
})
|
||||
}
|
||||
|
||||
function getVideoPlaylistWithToken (url: string, token: string, playlistId: number | string, statusCodeExpected = 200) {
|
||||
const path = '/api/v1/video-playlists/' + playlistId
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
token,
|
||||
path,
|
||||
statusCodeExpected
|
||||
})
|
||||
}
|
||||
|
||||
function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 204) {
|
||||
const path = '/api/v1/video-playlists/' + playlistId
|
||||
|
||||
|
@ -93,13 +170,15 @@ function updateVideoPlaylist (options: {
|
|||
})
|
||||
}
|
||||
|
||||
function addVideoInPlaylist (options: {
|
||||
async function addVideoInPlaylist (options: {
|
||||
url: string,
|
||||
token: string,
|
||||
playlistId: number | string,
|
||||
elementAttrs: VideoPlaylistElementCreate
|
||||
elementAttrs: VideoPlaylistElementCreate | { videoId: string }
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
options.elementAttrs.videoId = await videoUUIDToId(options.url, options.elementAttrs.videoId)
|
||||
|
||||
const path = '/api/v1/video-playlists/' + options.playlistId + '/videos'
|
||||
|
||||
return makePostBodyRequest({
|
||||
|
@ -135,7 +214,7 @@ function removeVideoFromPlaylist (options: {
|
|||
token: string,
|
||||
playlistId: number | string,
|
||||
videoId: number | string,
|
||||
expectedStatus: number
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId
|
||||
|
||||
|
@ -156,7 +235,7 @@ function reorderVideosPlaylist (options: {
|
|||
insertAfterPosition: number,
|
||||
reorderLength?: number
|
||||
},
|
||||
expectedStatus: number
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder'
|
||||
|
||||
|
@ -165,15 +244,37 @@ function reorderVideosPlaylist (options: {
|
|||
path,
|
||||
token: options.token,
|
||||
fields: options.elementAttrs,
|
||||
statusCodeExpected: options.expectedStatus
|
||||
statusCodeExpected: options.expectedStatus || 204
|
||||
})
|
||||
}
|
||||
|
||||
async function checkPlaylistFilesWereRemoved (
|
||||
playlistUUID: string,
|
||||
serverNumber: number,
|
||||
directories = [ 'thumbnails' ]
|
||||
) {
|
||||
const testDirectory = 'test' + serverNumber
|
||||
|
||||
for (const directory of directories) {
|
||||
const directoryPath = join(root(), testDirectory, directory)
|
||||
|
||||
const files = await readdir(directoryPath)
|
||||
for (const file of files) {
|
||||
expect(file).to.not.contain(playlistUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
getVideoPlaylistsList,
|
||||
getVideoChannelPlaylistsList,
|
||||
getAccountPlaylistsList,
|
||||
getAccountPlaylistsListWithToken,
|
||||
|
||||
getVideoPlaylist,
|
||||
getVideoPlaylistWithToken,
|
||||
|
||||
createVideoPlaylist,
|
||||
updateVideoPlaylist,
|
||||
|
@ -183,5 +284,7 @@ export {
|
|||
updateVideoPlaylistElement,
|
||||
removeVideoFromPlaylist,
|
||||
|
||||
reorderVideosPlaylist
|
||||
reorderVideosPlaylist,
|
||||
|
||||
checkPlaylistFilesWereRemoved
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* tslint:disable:no-unused-expression */
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { existsSync, readdir, readFile } from 'fs-extra'
|
||||
import { pathExists, readdir, readFile } from 'fs-extra'
|
||||
import * as parseTorrent from 'parse-torrent'
|
||||
import { extname, join } from 'path'
|
||||
import * as request from 'supertest'
|
||||
|
@ -16,7 +16,7 @@ import {
|
|||
ServerInfo,
|
||||
testImage
|
||||
} from '../'
|
||||
|
||||
import * as validator from 'validator'
|
||||
import { VideoDetails, VideoPrivacy } from '../../models/videos'
|
||||
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
|
||||
import { dateIsValid, webtorrentAdd } from '../miscs/miscs'
|
||||
|
@ -311,8 +311,8 @@ async function checkVideoFilesWereRemoved (
|
|||
for (const directory of directories) {
|
||||
const directoryPath = join(root(), testDirectory, directory)
|
||||
|
||||
const directoryExists = existsSync(directoryPath)
|
||||
if (!directoryExists) continue
|
||||
const directoryExists = await pathExists(directoryPath)
|
||||
if (directoryExists === false) continue
|
||||
|
||||
const files = await readdir(directoryPath)
|
||||
for (const file of files) {
|
||||
|
@ -597,12 +597,30 @@ async function completeVideoCheck (
|
|||
}
|
||||
}
|
||||
|
||||
async function videoUUIDToId (url: string, id: number | string) {
|
||||
if (validator.isUUID('' + id) === false) return id
|
||||
|
||||
const res = await getVideo(url, id)
|
||||
return res.body.id
|
||||
}
|
||||
|
||||
async function uploadVideoAndGetId (options: { server: ServerInfo, videoName: string, nsfw?: boolean, token?: string }) {
|
||||
const videoAttrs: any = { name: options.videoName }
|
||||
if (options.nsfw) videoAttrs.nsfw = options.nsfw
|
||||
|
||||
|
||||
const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs)
|
||||
|
||||
return { id: res.body.video.id, uuid: res.body.video.uuid }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
getVideoDescription,
|
||||
getVideoCategories,
|
||||
getVideoLicences,
|
||||
videoUUIDToId,
|
||||
getVideoPrivacies,
|
||||
getVideoLanguages,
|
||||
getMyVideos,
|
||||
|
@ -624,5 +642,6 @@ export {
|
|||
getLocalVideos,
|
||||
completeVideoCheck,
|
||||
checkVideoFilesWereRemoved,
|
||||
getPlaylistVideos
|
||||
getPlaylistVideos,
|
||||
uploadVideoAndGetId
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue