diff options
Diffstat (limited to 'server')
19 files changed, 1203 insertions, 728 deletions
diff --git a/server/controllers/api/users/my-video-playlists.ts b/server/controllers/api/users/my-video-playlists.ts index 15e92f4f3..735a3cbee 100644 --- a/server/controllers/api/users/my-video-playlists.ts +++ b/server/controllers/api/users/my-video-playlists.ts | |||
@@ -35,6 +35,7 @@ async function doVideosInPlaylistExist (req: express.Request, res: express.Respo | |||
35 | for (const result of results) { | 35 | for (const result of results) { |
36 | for (const element of result.VideoPlaylistElements) { | 36 | for (const element of result.VideoPlaylistElements) { |
37 | existObject[element.videoId].push({ | 37 | existObject[element.videoId].push({ |
38 | playlistElementId: element.id, | ||
38 | playlistId: result.id, | 39 | playlistId: result.id, |
39 | startTimestamp: element.startTimestamp, | 40 | startTimestamp: element.startTimestamp, |
40 | stopTimestamp: element.stopTimestamp | 41 | stopTimestamp: element.stopTimestamp |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index 62490e63b..540120cca 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -4,14 +4,13 @@ import { | |||
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
6 | authenticate, | 6 | authenticate, |
7 | commonVideosFiltersValidator, | ||
8 | optionalAuthenticate, | 7 | optionalAuthenticate, |
9 | paginationValidator, | 8 | paginationValidator, |
10 | setDefaultPagination, | 9 | setDefaultPagination, |
11 | setDefaultSort | 10 | setDefaultSort |
12 | } from '../../middlewares' | 11 | } from '../../middlewares' |
13 | import { videoPlaylistsSortValidator } from '../../middlewares/validators' | 12 | import { videoPlaylistsSortValidator } from '../../middlewares/validators' |
14 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 13 | import { buildNSFWFilter, createReqFiles } from '../../helpers/express-utils' |
15 | import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants' | 14 | import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants' |
16 | import { logger } from '../../helpers/logger' | 15 | import { logger } from '../../helpers/logger' |
17 | import { resetSequelizeInstance } from '../../helpers/database-utils' | 16 | import { resetSequelizeInstance } from '../../helpers/database-utils' |
@@ -32,7 +31,6 @@ import { join } from 'path' | |||
32 | import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send' | 31 | import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send' |
33 | import { getVideoPlaylistActivityPubUrl, getVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' | 32 | import { getVideoPlaylistActivityPubUrl, getVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' |
34 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' | 33 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' |
35 | import { VideoModel } from '../../models/video/video' | ||
36 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | 34 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' |
37 | import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model' | 35 | import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model' |
38 | import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model' | 36 | import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model' |
@@ -88,7 +86,6 @@ videoPlaylistRouter.get('/:playlistId/videos', | |||
88 | paginationValidator, | 86 | paginationValidator, |
89 | setDefaultPagination, | 87 | setDefaultPagination, |
90 | optionalAuthenticate, | 88 | optionalAuthenticate, |
91 | commonVideosFiltersValidator, | ||
92 | asyncMiddleware(getVideoPlaylistVideos) | 89 | asyncMiddleware(getVideoPlaylistVideos) |
93 | ) | 90 | ) |
94 | 91 | ||
@@ -104,13 +101,13 @@ videoPlaylistRouter.post('/:playlistId/videos/reorder', | |||
104 | asyncRetryTransactionMiddleware(reorderVideosPlaylist) | 101 | asyncRetryTransactionMiddleware(reorderVideosPlaylist) |
105 | ) | 102 | ) |
106 | 103 | ||
107 | videoPlaylistRouter.put('/:playlistId/videos/:videoId', | 104 | videoPlaylistRouter.put('/:playlistId/videos/:playlistElementId', |
108 | authenticate, | 105 | authenticate, |
109 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | 106 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), |
110 | asyncRetryTransactionMiddleware(updateVideoPlaylistElement) | 107 | asyncRetryTransactionMiddleware(updateVideoPlaylistElement) |
111 | ) | 108 | ) |
112 | 109 | ||
113 | videoPlaylistRouter.delete('/:playlistId/videos/:videoId', | 110 | videoPlaylistRouter.delete('/:playlistId/videos/:playlistElementId', |
114 | authenticate, | 111 | authenticate, |
115 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | 112 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), |
116 | asyncRetryTransactionMiddleware(removeVideoFromPlaylist) | 113 | asyncRetryTransactionMiddleware(removeVideoFromPlaylist) |
@@ -426,26 +423,20 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons | |||
426 | 423 | ||
427 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { | 424 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { |
428 | const videoPlaylistInstance = res.locals.videoPlaylist | 425 | const videoPlaylistInstance = res.locals.videoPlaylist |
429 | const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined | 426 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
427 | const server = await getServerActor() | ||
430 | 428 | ||
431 | const resultList = await VideoModel.listForApi({ | 429 | const resultList = await VideoPlaylistElementModel.listForApi({ |
432 | followerActorId, | ||
433 | start: req.query.start, | 430 | start: req.query.start, |
434 | count: req.query.count, | 431 | count: req.query.count, |
435 | sort: 'VideoPlaylistElements.position', | ||
436 | includeLocalVideos: true, | ||
437 | categoryOneOf: req.query.categoryOneOf, | ||
438 | licenceOneOf: req.query.licenceOneOf, | ||
439 | languageOneOf: req.query.languageOneOf, | ||
440 | tagsOneOf: req.query.tagsOneOf, | ||
441 | tagsAllOf: req.query.tagsAllOf, | ||
442 | filter: req.query.filter, | ||
443 | nsfw: buildNSFWFilter(res, req.query.nsfw), | ||
444 | withFiles: false, | ||
445 | videoPlaylistId: videoPlaylistInstance.id, | 432 | videoPlaylistId: videoPlaylistInstance.id, |
446 | user: res.locals.oauth ? res.locals.oauth.token.User : undefined | 433 | serverAccount: server.Account, |
434 | user | ||
447 | }) | 435 | }) |
448 | 436 | ||
449 | const additionalAttributes = { playlistInfo: true } | 437 | const options = { |
450 | return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) | 438 | displayNSFW: buildNSFWFilter(res, req.query.nsfw), |
439 | accountId: user ? user.Account.id : undefined | ||
440 | } | ||
441 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) | ||
451 | } | 442 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 5fe7d416c..8ab7c6bbd 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 405 | 17 | const LAST_MIGRATION_VERSION = 410 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
diff --git a/server/initializers/migrations/0410-video-playlist-element.ts b/server/initializers/migrations/0410-video-playlist-element.ts new file mode 100644 index 000000000..f536632a2 --- /dev/null +++ b/server/initializers/migrations/0410-video-playlist-element.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.INTEGER, | ||
12 | allowNull: true, | ||
13 | defaultValue: null | ||
14 | } | ||
15 | |||
16 | await utils.queryInterface.changeColumn('videoPlaylistElement', 'videoId', data) | ||
17 | } | ||
18 | |||
19 | await utils.queryInterface.removeConstraint('videoPlaylistElement', 'videoPlaylistElement_videoId_fkey') | ||
20 | |||
21 | await utils.queryInterface.addConstraint('videoPlaylistElement', [ 'videoId' ], { | ||
22 | type: 'foreign key', | ||
23 | references: { | ||
24 | table: 'video', | ||
25 | field: 'id' | ||
26 | }, | ||
27 | onDelete: 'set null', | ||
28 | onUpdate: 'CASCADE' | ||
29 | }) | ||
30 | } | ||
31 | |||
32 | function down (options) { | ||
33 | throw new Error('Not implemented.') | ||
34 | } | ||
35 | |||
36 | export { | ||
37 | up, | ||
38 | down | ||
39 | } | ||
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 2e9c8aa33..5823795be 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -207,8 +207,8 @@ const videoPlaylistsAddVideoValidator = [ | |||
207 | const videoPlaylistsUpdateOrRemoveVideoValidator = [ | 207 | const videoPlaylistsUpdateOrRemoveVideoValidator = [ |
208 | param('playlistId') | 208 | param('playlistId') |
209 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | 209 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), |
210 | param('videoId') | 210 | param('playlistElementId') |
211 | .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'), | 211 | .custom(isIdValid).withMessage('Should have an element id/uuid'), |
212 | body('startTimestamp') | 212 | body('startTimestamp') |
213 | .optional() | 213 | .optional() |
214 | .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'), | 214 | .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'), |
@@ -222,12 +222,10 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ | |||
222 | if (areValidationErrors(req, res)) return | 222 | if (areValidationErrors(req, res)) return |
223 | 223 | ||
224 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | 224 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return |
225 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return | ||
226 | 225 | ||
227 | const videoPlaylist = res.locals.videoPlaylist | 226 | const videoPlaylist = res.locals.videoPlaylist |
228 | const video = res.locals.video | ||
229 | 227 | ||
230 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) | 228 | const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) |
231 | if (!videoPlaylistElement) { | 229 | if (!videoPlaylistElement) { |
232 | res.status(404) | 230 | res.status(404) |
233 | .json({ error: 'Video playlist element not found' }) | 231 | .json({ error: 'Video playlist element not found' }) |
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 59f586b54..85af9e378 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -9,7 +9,7 @@ import { ActorModel } from '../activitypub/actor' | |||
9 | import { getSort, throwIfNotValid } from '../utils' | 9 | import { getSort, throwIfNotValid } from '../utils' |
10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
11 | import { AccountVideoRate } from '../../../shared' | 11 | import { AccountVideoRate } from '../../../shared' |
12 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from '../video/video-channel' | 12 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' |
13 | 13 | ||
14 | /* | 14 | /* |
15 | Account rates per video. | 15 | Account rates per video. |
@@ -109,7 +109,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
109 | required: true, | 109 | required: true, |
110 | include: [ | 110 | include: [ |
111 | { | 111 | { |
112 | model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, true] }), | 112 | model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), |
113 | required: true | 113 | required: true |
114 | } | 114 | } |
115 | ] | 115 | ] |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 09cada096..28014946f 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -27,12 +27,19 @@ import { UserModel } from './user' | |||
27 | import { AvatarModel } from '../avatar/avatar' | 27 | import { AvatarModel } from '../avatar/avatar' |
28 | import { VideoPlaylistModel } from '../video/video-playlist' | 28 | import { VideoPlaylistModel } from '../video/video-playlist' |
29 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 29 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
30 | import { Op, Transaction, WhereOptions } from 'sequelize' | 30 | import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequelize' |
31 | import { AccountBlocklistModel } from './account-blocklist' | ||
32 | import { ServerBlocklistModel } from '../server/server-blocklist' | ||
31 | 33 | ||
32 | export enum ScopeNames { | 34 | export enum ScopeNames { |
33 | SUMMARY = 'SUMMARY' | 35 | SUMMARY = 'SUMMARY' |
34 | } | 36 | } |
35 | 37 | ||
38 | export type SummaryOptions = { | ||
39 | whereActor?: WhereOptions | ||
40 | withAccountBlockerIds?: number[] | ||
41 | } | ||
42 | |||
36 | @DefaultScope(() => ({ | 43 | @DefaultScope(() => ({ |
37 | include: [ | 44 | include: [ |
38 | { | 45 | { |
@@ -42,8 +49,16 @@ export enum ScopeNames { | |||
42 | ] | 49 | ] |
43 | })) | 50 | })) |
44 | @Scopes(() => ({ | 51 | @Scopes(() => ({ |
45 | [ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => { | 52 | [ ScopeNames.SUMMARY ]: (options: SummaryOptions = {}) => { |
46 | return { | 53 | const whereActor = options.whereActor || undefined |
54 | |||
55 | const serverInclude: IncludeOptions = { | ||
56 | attributes: [ 'host' ], | ||
57 | model: ServerModel.unscoped(), | ||
58 | required: false | ||
59 | } | ||
60 | |||
61 | const query: FindOptions = { | ||
47 | attributes: [ 'id', 'name' ], | 62 | attributes: [ 'id', 'name' ], |
48 | include: [ | 63 | include: [ |
49 | { | 64 | { |
@@ -52,11 +67,8 @@ export enum ScopeNames { | |||
52 | required: true, | 67 | required: true, |
53 | where: whereActor, | 68 | where: whereActor, |
54 | include: [ | 69 | include: [ |
55 | { | 70 | serverInclude, |
56 | attributes: [ 'host' ], | 71 | |
57 | model: ServerModel.unscoped(), | ||
58 | required: false | ||
59 | }, | ||
60 | { | 72 | { |
61 | model: AvatarModel.unscoped(), | 73 | model: AvatarModel.unscoped(), |
62 | required: false | 74 | required: false |
@@ -65,6 +77,35 @@ export enum ScopeNames { | |||
65 | } | 77 | } |
66 | ] | 78 | ] |
67 | } | 79 | } |
80 | |||
81 | if (options.withAccountBlockerIds) { | ||
82 | query.include.push({ | ||
83 | attributes: [ 'id' ], | ||
84 | model: AccountBlocklistModel.unscoped(), | ||
85 | as: 'BlockedAccounts', | ||
86 | required: false, | ||
87 | where: { | ||
88 | accountId: { | ||
89 | [Op.in]: options.withAccountBlockerIds | ||
90 | } | ||
91 | } | ||
92 | }) | ||
93 | |||
94 | serverInclude.include = [ | ||
95 | { | ||
96 | attributes: [ 'id' ], | ||
97 | model: ServerBlocklistModel.unscoped(), | ||
98 | required: false, | ||
99 | where: { | ||
100 | accountId: { | ||
101 | [Op.in]: options.withAccountBlockerIds | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | ] | ||
106 | } | ||
107 | |||
108 | return query | ||
68 | } | 109 | } |
69 | })) | 110 | })) |
70 | @Table({ | 111 | @Table({ |
@@ -163,6 +204,16 @@ export class AccountModel extends Model<AccountModel> { | |||
163 | }) | 204 | }) |
164 | VideoComments: VideoCommentModel[] | 205 | VideoComments: VideoCommentModel[] |
165 | 206 | ||
207 | @HasMany(() => AccountBlocklistModel, { | ||
208 | foreignKey: { | ||
209 | name: 'targetAccountId', | ||
210 | allowNull: false | ||
211 | }, | ||
212 | as: 'BlockedAccounts', | ||
213 | onDelete: 'CASCADE' | ||
214 | }) | ||
215 | BlockedAccounts: AccountBlocklistModel[] | ||
216 | |||
166 | @BeforeDestroy | 217 | @BeforeDestroy |
167 | static async sendDeleteIfOwned (instance: AccountModel, options) { | 218 | static async sendDeleteIfOwned (instance: AccountModel, options) { |
168 | if (!instance.Actor) { | 219 | if (!instance.Actor) { |
@@ -343,4 +394,8 @@ export class AccountModel extends Model<AccountModel> { | |||
343 | getDisplayName () { | 394 | getDisplayName () { |
344 | return this.name | 395 | return this.name |
345 | } | 396 | } |
397 | |||
398 | isBlocked () { | ||
399 | return this.BlockedAccounts && this.BlockedAccounts.length !== 0 | ||
400 | } | ||
346 | } | 401 | } |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 92c01f642..5138b0f76 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -67,7 +67,6 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | |||
67 | 67 | ||
68 | @BelongsTo(() => ServerModel, { | 68 | @BelongsTo(() => ServerModel, { |
69 | foreignKey: { | 69 | foreignKey: { |
70 | name: 'targetServerId', | ||
71 | allowNull: false | 70 | allowNull: false |
72 | }, | 71 | }, |
73 | onDelete: 'CASCADE' | 72 | onDelete: 'CASCADE' |
diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 300d70938..1d211f1e0 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts | |||
@@ -2,6 +2,8 @@ import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, Updat | |||
2 | import { isHostValid } from '../../helpers/custom-validators/servers' | 2 | import { isHostValid } from '../../helpers/custom-validators/servers' |
3 | import { ActorModel } from '../activitypub/actor' | 3 | import { ActorModel } from '../activitypub/actor' |
4 | import { throwIfNotValid } from '../utils' | 4 | import { throwIfNotValid } from '../utils' |
5 | import { AccountBlocklistModel } from '../account/account-blocklist' | ||
6 | import { ServerBlocklistModel } from './server-blocklist' | ||
5 | 7 | ||
6 | @Table({ | 8 | @Table({ |
7 | tableName: 'server', | 9 | tableName: 'server', |
@@ -40,6 +42,14 @@ export class ServerModel extends Model<ServerModel> { | |||
40 | }) | 42 | }) |
41 | Actors: ActorModel[] | 43 | Actors: ActorModel[] |
42 | 44 | ||
45 | @HasMany(() => ServerBlocklistModel, { | ||
46 | foreignKey: { | ||
47 | allowNull: false | ||
48 | }, | ||
49 | onDelete: 'CASCADE' | ||
50 | }) | ||
51 | BlockedByAccounts: ServerBlocklistModel[] | ||
52 | |||
43 | static loadByHost (host: string) { | 53 | static loadByHost (host: string) { |
44 | const query = { | 54 | const query = { |
45 | where: { | 55 | where: { |
@@ -50,6 +60,10 @@ export class ServerModel extends Model<ServerModel> { | |||
50 | return ServerModel.findOne(query) | 60 | return ServerModel.findOne(query) |
51 | } | 61 | } |
52 | 62 | ||
63 | isBlocked () { | ||
64 | return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 | ||
65 | } | ||
66 | |||
53 | toFormattedJSON () { | 67 | toFormattedJSON () { |
54 | return { | 68 | return { |
55 | host: this.host | 69 | host: this.host |
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index baef1d6ce..22d949da0 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { getSortOnModel, SortType, throwIfNotValid } from '../utils' | 2 | import { getSortOnModel, SortType, throwIfNotValid } from '../utils' |
3 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' | 3 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' |
4 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' | 4 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' |
5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' | 5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' |
6 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' | 6 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' |
7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
@@ -71,7 +71,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
71 | required: true, | 71 | required: true, |
72 | include: [ | 72 | include: [ |
73 | { | 73 | { |
74 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }), | 74 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), |
75 | required: true | 75 | required: true |
76 | }, | 76 | }, |
77 | { | 77 | { |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index b0b261c88..6241a75a3 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -24,7 +24,7 @@ import { | |||
24 | isVideoChannelSupportValid | 24 | isVideoChannelSupportValid |
25 | } from '../../helpers/custom-validators/video-channels' | 25 | } from '../../helpers/custom-validators/video-channels' |
26 | import { sendDeleteActor } from '../../lib/activitypub/send' | 26 | import { sendDeleteActor } from '../../lib/activitypub/send' |
27 | import { AccountModel, ScopeNames as AccountModelScopeNames } from '../account/account' | 27 | import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' |
28 | import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' | 28 | import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' |
29 | import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' | 29 | import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' |
30 | import { VideoModel } from './video' | 30 | import { VideoModel } from './video' |
@@ -58,6 +58,11 @@ type AvailableForListOptions = { | |||
58 | actorId: number | 58 | actorId: number |
59 | } | 59 | } |
60 | 60 | ||
61 | export type SummaryOptions = { | ||
62 | withAccount?: boolean // Default: false | ||
63 | withAccountBlockerIds?: number[] | ||
64 | } | ||
65 | |||
61 | @DefaultScope(() => ({ | 66 | @DefaultScope(() => ({ |
62 | include: [ | 67 | include: [ |
63 | { | 68 | { |
@@ -67,7 +72,7 @@ type AvailableForListOptions = { | |||
67 | ] | 72 | ] |
68 | })) | 73 | })) |
69 | @Scopes(() => ({ | 74 | @Scopes(() => ({ |
70 | [ScopeNames.SUMMARY]: (withAccount = false) => { | 75 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { |
71 | const base: FindOptions = { | 76 | const base: FindOptions = { |
72 | attributes: [ 'name', 'description', 'id', 'actorId' ], | 77 | attributes: [ 'name', 'description', 'id', 'actorId' ], |
73 | include: [ | 78 | include: [ |
@@ -90,9 +95,11 @@ type AvailableForListOptions = { | |||
90 | ] | 95 | ] |
91 | } | 96 | } |
92 | 97 | ||
93 | if (withAccount === true) { | 98 | if (options.withAccount === true) { |
94 | base.include.push({ | 99 | base.include.push({ |
95 | model: AccountModel.scope(AccountModelScopeNames.SUMMARY), | 100 | model: AccountModel.scope({ |
101 | method: [ AccountModelScopeNames.SUMMARY, { withAccountBlockerIds: options.withAccountBlockerIds } as AccountSummaryOptions ] | ||
102 | }), | ||
96 | required: true | 103 | required: true |
97 | }) | 104 | }) |
98 | } | 105 | } |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index b947eb16f..284539def 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -26,7 +26,6 @@ export type VideoFormattingJSONOptions = { | |||
26 | waitTranscoding?: boolean, | 26 | waitTranscoding?: boolean, |
27 | scheduledUpdate?: boolean, | 27 | scheduledUpdate?: boolean, |
28 | blacklistInfo?: boolean | 28 | blacklistInfo?: boolean |
29 | playlistInfo?: boolean | ||
30 | } | 29 | } |
31 | } | 30 | } |
32 | function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { | 31 | function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { |
@@ -98,17 +97,6 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
98 | videoObject.blacklisted = !!video.VideoBlacklist | 97 | videoObject.blacklisted = !!video.VideoBlacklist |
99 | videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null | 98 | videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null |
100 | } | 99 | } |
101 | |||
102 | if (options.additionalAttributes.playlistInfo === true) { | ||
103 | // We filtered on a specific videoId/videoPlaylistId, that is unique | ||
104 | const playlistElement = video.VideoPlaylistElements[0] | ||
105 | |||
106 | videoObject.playlistElement = { | ||
107 | position: playlistElement.position, | ||
108 | startTimestamp: playlistElement.startTimestamp, | ||
109 | stopTimestamp: playlistElement.stopTimestamp | ||
110 | } | ||
111 | } | ||
112 | } | 100 | } |
113 | 101 | ||
114 | return videoObject | 102 | return videoObject |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index eeb3d6bbd..bed6f8eaf 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -13,14 +13,18 @@ import { | |||
13 | Table, | 13 | Table, |
14 | UpdatedAt | 14 | UpdatedAt |
15 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
16 | import { VideoModel } from './video' | 16 | import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' |
17 | import { VideoPlaylistModel } from './video-playlist' | 17 | import { VideoPlaylistModel } from './video-playlist' |
18 | import { getSort, throwIfNotValid } from '../utils' | 18 | import { getSort, throwIfNotValid } from '../utils' |
19 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 19 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
20 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 20 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
21 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' | 21 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' |
22 | import * as validator from 'validator' | 22 | import * as validator from 'validator' |
23 | import { AggregateOptions, Op, Sequelize, Transaction } from 'sequelize' | 23 | import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize' |
24 | import { UserModel } from '../account/user' | ||
25 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' | ||
26 | import { AccountModel } from '../account/account' | ||
27 | import { VideoPrivacy } from '../../../shared/models/videos' | ||
24 | 28 | ||
25 | @Table({ | 29 | @Table({ |
26 | tableName: 'videoPlaylistElement', | 30 | tableName: 'videoPlaylistElement', |
@@ -90,9 +94,9 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
90 | 94 | ||
91 | @BelongsTo(() => VideoModel, { | 95 | @BelongsTo(() => VideoModel, { |
92 | foreignKey: { | 96 | foreignKey: { |
93 | allowNull: false | 97 | allowNull: true |
94 | }, | 98 | }, |
95 | onDelete: 'CASCADE' | 99 | onDelete: 'set null' |
96 | }) | 100 | }) |
97 | Video: VideoModel | 101 | Video: VideoModel |
98 | 102 | ||
@@ -107,6 +111,57 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
107 | return VideoPlaylistElementModel.destroy(query) | 111 | return VideoPlaylistElementModel.destroy(query) |
108 | } | 112 | } |
109 | 113 | ||
114 | static listForApi (options: { | ||
115 | start: number, | ||
116 | count: number, | ||
117 | videoPlaylistId: number, | ||
118 | serverAccount: AccountModel, | ||
119 | user?: UserModel | ||
120 | }) { | ||
121 | const accountIds = [ options.serverAccount.id ] | ||
122 | const videoScope: (ScopeOptions | string)[] = [ | ||
123 | VideoScopeNames.WITH_BLACKLISTED | ||
124 | ] | ||
125 | |||
126 | if (options.user) { | ||
127 | accountIds.push(options.user.Account.id) | ||
128 | videoScope.push({ method: [ VideoScopeNames.WITH_USER_HISTORY, options.user.id ] }) | ||
129 | } | ||
130 | |||
131 | const forApiOptions: ForAPIOptions = { withAccountBlockerIds: accountIds } | ||
132 | videoScope.push({ | ||
133 | method: [ | ||
134 | VideoScopeNames.FOR_API, forApiOptions | ||
135 | ] | ||
136 | }) | ||
137 | |||
138 | const findQuery = { | ||
139 | offset: options.start, | ||
140 | limit: options.count, | ||
141 | order: getSort('position'), | ||
142 | where: { | ||
143 | videoPlaylistId: options.videoPlaylistId | ||
144 | }, | ||
145 | include: [ | ||
146 | { | ||
147 | model: VideoModel.scope(videoScope), | ||
148 | required: false | ||
149 | } | ||
150 | ] | ||
151 | } | ||
152 | |||
153 | const countQuery = { | ||
154 | where: { | ||
155 | videoPlaylistId: options.videoPlaylistId | ||
156 | } | ||
157 | } | ||
158 | |||
159 | return Promise.all([ | ||
160 | VideoPlaylistElementModel.count(countQuery), | ||
161 | VideoPlaylistElementModel.findAll(findQuery) | ||
162 | ]).then(([ total, data ]) => ({ total, data })) | ||
163 | } | ||
164 | |||
110 | static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { | 165 | static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { |
111 | const query = { | 166 | const query = { |
112 | where: { | 167 | where: { |
@@ -118,6 +173,10 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
118 | return VideoPlaylistElementModel.findOne(query) | 173 | return VideoPlaylistElementModel.findOne(query) |
119 | } | 174 | } |
120 | 175 | ||
176 | static loadById (playlistElementId: number) { | ||
177 | return VideoPlaylistElementModel.findByPk(playlistElementId) | ||
178 | } | ||
179 | |||
121 | static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { | 180 | static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { |
122 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } | 181 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } |
123 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } | 182 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } |
@@ -213,6 +272,42 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
213 | return VideoPlaylistElementModel.increment({ position: by }, query) | 272 | return VideoPlaylistElementModel.increment({ position: by }, query) |
214 | } | 273 | } |
215 | 274 | ||
275 | getType (displayNSFW?: boolean, accountId?: number) { | ||
276 | const video = this.Video | ||
277 | |||
278 | if (!video) return VideoPlaylistElementType.DELETED | ||
279 | |||
280 | // Owned video, don't filter it | ||
281 | if (accountId && video.VideoChannel.Account.id === accountId) return VideoPlaylistElementType.REGULAR | ||
282 | |||
283 | if (video.privacy === VideoPrivacy.PRIVATE) return VideoPlaylistElementType.PRIVATE | ||
284 | |||
285 | if (video.isBlacklisted() || video.isBlocked()) return VideoPlaylistElementType.UNAVAILABLE | ||
286 | if (video.nsfw === true && displayNSFW === false) return VideoPlaylistElementType.UNAVAILABLE | ||
287 | |||
288 | return VideoPlaylistElementType.REGULAR | ||
289 | } | ||
290 | |||
291 | getVideoElement (displayNSFW?: boolean, accountId?: number) { | ||
292 | if (!this.Video) return null | ||
293 | if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null | ||
294 | |||
295 | return this.Video.toFormattedJSON() | ||
296 | } | ||
297 | |||
298 | toFormattedJSON (options: { displayNSFW?: boolean, accountId?: number } = {}): VideoPlaylistElement { | ||
299 | return { | ||
300 | id: this.id, | ||
301 | position: this.position, | ||
302 | startTimestamp: this.startTimestamp, | ||
303 | stopTimestamp: this.stopTimestamp, | ||
304 | |||
305 | type: this.getType(options.displayNSFW, options.accountId), | ||
306 | |||
307 | video: this.getVideoElement(options.displayNSFW, options.accountId) | ||
308 | } | ||
309 | } | ||
310 | |||
216 | toActivityPubObject (): PlaylistElementObject { | 311 | toActivityPubObject (): PlaylistElementObject { |
217 | const base: PlaylistElementObject = { | 312 | const base: PlaylistElementObject = { |
218 | id: this.url, | 313 | id: this.url, |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index 63b4a0715..61ff78bd2 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -33,7 +33,7 @@ import { | |||
33 | WEBSERVER | 33 | WEBSERVER |
34 | } from '../../initializers/constants' | 34 | } from '../../initializers/constants' |
35 | import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model' | 35 | import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model' |
36 | import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' | 36 | import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account' |
37 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' | 37 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' |
38 | import { join } from 'path' | 38 | import { join } from 'path' |
39 | import { VideoPlaylistElementModel } from './video-playlist-element' | 39 | import { VideoPlaylistElementModel } from './video-playlist-element' |
@@ -115,7 +115,7 @@ type AvailableForListOptions = { | |||
115 | [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { | 115 | [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { |
116 | // Only list local playlists OR playlists that are on an instance followed by actorId | 116 | // Only list local playlists OR playlists that are on an instance followed by actorId |
117 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) | 117 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) |
118 | const actorWhere = { | 118 | const whereActor = { |
119 | [ Op.or ]: [ | 119 | [ Op.or ]: [ |
120 | { | 120 | { |
121 | serverId: null | 121 | serverId: null |
@@ -159,7 +159,7 @@ type AvailableForListOptions = { | |||
159 | } | 159 | } |
160 | 160 | ||
161 | const accountScope = { | 161 | const accountScope = { |
162 | method: [ AccountScopeNames.SUMMARY, actorWhere ] | 162 | method: [ AccountScopeNames.SUMMARY, { whereActor } as SummaryOptions ] |
163 | } | 163 | } |
164 | 164 | ||
165 | return { | 165 | return { |
@@ -341,7 +341,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
341 | }, | 341 | }, |
342 | include: [ | 342 | include: [ |
343 | { | 343 | { |
344 | attributes: [ 'videoId', 'startTimestamp', 'stopTimestamp' ], | 344 | attributes: [ 'id', 'videoId', 'startTimestamp', 'stopTimestamp' ], |
345 | model: VideoPlaylistElementModel.unscoped(), | 345 | model: VideoPlaylistElementModel.unscoped(), |
346 | where: { | 346 | where: { |
347 | videoId: { | 347 | videoId: { |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index c7f2658ed..05d625fc1 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -91,7 +91,7 @@ import { | |||
91 | } from '../utils' | 91 | } from '../utils' |
92 | import { TagModel } from './tag' | 92 | import { TagModel } from './tag' |
93 | import { VideoAbuseModel } from './video-abuse' | 93 | import { VideoAbuseModel } from './video-abuse' |
94 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' | 94 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' |
95 | import { VideoCommentModel } from './video-comment' | 95 | import { VideoCommentModel } from './video-comment' |
96 | import { VideoFileModel } from './video-file' | 96 | import { VideoFileModel } from './video-file' |
97 | import { VideoShareModel } from './video-share' | 97 | import { VideoShareModel } from './video-share' |
@@ -190,26 +190,29 @@ export enum ScopeNames { | |||
190 | WITH_FILES = 'WITH_FILES', | 190 | WITH_FILES = 'WITH_FILES', |
191 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', | 191 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', |
192 | WITH_BLACKLISTED = 'WITH_BLACKLISTED', | 192 | WITH_BLACKLISTED = 'WITH_BLACKLISTED', |
193 | WITH_BLOCKLIST = 'WITH_BLOCKLIST', | ||
193 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', | 194 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', |
194 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', | 195 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', |
195 | WITH_USER_ID = 'WITH_USER_ID', | 196 | WITH_USER_ID = 'WITH_USER_ID', |
196 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' | 197 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' |
197 | } | 198 | } |
198 | 199 | ||
199 | type ForAPIOptions = { | 200 | export type ForAPIOptions = { |
200 | ids: number[] | 201 | ids?: number[] |
201 | 202 | ||
202 | videoPlaylistId?: number | 203 | videoPlaylistId?: number |
203 | 204 | ||
204 | withFiles?: boolean | 205 | withFiles?: boolean |
206 | |||
207 | withAccountBlockerIds?: number[] | ||
205 | } | 208 | } |
206 | 209 | ||
207 | type AvailableForListIDsOptions = { | 210 | export type AvailableForListIDsOptions = { |
208 | serverAccountId: number | 211 | serverAccountId: number |
209 | followerActorId: number | 212 | followerActorId: number |
210 | includeLocalVideos: boolean | 213 | includeLocalVideos: boolean |
211 | 214 | ||
212 | withoutId?: boolean | 215 | attributesType?: 'none' | 'id' | 'all' |
213 | 216 | ||
214 | filter?: VideoFilter | 217 | filter?: VideoFilter |
215 | categoryOneOf?: number[] | 218 | categoryOneOf?: number[] |
@@ -236,14 +239,16 @@ type AvailableForListIDsOptions = { | |||
236 | @Scopes(() => ({ | 239 | @Scopes(() => ({ |
237 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { | 240 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { |
238 | const query: FindOptions = { | 241 | const query: FindOptions = { |
239 | where: { | ||
240 | id: { | ||
241 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken | ||
242 | } | ||
243 | }, | ||
244 | include: [ | 242 | include: [ |
245 | { | 243 | { |
246 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }), | 244 | model: VideoChannelModel.scope({ |
245 | method: [ | ||
246 | VideoChannelScopeNames.SUMMARY, { | ||
247 | withAccount: true, | ||
248 | withAccountBlockerIds: options.withAccountBlockerIds | ||
249 | } as SummaryOptions | ||
250 | ] | ||
251 | }), | ||
247 | required: true | 252 | required: true |
248 | }, | 253 | }, |
249 | { | 254 | { |
@@ -254,6 +259,14 @@ type AvailableForListIDsOptions = { | |||
254 | ] | 259 | ] |
255 | } | 260 | } |
256 | 261 | ||
262 | if (options.ids) { | ||
263 | query.where = { | ||
264 | id: { | ||
265 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken | ||
266 | } | ||
267 | } | ||
268 | } | ||
269 | |||
257 | if (options.withFiles === true) { | 270 | if (options.withFiles === true) { |
258 | query.include.push({ | 271 | query.include.push({ |
259 | model: VideoFileModel.unscoped(), | 272 | model: VideoFileModel.unscoped(), |
@@ -278,10 +291,14 @@ type AvailableForListIDsOptions = { | |||
278 | 291 | ||
279 | const query: FindOptions = { | 292 | const query: FindOptions = { |
280 | raw: true, | 293 | raw: true, |
281 | attributes: options.withoutId === true ? [] : [ 'id' ], | ||
282 | include: [] | 294 | include: [] |
283 | } | 295 | } |
284 | 296 | ||
297 | const attributesType = options.attributesType || 'id' | ||
298 | |||
299 | if (attributesType === 'id') query.attributes = [ 'id' ] | ||
300 | else if (attributesType === 'none') query.attributes = [ ] | ||
301 | |||
285 | whereAnd.push({ | 302 | whereAnd.push({ |
286 | id: { | 303 | id: { |
287 | [ Op.notIn ]: Sequelize.literal( | 304 | [ Op.notIn ]: Sequelize.literal( |
@@ -290,17 +307,19 @@ type AvailableForListIDsOptions = { | |||
290 | } | 307 | } |
291 | }) | 308 | }) |
292 | 309 | ||
293 | whereAnd.push({ | 310 | if (options.serverAccountId) { |
294 | channelId: { | 311 | whereAnd.push({ |
295 | [ Op.notIn ]: Sequelize.literal( | 312 | channelId: { |
296 | '(' + | 313 | [ Op.notIn ]: Sequelize.literal( |
297 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + | 314 | '(' + |
298 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + | 315 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + |
299 | ')' + | 316 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + |
300 | ')' | 317 | ')' + |
301 | ) | 318 | ')' |
302 | } | 319 | ) |
303 | }) | 320 | } |
321 | }) | ||
322 | } | ||
304 | 323 | ||
305 | // Only list public/published videos | 324 | // Only list public/published videos |
306 | if (!options.filter || options.filter !== 'all-local') { | 325 | if (!options.filter || options.filter !== 'all-local') { |
@@ -528,6 +547,9 @@ type AvailableForListIDsOptions = { | |||
528 | 547 | ||
529 | return query | 548 | return query |
530 | }, | 549 | }, |
550 | [ScopeNames.WITH_BLOCKLIST]: { | ||
551 | |||
552 | }, | ||
531 | [ ScopeNames.WITH_THUMBNAILS ]: { | 553 | [ ScopeNames.WITH_THUMBNAILS ]: { |
532 | include: [ | 554 | include: [ |
533 | { | 555 | { |
@@ -845,9 +867,9 @@ export class VideoModel extends Model<VideoModel> { | |||
845 | @HasMany(() => VideoPlaylistElementModel, { | 867 | @HasMany(() => VideoPlaylistElementModel, { |
846 | foreignKey: { | 868 | foreignKey: { |
847 | name: 'videoId', | 869 | name: 'videoId', |
848 | allowNull: false | 870 | allowNull: true |
849 | }, | 871 | }, |
850 | onDelete: 'cascade' | 872 | onDelete: 'set null' |
851 | }) | 873 | }) |
852 | VideoPlaylistElements: VideoPlaylistElementModel[] | 874 | VideoPlaylistElements: VideoPlaylistElementModel[] |
853 | 875 | ||
@@ -1586,7 +1608,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1586 | serverAccountId: serverActor.Account.id, | 1608 | serverAccountId: serverActor.Account.id, |
1587 | followerActorId, | 1609 | followerActorId, |
1588 | includeLocalVideos: true, | 1610 | includeLocalVideos: true, |
1589 | withoutId: true // Don't break aggregation | 1611 | attributesType: 'none' // Don't break aggregation |
1590 | } | 1612 | } |
1591 | 1613 | ||
1592 | const query: FindOptions = { | 1614 | const query: FindOptions = { |
@@ -1719,6 +1741,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1719 | return !!this.VideoBlacklist | 1741 | return !!this.VideoBlacklist |
1720 | } | 1742 | } |
1721 | 1743 | ||
1744 | isBlocked () { | ||
1745 | return (this.VideoChannel.Account.Actor.Server && this.VideoChannel.Account.Actor.Server.isBlocked()) || | ||
1746 | this.VideoChannel.Account.isBlocked() | ||
1747 | } | ||
1748 | |||
1722 | getOriginalFile () { | 1749 | getOriginalFile () { |
1723 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1750 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1724 | 1751 | ||
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts index 8c5e44bdd..ae5aa287f 100644 --- a/server/tests/api/check-params/video-playlists.ts +++ b/server/tests/api/check-params/video-playlists.ts | |||
@@ -37,6 +37,7 @@ describe('Test video playlists API validator', function () { | |||
37 | let watchLaterPlaylistId: number | 37 | let watchLaterPlaylistId: number |
38 | let videoId: number | 38 | let videoId: number |
39 | let videoId2: number | 39 | let videoId2: number |
40 | let playlistElementId: number | ||
40 | 41 | ||
41 | // --------------------------------------------------------------- | 42 | // --------------------------------------------------------------- |
42 | 43 | ||
@@ -132,18 +133,18 @@ describe('Test video playlists API validator', function () { | |||
132 | }) | 133 | }) |
133 | 134 | ||
134 | describe('When listing videos of a playlist', function () { | 135 | describe('When listing videos of a playlist', function () { |
135 | const path = '/api/v1/video-playlists' | 136 | const path = '/api/v1/video-playlists/' |
136 | 137 | ||
137 | it('Should fail with a bad start pagination', async function () { | 138 | it('Should fail with a bad start pagination', async function () { |
138 | await checkBadStartPagination(server.url, path, server.accessToken) | 139 | await checkBadStartPagination(server.url, path + playlistUUID + '/videos', server.accessToken) |
139 | }) | 140 | }) |
140 | 141 | ||
141 | it('Should fail with a bad count pagination', async function () { | 142 | it('Should fail with a bad count pagination', async function () { |
142 | await checkBadCountPagination(server.url, path, server.accessToken) | 143 | await checkBadCountPagination(server.url, path + playlistUUID + '/videos', server.accessToken) |
143 | }) | 144 | }) |
144 | 145 | ||
145 | it('Should fail with a bad filter', async function () { | 146 | it('Should success with the correct parameters', async function () { |
146 | await checkBadSortPagination(server.url, path, server.accessToken) | 147 | await makeGetRequest({ url: server.url, path: path + playlistUUID + '/videos', statusCodeExpected: 200 }) |
147 | }) | 148 | }) |
148 | }) | 149 | }) |
149 | 150 | ||
@@ -296,7 +297,7 @@ describe('Test video playlists API validator', function () { | |||
296 | token: server.accessToken, | 297 | token: server.accessToken, |
297 | playlistId: playlistUUID, | 298 | playlistId: playlistUUID, |
298 | elementAttrs: Object.assign({ | 299 | elementAttrs: Object.assign({ |
299 | videoId: videoId, | 300 | videoId, |
300 | startTimestamp: 2, | 301 | startTimestamp: 2, |
301 | stopTimestamp: 3 | 302 | stopTimestamp: 3 |
302 | }, elementAttrs) | 303 | }, elementAttrs) |
@@ -344,7 +345,8 @@ describe('Test video playlists API validator', function () { | |||
344 | 345 | ||
345 | it('Succeed with the correct params', async function () { | 346 | it('Succeed with the correct params', async function () { |
346 | const params = getBase({}, { expectedStatus: 200 }) | 347 | const params = getBase({}, { expectedStatus: 200 }) |
347 | await addVideoInPlaylist(params) | 348 | const res = await addVideoInPlaylist(params) |
349 | playlistElementId = res.body.videoPlaylistElement.id | ||
348 | }) | 350 | }) |
349 | 351 | ||
350 | it('Should fail if the video was already added in the playlist', async function () { | 352 | it('Should fail if the video was already added in the playlist', async function () { |
@@ -362,7 +364,7 @@ describe('Test video playlists API validator', function () { | |||
362 | startTimestamp: 1, | 364 | startTimestamp: 1, |
363 | stopTimestamp: 2 | 365 | stopTimestamp: 2 |
364 | }, elementAttrs), | 366 | }, elementAttrs), |
365 | videoId: videoId, | 367 | playlistElementId, |
366 | playlistId: playlistUUID, | 368 | playlistId: playlistUUID, |
367 | expectedStatus: 400 | 369 | expectedStatus: 400 |
368 | }, wrapper) | 370 | }, wrapper) |
@@ -390,14 +392,14 @@ describe('Test video playlists API validator', function () { | |||
390 | } | 392 | } |
391 | }) | 393 | }) |
392 | 394 | ||
393 | it('Should fail with an unknown or incorrect video id', async function () { | 395 | it('Should fail with an unknown or incorrect playlistElement id', async function () { |
394 | { | 396 | { |
395 | const params = getBase({}, { videoId: 'toto' }) | 397 | const params = getBase({}, { playlistElementId: 'toto' }) |
396 | await updateVideoPlaylistElement(params) | 398 | await updateVideoPlaylistElement(params) |
397 | } | 399 | } |
398 | 400 | ||
399 | { | 401 | { |
400 | const params = getBase({}, { videoId: 42, expectedStatus: 404 }) | 402 | const params = getBase({}, { playlistElementId: 42, expectedStatus: 404 }) |
401 | await updateVideoPlaylistElement(params) | 403 | await updateVideoPlaylistElement(params) |
402 | } | 404 | } |
403 | }) | 405 | }) |
@@ -415,7 +417,7 @@ describe('Test video playlists API validator', function () { | |||
415 | }) | 417 | }) |
416 | 418 | ||
417 | it('Should fail with an unknown element', async function () { | 419 | it('Should fail with an unknown element', async function () { |
418 | const params = getBase({}, { videoId: videoId2, expectedStatus: 404 }) | 420 | const params = getBase({}, { playlistElementId: 888, expectedStatus: 404 }) |
419 | await updateVideoPlaylistElement(params) | 421 | await updateVideoPlaylistElement(params) |
420 | }) | 422 | }) |
421 | 423 | ||
@@ -587,7 +589,7 @@ describe('Test video playlists API validator', function () { | |||
587 | return Object.assign({ | 589 | return Object.assign({ |
588 | url: server.url, | 590 | url: server.url, |
589 | token: server.accessToken, | 591 | token: server.accessToken, |
590 | videoId: videoId, | 592 | playlistElementId, |
591 | playlistId: playlistUUID, | 593 | playlistId: playlistUUID, |
592 | expectedStatus: 400 | 594 | expectedStatus: 400 |
593 | }, wrapper) | 595 | }, wrapper) |
@@ -617,18 +619,18 @@ describe('Test video playlists API validator', function () { | |||
617 | 619 | ||
618 | it('Should fail with an unknown or incorrect video id', async function () { | 620 | it('Should fail with an unknown or incorrect video id', async function () { |
619 | { | 621 | { |
620 | const params = getBase({ videoId: 'toto' }) | 622 | const params = getBase({ playlistElementId: 'toto' }) |
621 | await removeVideoFromPlaylist(params) | 623 | await removeVideoFromPlaylist(params) |
622 | } | 624 | } |
623 | 625 | ||
624 | { | 626 | { |
625 | const params = getBase({ videoId: 42, expectedStatus: 404 }) | 627 | const params = getBase({ playlistElementId: 42, expectedStatus: 404 }) |
626 | await removeVideoFromPlaylist(params) | 628 | await removeVideoFromPlaylist(params) |
627 | } | 629 | } |
628 | }) | 630 | }) |
629 | 631 | ||
630 | it('Should fail with an unknown element', async function () { | 632 | it('Should fail with an unknown element', async function () { |
631 | const params = getBase({ videoId: videoId2, expectedStatus: 404 }) | 633 | const params = getBase({ playlistElementId: 888, expectedStatus: 404 }) |
632 | await removeVideoFromPlaylist(params) | 634 | await removeVideoFromPlaylist(params) |
633 | }) | 635 | }) |
634 | 636 | ||
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts index babef8223..5a5668665 100644 --- a/server/tests/api/check-params/videos-filter.ts +++ b/server/tests/api/check-params/videos-filter.ts | |||
@@ -15,13 +15,12 @@ import { | |||
15 | import { UserRole } from '../../../../shared/models/users' | 15 | import { UserRole } from '../../../../shared/models/users' |
16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
17 | 17 | ||
18 | async function testEndpoints (server: ServerInfo, token: string, filter: string, playlistUUID: string, statusCodeExpected: number) { | 18 | async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { |
19 | const paths = [ | 19 | const paths = [ |
20 | '/api/v1/video-channels/root_channel/videos', | 20 | '/api/v1/video-channels/root_channel/videos', |
21 | '/api/v1/accounts/root/videos', | 21 | '/api/v1/accounts/root/videos', |
22 | '/api/v1/videos', | 22 | '/api/v1/videos', |
23 | '/api/v1/search/videos', | 23 | '/api/v1/search/videos' |
24 | '/api/v1/video-playlists/' + playlistUUID + '/videos' | ||
25 | ] | 24 | ] |
26 | 25 | ||
27 | for (const path of paths) { | 26 | for (const path of paths) { |
@@ -70,39 +69,28 @@ describe('Test videos filters', function () { | |||
70 | } | 69 | } |
71 | ) | 70 | ) |
72 | moderatorAccessToken = await userLogin(server, moderator) | 71 | moderatorAccessToken = await userLogin(server, moderator) |
73 | |||
74 | const res = await createVideoPlaylist({ | ||
75 | url: server.url, | ||
76 | token: server.accessToken, | ||
77 | playlistAttrs: { | ||
78 | displayName: 'super playlist', | ||
79 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
80 | videoChannelId: server.videoChannel.id | ||
81 | } | ||
82 | }) | ||
83 | playlistUUID = res.body.videoPlaylist.uuid | ||
84 | }) | 72 | }) |
85 | 73 | ||
86 | describe('When setting a video filter', function () { | 74 | describe('When setting a video filter', function () { |
87 | 75 | ||
88 | it('Should fail with a bad filter', async function () { | 76 | it('Should fail with a bad filter', async function () { |
89 | await testEndpoints(server, server.accessToken, 'bad-filter', playlistUUID, 400) | 77 | await testEndpoints(server, server.accessToken, 'bad-filter', 400) |
90 | }) | 78 | }) |
91 | 79 | ||
92 | it('Should succeed with a good filter', async function () { | 80 | it('Should succeed with a good filter', async function () { |
93 | await testEndpoints(server, server.accessToken,'local', playlistUUID, 200) | 81 | await testEndpoints(server, server.accessToken,'local', 200) |
94 | }) | 82 | }) |
95 | 83 | ||
96 | it('Should fail to list all-local with a simple user', async function () { | 84 | it('Should fail to list all-local with a simple user', async function () { |
97 | await testEndpoints(server, userAccessToken, 'all-local', playlistUUID, 401) | 85 | await testEndpoints(server, userAccessToken, 'all-local', 401) |
98 | }) | 86 | }) |
99 | 87 | ||
100 | it('Should succeed to list all-local with a moderator', async function () { | 88 | it('Should succeed to list all-local with a moderator', async function () { |
101 | await testEndpoints(server, moderatorAccessToken, 'all-local', playlistUUID, 200) | 89 | await testEndpoints(server, moderatorAccessToken, 'all-local', 200) |
102 | }) | 90 | }) |
103 | 91 | ||
104 | it('Should succeed to list all-local with an admin', async function () { | 92 | it('Should succeed to list all-local with an admin', async function () { |
105 | await testEndpoints(server, server.accessToken, 'all-local', playlistUUID, 200) | 93 | await testEndpoints(server, server.accessToken, 'all-local', 200) |
106 | }) | 94 | }) |
107 | 95 | ||
108 | // Because we cannot authenticate the user on the RSS endpoint | 96 | // Because we cannot authenticate the user on the RSS endpoint |
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 51e592a15..fa6d6f622 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -62,7 +62,7 @@ describe('Test videos API validator', function () { | |||
62 | } | 62 | } |
63 | }) | 63 | }) |
64 | 64 | ||
65 | describe('When listing a video', function () { | 65 | describe('When listing videos', function () { |
66 | it('Should fail with a bad start pagination', async function () { | 66 | it('Should fail with a bad start pagination', async function () { |
67 | await checkBadStartPagination(server.url, path) | 67 | await checkBadStartPagination(server.url, path) |
68 | }) | 68 | }) |
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts index f82c8cbce..7d5e3914b 100644 --- a/server/tests/api/videos/video-playlists.ts +++ b/server/tests/api/videos/video-playlists.ts | |||
@@ -5,6 +5,7 @@ import 'mocha' | |||
5 | import { | 5 | import { |
6 | addVideoChannel, | 6 | addVideoChannel, |
7 | addVideoInPlaylist, | 7 | addVideoInPlaylist, |
8 | addVideoToBlacklist, | ||
8 | checkPlaylistFilesWereRemoved, | 9 | checkPlaylistFilesWereRemoved, |
9 | cleanupTests, | 10 | cleanupTests, |
10 | createUser, | 11 | createUser, |
@@ -14,6 +15,8 @@ import { | |||
14 | doubleFollow, | 15 | doubleFollow, |
15 | doVideosExistInMyPlaylist, | 16 | doVideosExistInMyPlaylist, |
16 | flushAndRunMultipleServers, | 17 | flushAndRunMultipleServers, |
18 | generateUserAccessToken, | ||
19 | getAccessToken, | ||
17 | getAccountPlaylistsList, | 20 | getAccountPlaylistsList, |
18 | getAccountPlaylistsListWithToken, | 21 | getAccountPlaylistsListWithToken, |
19 | getMyUserInformation, | 22 | getMyUserInformation, |
@@ -24,6 +27,7 @@ import { | |||
24 | getVideoPlaylistsList, | 27 | getVideoPlaylistsList, |
25 | getVideoPlaylistWithToken, | 28 | getVideoPlaylistWithToken, |
26 | removeUser, | 29 | removeUser, |
30 | removeVideoFromBlacklist, | ||
27 | removeVideoFromPlaylist, | 31 | removeVideoFromPlaylist, |
28 | reorderVideosPlaylist, | 32 | reorderVideosPlaylist, |
29 | ServerInfo, | 33 | ServerInfo, |
@@ -31,23 +35,58 @@ import { | |||
31 | setDefaultVideoChannel, | 35 | setDefaultVideoChannel, |
32 | testImage, | 36 | testImage, |
33 | unfollow, | 37 | unfollow, |
38 | updateVideo, | ||
34 | updateVideoPlaylist, | 39 | updateVideoPlaylist, |
35 | updateVideoPlaylistElement, | 40 | updateVideoPlaylistElement, |
36 | uploadVideo, | 41 | uploadVideo, |
37 | uploadVideoAndGetId, | 42 | uploadVideoAndGetId, |
38 | userLogin, | 43 | userLogin, |
39 | waitJobs, | 44 | waitJobs |
40 | generateUserAccessToken | ||
41 | } from '../../../../shared/extra-utils' | 45 | } from '../../../../shared/extra-utils' |
42 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 46 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
43 | import { VideoPlaylist } from '../../../../shared/models/videos/playlist/video-playlist.model' | 47 | import { VideoPlaylist } from '../../../../shared/models/videos/playlist/video-playlist.model' |
44 | import { Video } from '../../../../shared/models/videos' | 48 | import { VideoPrivacy } from '../../../../shared/models/videos' |
45 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | 49 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' |
46 | import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' | 50 | import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' |
47 | import { User } from '../../../../shared/models/users' | 51 | import { User } from '../../../../shared/models/users' |
52 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../../shared/models/videos/playlist/video-playlist-element.model' | ||
53 | import { | ||
54 | addAccountToAccountBlocklist, | ||
55 | addAccountToServerBlocklist, | ||
56 | addServerToAccountBlocklist, | ||
57 | addServerToServerBlocklist, | ||
58 | removeAccountFromAccountBlocklist, | ||
59 | removeAccountFromServerBlocklist, | ||
60 | removeServerFromAccountBlocklist, | ||
61 | removeServerFromServerBlocklist | ||
62 | } from '../../../../shared/extra-utils/users/blocklist' | ||
48 | 63 | ||
49 | const expect = chai.expect | 64 | const expect = chai.expect |
50 | 65 | ||
66 | async function checkPlaylistElementType ( | ||
67 | servers: ServerInfo[], | ||
68 | playlistId: string, | ||
69 | type: VideoPlaylistElementType, | ||
70 | position: number, | ||
71 | name: string, | ||
72 | total: number | ||
73 | ) { | ||
74 | for (const server of servers) { | ||
75 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistId, 0, 10) | ||
76 | expect(res.body.total).to.equal(total) | ||
77 | |||
78 | const videoElement: VideoPlaylistElement = res.body.data.find((e: VideoPlaylistElement) => e.position === position) | ||
79 | expect(videoElement.type).to.equal(type, 'On server ' + server.url) | ||
80 | |||
81 | if (type === VideoPlaylistElementType.REGULAR) { | ||
82 | expect(videoElement.video).to.not.be.null | ||
83 | expect(videoElement.video.name).to.equal(name) | ||
84 | } else { | ||
85 | expect(videoElement.video).to.be.null | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
51 | describe('Test video playlists', function () { | 90 | describe('Test video playlists', function () { |
52 | let servers: ServerInfo[] = [] | 91 | let servers: ServerInfo[] = [] |
53 | 92 | ||
@@ -57,9 +96,16 @@ describe('Test video playlists', function () { | |||
57 | 96 | ||
58 | let playlistServer1Id: number | 97 | let playlistServer1Id: number |
59 | let playlistServer1UUID: string | 98 | let playlistServer1UUID: string |
99 | let playlistServer1UUID2: string | ||
100 | |||
101 | let playlistElementServer1Video4: number | ||
102 | let playlistElementServer1Video5: number | ||
103 | let playlistElementNSFW: number | ||
60 | 104 | ||
61 | let nsfwVideoServer1: number | 105 | let nsfwVideoServer1: number |
62 | 106 | ||
107 | let userAccessTokenServer1: string | ||
108 | |||
63 | before(async function () { | 109 | before(async function () { |
64 | this.timeout(120000) | 110 | this.timeout(120000) |
65 | 111 | ||
@@ -97,814 +143,1039 @@ describe('Test video playlists', function () { | |||
97 | 143 | ||
98 | nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'NSFW video', nsfw: true })).id | 144 | nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'NSFW video', nsfw: true })).id |
99 | 145 | ||
146 | { | ||
147 | await createUser({ | ||
148 | url: servers[ 0 ].url, | ||
149 | accessToken: servers[ 0 ].accessToken, | ||
150 | username: 'user1', | ||
151 | password: 'password' | ||
152 | }) | ||
153 | userAccessTokenServer1 = await getAccessToken(servers[0].url, 'user1', 'password') | ||
154 | } | ||
155 | |||
100 | await waitJobs(servers) | 156 | await waitJobs(servers) |
101 | }) | 157 | }) |
102 | 158 | ||
103 | it('Should list video playlist privacies', async function () { | 159 | describe('Get default playlists', function () { |
104 | const res = await getVideoPlaylistPrivacies(servers[0].url) | 160 | it('Should list video playlist privacies', async function () { |
161 | const res = await getVideoPlaylistPrivacies(servers[ 0 ].url) | ||
105 | 162 | ||
106 | const privacies = res.body | 163 | const privacies = res.body |
107 | expect(Object.keys(privacies)).to.have.length.at.least(3) | 164 | expect(Object.keys(privacies)).to.have.length.at.least(3) |
108 | 165 | ||
109 | expect(privacies[3]).to.equal('Private') | 166 | expect(privacies[ 3 ]).to.equal('Private') |
110 | }) | 167 | }) |
111 | 168 | ||
112 | it('Should list watch later playlist', async function () { | 169 | it('Should list watch later playlist', async function () { |
113 | const url = servers[ 0 ].url | 170 | const url = servers[ 0 ].url |
114 | const accessToken = servers[ 0 ].accessToken | 171 | const accessToken = servers[ 0 ].accessToken |
115 | 172 | ||
116 | { | 173 | { |
117 | const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER) | 174 | const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER) |
175 | |||
176 | expect(res.body.total).to.equal(1) | ||
177 | expect(res.body.data).to.have.lengthOf(1) | ||
178 | |||
179 | const playlist: VideoPlaylist = res.body.data[ 0 ] | ||
180 | expect(playlist.displayName).to.equal('Watch later') | ||
181 | expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) | ||
182 | expect(playlist.type.label).to.equal('Watch later') | ||
183 | } | ||
184 | |||
185 | { | ||
186 | const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.REGULAR) | ||
187 | |||
188 | expect(res.body.total).to.equal(0) | ||
189 | expect(res.body.data).to.have.lengthOf(0) | ||
190 | } | ||
191 | |||
192 | { | ||
193 | const res = await getAccountPlaylistsList(url, 'root', 0, 5) | ||
194 | expect(res.body.total).to.equal(0) | ||
195 | expect(res.body.data).to.have.lengthOf(0) | ||
196 | } | ||
197 | }) | ||
198 | |||
199 | it('Should get private playlist for a classic user', async function () { | ||
200 | const token = await generateUserAccessToken(servers[ 0 ], 'toto') | ||
201 | |||
202 | const res = await getAccountPlaylistsListWithToken(servers[ 0 ].url, token, 'toto', 0, 5) | ||
118 | 203 | ||
119 | expect(res.body.total).to.equal(1) | 204 | expect(res.body.total).to.equal(1) |
120 | expect(res.body.data).to.have.lengthOf(1) | 205 | expect(res.body.data).to.have.lengthOf(1) |
121 | 206 | ||
122 | const playlist: VideoPlaylist = res.body.data[ 0 ] | 207 | const playlistId = res.body.data[ 0 ].id |
123 | expect(playlist.displayName).to.equal('Watch later') | 208 | await getPlaylistVideos(servers[ 0 ].url, token, playlistId, 0, 5) |
124 | expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) | 209 | }) |
125 | expect(playlist.type.label).to.equal('Watch later') | 210 | }) |
126 | } | ||
127 | 211 | ||
128 | { | 212 | describe('Create and federate playlists', function () { |
129 | const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.REGULAR) | ||
130 | 213 | ||
131 | expect(res.body.total).to.equal(0) | 214 | it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () { |
132 | expect(res.body.data).to.have.lengthOf(0) | 215 | this.timeout(30000) |
133 | } | ||
134 | 216 | ||
135 | { | 217 | await createVideoPlaylist({ |
136 | const res = await getAccountPlaylistsList(url, 'root', 0, 5) | 218 | url: servers[ 0 ].url, |
137 | expect(res.body.total).to.equal(0) | 219 | token: servers[ 0 ].accessToken, |
138 | expect(res.body.data).to.have.lengthOf(0) | 220 | playlistAttrs: { |
139 | } | 221 | displayName: 'my super playlist', |
140 | }) | 222 | privacy: VideoPlaylistPrivacy.PUBLIC, |
223 | description: 'my super description', | ||
224 | thumbnailfile: 'thumbnail.jpg', | ||
225 | videoChannelId: servers[ 0 ].videoChannel.id | ||
226 | } | ||
227 | }) | ||
228 | |||
229 | await waitJobs(servers) | ||
230 | |||
231 | for (const server of servers) { | ||
232 | const res = await getVideoPlaylistsList(server.url, 0, 5) | ||
233 | expect(res.body.total).to.equal(1) | ||
234 | expect(res.body.data).to.have.lengthOf(1) | ||
235 | |||
236 | const playlistFromList = res.body.data[ 0 ] as VideoPlaylist | ||
237 | |||
238 | const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) | ||
239 | const playlistFromGet = res2.body | ||
240 | |||
241 | for (const playlist of [ playlistFromGet, playlistFromList ]) { | ||
242 | expect(playlist.id).to.be.a('number') | ||
243 | expect(playlist.uuid).to.be.a('string') | ||
244 | |||
245 | expect(playlist.isLocal).to.equal(server.serverNumber === 1) | ||
246 | |||
247 | expect(playlist.displayName).to.equal('my super playlist') | ||
248 | expect(playlist.description).to.equal('my super description') | ||
249 | expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC) | ||
250 | expect(playlist.privacy.label).to.equal('Public') | ||
251 | expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR) | ||
252 | expect(playlist.type.label).to.equal('Regular') | ||
253 | |||
254 | expect(playlist.videosLength).to.equal(0) | ||
141 | 255 | ||
142 | it('Should get private playlist for a classic user', async function () { | 256 | expect(playlist.ownerAccount.name).to.equal('root') |
143 | const token = await generateUserAccessToken(servers[0], 'toto') | 257 | expect(playlist.ownerAccount.displayName).to.equal('root') |
258 | expect(playlist.videoChannel.name).to.equal('root_channel') | ||
259 | expect(playlist.videoChannel.displayName).to.equal('Main root channel') | ||
260 | } | ||
261 | } | ||
262 | }) | ||
144 | 263 | ||
145 | const res = await getAccountPlaylistsListWithToken(servers[0].url, token, 'toto', 0, 5) | 264 | it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () { |
265 | this.timeout(30000) | ||
266 | |||
267 | { | ||
268 | const res = await createVideoPlaylist({ | ||
269 | url: servers[ 1 ].url, | ||
270 | token: servers[ 1 ].accessToken, | ||
271 | playlistAttrs: { | ||
272 | displayName: 'playlist 2', | ||
273 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
274 | videoChannelId: servers[ 1 ].videoChannel.id | ||
275 | } | ||
276 | }) | ||
277 | playlistServer2Id1 = res.body.videoPlaylist.id | ||
278 | } | ||
146 | 279 | ||
147 | expect(res.body.total).to.equal(1) | 280 | { |
148 | expect(res.body.data).to.have.lengthOf(1) | 281 | const res = await createVideoPlaylist({ |
282 | url: servers[ 1 ].url, | ||
283 | token: servers[ 1 ].accessToken, | ||
284 | playlistAttrs: { | ||
285 | displayName: 'playlist 3', | ||
286 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
287 | thumbnailfile: 'thumbnail.jpg', | ||
288 | videoChannelId: servers[ 1 ].videoChannel.id | ||
289 | } | ||
290 | }) | ||
291 | |||
292 | playlistServer2Id2 = res.body.videoPlaylist.id | ||
293 | playlistServer2UUID2 = res.body.videoPlaylist.uuid | ||
294 | } | ||
295 | |||
296 | for (let id of [ playlistServer2Id1, playlistServer2Id2 ]) { | ||
297 | await addVideoInPlaylist({ | ||
298 | url: servers[ 1 ].url, | ||
299 | token: servers[ 1 ].accessToken, | ||
300 | playlistId: id, | ||
301 | elementAttrs: { videoId: servers[ 1 ].videos[ 0 ].id, startTimestamp: 1, stopTimestamp: 2 } | ||
302 | }) | ||
303 | await addVideoInPlaylist({ | ||
304 | url: servers[ 1 ].url, | ||
305 | token: servers[ 1 ].accessToken, | ||
306 | playlistId: id, | ||
307 | elementAttrs: { videoId: servers[ 1 ].videos[ 1 ].id } | ||
308 | }) | ||
309 | } | ||
310 | |||
311 | await waitJobs(servers) | ||
312 | |||
313 | for (const server of [ servers[ 0 ], servers[ 1 ] ]) { | ||
314 | const res = await getVideoPlaylistsList(server.url, 0, 5) | ||
315 | |||
316 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | ||
317 | expect(playlist2).to.not.be.undefined | ||
318 | await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath) | ||
319 | |||
320 | const playlist3 = res.body.data.find(p => p.displayName === 'playlist 3') | ||
321 | expect(playlist3).to.not.be.undefined | ||
322 | await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) | ||
323 | } | ||
149 | 324 | ||
150 | const playlistId = res.body.data[0].id | 325 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) |
151 | await getPlaylistVideos(servers[0].url, token, playlistId, 0, 5) | 326 | expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined |
327 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined | ||
328 | }) | ||
329 | |||
330 | it('Should have the playlist on server 3 after a new follow', async function () { | ||
331 | this.timeout(30000) | ||
332 | |||
333 | // Server 2 and server 3 follow each other | ||
334 | await doubleFollow(servers[ 1 ], servers[ 2 ]) | ||
335 | |||
336 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | ||
337 | |||
338 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | ||
339 | expect(playlist2).to.not.be.undefined | ||
340 | await testImage(servers[ 2 ].url, 'thumbnail-playlist', playlist2.thumbnailPath) | ||
341 | |||
342 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined | ||
343 | }) | ||
152 | }) | 344 | }) |
153 | 345 | ||
154 | it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () { | 346 | describe('List playlists', function () { |
155 | this.timeout(30000) | 347 | it('Should correctly list the playlists', async function () { |
348 | this.timeout(30000) | ||
349 | |||
350 | { | ||
351 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, 'createdAt') | ||
156 | 352 | ||
157 | await createVideoPlaylist({ | 353 | expect(res.body.total).to.equal(3) |
158 | url: servers[0].url, | 354 | |
159 | token: servers[0].accessToken, | 355 | const data: VideoPlaylist[] = res.body.data |
160 | playlistAttrs: { | 356 | expect(data).to.have.lengthOf(2) |
161 | displayName: 'my super playlist', | 357 | expect(data[ 0 ].displayName).to.equal('playlist 2') |
162 | privacy: VideoPlaylistPrivacy.PUBLIC, | 358 | expect(data[ 1 ].displayName).to.equal('playlist 3') |
163 | description: 'my super description', | 359 | } |
164 | thumbnailfile: 'thumbnail.jpg', | 360 | |
165 | videoChannelId: servers[0].videoChannel.id | 361 | { |
362 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, '-createdAt') | ||
363 | |||
364 | expect(res.body.total).to.equal(3) | ||
365 | |||
366 | const data: VideoPlaylist[] = res.body.data | ||
367 | expect(data).to.have.lengthOf(2) | ||
368 | expect(data[ 0 ].displayName).to.equal('playlist 2') | ||
369 | expect(data[ 1 ].displayName).to.equal('my super playlist') | ||
166 | } | 370 | } |
167 | }) | 371 | }) |
168 | 372 | ||
169 | await waitJobs(servers) | 373 | it('Should list video channel playlists', async function () { |
374 | this.timeout(30000) | ||
170 | 375 | ||
171 | for (const server of servers) { | 376 | { |
172 | const res = await getVideoPlaylistsList(server.url, 0, 5) | 377 | const res = await getVideoChannelPlaylistsList(servers[ 0 ].url, 'root_channel', 0, 2, '-createdAt') |
173 | expect(res.body.total).to.equal(1) | ||
174 | expect(res.body.data).to.have.lengthOf(1) | ||
175 | 378 | ||
176 | const playlistFromList = res.body.data[0] as VideoPlaylist | 379 | expect(res.body.total).to.equal(1) |
177 | 380 | ||
178 | const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) | 381 | const data: VideoPlaylist[] = res.body.data |
179 | const playlistFromGet = res2.body | 382 | expect(data).to.have.lengthOf(1) |
383 | expect(data[ 0 ].displayName).to.equal('my super playlist') | ||
384 | } | ||
385 | }) | ||
180 | 386 | ||
181 | for (const playlist of [ playlistFromGet, playlistFromList ]) { | 387 | it('Should list account playlists', async function () { |
182 | expect(playlist.id).to.be.a('number') | 388 | this.timeout(30000) |
183 | expect(playlist.uuid).to.be.a('string') | ||
184 | 389 | ||
185 | expect(playlist.isLocal).to.equal(server.serverNumber === 1) | 390 | { |
391 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, '-createdAt') | ||
186 | 392 | ||
187 | expect(playlist.displayName).to.equal('my super playlist') | 393 | expect(res.body.total).to.equal(2) |
188 | expect(playlist.description).to.equal('my super description') | 394 | |
189 | expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC) | 395 | const data: VideoPlaylist[] = res.body.data |
190 | expect(playlist.privacy.label).to.equal('Public') | 396 | expect(data).to.have.lengthOf(1) |
191 | expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR) | 397 | expect(data[ 0 ].displayName).to.equal('playlist 2') |
192 | expect(playlist.type.label).to.equal('Regular') | 398 | } |
193 | 399 | ||
194 | expect(playlist.videosLength).to.equal(0) | 400 | { |
401 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, 'createdAt') | ||
195 | 402 | ||
196 | expect(playlist.ownerAccount.name).to.equal('root') | 403 | expect(res.body.total).to.equal(2) |
197 | expect(playlist.ownerAccount.displayName).to.equal('root') | 404 | |
198 | expect(playlist.videoChannel.name).to.equal('root_channel') | 405 | const data: VideoPlaylist[] = res.body.data |
199 | expect(playlist.videoChannel.displayName).to.equal('Main root channel') | 406 | expect(data).to.have.lengthOf(1) |
407 | expect(data[ 0 ].displayName).to.equal('playlist 3') | ||
200 | } | 408 | } |
201 | } | 409 | }) |
202 | }) | ||
203 | 410 | ||
204 | it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () { | 411 | it('Should not list unlisted or private playlists', async function () { |
205 | this.timeout(30000) | 412 | this.timeout(30000) |
206 | 413 | ||
207 | { | 414 | await createVideoPlaylist({ |
208 | const res = await createVideoPlaylist({ | 415 | url: servers[ 1 ].url, |
209 | url: servers[1].url, | 416 | token: servers[ 1 ].accessToken, |
210 | token: servers[1].accessToken, | ||
211 | playlistAttrs: { | 417 | playlistAttrs: { |
212 | displayName: 'playlist 2', | 418 | displayName: 'playlist unlisted', |
213 | privacy: VideoPlaylistPrivacy.PUBLIC, | 419 | privacy: VideoPlaylistPrivacy.UNLISTED |
214 | videoChannelId: servers[1].videoChannel.id | ||
215 | } | 420 | } |
216 | }) | 421 | }) |
217 | playlistServer2Id1 = res.body.videoPlaylist.id | ||
218 | } | ||
219 | 422 | ||
220 | { | 423 | await createVideoPlaylist({ |
221 | const res = await createVideoPlaylist({ | ||
222 | url: servers[ 1 ].url, | 424 | url: servers[ 1 ].url, |
223 | token: servers[ 1 ].accessToken, | 425 | token: servers[ 1 ].accessToken, |
224 | playlistAttrs: { | 426 | playlistAttrs: { |
225 | displayName: 'playlist 3', | 427 | displayName: 'playlist private', |
226 | privacy: VideoPlaylistPrivacy.PUBLIC, | 428 | privacy: VideoPlaylistPrivacy.PRIVATE |
227 | thumbnailfile: 'thumbnail.jpg', | ||
228 | videoChannelId: servers[1].videoChannel.id | ||
229 | } | 429 | } |
230 | }) | 430 | }) |
231 | 431 | ||
232 | playlistServer2Id2 = res.body.videoPlaylist.id | 432 | await waitJobs(servers) |
233 | playlistServer2UUID2 = res.body.videoPlaylist.uuid | ||
234 | } | ||
235 | 433 | ||
236 | for (let id of [ playlistServer2Id1, playlistServer2Id2 ]) { | 434 | for (const server of servers) { |
237 | await addVideoInPlaylist({ | 435 | const results = [ |
238 | url: servers[ 1 ].url, | 436 | await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[ 1 ].port, 0, 5, '-createdAt'), |
239 | token: servers[ 1 ].accessToken, | 437 | await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') |
240 | playlistId: id, | 438 | ] |
241 | elementAttrs: { videoId: servers[ 1 ].videos[ 0 ].id, startTimestamp: 1, stopTimestamp: 2 } | 439 | |
242 | }) | 440 | expect(results[ 0 ].body.total).to.equal(2) |
243 | await addVideoInPlaylist({ | 441 | expect(results[ 1 ].body.total).to.equal(3) |
244 | url: servers[ 1 ].url, | 442 | |
245 | token: servers[ 1 ].accessToken, | 443 | for (const res of results) { |
246 | playlistId: id, | 444 | const data: VideoPlaylist[] = res.body.data |
247 | elementAttrs: { videoId: servers[ 1 ].videos[ 1 ].id } | 445 | expect(data).to.have.lengthOf(2) |
248 | }) | 446 | expect(data[ 0 ].displayName).to.equal('playlist 3') |
249 | } | 447 | expect(data[ 1 ].displayName).to.equal('playlist 2') |
448 | } | ||
449 | } | ||
450 | }) | ||
451 | }) | ||
250 | 452 | ||
251 | await waitJobs(servers) | 453 | describe('Update playlists', function () { |
252 | 454 | ||
253 | for (const server of [ servers[0], servers[1] ]) { | 455 | it('Should update a playlist', async function () { |
254 | const res = await getVideoPlaylistsList(server.url, 0, 5) | 456 | this.timeout(30000) |
255 | 457 | ||
256 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | 458 | await updateVideoPlaylist({ |
257 | expect(playlist2).to.not.be.undefined | 459 | url: servers[1].url, |
258 | await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath) | 460 | token: servers[1].accessToken, |
461 | playlistAttrs: { | ||
462 | displayName: 'playlist 3 updated', | ||
463 | description: 'description updated', | ||
464 | privacy: VideoPlaylistPrivacy.UNLISTED, | ||
465 | thumbnailfile: 'thumbnail.jpg', | ||
466 | videoChannelId: servers[1].videoChannel.id | ||
467 | }, | ||
468 | playlistId: playlistServer2Id2 | ||
469 | }) | ||
259 | 470 | ||
260 | const playlist3 = res.body.data.find(p => p.displayName === 'playlist 3') | 471 | await waitJobs(servers) |
261 | expect(playlist3).to.not.be.undefined | ||
262 | await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) | ||
263 | } | ||
264 | 472 | ||
265 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) | 473 | for (const server of servers) { |
266 | expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined | 474 | const res = await getVideoPlaylist(server.url, playlistServer2UUID2) |
267 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined | 475 | const playlist: VideoPlaylist = res.body |
268 | }) | ||
269 | 476 | ||
270 | it('Should have the playlist on server 3 after a new follow', async function () { | 477 | expect(playlist.displayName).to.equal('playlist 3 updated') |
271 | this.timeout(30000) | 478 | expect(playlist.description).to.equal('description updated') |
272 | 479 | ||
273 | // Server 2 and server 3 follow each other | 480 | expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED) |
274 | await doubleFollow(servers[1], servers[2]) | 481 | expect(playlist.privacy.label).to.equal('Unlisted') |
275 | 482 | ||
276 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) | 483 | expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR) |
484 | expect(playlist.type.label).to.equal('Regular') | ||
277 | 485 | ||
278 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | 486 | expect(playlist.videosLength).to.equal(2) |
279 | expect(playlist2).to.not.be.undefined | ||
280 | await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath) | ||
281 | 487 | ||
282 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined | 488 | expect(playlist.ownerAccount.name).to.equal('root') |
489 | expect(playlist.ownerAccount.displayName).to.equal('root') | ||
490 | expect(playlist.videoChannel.name).to.equal('root_channel') | ||
491 | expect(playlist.videoChannel.displayName).to.equal('Main root channel') | ||
492 | } | ||
493 | }) | ||
283 | }) | 494 | }) |
284 | 495 | ||
285 | it('Should correctly list the playlists', async function () { | 496 | describe('Element timestamps', function () { |
286 | this.timeout(30000) | ||
287 | 497 | ||
288 | { | 498 | it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () { |
289 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, 'createdAt') | 499 | this.timeout(30000) |
290 | 500 | ||
291 | expect(res.body.total).to.equal(3) | 501 | const addVideo = (elementAttrs: any) => { |
502 | return addVideoInPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: playlistServer1Id, elementAttrs }) | ||
503 | } | ||
292 | 504 | ||
293 | const data: VideoPlaylist[] = res.body.data | 505 | const res = await createVideoPlaylist({ |
294 | expect(data).to.have.lengthOf(2) | 506 | url: servers[ 0 ].url, |
295 | expect(data[ 0 ].displayName).to.equal('playlist 2') | 507 | token: servers[ 0 ].accessToken, |
296 | expect(data[ 1 ].displayName).to.equal('playlist 3') | 508 | playlistAttrs: { |
297 | } | 509 | displayName: 'playlist 4', |
510 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
511 | videoChannelId: servers[ 0 ].videoChannel.id | ||
512 | } | ||
513 | }) | ||
298 | 514 | ||
299 | { | 515 | playlistServer1Id = res.body.videoPlaylist.id |
300 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, '-createdAt') | 516 | playlistServer1UUID = res.body.videoPlaylist.uuid |
301 | 517 | ||
302 | expect(res.body.total).to.equal(3) | 518 | await addVideo({ videoId: servers[ 0 ].videos[ 0 ].uuid, startTimestamp: 15, stopTimestamp: 28 }) |
519 | await addVideo({ videoId: servers[ 2 ].videos[ 1 ].uuid, startTimestamp: 35 }) | ||
520 | await addVideo({ videoId: servers[ 2 ].videos[ 2 ].uuid }) | ||
521 | { | ||
522 | const res = await addVideo({ videoId: servers[ 0 ].videos[ 3 ].uuid, stopTimestamp: 35 }) | ||
523 | playlistElementServer1Video4 = res.body.videoPlaylistElement.id | ||
524 | } | ||
303 | 525 | ||
304 | const data: VideoPlaylist[] = res.body.data | 526 | { |
305 | expect(data).to.have.lengthOf(2) | 527 | const res = await addVideo({ videoId: servers[ 0 ].videos[ 4 ].uuid, startTimestamp: 45, stopTimestamp: 60 }) |
306 | expect(data[ 0 ].displayName).to.equal('playlist 2') | 528 | playlistElementServer1Video5 = res.body.videoPlaylistElement.id |
307 | expect(data[ 1 ].displayName).to.equal('my super playlist') | 529 | } |
308 | } | ||
309 | }) | ||
310 | 530 | ||
311 | it('Should list video channel playlists', async function () { | 531 | { |
312 | this.timeout(30000) | 532 | const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) |
533 | playlistElementNSFW = res.body.videoPlaylistElement.id | ||
534 | } | ||
313 | 535 | ||
314 | { | 536 | await waitJobs(servers) |
315 | const res = await getVideoChannelPlaylistsList(servers[ 0 ].url, 'root_channel', 0, 2, '-createdAt') | 537 | }) |
316 | 538 | ||
317 | expect(res.body.total).to.equal(1) | 539 | it('Should correctly list playlist videos', async function () { |
540 | this.timeout(30000) | ||
318 | 541 | ||
319 | const data: VideoPlaylist[] = res.body.data | 542 | for (const server of servers) { |
320 | expect(data).to.have.lengthOf(1) | 543 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) |
321 | expect(data[ 0 ].displayName).to.equal('my super playlist') | ||
322 | } | ||
323 | }) | ||
324 | 544 | ||
325 | it('Should list account playlists', async function () { | 545 | expect(res.body.total).to.equal(6) |
326 | this.timeout(30000) | ||
327 | 546 | ||
328 | { | 547 | const videoElements: VideoPlaylistElement[] = res.body.data |
329 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, '-createdAt') | 548 | expect(videoElements).to.have.lengthOf(6) |
330 | 549 | ||
331 | expect(res.body.total).to.equal(2) | 550 | expect(videoElements[ 0 ].video.name).to.equal('video 0 server 1') |
551 | expect(videoElements[ 0 ].position).to.equal(1) | ||
552 | expect(videoElements[ 0 ].startTimestamp).to.equal(15) | ||
553 | expect(videoElements[ 0 ].stopTimestamp).to.equal(28) | ||
332 | 554 | ||
333 | const data: VideoPlaylist[] = res.body.data | 555 | expect(videoElements[ 1 ].video.name).to.equal('video 1 server 3') |
334 | expect(data).to.have.lengthOf(1) | 556 | expect(videoElements[ 1 ].position).to.equal(2) |
335 | expect(data[ 0 ].displayName).to.equal('playlist 2') | 557 | expect(videoElements[ 1 ].startTimestamp).to.equal(35) |
336 | } | 558 | expect(videoElements[ 1 ].stopTimestamp).to.be.null |
337 | 559 | ||
338 | { | 560 | expect(videoElements[ 2 ].video.name).to.equal('video 2 server 3') |
339 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, 'createdAt') | 561 | expect(videoElements[ 2 ].position).to.equal(3) |
562 | expect(videoElements[ 2 ].startTimestamp).to.be.null | ||
563 | expect(videoElements[ 2 ].stopTimestamp).to.be.null | ||
340 | 564 | ||
341 | expect(res.body.total).to.equal(2) | 565 | expect(videoElements[ 3 ].video.name).to.equal('video 3 server 1') |
566 | expect(videoElements[ 3 ].position).to.equal(4) | ||
567 | expect(videoElements[ 3 ].startTimestamp).to.be.null | ||
568 | expect(videoElements[ 3 ].stopTimestamp).to.equal(35) | ||
342 | 569 | ||
343 | const data: VideoPlaylist[] = res.body.data | 570 | expect(videoElements[ 4 ].video.name).to.equal('video 4 server 1') |
344 | expect(data).to.have.lengthOf(1) | 571 | expect(videoElements[ 4 ].position).to.equal(5) |
345 | expect(data[ 0 ].displayName).to.equal('playlist 3') | 572 | expect(videoElements[ 4 ].startTimestamp).to.equal(45) |
346 | } | 573 | expect(videoElements[ 4 ].stopTimestamp).to.equal(60) |
347 | }) | ||
348 | 574 | ||
349 | it('Should not list unlisted or private playlists', async function () { | 575 | expect(videoElements[ 5 ].video.name).to.equal('NSFW video') |
350 | this.timeout(30000) | 576 | expect(videoElements[ 5 ].position).to.equal(6) |
577 | expect(videoElements[ 5 ].startTimestamp).to.equal(5) | ||
578 | expect(videoElements[ 5 ].stopTimestamp).to.be.null | ||
351 | 579 | ||
352 | await createVideoPlaylist({ | 580 | const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) |
353 | url: servers[ 1 ].url, | 581 | expect(res3.body.data).to.have.lengthOf(2) |
354 | token: servers[ 1 ].accessToken, | ||
355 | playlistAttrs: { | ||
356 | displayName: 'playlist unlisted', | ||
357 | privacy: VideoPlaylistPrivacy.UNLISTED | ||
358 | } | 582 | } |
359 | }) | 583 | }) |
584 | }) | ||
360 | 585 | ||
361 | await createVideoPlaylist({ | 586 | describe('Element type', function () { |
362 | url: servers[ 1 ].url, | 587 | let groupUser1: ServerInfo[] |
363 | token: servers[ 1 ].accessToken, | 588 | let groupWithoutToken1: ServerInfo[] |
364 | playlistAttrs: { | 589 | let group1: ServerInfo[] |
365 | displayName: 'playlist private', | 590 | let group2: ServerInfo[] |
366 | privacy: VideoPlaylistPrivacy.PRIVATE | ||
367 | } | ||
368 | }) | ||
369 | 591 | ||
370 | await waitJobs(servers) | 592 | let video1: string |
593 | let video2: string | ||
594 | let video3: string | ||
371 | 595 | ||
372 | for (const server of servers) { | 596 | before(async function () { |
373 | const results = [ | 597 | this.timeout(30000) |
374 | await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'), | ||
375 | await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') | ||
376 | ] | ||
377 | 598 | ||
378 | expect(results[0].body.total).to.equal(2) | 599 | groupUser1 = [ Object.assign({}, servers[ 0 ], { accessToken: userAccessTokenServer1 }) ] |
379 | expect(results[1].body.total).to.equal(3) | 600 | groupWithoutToken1 = [ Object.assign({}, servers[ 0 ], { accessToken: undefined }) ] |
601 | group1 = [ servers[ 0 ] ] | ||
602 | group2 = [ servers[ 1 ], servers[ 2 ] ] | ||
380 | 603 | ||
381 | for (const res of results) { | 604 | const res = await createVideoPlaylist({ |
382 | const data: VideoPlaylist[] = res.body.data | 605 | url: servers[ 0 ].url, |
383 | expect(data).to.have.lengthOf(2) | 606 | token: userAccessTokenServer1, |
384 | expect(data[ 0 ].displayName).to.equal('playlist 3') | 607 | playlistAttrs: { |
385 | expect(data[ 1 ].displayName).to.equal('playlist 2') | 608 | displayName: 'playlist 56', |
386 | } | 609 | privacy: VideoPlaylistPrivacy.PUBLIC, |
387 | } | 610 | videoChannelId: servers[ 0 ].videoChannel.id |
388 | }) | 611 | } |
612 | }) | ||
389 | 613 | ||
390 | it('Should update a playlist', async function () { | 614 | const playlistServer1Id2 = res.body.videoPlaylist.id |
391 | this.timeout(30000) | 615 | playlistServer1UUID2 = res.body.videoPlaylist.uuid |
392 | |||
393 | await updateVideoPlaylist({ | ||
394 | url: servers[1].url, | ||
395 | token: servers[1].accessToken, | ||
396 | playlistAttrs: { | ||
397 | displayName: 'playlist 3 updated', | ||
398 | description: 'description updated', | ||
399 | privacy: VideoPlaylistPrivacy.UNLISTED, | ||
400 | thumbnailfile: 'thumbnail.jpg', | ||
401 | videoChannelId: servers[1].videoChannel.id | ||
402 | }, | ||
403 | playlistId: playlistServer2Id2 | ||
404 | }) | ||
405 | 616 | ||
406 | await waitJobs(servers) | 617 | const addVideo = (elementAttrs: any) => { |
618 | return addVideoInPlaylist({ url: servers[ 0 ].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs }) | ||
619 | } | ||
407 | 620 | ||
408 | for (const server of servers) { | 621 | video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid |
409 | const res = await getVideoPlaylist(server.url, playlistServer2UUID2) | 622 | video2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 90' })).uuid |
410 | const playlist: VideoPlaylist = res.body | 623 | video3 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 91', nsfw: true })).uuid |
411 | 624 | ||
412 | expect(playlist.displayName).to.equal('playlist 3 updated') | 625 | await addVideo({ videoId: video1, startTimestamp: 15, stopTimestamp: 28 }) |
413 | expect(playlist.description).to.equal('description updated') | 626 | await addVideo({ videoId: video2, startTimestamp: 35 }) |
627 | await addVideo({ videoId: video3 }) | ||
414 | 628 | ||
415 | expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED) | 629 | await waitJobs(servers) |
416 | expect(playlist.privacy.label).to.equal('Unlisted') | 630 | }) |
417 | 631 | ||
418 | expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR) | 632 | it('Should update the element type if the video is private', async function () { |
419 | expect(playlist.type.label).to.equal('Regular') | 633 | this.timeout(20000) |
420 | 634 | ||
421 | expect(playlist.videosLength).to.equal(2) | 635 | const name = 'video 89' |
636 | const position = 1 | ||
422 | 637 | ||
423 | expect(playlist.ownerAccount.name).to.equal('root') | 638 | { |
424 | expect(playlist.ownerAccount.displayName).to.equal('root') | 639 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PRIVATE }) |
425 | expect(playlist.videoChannel.name).to.equal('root_channel') | 640 | await waitJobs(servers) |
426 | expect(playlist.videoChannel.displayName).to.equal('Main root channel') | ||
427 | } | ||
428 | }) | ||
429 | 641 | ||
430 | it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () { | 642 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
431 | this.timeout(30000) | 643 | await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3) |
644 | await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.PRIVATE, position, name, 3) | ||
645 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3) | ||
646 | } | ||
432 | 647 | ||
433 | const addVideo = (elementAttrs: any) => { | 648 | { |
434 | return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs }) | 649 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PUBLIC }) |
435 | } | 650 | await waitJobs(servers) |
436 | 651 | ||
437 | const res = await createVideoPlaylist({ | 652 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
438 | url: servers[ 0 ].url, | 653 | await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
439 | token: servers[ 0 ].accessToken, | 654 | await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
440 | playlistAttrs: { | 655 | // We deleted the video, so even if we recreated it, the old entry is still deleted |
441 | displayName: 'playlist 4', | 656 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3) |
442 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
443 | videoChannelId: servers[0].videoChannel.id | ||
444 | } | 657 | } |
445 | }) | 658 | }) |
446 | 659 | ||
447 | playlistServer1Id = res.body.videoPlaylist.id | 660 | it('Should update the element type if the video is blacklisted', async function () { |
448 | playlistServer1UUID = res.body.videoPlaylist.uuid | 661 | this.timeout(20000) |
449 | 662 | ||
450 | await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 }) | 663 | const name = 'video 89' |
451 | await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 }) | 664 | const position = 1 |
452 | await addVideo({ videoId: servers[2].videos[2].uuid }) | ||
453 | await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 }) | ||
454 | await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 }) | ||
455 | await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) | ||
456 | 665 | ||
457 | await waitJobs(servers) | 666 | { |
458 | }) | 667 | await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1, 'reason', true) |
668 | await waitJobs(servers) | ||
459 | 669 | ||
460 | it('Should correctly list playlist videos', async function () { | 670 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
461 | this.timeout(30000) | 671 | await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
672 | await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) | ||
673 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3) | ||
674 | } | ||
462 | 675 | ||
463 | for (const server of servers) { | 676 | { |
464 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 677 | await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1) |
678 | await waitJobs(servers) | ||
465 | 679 | ||
466 | expect(res.body.total).to.equal(6) | 680 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
681 | await checkPlaylistElementType(groupWithoutToken1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | ||
682 | await checkPlaylistElementType(group1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | ||
683 | // We deleted the video (because unfederated), so even if we recreated it, the old entry is still deleted | ||
684 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.DELETED, position, name, 3) | ||
685 | } | ||
686 | }) | ||
467 | 687 | ||
468 | const videos: Video[] = res.body.data | 688 | it('Should update the element type if the account or server of the video is blocked', async function () { |
469 | expect(videos).to.have.lengthOf(6) | 689 | this.timeout(90000) |
470 | 690 | ||
471 | expect(videos[0].name).to.equal('video 0 server 1') | 691 | const name = 'video 90' |
472 | expect(videos[0].playlistElement.position).to.equal(1) | 692 | const position = 2 |
473 | expect(videos[0].playlistElement.startTimestamp).to.equal(15) | ||
474 | expect(videos[0].playlistElement.stopTimestamp).to.equal(28) | ||
475 | 693 | ||
476 | expect(videos[1].name).to.equal('video 1 server 3') | 694 | { |
477 | expect(videos[1].playlistElement.position).to.equal(2) | 695 | await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) |
478 | expect(videos[1].playlistElement.startTimestamp).to.equal(35) | 696 | await waitJobs(servers) |
479 | expect(videos[1].playlistElement.stopTimestamp).to.be.null | ||
480 | 697 | ||
481 | expect(videos[2].name).to.equal('video 2 server 3') | 698 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
482 | expect(videos[2].playlistElement.position).to.equal(3) | 699 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
483 | expect(videos[2].playlistElement.startTimestamp).to.be.null | ||
484 | expect(videos[2].playlistElement.stopTimestamp).to.be.null | ||
485 | 700 | ||
486 | expect(videos[3].name).to.equal('video 3 server 1') | 701 | await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) |
487 | expect(videos[3].playlistElement.position).to.equal(4) | 702 | await waitJobs(servers) |
488 | expect(videos[3].playlistElement.startTimestamp).to.be.null | ||
489 | expect(videos[3].playlistElement.stopTimestamp).to.equal(35) | ||
490 | 703 | ||
491 | expect(videos[4].name).to.equal('video 4 server 1') | 704 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
492 | expect(videos[4].playlistElement.position).to.equal(5) | 705 | } |
493 | expect(videos[4].playlistElement.startTimestamp).to.equal(45) | ||
494 | expect(videos[4].playlistElement.stopTimestamp).to.equal(60) | ||
495 | 706 | ||
496 | expect(videos[5].name).to.equal('NSFW video') | 707 | { |
497 | expect(videos[5].playlistElement.position).to.equal(6) | 708 | await addServerToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) |
498 | expect(videos[5].playlistElement.startTimestamp).to.equal(5) | 709 | await waitJobs(servers) |
499 | expect(videos[5].playlistElement.stopTimestamp).to.be.null | ||
500 | 710 | ||
501 | const res2 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10, { nsfw: false }) | 711 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
502 | expect(res2.body.total).to.equal(5) | 712 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
503 | expect(res2.body.data.find(v => v.name === 'NSFW video')).to.be.undefined | ||
504 | 713 | ||
505 | const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) | 714 | await removeServerFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) |
506 | expect(res3.body.data).to.have.lengthOf(2) | 715 | await waitJobs(servers) |
507 | } | ||
508 | }) | ||
509 | 716 | ||
510 | it('Should reorder the playlist', async function () { | 717 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
511 | this.timeout(30000) | 718 | } |
512 | 719 | ||
513 | { | 720 | { |
514 | await reorderVideosPlaylist({ | 721 | await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) |
515 | url: servers[ 0 ].url, | 722 | await waitJobs(servers) |
516 | token: servers[ 0 ].accessToken, | ||
517 | playlistId: playlistServer1Id, | ||
518 | elementAttrs: { | ||
519 | startPosition: 2, | ||
520 | insertAfterPosition: 3 | ||
521 | } | ||
522 | }) | ||
523 | 723 | ||
524 | await waitJobs(servers) | 724 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
725 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | ||
525 | 726 | ||
526 | for (const server of servers) { | 727 | await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) |
527 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 728 | await waitJobs(servers) |
528 | const names = res.body.data.map(v => v.name) | ||
529 | 729 | ||
530 | expect(names).to.deep.equal([ | 730 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
531 | 'video 0 server 1', | ||
532 | 'video 2 server 3', | ||
533 | 'video 1 server 3', | ||
534 | 'video 3 server 1', | ||
535 | 'video 4 server 1', | ||
536 | 'NSFW video' | ||
537 | ]) | ||
538 | } | 731 | } |
539 | } | ||
540 | 732 | ||
541 | { | 733 | { |
542 | await reorderVideosPlaylist({ | 734 | await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) |
543 | url: servers[0].url, | 735 | await waitJobs(servers) |
544 | token: servers[0].accessToken, | ||
545 | playlistId: playlistServer1Id, | ||
546 | elementAttrs: { | ||
547 | startPosition: 1, | ||
548 | reorderLength: 3, | ||
549 | insertAfterPosition: 4 | ||
550 | } | ||
551 | }) | ||
552 | 736 | ||
553 | await waitJobs(servers) | 737 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
738 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | ||
554 | 739 | ||
555 | for (const server of servers) { | 740 | await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) |
556 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 741 | await waitJobs(servers) |
557 | const names = res.body.data.map(v => v.name) | ||
558 | 742 | ||
559 | expect(names).to.deep.equal([ | 743 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
560 | 'video 3 server 1', | ||
561 | 'video 0 server 1', | ||
562 | 'video 2 server 3', | ||
563 | 'video 1 server 3', | ||
564 | 'video 4 server 1', | ||
565 | 'NSFW video' | ||
566 | ]) | ||
567 | } | 744 | } |
568 | } | 745 | }) |
569 | |||
570 | { | ||
571 | await reorderVideosPlaylist({ | ||
572 | url: servers[0].url, | ||
573 | token: servers[0].accessToken, | ||
574 | playlistId: playlistServer1Id, | ||
575 | elementAttrs: { | ||
576 | startPosition: 6, | ||
577 | insertAfterPosition: 3 | ||
578 | } | ||
579 | }) | ||
580 | 746 | ||
581 | await waitJobs(servers) | 747 | it('Should hide the video if it is NSFW', async function () { |
748 | const res = await getPlaylistVideos(servers[0].url, userAccessTokenServer1, playlistServer1UUID2, 0, 10, { nsfw: false }) | ||
749 | expect(res.body.total).to.equal(3) | ||
582 | 750 | ||
583 | for (const server of servers) { | 751 | const elements: VideoPlaylistElement[] = res.body.data |
584 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 752 | const element = elements.find(e => e.position === 3) |
585 | const videos: Video[] = res.body.data | ||
586 | 753 | ||
587 | const names = videos.map(v => v.name) | 754 | expect(element).to.exist |
755 | expect(element.video).to.be.null | ||
756 | expect(element.type).to.equal(VideoPlaylistElementType.UNAVAILABLE) | ||
757 | }) | ||
588 | 758 | ||
589 | expect(names).to.deep.equal([ | 759 | }) |
590 | 'video 3 server 1', | ||
591 | 'video 0 server 1', | ||
592 | 'video 2 server 3', | ||
593 | 'NSFW video', | ||
594 | 'video 1 server 3', | ||
595 | 'video 4 server 1' | ||
596 | ]) | ||
597 | 760 | ||
598 | for (let i = 1; i <= videos.length; i++) { | 761 | describe('Managing playlist elements', function () { |
599 | expect(videos[i - 1].playlistElement.position).to.equal(i) | 762 | |
763 | it('Should reorder the playlist', async function () { | ||
764 | this.timeout(30000) | ||
765 | |||
766 | { | ||
767 | await reorderVideosPlaylist({ | ||
768 | url: servers[ 0 ].url, | ||
769 | token: servers[ 0 ].accessToken, | ||
770 | playlistId: playlistServer1Id, | ||
771 | elementAttrs: { | ||
772 | startPosition: 2, | ||
773 | insertAfterPosition: 3 | ||
774 | } | ||
775 | }) | ||
776 | |||
777 | await waitJobs(servers) | ||
778 | |||
779 | for (const server of servers) { | ||
780 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
781 | const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name) | ||
782 | |||
783 | expect(names).to.deep.equal([ | ||
784 | 'video 0 server 1', | ||
785 | 'video 2 server 3', | ||
786 | 'video 1 server 3', | ||
787 | 'video 3 server 1', | ||
788 | 'video 4 server 1', | ||
789 | 'NSFW video' | ||
790 | ]) | ||
600 | } | 791 | } |
601 | } | 792 | } |
602 | } | ||
603 | }) | ||
604 | |||
605 | it('Should update startTimestamp/endTimestamp of some elements', async function () { | ||
606 | this.timeout(30000) | ||
607 | 793 | ||
608 | await updateVideoPlaylistElement({ | 794 | { |
609 | url: servers[0].url, | 795 | await reorderVideosPlaylist({ |
610 | token: servers[0].accessToken, | 796 | url: servers[ 0 ].url, |
611 | playlistId: playlistServer1Id, | 797 | token: servers[ 0 ].accessToken, |
612 | videoId: servers[0].videos[3].uuid, | 798 | playlistId: playlistServer1Id, |
613 | elementAttrs: { | 799 | elementAttrs: { |
614 | startTimestamp: 1 | 800 | startPosition: 1, |
801 | reorderLength: 3, | ||
802 | insertAfterPosition: 4 | ||
803 | } | ||
804 | }) | ||
805 | |||
806 | await waitJobs(servers) | ||
807 | |||
808 | for (const server of servers) { | ||
809 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
810 | const names = (res.body.data as VideoPlaylistElement[]).map(v => v.video.name) | ||
811 | |||
812 | expect(names).to.deep.equal([ | ||
813 | 'video 3 server 1', | ||
814 | 'video 0 server 1', | ||
815 | 'video 2 server 3', | ||
816 | 'video 1 server 3', | ||
817 | 'video 4 server 1', | ||
818 | 'NSFW video' | ||
819 | ]) | ||
820 | } | ||
615 | } | 821 | } |
616 | }) | ||
617 | 822 | ||
618 | await updateVideoPlaylistElement({ | 823 | { |
619 | url: servers[0].url, | 824 | await reorderVideosPlaylist({ |
620 | token: servers[0].accessToken, | 825 | url: servers[ 0 ].url, |
621 | playlistId: playlistServer1Id, | 826 | token: servers[ 0 ].accessToken, |
622 | videoId: servers[0].videos[4].uuid, | 827 | playlistId: playlistServer1Id, |
623 | elementAttrs: { | 828 | elementAttrs: { |
624 | stopTimestamp: null | 829 | startPosition: 6, |
830 | insertAfterPosition: 3 | ||
831 | } | ||
832 | }) | ||
833 | |||
834 | await waitJobs(servers) | ||
835 | |||
836 | for (const server of servers) { | ||
837 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
838 | const elements: VideoPlaylistElement[] = res.body.data | ||
839 | const names = elements.map(v => v.video.name) | ||
840 | |||
841 | expect(names).to.deep.equal([ | ||
842 | 'video 3 server 1', | ||
843 | 'video 0 server 1', | ||
844 | 'video 2 server 3', | ||
845 | 'NSFW video', | ||
846 | 'video 1 server 3', | ||
847 | 'video 4 server 1' | ||
848 | ]) | ||
849 | |||
850 | for (let i = 1; i <= elements.length; i++) { | ||
851 | expect(elements[ i - 1 ].position).to.equal(i) | ||
852 | } | ||
853 | } | ||
625 | } | 854 | } |
626 | }) | 855 | }) |
627 | 856 | ||
628 | await waitJobs(servers) | 857 | it('Should update startTimestamp/endTimestamp of some elements', async function () { |
858 | this.timeout(30000) | ||
859 | |||
860 | await updateVideoPlaylistElement({ | ||
861 | url: servers[ 0 ].url, | ||
862 | token: servers[ 0 ].accessToken, | ||
863 | playlistId: playlistServer1Id, | ||
864 | playlistElementId: playlistElementServer1Video4, | ||
865 | elementAttrs: { | ||
866 | startTimestamp: 1 | ||
867 | } | ||
868 | }) | ||
629 | 869 | ||
630 | for (const server of servers) { | 870 | await updateVideoPlaylistElement({ |
631 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 871 | url: servers[ 0 ].url, |
632 | const videos: Video[] = res.body.data | 872 | token: servers[ 0 ].accessToken, |
873 | playlistId: playlistServer1Id, | ||
874 | playlistElementId: playlistElementServer1Video5, | ||
875 | elementAttrs: { | ||
876 | stopTimestamp: null | ||
877 | } | ||
878 | }) | ||
633 | 879 | ||
634 | expect(videos[0].name).to.equal('video 3 server 1') | 880 | await waitJobs(servers) |
635 | expect(videos[0].playlistElement.position).to.equal(1) | ||
636 | expect(videos[0].playlistElement.startTimestamp).to.equal(1) | ||
637 | expect(videos[0].playlistElement.stopTimestamp).to.equal(35) | ||
638 | 881 | ||
639 | expect(videos[5].name).to.equal('video 4 server 1') | 882 | for (const server of servers) { |
640 | expect(videos[5].playlistElement.position).to.equal(6) | 883 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) |
641 | expect(videos[5].playlistElement.startTimestamp).to.equal(45) | 884 | const elements: VideoPlaylistElement[] = res.body.data |
642 | expect(videos[5].playlistElement.stopTimestamp).to.be.null | ||
643 | } | ||
644 | }) | ||
645 | 885 | ||
646 | it('Should check videos existence in my playlist', async function () { | 886 | expect(elements[ 0 ].video.name).to.equal('video 3 server 1') |
647 | const videoIds = [ | 887 | expect(elements[ 0 ].position).to.equal(1) |
648 | servers[0].videos[0].id, | 888 | expect(elements[ 0 ].startTimestamp).to.equal(1) |
649 | 42000, | 889 | expect(elements[ 0 ].stopTimestamp).to.equal(35) |
650 | servers[0].videos[3].id, | ||
651 | 43000, | ||
652 | servers[0].videos[4].id | ||
653 | ] | ||
654 | const res = await doVideosExistInMyPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, videoIds) | ||
655 | const obj = res.body as VideoExistInPlaylist | ||
656 | 890 | ||
657 | { | 891 | expect(elements[ 5 ].video.name).to.equal('video 4 server 1') |
658 | const elem = obj[servers[0].videos[0].id] | 892 | expect(elements[ 5 ].position).to.equal(6) |
659 | expect(elem).to.have.lengthOf(1) | 893 | expect(elements[ 5 ].startTimestamp).to.equal(45) |
660 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | 894 | expect(elements[ 5 ].stopTimestamp).to.be.null |
661 | expect(elem[ 0 ].startTimestamp).to.equal(15) | 895 | } |
662 | expect(elem[ 0 ].stopTimestamp).to.equal(28) | 896 | }) |
663 | } | ||
664 | 897 | ||
665 | { | 898 | it('Should check videos existence in my playlist', async function () { |
666 | const elem = obj[servers[0].videos[3].id] | 899 | const videoIds = [ |
667 | expect(elem).to.have.lengthOf(1) | 900 | servers[ 0 ].videos[ 0 ].id, |
668 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | 901 | 42000, |
669 | expect(elem[ 0 ].startTimestamp).to.equal(1) | 902 | servers[ 0 ].videos[ 3 ].id, |
670 | expect(elem[ 0 ].stopTimestamp).to.equal(35) | 903 | 43000, |
671 | } | 904 | servers[ 0 ].videos[ 4 ].id |
905 | ] | ||
906 | const res = await doVideosExistInMyPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, videoIds) | ||
907 | const obj = res.body as VideoExistInPlaylist | ||
908 | |||
909 | { | ||
910 | const elem = obj[ servers[ 0 ].videos[ 0 ].id ] | ||
911 | expect(elem).to.have.lengthOf(1) | ||
912 | expect(elem[ 0 ].playlistElementId).to.exist | ||
913 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | ||
914 | expect(elem[ 0 ].startTimestamp).to.equal(15) | ||
915 | expect(elem[ 0 ].stopTimestamp).to.equal(28) | ||
916 | } | ||
672 | 917 | ||
673 | { | 918 | { |
674 | const elem = obj[servers[0].videos[4].id] | 919 | const elem = obj[ servers[ 0 ].videos[ 3 ].id ] |
675 | expect(elem).to.have.lengthOf(1) | 920 | expect(elem).to.have.lengthOf(1) |
676 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | 921 | expect(elem[ 0 ].playlistElementId).to.equal(playlistElementServer1Video4) |
677 | expect(elem[ 0 ].startTimestamp).to.equal(45) | 922 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) |
678 | expect(elem[ 0 ].stopTimestamp).to.equal(null) | 923 | expect(elem[ 0 ].startTimestamp).to.equal(1) |
679 | } | 924 | expect(elem[ 0 ].stopTimestamp).to.equal(35) |
925 | } | ||
680 | 926 | ||
681 | expect(obj[42000]).to.have.lengthOf(0) | 927 | { |
682 | expect(obj[43000]).to.have.lengthOf(0) | 928 | const elem = obj[ servers[ 0 ].videos[ 4 ].id ] |
683 | }) | 929 | expect(elem).to.have.lengthOf(1) |
930 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | ||
931 | expect(elem[ 0 ].startTimestamp).to.equal(45) | ||
932 | expect(elem[ 0 ].stopTimestamp).to.equal(null) | ||
933 | } | ||
684 | 934 | ||
685 | it('Should automatically update updatedAt field of playlists', async function () { | 935 | expect(obj[ 42000 ]).to.have.lengthOf(0) |
686 | const server = servers[1] | 936 | expect(obj[ 43000 ]).to.have.lengthOf(0) |
687 | const videoId = servers[1].videos[5].id | 937 | }) |
688 | 938 | ||
689 | async function getPlaylistNames () { | 939 | it('Should automatically update updatedAt field of playlists', async function () { |
690 | const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') | 940 | const server = servers[ 1 ] |
941 | const videoId = servers[ 1 ].videos[ 5 ].id | ||
691 | 942 | ||
692 | return (res.body.data as VideoPlaylist[]).map(p => p.displayName) | 943 | async function getPlaylistNames () { |
693 | } | 944 | const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') |
694 | 945 | ||
695 | const elementAttrs = { videoId } | 946 | return (res.body.data as VideoPlaylist[]).map(p => p.displayName) |
696 | await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, elementAttrs }) | 947 | } |
697 | await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, elementAttrs }) | ||
698 | 948 | ||
699 | const names1 = await getPlaylistNames() | 949 | const elementAttrs = { videoId } |
700 | expect(names1[0]).to.equal('playlist 3 updated') | 950 | const res1 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, elementAttrs }) |
701 | expect(names1[1]).to.equal('playlist 2') | 951 | const res2 = await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, elementAttrs }) |
702 | 952 | ||
703 | await removeVideoFromPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, videoId }) | 953 | const element1 = res1.body.videoPlaylistElement.id |
954 | const element2 = res2.body.videoPlaylistElement.id | ||
704 | 955 | ||
705 | const names2 = await getPlaylistNames() | 956 | const names1 = await getPlaylistNames() |
706 | expect(names2[0]).to.equal('playlist 2') | 957 | expect(names1[ 0 ]).to.equal('playlist 3 updated') |
707 | expect(names2[1]).to.equal('playlist 3 updated') | 958 | expect(names1[ 1 ]).to.equal('playlist 2') |
708 | 959 | ||
709 | await removeVideoFromPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, videoId }) | 960 | await removeVideoFromPlaylist({ |
961 | url: server.url, | ||
962 | token: server.accessToken, | ||
963 | playlistId: playlistServer2Id1, | ||
964 | playlistElementId: element1 | ||
965 | }) | ||
710 | 966 | ||
711 | const names3 = await getPlaylistNames() | 967 | const names2 = await getPlaylistNames() |
712 | expect(names3[0]).to.equal('playlist 3 updated') | 968 | expect(names2[ 0 ]).to.equal('playlist 2') |
713 | expect(names3[1]).to.equal('playlist 2') | 969 | expect(names2[ 1 ]).to.equal('playlist 3 updated') |
714 | }) | ||
715 | 970 | ||
716 | it('Should delete some elements', async function () { | 971 | await removeVideoFromPlaylist({ |
717 | this.timeout(30000) | 972 | url: server.url, |
973 | token: server.accessToken, | ||
974 | playlistId: playlistServer2Id2, | ||
975 | playlistElementId: element2 | ||
976 | }) | ||
718 | 977 | ||
719 | await removeVideoFromPlaylist({ | 978 | const names3 = await getPlaylistNames() |
720 | url: servers[0].url, | 979 | expect(names3[ 0 ]).to.equal('playlist 3 updated') |
721 | token: servers[0].accessToken, | 980 | expect(names3[ 1 ]).to.equal('playlist 2') |
722 | playlistId: playlistServer1Id, | ||
723 | videoId: servers[0].videos[3].uuid | ||
724 | }) | 981 | }) |
725 | 982 | ||
726 | await removeVideoFromPlaylist({ | 983 | it('Should delete some elements', async function () { |
727 | url: servers[0].url, | 984 | this.timeout(30000) |
728 | token: servers[0].accessToken, | ||
729 | playlistId: playlistServer1Id, | ||
730 | videoId: nsfwVideoServer1 | ||
731 | }) | ||
732 | 985 | ||
733 | await waitJobs(servers) | 986 | await removeVideoFromPlaylist({ |
987 | url: servers[ 0 ].url, | ||
988 | token: servers[ 0 ].accessToken, | ||
989 | playlistId: playlistServer1Id, | ||
990 | playlistElementId: playlistElementServer1Video4 | ||
991 | }) | ||
734 | 992 | ||
735 | for (const server of servers) { | 993 | await removeVideoFromPlaylist({ |
736 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 994 | url: servers[ 0 ].url, |
995 | token: servers[ 0 ].accessToken, | ||
996 | playlistId: playlistServer1Id, | ||
997 | playlistElementId: playlistElementNSFW | ||
998 | }) | ||
737 | 999 | ||
738 | expect(res.body.total).to.equal(4) | 1000 | await waitJobs(servers) |
739 | 1001 | ||
740 | const videos: Video[] = res.body.data | 1002 | for (const server of servers) { |
741 | expect(videos).to.have.lengthOf(4) | 1003 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) |
742 | 1004 | ||
743 | expect(videos[ 0 ].name).to.equal('video 0 server 1') | 1005 | expect(res.body.total).to.equal(4) |
744 | expect(videos[ 0 ].playlistElement.position).to.equal(1) | ||
745 | 1006 | ||
746 | expect(videos[ 1 ].name).to.equal('video 2 server 3') | 1007 | const elements: VideoPlaylistElement[] = res.body.data |
747 | expect(videos[ 1 ].playlistElement.position).to.equal(2) | 1008 | expect(elements).to.have.lengthOf(4) |
748 | 1009 | ||
749 | expect(videos[ 2 ].name).to.equal('video 1 server 3') | 1010 | expect(elements[ 0 ].video.name).to.equal('video 0 server 1') |
750 | expect(videos[ 2 ].playlistElement.position).to.equal(3) | 1011 | expect(elements[ 0 ].position).to.equal(1) |
751 | 1012 | ||
752 | expect(videos[ 3 ].name).to.equal('video 4 server 1') | 1013 | expect(elements[ 1 ].video.name).to.equal('video 2 server 3') |
753 | expect(videos[ 3 ].playlistElement.position).to.equal(4) | 1014 | expect(elements[ 1 ].position).to.equal(2) |
754 | } | ||
755 | }) | ||
756 | 1015 | ||
757 | it('Should be able to create a public playlist, and set it to private', async function () { | 1016 | expect(elements[ 2 ].video.name).to.equal('video 1 server 3') |
758 | this.timeout(30000) | 1017 | expect(elements[ 2 ].position).to.equal(3) |
759 | 1018 | ||
760 | const res = await createVideoPlaylist({ | 1019 | expect(elements[ 3 ].video.name).to.equal('video 4 server 1') |
761 | url: servers[0].url, | 1020 | expect(elements[ 3 ].position).to.equal(4) |
762 | token: servers[0].accessToken, | ||
763 | playlistAttrs: { | ||
764 | displayName: 'my super public playlist', | ||
765 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
766 | videoChannelId: servers[0].videoChannel.id | ||
767 | } | 1021 | } |
768 | }) | 1022 | }) |
769 | const videoPlaylistIds = res.body.videoPlaylist | ||
770 | 1023 | ||
771 | await waitJobs(servers) | 1024 | it('Should be able to create a public playlist, and set it to private', async function () { |
1025 | this.timeout(30000) | ||
772 | 1026 | ||
773 | for (const server of servers) { | 1027 | const res = await createVideoPlaylist({ |
774 | await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 200) | 1028 | url: servers[ 0 ].url, |
775 | } | 1029 | token: servers[ 0 ].accessToken, |
1030 | playlistAttrs: { | ||
1031 | displayName: 'my super public playlist', | ||
1032 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
1033 | videoChannelId: servers[ 0 ].videoChannel.id | ||
1034 | } | ||
1035 | }) | ||
1036 | const videoPlaylistIds = res.body.videoPlaylist | ||
776 | 1037 | ||
777 | const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE } | 1038 | await waitJobs(servers) |
778 | await updateVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs }) | ||
779 | 1039 | ||
780 | await waitJobs(servers) | 1040 | for (const server of servers) { |
1041 | await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 200) | ||
1042 | } | ||
781 | 1043 | ||
782 | for (const server of [ servers[1], servers[2] ]) { | 1044 | const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE } |
783 | await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404) | 1045 | await updateVideoPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs }) |
784 | } | ||
785 | await getVideoPlaylist(servers[0].url, videoPlaylistIds.uuid, 401) | ||
786 | 1046 | ||
787 | await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistIds.uuid, 200) | 1047 | await waitJobs(servers) |
788 | }) | ||
789 | 1048 | ||
790 | it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { | 1049 | for (const server of [ servers[ 1 ], servers[ 2 ] ]) { |
791 | this.timeout(30000) | 1050 | await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404) |
1051 | } | ||
1052 | await getVideoPlaylist(servers[ 0 ].url, videoPlaylistIds.uuid, 401) | ||
792 | 1053 | ||
793 | await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id) | 1054 | await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistIds.uuid, 200) |
1055 | }) | ||
1056 | }) | ||
794 | 1057 | ||
795 | await waitJobs(servers) | 1058 | describe('Playlist deletion', function () { |
796 | 1059 | ||
797 | for (const server of servers) { | 1060 | it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { |
798 | await getVideoPlaylist(server.url, playlistServer1UUID, 404) | 1061 | this.timeout(30000) |
799 | } | ||
800 | }) | ||
801 | 1062 | ||
802 | it('Should have deleted the thumbnail on server 1, 2 and 3', async function () { | 1063 | await deleteVideoPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, playlistServer1Id) |
803 | this.timeout(30000) | ||
804 | 1064 | ||
805 | for (const server of servers) { | 1065 | await waitJobs(servers) |
806 | await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber) | ||
807 | } | ||
808 | }) | ||
809 | 1066 | ||
810 | it('Should unfollow servers 1 and 2 and hide their playlists', async function () { | 1067 | for (const server of servers) { |
811 | this.timeout(30000) | 1068 | await getVideoPlaylist(server.url, playlistServer1UUID, 404) |
1069 | } | ||
1070 | }) | ||
812 | 1071 | ||
813 | const finder = data => data.find(p => p.displayName === 'my super playlist') | 1072 | it('Should have deleted the thumbnail on server 1, 2 and 3', async function () { |
1073 | this.timeout(30000) | ||
814 | 1074 | ||
815 | { | 1075 | for (const server of servers) { |
816 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | 1076 | await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber) |
817 | expect(res.body.total).to.equal(2) | 1077 | } |
818 | expect(finder(res.body.data)).to.not.be.undefined | 1078 | }) |
819 | } | ||
820 | 1079 | ||
821 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) | 1080 | it('Should unfollow servers 1 and 2 and hide their playlists', async function () { |
1081 | this.timeout(30000) | ||
822 | 1082 | ||
823 | { | 1083 | const finder = data => data.find(p => p.displayName === 'my super playlist') |
824 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | ||
825 | expect(res.body.total).to.equal(1) | ||
826 | 1084 | ||
827 | expect(finder(res.body.data)).to.be.undefined | 1085 | { |
828 | } | 1086 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) |
829 | }) | 1087 | expect(res.body.total).to.equal(3) |
1088 | expect(finder(res.body.data)).to.not.be.undefined | ||
1089 | } | ||
830 | 1090 | ||
831 | it('Should delete a channel and put the associated playlist in private mode', async function () { | 1091 | await unfollow(servers[ 2 ].url, servers[ 2 ].accessToken, servers[ 0 ]) |
832 | this.timeout(30000) | ||
833 | 1092 | ||
834 | const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' }) | 1093 | { |
835 | const videoChannelId = res.body.videoChannel.id | 1094 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) |
1095 | expect(res.body.total).to.equal(1) | ||
836 | 1096 | ||
837 | const res2 = await createVideoPlaylist({ | 1097 | expect(finder(res.body.data)).to.be.undefined |
838 | url: servers[0].url, | ||
839 | token: servers[0].accessToken, | ||
840 | playlistAttrs: { | ||
841 | displayName: 'channel playlist', | ||
842 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
843 | videoChannelId | ||
844 | } | 1098 | } |
845 | }) | 1099 | }) |
846 | const videoPlaylistUUID = res2.body.videoPlaylist.uuid | ||
847 | 1100 | ||
848 | await waitJobs(servers) | 1101 | it('Should delete a channel and put the associated playlist in private mode', async function () { |
1102 | this.timeout(30000) | ||
849 | 1103 | ||
850 | await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel') | 1104 | const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'super_channel', displayName: 'super channel' }) |
1105 | const videoChannelId = res.body.videoChannel.id | ||
851 | 1106 | ||
852 | await waitJobs(servers) | 1107 | const res2 = await createVideoPlaylist({ |
1108 | url: servers[ 0 ].url, | ||
1109 | token: servers[ 0 ].accessToken, | ||
1110 | playlistAttrs: { | ||
1111 | displayName: 'channel playlist', | ||
1112 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
1113 | videoChannelId | ||
1114 | } | ||
1115 | }) | ||
1116 | const videoPlaylistUUID = res2.body.videoPlaylist.uuid | ||
1117 | |||
1118 | await waitJobs(servers) | ||
853 | 1119 | ||
854 | const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID) | 1120 | await deleteVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, 'super_channel') |
855 | expect(res3.body.displayName).to.equal('channel playlist') | ||
856 | expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) | ||
857 | 1121 | ||
858 | await getVideoPlaylist(servers[1].url, videoPlaylistUUID, 404) | 1122 | await waitJobs(servers) |
859 | }) | ||
860 | 1123 | ||
861 | it('Should delete an account and delete its playlists', async function () { | 1124 | const res3 = await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistUUID) |
862 | this.timeout(30000) | 1125 | expect(res3.body.displayName).to.equal('channel playlist') |
1126 | expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) | ||
863 | 1127 | ||
864 | const user = { username: 'user_1', password: 'password' } | 1128 | await getVideoPlaylist(servers[ 1 ].url, videoPlaylistUUID, 404) |
865 | const res = await createUser({ | ||
866 | url: servers[ 0 ].url, | ||
867 | accessToken: servers[ 0 ].accessToken, | ||
868 | username: user.username, | ||
869 | password: user.password | ||
870 | }) | 1129 | }) |
871 | 1130 | ||
872 | const userId = res.body.user.id | 1131 | it('Should delete an account and delete its playlists', async function () { |
873 | const userAccessToken = await userLogin(servers[0], user) | 1132 | this.timeout(30000) |
874 | 1133 | ||
875 | const resChannel = await getMyUserInformation(servers[0].url, userAccessToken) | 1134 | const user = { username: 'user_1', password: 'password' } |
876 | const userChannel = (resChannel.body as User).videoChannels[0] | 1135 | const res = await createUser({ |
1136 | url: servers[ 0 ].url, | ||
1137 | accessToken: servers[ 0 ].accessToken, | ||
1138 | username: user.username, | ||
1139 | password: user.password | ||
1140 | }) | ||
877 | 1141 | ||
878 | await createVideoPlaylist({ | 1142 | const userId = res.body.user.id |
879 | url: servers[0].url, | 1143 | const userAccessToken = await userLogin(servers[ 0 ], user) |
880 | token: userAccessToken, | ||
881 | playlistAttrs: { | ||
882 | displayName: 'playlist to be deleted', | ||
883 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
884 | videoChannelId: userChannel.id | ||
885 | } | ||
886 | }) | ||
887 | 1144 | ||
888 | await waitJobs(servers) | 1145 | const resChannel = await getMyUserInformation(servers[ 0 ].url, userAccessToken) |
1146 | const userChannel = (resChannel.body as User).videoChannels[ 0 ] | ||
889 | 1147 | ||
890 | const finder = data => data.find(p => p.displayName === 'playlist to be deleted') | 1148 | await createVideoPlaylist({ |
1149 | url: servers[ 0 ].url, | ||
1150 | token: userAccessToken, | ||
1151 | playlistAttrs: { | ||
1152 | displayName: 'playlist to be deleted', | ||
1153 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
1154 | videoChannelId: userChannel.id | ||
1155 | } | ||
1156 | }) | ||
891 | 1157 | ||
892 | { | 1158 | await waitJobs(servers) |
893 | for (const server of [ servers[0], servers[1] ]) { | 1159 | |
894 | const res = await getVideoPlaylistsList(server.url, 0, 15) | 1160 | const finder = data => data.find(p => p.displayName === 'playlist to be deleted') |
895 | expect(finder(res.body.data)).to.not.be.undefined | 1161 | |
1162 | { | ||
1163 | for (const server of [ servers[ 0 ], servers[ 1 ] ]) { | ||
1164 | const res = await getVideoPlaylistsList(server.url, 0, 15) | ||
1165 | expect(finder(res.body.data)).to.not.be.undefined | ||
1166 | } | ||
896 | } | 1167 | } |
897 | } | ||
898 | 1168 | ||
899 | await removeUser(servers[0].url, userId, servers[0].accessToken) | 1169 | await removeUser(servers[ 0 ].url, userId, servers[ 0 ].accessToken) |
900 | await waitJobs(servers) | 1170 | await waitJobs(servers) |
901 | 1171 | ||
902 | { | 1172 | { |
903 | for (const server of [ servers[0], servers[1] ]) { | 1173 | for (const server of [ servers[ 0 ], servers[ 1 ] ]) { |
904 | const res = await getVideoPlaylistsList(server.url, 0, 15) | 1174 | const res = await getVideoPlaylistsList(server.url, 0, 15) |
905 | expect(finder(res.body.data)).to.be.undefined | 1175 | expect(finder(res.body.data)).to.be.undefined |
1176 | } | ||
906 | } | 1177 | } |
907 | } | 1178 | }) |
908 | }) | 1179 | }) |
909 | 1180 | ||
910 | after(async function () { | 1181 | after(async function () { |