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)
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()
videoPlaylistsSortValidator,
setDefaultSort,
setDefaultPagination,
+ commonVideoPlaylistFiltersValidator,
asyncMiddleware(listAccountPlaylists)
)
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))
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,
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)
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)
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 })
videoPlaylistsSortValidator,
setDefaultSort,
setDefaultPagination,
+ commonVideoPlaylistFiltersValidator,
asyncMiddleware(listVideoChannelPlaylists)
)
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()))
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))
import { resetSequelizeInstance } from '../../helpers/database-utils'
import { VideoPlaylistModel } from '../../models/video/video-playlist'
import {
+ commonVideoPlaylistFiltersValidator,
videoPlaylistsAddValidator,
videoPlaylistsAddVideoValidator,
videoPlaylistsDeleteValidator,
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 })
videoPlaylistsSortValidator,
setDefaultSort,
setDefaultPagination,
+ commonVideoPlaylistFiltersValidator,
asyncMiddleware(listVideoPlaylists)
)
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))
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
const videoChannel = res.locals.videoChannel as VideoChannelModel
videoPlaylistInstance.videoChannelId = videoChannel.id
+ videoPlaylistInstance.VideoChannel = videoChannel
}
}
}
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
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
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
// 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)
// 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)
})
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 }))
}
-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'
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) {
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
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)
isVideoPlaylistExist,
isVideoPlaylistNameValid,
isVideoPlaylistDescriptionValid,
- isVideoPlaylistPrivacyValid
+ isVideoPlaylistPrivacyValid,
+ isVideoPlaylistTimestampValid,
+ isVideoPlaylistTypeValid
}
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')
[VideoPlaylistPrivacy.PRIVATE]: 'Private'
}
+const VIDEO_PLAYLIST_TYPES = {
+ [VideoPlaylistType.REGULAR]: 'Regular',
+ [VideoPlaylistType.WATCH_LATER]: 'Watch later'
+}
+
const MIMETYPES = {
VIDEO: {
MIMETYPE_EXT: buildVideoMimetypeExt(),
STATIC_MAX_AGE,
STATIC_PATHS,
VIDEO_IMPORT_TIMEOUT,
+ VIDEO_PLAYLIST_TYPES,
ACTIVITY_PUB,
ACTIVITY_PUB_ACTOR_TYPES,
THUMBNAILS_SIZE,
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'
}
const user = new UserModel(userData)
- await createUserAccountAndChannel(user, validatePassword)
+ await createUserAccountAndChannelAndPlaylist(user, validatePassword)
logger.info('Username: ' + username)
logger.info('User password: ' + password)
}
url: playlistObject.id,
uuid: playlistObject.uuid,
ownerAccountId: byAccount.id,
- videoChannelId: null
+ videoChannelId: null,
+ createdAt: new Date(playlistObject.published),
+ updatedAt: new Date(playlistObject.updated)
}
}
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
}
}
+ {
+ 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
}
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)
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()
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)
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)
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)
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,
}
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([
export {
createApplicationActor,
- createUserAccountAndChannel,
+ createUserAccountAndChannelAndPlaylist,
createLocalAccountWithoutKeys
}
--- /dev/null
+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
+}
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'
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'
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) => {
.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()
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
}
.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 })
.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 })
}
]
+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 {
videoPlaylistsUpdateOrRemoveVideoValidator,
videoPlaylistsReorderVideosValidator,
- videoPlaylistElementAPGetValidator
+ videoPlaylistElementAPGetValidator,
+
+ commonVideoPlaylistFiltersValidator
}
// ---------------------------------------------------------------------------
]
})
@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' ],
foreignKey: {
allowNull: true
},
- onDelete: 'cascade',
+ onDelete: 'CASCADE',
hooks: true
})
VideoPlaylists: VideoPlaylistModel[]
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',
fields: [ 'videoPlaylistId', 'videoId' ],
unique: true
},
- {
- fields: [ 'videoPlaylistId', 'position' ],
- unique: true
- },
{
fields: [ 'url' ],
unique: true
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,
order: getSort('position'),
where: {
videoPlaylistId
- }
+ },
+ transaction: t
}
return VideoPlaylistElementModel
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'
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: [
[
]
}
},
- [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),
}
]
},
- [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 = {
})
}
+ if (options.type) {
+ whereAnd.push({
+ type: options.type
+ })
+ }
+
const where = {
[Sequelize.Op.and]: whereAnd
}
@Column(DataType.UUID)
uuid: string
+ @AllowNull(false)
+ @Default(VideoPlaylistType.REGULAR)
+ @Column
+ type: VideoPlaylistType
+
@ForeignKey(() => AccountModel)
@Column
ownerAccountId: number
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)
start: number,
count: number,
sort: string,
+ type?: VideoPlaylistType,
accountId?: number,
videoChannelId?: number,
privateAndUnlisted?: boolean
method: [
ScopeNames.AVAILABLE_FOR_LIST,
{
+ type: options.type,
followerActorId: options.followerActorId,
accountId: options.accountId,
videoChannelId: options.videoChannelId,
.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 = {
}
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'
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,
}
}
- 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',
.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: [
},
include: [
{
- model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY)
+ model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] })
}
]
}
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,
)
]
}
+
+ // 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 {
import './video-channels'
import './video-comments'
import './video-imports'
+import './video-playlists'
import './videos'
import './videos-filter'
import './videos-history'
import 'mocha'
import {
- createUser,
+ addVideoInPlaylist,
createVideoPlaylist,
deleteVideoPlaylist,
flushTests,
+ generateUserAccessToken,
+ getAccountPlaylistsListWithToken,
getVideoPlaylist,
immutableAssign,
killallServers,
makeGetRequest,
+ removeVideoFromPlaylist,
+ reorderVideosPlaylist,
runServer,
ServerInfo,
setAccessTokensToServers,
updateVideoPlaylist,
- userLogin,
- addVideoInPlaylist, uploadVideo, updateVideoPlaylistElement, removeVideoFromPlaylist, reorderVideosPlaylist
+ updateVideoPlaylistElement,
+ uploadVideoAndGetId
} from '../../../../shared/utils'
import {
checkBadCountPagination,
checkBadStartPagination
} from '../../../../shared/utils/requests/check-api-params'
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
+import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
describe('Test video playlists API validator', function () {
let server: ServerInfo
- let userAccessToken = ''
+ let userAccessToken: string
let playlistUUID: string
+ let watchLaterPlaylistId: number
let videoId: number
let videoId2: number
await setAccessTokensToServers([ server ])
- const username = 'user1'
- const password = 'my super password'
- await createUser(server.url, server.accessToken, username, password)
- userAccessToken = await userLogin(server, { username, password })
+ userAccessToken = await generateUserAccessToken(server, 'user1')
+ videoId = (await uploadVideoAndGetId({ server, videoName: 'video 1' })).id
+ videoId2 = (await uploadVideoAndGetId({ server, videoName: 'video 2' })).id
{
- const res = await uploadVideo(server.url, server.accessToken, { name: 'video 1' })
- videoId = res.body.video.id
- }
-
- {
- const res = await uploadVideo(server.url, server.accessToken, { name: 'video 2' })
- videoId2 = res.body.video.id
+ const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root',0, 5, VideoPlaylistType.WATCH_LATER)
+ watchLaterPlaylistId = res.body.data[0].id
}
{
await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
})
+ it('Should fail with a bad playlist type', async function () {
+ await makeGetRequest({ url: server.url, path: globalPath, query: { playlistType: 3 } })
+ await makeGetRequest({ url: server.url, path: accountPath, query: { playlistType: 3 } })
+ await makeGetRequest({ url: server.url, path: videoChannelPath, query: { playlistType: 3 } })
+ })
+
it('Should fail with a bad account parameter', async function () {
const accountPath = '/api/v1/accounts/root2/video-playlists'
})
describe('When creating/updating a video playlist', function () {
+ const getBase = (playlistAttrs: any = {}, wrapper: any = {}) => {
+ return Object.assign({
+ expectedStatus: 400,
+ url: server.url,
+ token: server.accessToken,
+ playlistAttrs: Object.assign({
+ displayName: 'display name',
+ privacy: VideoPlaylistPrivacy.UNLISTED,
+ thumbnailfile: 'thumbnail.jpg'
+ }, playlistAttrs)
+ }, wrapper)
+ }
+ const getUpdate = (params: any, playlistId: number | string) => {
+ return immutableAssign(params, { playlistId: playlistId })
+ }
it('Should fail with an unauthenticated user', async function () {
- const baseParams = {
- url: server.url,
- token: null,
- playlistAttrs: {
- displayName: 'super playlist',
- privacy: VideoPlaylistPrivacy.PUBLIC
- },
- expectedStatus: 401
- }
+ const params = getBase({}, { token: null, expectedStatus: 401 })
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ await createVideoPlaylist(params)
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
})
it('Should fail without displayName', async function () {
- const baseParams = {
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- privacy: VideoPlaylistPrivacy.PUBLIC
- } as any,
- expectedStatus: 400
- }
+ const params = getBase({ displayName: undefined })
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ await createVideoPlaylist(params)
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
})
it('Should fail with an incorrect display name', async function () {
- const baseParams = {
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- displayName: 's'.repeat(300),
- privacy: VideoPlaylistPrivacy.PUBLIC
- },
- expectedStatus: 400
- }
+ const params = getBase({ displayName: 's'.repeat(300) })
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ await createVideoPlaylist(params)
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
})
it('Should fail with an incorrect description', async function () {
- const baseParams = {
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- displayName: 'display name',
- privacy: VideoPlaylistPrivacy.PUBLIC,
- description: 't'
- },
- expectedStatus: 400
- }
+ const params = getBase({ description: 't' })
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ await createVideoPlaylist(params)
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
})
it('Should fail with an incorrect privacy', async function () {
- const baseParams = {
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- displayName: 'display name',
- privacy: 45
- } as any,
- expectedStatus: 400
- }
+ const params = getBase({ privacy: 45 })
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ await createVideoPlaylist(params)
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
})
it('Should fail with an unknown video channel id', async function () {
- const baseParams = {
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- displayName: 'display name',
- privacy: VideoPlaylistPrivacy.PUBLIC,
- videoChannelId: 42
- },
- expectedStatus: 404
- }
+ const params = getBase({ videoChannelId: 42 }, { expectedStatus: 404 })
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ await createVideoPlaylist(params)
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
})
it('Should fail with an incorrect thumbnail file', async function () {
- const baseParams = {
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- displayName: 'display name',
- privacy: VideoPlaylistPrivacy.PUBLIC,
- thumbnailfile: 'avatar.png'
- },
- expectedStatus: 400
- }
+ const params = getBase({ thumbnailfile: 'avatar.png' })
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ await createVideoPlaylist(params)
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
})
it('Should fail with an unknown playlist to update', async function () {
- await updateVideoPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: 42,
- playlistAttrs: {
- displayName: 'display name',
- privacy: VideoPlaylistPrivacy.PUBLIC
- },
- expectedStatus: 404
- })
+ await updateVideoPlaylist(getUpdate(
+ getBase({}, { expectedStatus: 404 }),
+ 42
+ ))
})
it('Should fail to update a playlist of another user', async function () {
- await updateVideoPlaylist({
- url: server.url,
- token: userAccessToken,
- playlistId: playlistUUID,
- playlistAttrs: {
- displayName: 'display name',
- privacy: VideoPlaylistPrivacy.PUBLIC
- },
- expectedStatus: 403
- })
+ await updateVideoPlaylist(getUpdate(
+ getBase({}, { token: userAccessToken, expectedStatus: 403 }),
+ playlistUUID
+ ))
})
it('Should fail to update to private a public/unlisted playlist', async function () {
- const res = await createVideoPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- displayName: 'super playlist',
- privacy: VideoPlaylistPrivacy.PUBLIC
- }
- })
+ const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC }, { expectedStatus: 200 })
+
+ const res = await createVideoPlaylist(params)
const playlist = res.body.videoPlaylist
- await updateVideoPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlist.id,
- playlistAttrs: {
- displayName: 'display name',
- privacy: VideoPlaylistPrivacy.PRIVATE
- },
- expectedStatus: 409
- })
+ const paramsUpdate = getBase({ privacy: VideoPlaylistPrivacy.PRIVATE }, { expectedStatus: 409 })
+
+ await updateVideoPlaylist(getUpdate(paramsUpdate, playlist.id))
+ })
+
+ it('Should fail to update the watch later playlist', async function () {
+ await updateVideoPlaylist(getUpdate(
+ getBase({}, { expectedStatus: 409 }),
+ watchLaterPlaylistId
+ ))
})
it('Should succeed with the correct params', async function () {
- const baseParams = {
- url: server.url,
- token: server.accessToken,
- playlistAttrs: {
- displayName: 'display name',
- privacy: VideoPlaylistPrivacy.UNLISTED,
- thumbnailfile: 'thumbnail.jpg'
- }
+ {
+ const params = getBase({}, { expectedStatus: 200 })
+ await createVideoPlaylist(params)
}
- await createVideoPlaylist(baseParams)
- await updateVideoPlaylist(immutableAssign(baseParams, { playlistId: playlistUUID }))
+ {
+ const params = getBase({}, { expectedStatus: 204 })
+ await updateVideoPlaylist(getUpdate(params, playlistUUID))
+ }
})
})
describe('When adding an element in a playlist', function () {
- it('Should fail with an unauthenticated user', async function () {
- await addVideoInPlaylist({
+ const getBase = (elementAttrs: any = {}, wrapper: any = {}) => {
+ return Object.assign({
+ expectedStatus: 400,
url: server.url,
- token: null,
- elementAttrs: {
- videoId: videoId
- },
+ token: server.accessToken,
playlistId: playlistUUID,
- expectedStatus: 401
- })
+ elementAttrs: Object.assign({
+ videoId: videoId,
+ startTimestamp: 2,
+ stopTimestamp: 3
+ }, elementAttrs)
+ }, wrapper)
+ }
+
+ it('Should fail with an unauthenticated user', async function () {
+ const params = getBase({}, { token: null, expectedStatus: 401 })
+ await addVideoInPlaylist(params)
})
it('Should fail with the playlist of another user', async function () {
- await addVideoInPlaylist({
- url: server.url,
- token: userAccessToken,
- elementAttrs: {
- videoId: videoId
- },
- playlistId: playlistUUID,
- expectedStatus: 403
- })
+ const params = getBase({}, { token: userAccessToken, expectedStatus: 403 })
+ await addVideoInPlaylist(params)
})
it('Should fail with an unknown or incorrect playlist id', async function () {
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: videoId
- },
- playlistId: 'toto',
- expectedStatus: 400
- })
+ {
+ const params = getBase({}, { playlistId: 'toto' })
+ await addVideoInPlaylist(params)
+ }
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: videoId
- },
- playlistId: 42,
- expectedStatus: 404
- })
+ {
+ const params = getBase({}, { playlistId: 42, expectedStatus: 404 })
+ await addVideoInPlaylist(params)
+ }
})
it('Should fail with an unknown or incorrect video id', async function () {
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: 'toto' as any
- },
- playlistId: playlistUUID,
- expectedStatus: 400
- })
-
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: 42
- },
- playlistId: playlistUUID,
- expectedStatus: 404
- })
+ const params = getBase({ videoId: 42 }, { expectedStatus: 404 })
+ await addVideoInPlaylist(params)
})
it('Should fail with a bad start/stop timestamp', async function () {
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: videoId,
- startTimestamp: -42
- },
- playlistId: playlistUUID,
- expectedStatus: 400
- })
+ {
+ const params = getBase({ startTimestamp: -42 })
+ await addVideoInPlaylist(params)
+ }
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: videoId,
- stopTimestamp: 'toto' as any
- },
- playlistId: playlistUUID,
- expectedStatus: 400
- })
+ {
+ const params = getBase({ stopTimestamp: 'toto' as any })
+ await addVideoInPlaylist(params)
+ }
})
it('Succeed with the correct params', async function () {
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: videoId,
- stopTimestamp: 3
- },
- playlistId: playlistUUID,
- expectedStatus: 200
- })
+ const params = getBase({}, { expectedStatus: 200 })
+ await addVideoInPlaylist(params)
})
it('Should fail if the video was already added in the playlist', async function () {
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- videoId: videoId,
- stopTimestamp: 3
- },
- playlistId: playlistUUID,
- expectedStatus: 409
- })
+ const params = getBase({}, { expectedStatus: 409 })
+ await addVideoInPlaylist(params)
})
})
describe('When updating an element in a playlist', function () {
- it('Should fail with an unauthenticated user', async function () {
- await updateVideoPlaylistElement({
+ const getBase = (elementAttrs: any = {}, wrapper: any = {}) => {
+ return Object.assign({
url: server.url,
- token: null,
- elementAttrs: { },
+ token: server.accessToken,
+ elementAttrs: Object.assign({
+ startTimestamp: 1,
+ stopTimestamp: 2
+ }, elementAttrs),
videoId: videoId,
playlistId: playlistUUID,
- expectedStatus: 401
- })
+ expectedStatus: 400
+ }, wrapper)
+ }
+
+ it('Should fail with an unauthenticated user', async function () {
+ const params = getBase({}, { token: null, expectedStatus: 401 })
+ await updateVideoPlaylistElement(params)
})
it('Should fail with the playlist of another user', async function () {
- await updateVideoPlaylistElement({
- url: server.url,
- token: userAccessToken,
- elementAttrs: { },
- videoId: videoId,
- playlistId: playlistUUID,
- expectedStatus: 403
- })
+ const params = getBase({}, { token: userAccessToken, expectedStatus: 403 })
+ await updateVideoPlaylistElement(params)
})
it('Should fail with an unknown or incorrect playlist id', async function () {
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: { },
- videoId: videoId,
- playlistId: 'toto',
- expectedStatus: 400
- })
+ {
+ const params = getBase({}, { playlistId: 'toto' })
+ await updateVideoPlaylistElement(params)
+ }
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: { },
- videoId: videoId,
- playlistId: 42,
- expectedStatus: 404
- })
+ {
+ const params = getBase({}, { playlistId: 42, expectedStatus: 404 })
+ await updateVideoPlaylistElement(params)
+ }
})
it('Should fail with an unknown or incorrect video id', async function () {
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: { },
- videoId: 'toto',
- playlistId: playlistUUID,
- expectedStatus: 400
- })
+ {
+ const params = getBase({}, { videoId: 'toto' })
+ await updateVideoPlaylistElement(params)
+ }
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: { },
- videoId: 42,
- playlistId: playlistUUID,
- expectedStatus: 404
- })
+ {
+ const params = getBase({}, { videoId: 42, expectedStatus: 404 })
+ await updateVideoPlaylistElement(params)
+ }
})
it('Should fail with a bad start/stop timestamp', async function () {
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- startTimestamp: 'toto' as any
- },
- videoId: videoId,
- playlistId: playlistUUID,
- expectedStatus: 400
- })
+ {
+ const params = getBase({ startTimestamp: 'toto' as any })
+ await updateVideoPlaylistElement(params)
+ }
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- stopTimestamp: -42
- },
- videoId: videoId,
- playlistId: playlistUUID,
- expectedStatus: 400
- })
+ {
+ const params = getBase({ stopTimestamp: -42 })
+ await updateVideoPlaylistElement(params)
+ }
})
it('Should fail with an unknown element', async function () {
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- stopTimestamp: 2
- },
- videoId: videoId2,
- playlistId: playlistUUID,
- expectedStatus: 404
- })
+ const params = getBase({}, { videoId: videoId2, expectedStatus: 404 })
+ await updateVideoPlaylistElement(params)
})
it('Succeed with the correct params', async function () {
- await updateVideoPlaylistElement({
- url: server.url,
- token: server.accessToken,
- elementAttrs: {
- stopTimestamp: 2
- },
- videoId: videoId,
- playlistId: playlistUUID,
- expectedStatus: 204
- })
+ const params = getBase({}, { expectedStatus: 204 })
+ await updateVideoPlaylistElement(params)
})
})
let videoId3: number
let videoId4: number
- before(async function () {
- {
- const res = await uploadVideo(server.url, server.accessToken, { name: 'video 3' })
- videoId3 = res.body.video.id
- }
-
- {
- const res = await uploadVideo(server.url, server.accessToken, { name: 'video 4' })
- videoId4 = res.body.video.id
- }
-
- await addVideoInPlaylist({
+ const getBase = (elementAttrs: any = {}, wrapper: any = {}) => {
+ return Object.assign({
url: server.url,
token: server.accessToken,
playlistId: playlistUUID,
- elementAttrs: { videoId: videoId3 }
- })
+ elementAttrs: Object.assign({
+ startPosition: 1,
+ insertAfterPosition: 2,
+ reorderLength: 3
+ }, elementAttrs),
+ expectedStatus: 400
+ }, wrapper)
+ }
- await addVideoInPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: { videoId: videoId4 }
- })
+ before(async function () {
+ videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id
+ videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id
+
+ for (let id of [ videoId3, videoId4 ]) {
+ await addVideoInPlaylist({
+ url: server.url,
+ token: server.accessToken,
+ playlistId: playlistUUID,
+ elementAttrs: { videoId: id }
+ })
+ }
})
it('Should fail with an unauthenticated user', async function () {
- await reorderVideosPlaylist({
- url: server.url,
- token: null,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2
- },
- expectedStatus: 401
- })
+ const params = getBase({}, { token: null, expectedStatus: 401 })
+ await reorderVideosPlaylist(params)
})
it('Should fail with the playlist of another user', async function () {
- await reorderVideosPlaylist({
- url: server.url,
- token: userAccessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2
- },
- expectedStatus: 403
- })
+ const params = getBase({}, { token: userAccessToken, expectedStatus: 403 })
+ await reorderVideosPlaylist(params)
})
it('Should fail with an invalid playlist', async function () {
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: 'toto',
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({}, { playlistId: 'toto' })
+ await reorderVideosPlaylist(params)
+ }
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: 42,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2
- },
- expectedStatus: 404
- })
+ {
+ const params = getBase({}, { playlistId: 42, expectedStatus: 404 })
+ await reorderVideosPlaylist(params)
+ }
})
it('Should fail with an invalid start position', async function () {
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: -1,
- insertAfterPosition: 2
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ startPosition: -1 })
+ await reorderVideosPlaylist(params)
+ }
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 'toto' as any,
- insertAfterPosition: 2
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ startPosition: 'toto' as any })
+ await reorderVideosPlaylist(params)
+ }
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 42,
- insertAfterPosition: 2
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ startPosition: 42 })
+ await reorderVideosPlaylist(params)
+ }
})
it('Should fail with an invalid insert after position', async function () {
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 'toto' as any
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ insertAfterPosition: 'toto' as any })
+ await reorderVideosPlaylist(params)
+ }
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: -2
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ insertAfterPosition: -2 })
+ await reorderVideosPlaylist(params)
+ }
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 42
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ insertAfterPosition: 42 })
+ await reorderVideosPlaylist(params)
+ }
})
it('Should fail with an invalid reorder length', async function () {
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2,
- reorderLength: 'toto' as any
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ reorderLength: 'toto' as any })
+ await reorderVideosPlaylist(params)
+ }
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2,
- reorderLength: -1
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ reorderLength: -2 })
+ await reorderVideosPlaylist(params)
+ }
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2,
- reorderLength: 4
- },
- expectedStatus: 400
- })
+ {
+ const params = getBase({ reorderLength: 42 })
+ await reorderVideosPlaylist(params)
+ }
})
it('Succeed with the correct params', async function () {
- await reorderVideosPlaylist({
- url: server.url,
- token: server.accessToken,
- playlistId: playlistUUID,
- elementAttrs: {
- startPosition: 1,
- insertAfterPosition: 2,
- reorderLength: 3
- },
- expectedStatus: 204
- })
+ const params = getBase({}, { expectedStatus: 204 })
+ await reorderVideosPlaylist(params)
})
})
describe('When deleting an element in a playlist', function () {
- it('Should fail with an unauthenticated user', async function () {
- await removeVideoFromPlaylist({
+ const getBase = (wrapper: any = {}) => {
+ return Object.assign({
url: server.url,
- token: null,
- videoId,
+ token: server.accessToken,
+ videoId: videoId,
playlistId: playlistUUID,
- expectedStatus: 401
- })
+ expectedStatus: 400
+ }, wrapper)
+ }
+
+ it('Should fail with an unauthenticated user', async function () {
+ const params = getBase({ token: null, expectedStatus: 401 })
+ await removeVideoFromPlaylist(params)
})
it('Should fail with the playlist of another user', async function () {
- await removeVideoFromPlaylist({
- url: server.url,
- token: userAccessToken,
- videoId,
- playlistId: playlistUUID,
- expectedStatus: 403
- })
+ const params = getBase({ token: userAccessToken, expectedStatus: 403 })
+ await removeVideoFromPlaylist(params)
})
it('Should fail with an unknown or incorrect playlist id', async function () {
- await removeVideoFromPlaylist({
- url: server.url,
- token: server.accessToken,
- videoId,
- playlistId: 'toto',
- expectedStatus: 400
- })
+ {
+ const params = getBase({ playlistId: 'toto' })
+ await removeVideoFromPlaylist(params)
+ }
- await removeVideoFromPlaylist({
- url: server.url,
- token: server.accessToken,
- videoId,
- playlistId: 42,
- expectedStatus: 404
- })
+ {
+ const params = getBase({ playlistId: 42, expectedStatus: 404 })
+ await removeVideoFromPlaylist(params)
+ }
})
it('Should fail with an unknown or incorrect video id', async function () {
- await removeVideoFromPlaylist({
- url: server.url,
- token: server.accessToken,
- videoId: 'toto',
- playlistId: playlistUUID,
- expectedStatus: 400
- })
+ {
+ const params = getBase({ videoId: 'toto' })
+ await removeVideoFromPlaylist(params)
+ }
- await removeVideoFromPlaylist({
- url: server.url,
- token: server.accessToken,
- videoId: 42,
- playlistId: playlistUUID,
- expectedStatus: 404
- })
+ {
+ const params = getBase({ videoId: 42, expectedStatus: 404 })
+ await removeVideoFromPlaylist(params)
+ }
})
it('Should fail with an unknown element', async function () {
- await removeVideoFromPlaylist({
- url: server.url,
- token: server.accessToken,
- videoId: videoId2,
- playlistId: playlistUUID,
- expectedStatus: 404
- })
+ const params = getBase({ videoId: videoId2, expectedStatus: 404 })
+ await removeVideoFromPlaylist(params)
})
it('Succeed with the correct params', async function () {
- await removeVideoFromPlaylist({
- url: server.url,
- token: server.accessToken,
- videoId: videoId,
- playlistId: playlistUUID,
- expectedStatus: 204
- })
+ const params = getBase({ expectedStatus: 204 })
+ await removeVideoFromPlaylist(params)
})
})
await deleteVideoPlaylist(server.url, userAccessToken, playlistUUID, 403)
})
+ it('Should fail with the watch later playlist', async function () {
+ await deleteVideoPlaylist(server.url, server.accessToken, watchLaterPlaylistId, 409)
+ })
+
it('Should succeed with the correct params', async function () {
await deleteVideoPlaylist(server.url, server.accessToken, playlistUUID)
})
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 create a playlist on server 1 and have the playlist on server 2 and 3', async function () {
+ 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
-
- // update other details
+ this.timeout(30000)
+
+ 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 () {
icon: ActivityIconObject
+ published: string
+ updated: string
+
orderedItems?: string[]
partOf?: string
--- /dev/null
+export enum VideoPlaylistType {
+ REGULAR = 1,
+ WATCH_LATER = 2
+}
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
videosLength: number
+ type: VideoConstant<VideoPlaylistType>
+
createdAt: Date | string
updatedAt: Date | string
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])
import { readdir, readFile } from 'fs-extra'
import { existsSync } from 'fs'
import { expect } from 'chai'
+import { VideoChannel } from '../../models/videos'
interface ServerInfo {
app: ChildProcess,
}
accessToken?: string
+ videoChannel?: VideoChannel
video?: {
id: number
id: number
uuid: string
}
+
+ videos?: { id: number, uuid: string }[]
}
function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
import { UserRole } from '../../index'
import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
+import { ServerInfo, userLogin } from '..'
function createUser (
url: string,
.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 = {
resetPassword,
updateMyAvatar,
askSendVerifyEmail,
+ generateUserAccessToken,
verifyEmail
}
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'
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 {
addVideoChannel,
updateVideoChannel,
deleteVideoChannel,
- getVideoChannel
+ getVideoChannel,
+ setDefaultVideoChannel
}
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'
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
})
}
})
}
+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
})
}
-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({
token: string,
playlistId: number | string,
videoId: number | string,
- expectedStatus: number
+ expectedStatus?: number
}) {
const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId
insertAfterPosition: number,
reorderLength?: number
},
- expectedStatus: number
+ expectedStatus?: number
}) {
const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder'
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,
updateVideoPlaylistElement,
removeVideoFromPlaylist,
- reorderVideosPlaylist
+ reorderVideosPlaylist,
+
+ checkPlaylistFilesWereRemoved
}
/* 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'
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'
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) {
}
}
+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,
getLocalVideos,
completeVideoCheck,
checkVideoFilesWereRemoved,
- getPlaylistVideos
+ getPlaylistVideos,
+ uploadVideoAndGetId
}