aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts2
-rw-r--r--server/controllers/api/accounts.ts15
-rw-r--r--server/controllers/api/users/me.ts3
-rw-r--r--server/controllers/api/users/my-subscriptions.ts19
-rw-r--r--server/controllers/api/video-channel.ts41
-rw-r--r--server/controllers/api/video-playlist.ts4
-rw-r--r--server/controllers/api/videos/index.ts56
-rw-r--r--server/controllers/feeds.ts18
-rw-r--r--server/initializers/constants.ts15
-rw-r--r--server/initializers/migrations/0530-playlist-multiple-video.ts46
-rw-r--r--server/lib/activitypub/url.ts7
-rw-r--r--server/middlewares/sort.ts2
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts19
-rw-r--r--server/models/utils.ts25
-rw-r--r--server/models/video/video-comment.ts29
-rw-r--r--server/models/video/video-format-utils.ts5
-rw-r--r--server/models/video/video-playlist-element.ts21
-rw-r--r--server/tests/api/check-params/video-playlists.ts5
-rw-r--r--server/tests/api/notifications/user-notifications.ts24
-rw-r--r--server/tests/api/videos/video-playlists.ts35
20 files changed, 244 insertions, 147 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index acce53713..1da44d096 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -159,7 +159,7 @@ activityPubClientRouter.get('/video-playlists/:playlistId',
159 asyncMiddleware(videoPlaylistsGetValidator('all')), 159 asyncMiddleware(videoPlaylistsGetValidator('all')),
160 asyncMiddleware(videoPlaylistController) 160 asyncMiddleware(videoPlaylistController)
161) 161)
162activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', 162activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId',
163 executeIfActivityPub, 163 executeIfActivityPub,
164 asyncMiddleware(videoPlaylistElementAPGetValidator), 164 asyncMiddleware(videoPlaylistElementAPGetValidator),
165 videoPlaylistElementController 165 videoPlaylistElementController
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index b1c05c6c0..0a73dfcbf 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,5 +1,8 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getServerActor } from '@server/models/application/application'
3import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
2import { getFormattedObjects } from '../../helpers/utils' 4import { getFormattedObjects } from '../../helpers/utils'
5import { JobQueue } from '../../lib/job-queue'
3import { 6import {
4 asyncMiddleware, 7 asyncMiddleware,
5 authenticate, 8 authenticate,
@@ -8,6 +11,7 @@ import {
8 paginationValidator, 11 paginationValidator,
9 setDefaultPagination, 12 setDefaultPagination,
10 setDefaultSort, 13 setDefaultSort,
14 setDefaultVideosSort,
11 videoPlaylistsSortValidator, 15 videoPlaylistsSortValidator,
12 videoRatesSortValidator, 16 videoRatesSortValidator,
13 videoRatingValidator 17 videoRatingValidator
@@ -17,18 +21,15 @@ import {
17 accountsSortValidator, 21 accountsSortValidator,
18 ensureAuthUserOwnsAccountValidator, 22 ensureAuthUserOwnsAccountValidator,
19 videoChannelsSortValidator, 23 videoChannelsSortValidator,
20 videosSortValidator, 24 videoChannelStatsValidator,
21 videoChannelStatsValidator 25 videosSortValidator
22} from '../../middlewares/validators' 26} from '../../middlewares/validators'
27import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists'
23import { AccountModel } from '../../models/account/account' 28import { AccountModel } from '../../models/account/account'
24import { AccountVideoRateModel } from '../../models/account/account-video-rate' 29import { AccountVideoRateModel } from '../../models/account/account-video-rate'
25import { VideoModel } from '../../models/video/video' 30import { VideoModel } from '../../models/video/video'
26import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
27import { VideoChannelModel } from '../../models/video/video-channel' 31import { VideoChannelModel } from '../../models/video/video-channel'
28import { JobQueue } from '../../lib/job-queue'
29import { VideoPlaylistModel } from '../../models/video/video-playlist' 32import { VideoPlaylistModel } from '../../models/video/video-playlist'
30import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists'
31import { getServerActor } from '@server/models/application/application'
32 33
33const accountsRouter = express.Router() 34const accountsRouter = express.Router()
34 35
@@ -49,7 +50,7 @@ accountsRouter.get('/:accountName/videos',
49 asyncMiddleware(accountNameWithHostGetValidator), 50 asyncMiddleware(accountNameWithHostGetValidator),
50 paginationValidator, 51 paginationValidator,
51 videosSortValidator, 52 videosSortValidator,
52 setDefaultSort, 53 setDefaultVideosSort,
53 setDefaultPagination, 54 setDefaultPagination,
54 optionalAuthenticate, 55 optionalAuthenticate,
55 commonVideosFiltersValidator, 56 commonVideosFiltersValidator,
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 914c52e27..ba60a3d2a 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -17,6 +17,7 @@ import {
17 paginationValidator, 17 paginationValidator,
18 setDefaultPagination, 18 setDefaultPagination,
19 setDefaultSort, 19 setDefaultSort,
20 setDefaultVideosSort,
20 usersUpdateMeValidator, 21 usersUpdateMeValidator,
21 usersVideoRatingValidator 22 usersVideoRatingValidator
22} from '../../../middlewares' 23} from '../../../middlewares'
@@ -60,7 +61,7 @@ meRouter.get('/me/videos',
60 authenticate, 61 authenticate,
61 paginationValidator, 62 paginationValidator,
62 videosSortValidator, 63 videosSortValidator,
63 setDefaultSort, 64 setDefaultVideosSort,
64 setDefaultPagination, 65 setDefaultPagination,
65 asyncMiddleware(getUserVideos) 66 asyncMiddleware(getUserVideos)
66) 67)
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 8b88feaf3..b8c234eef 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -1,7 +1,11 @@
1import * as express from 'express'
2import 'multer' 1import 'multer'
2import * as express from 'express'
3import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
4import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
3import { getFormattedObjects } from '../../../helpers/utils' 5import { getFormattedObjects } from '../../../helpers/utils'
4import { WEBSERVER } from '../../../initializers/constants' 6import { WEBSERVER } from '../../../initializers/constants'
7import { sequelizeTypescript } from '../../../initializers/database'
8import { JobQueue } from '../../../lib/job-queue'
5import { 9import {
6 asyncMiddleware, 10 asyncMiddleware,
7 asyncRetryTransactionMiddleware, 11 asyncRetryTransactionMiddleware,
@@ -10,21 +14,18 @@ import {
10 paginationValidator, 14 paginationValidator,
11 setDefaultPagination, 15 setDefaultPagination,
12 setDefaultSort, 16 setDefaultSort,
17 setDefaultVideosSort,
13 userSubscriptionAddValidator, 18 userSubscriptionAddValidator,
14 userSubscriptionGetValidator 19 userSubscriptionGetValidator
15} from '../../../middlewares' 20} from '../../../middlewares'
16import { 21import {
17 areSubscriptionsExistValidator, 22 areSubscriptionsExistValidator,
23 userSubscriptionListValidator,
18 userSubscriptionsSortValidator, 24 userSubscriptionsSortValidator,
19 videosSortValidator, 25 videosSortValidator
20 userSubscriptionListValidator
21} from '../../../middlewares/validators' 26} from '../../../middlewares/validators'
22import { VideoModel } from '../../../models/video/video'
23import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
24import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
25import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 27import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
26import { JobQueue } from '../../../lib/job-queue' 28import { VideoModel } from '../../../models/video/video'
27import { sequelizeTypescript } from '../../../initializers/database'
28 29
29const mySubscriptionsRouter = express.Router() 30const mySubscriptionsRouter = express.Router()
30 31
@@ -32,7 +33,7 @@ mySubscriptionsRouter.get('/me/subscriptions/videos',
32 authenticate, 33 authenticate,
33 paginationValidator, 34 paginationValidator,
34 videosSortValidator, 35 videosSortValidator,
35 setDefaultSort, 36 setDefaultVideosSort,
36 setDefaultPagination, 37 setDefaultPagination,
37 commonVideosFiltersValidator, 38 commonVideosFiltersValidator,
38 asyncMiddleware(getUserSubscriptionVideos) 39 asyncMiddleware(getUserSubscriptionVideos)
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index f705034fd..45c936978 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -1,5 +1,20 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getServerActor } from '@server/models/application/application'
3import { MChannelAccountDefault } from '@server/types/models'
4import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
5import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
6import { resetSequelizeInstance } from '../../helpers/database-utils'
7import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
8import { logger } from '../../helpers/logger'
2import { getFormattedObjects } from '../../helpers/utils' 9import { getFormattedObjects } from '../../helpers/utils'
10import { CONFIG } from '../../initializers/config'
11import { MIMETYPES } from '../../initializers/constants'
12import { sequelizeTypescript } from '../../initializers/database'
13import { setAsyncActorKeys } from '../../lib/activitypub/actor'
14import { sendUpdateActor } from '../../lib/activitypub/send'
15import { updateActorAvatarFile } from '../../lib/avatar'
16import { JobQueue } from '../../lib/job-queue'
17import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
3import { 18import {
4 asyncMiddleware, 19 asyncMiddleware,
5 asyncRetryTransactionMiddleware, 20 asyncRetryTransactionMiddleware,
@@ -9,34 +24,20 @@ import {
9 paginationValidator, 24 paginationValidator,
10 setDefaultPagination, 25 setDefaultPagination,
11 setDefaultSort, 26 setDefaultSort,
27 setDefaultVideosSort,
12 videoChannelsAddValidator, 28 videoChannelsAddValidator,
13 videoChannelsRemoveValidator, 29 videoChannelsRemoveValidator,
14 videoChannelsSortValidator, 30 videoChannelsSortValidator,
15 videoChannelsUpdateValidator, 31 videoChannelsUpdateValidator,
16 videoPlaylistsSortValidator 32 videoPlaylistsSortValidator
17} from '../../middlewares' 33} from '../../middlewares'
18import { VideoChannelModel } from '../../models/video/video-channel' 34import { videoChannelsNameWithHostValidator, videoChannelsOwnSearchValidator, videosSortValidator } from '../../middlewares/validators'
19import { videoChannelsNameWithHostValidator, videosSortValidator, videoChannelsOwnSearchValidator } from '../../middlewares/validators' 35import { updateAvatarValidator } from '../../middlewares/validators/avatar'
20import { sendUpdateActor } from '../../lib/activitypub/send' 36import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
22import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
23import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
24import { setAsyncActorKeys } from '../../lib/activitypub/actor'
25import { AccountModel } from '../../models/account/account' 37import { AccountModel } from '../../models/account/account'
26import { MIMETYPES } from '../../initializers/constants'
27import { logger } from '../../helpers/logger'
28import { VideoModel } from '../../models/video/video' 38import { VideoModel } from '../../models/video/video'
29import { updateAvatarValidator } from '../../middlewares/validators/avatar' 39import { VideoChannelModel } from '../../models/video/video-channel'
30import { updateActorAvatarFile } from '../../lib/avatar'
31import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
32import { resetSequelizeInstance } from '../../helpers/database-utils'
33import { JobQueue } from '../../lib/job-queue'
34import { VideoPlaylistModel } from '../../models/video/video-playlist' 40import { VideoPlaylistModel } from '../../models/video/video-playlist'
35import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
36import { CONFIG } from '../../initializers/config'
37import { sequelizeTypescript } from '../../initializers/database'
38import { MChannelAccountDefault } from '@server/types/models'
39import { getServerActor } from '@server/models/application/application'
40 41
41const auditLogger = auditLoggerFactory('channels') 42const auditLogger = auditLoggerFactory('channels')
42const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 43const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
@@ -98,7 +99,7 @@ videoChannelRouter.get('/:nameWithHost/videos',
98 asyncMiddleware(videoChannelsNameWithHostValidator), 99 asyncMiddleware(videoChannelsNameWithHostValidator),
99 paginationValidator, 100 paginationValidator,
100 videosSortValidator, 101 videosSortValidator,
101 setDefaultSort, 102 setDefaultVideosSort,
102 setDefaultPagination, 103 setDefaultPagination,
103 optionalAuthenticate, 104 optionalAuthenticate,
104 commonVideosFiltersValidator, 105 commonVideosFiltersValidator,
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index 88a2314fb..41a0e07ff 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -297,7 +297,6 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
297 const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) 297 const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t)
298 298
299 const playlistElement = await VideoPlaylistElementModel.create({ 299 const playlistElement = await VideoPlaylistElementModel.create({
300 url: getVideoPlaylistElementActivityPubUrl(videoPlaylist, video),
301 position, 300 position,
302 startTimestamp: body.startTimestamp || null, 301 startTimestamp: body.startTimestamp || null,
303 stopTimestamp: body.stopTimestamp || null, 302 stopTimestamp: body.stopTimestamp || null,
@@ -305,6 +304,9 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
305 videoId: video.id 304 videoId: video.id
306 }, { transaction: t }) 305 }, { transaction: t })
307 306
307 playlistElement.url = getVideoPlaylistElementActivityPubUrl(videoPlaylist, playlistElement)
308 await playlistElement.save({ transaction: t })
309
308 videoPlaylist.changed('updatedAt', true) 310 videoPlaylist.changed('updatedAt', true)
309 await videoPlaylist.save({ transaction: t }) 311 await videoPlaylist.save({ transaction: t })
310 312
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 1dfd7c7a0..15b6f214f 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -1,11 +1,24 @@
1import * as express from 'express' 1import * as express from 'express'
2import { move } from 'fs-extra'
2import { extname } from 'path' 3import { extname } from 'path'
4import toInt from 'validator/lib/toInt'
5import { addOptimizeOrMergeAudioJob } from '@server/helpers/video'
6import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
7import { changeVideoChannelShare } from '@server/lib/activitypub/share'
8import { getVideoActivityPubUrl } from '@server/lib/activitypub/url'
9import { getVideoFilePath } from '@server/lib/video-paths'
10import { getServerActor } from '@server/models/application/application'
11import { MVideoDetails, MVideoFullLight } from '@server/types/models'
3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' 12import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared'
13import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
14import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
15import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
16import { resetSequelizeInstance } from '../../../helpers/database-utils'
17import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
4import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 18import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
5import { logger } from '../../../helpers/logger' 19import { logger } from '../../../helpers/logger'
6import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
7import { getFormattedObjects } from '../../../helpers/utils' 20import { getFormattedObjects } from '../../../helpers/utils'
8import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' 21import { CONFIG } from '../../../initializers/config'
9import { 22import {
10 DEFAULT_AUDIO_RESOLUTION, 23 DEFAULT_AUDIO_RESOLUTION,
11 MIMETYPES, 24 MIMETYPES,
@@ -14,9 +27,15 @@ import {
14 VIDEO_LICENCES, 27 VIDEO_LICENCES,
15 VIDEO_PRIVACIES 28 VIDEO_PRIVACIES
16} from '../../../initializers/constants' 29} from '../../../initializers/constants'
30import { sequelizeTypescript } from '../../../initializers/database'
31import { sendView } from '../../../lib/activitypub/send/send-view'
17import { federateVideoIfNeeded, fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' 32import { federateVideoIfNeeded, fetchRemoteVideoDescription } from '../../../lib/activitypub/videos'
18import { JobQueue } from '../../../lib/job-queue' 33import { JobQueue } from '../../../lib/job-queue'
34import { Notifier } from '../../../lib/notifier'
35import { Hooks } from '../../../lib/plugins/hooks'
19import { Redis } from '../../../lib/redis' 36import { Redis } from '../../../lib/redis'
37import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
38import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
20import { 39import {
21 asyncMiddleware, 40 asyncMiddleware,
22 asyncRetryTransactionMiddleware, 41 asyncRetryTransactionMiddleware,
@@ -26,7 +45,7 @@ import {
26 optionalAuthenticate, 45 optionalAuthenticate,
27 paginationValidator, 46 paginationValidator,
28 setDefaultPagination, 47 setDefaultPagination,
29 setDefaultSort, 48 setDefaultVideosSort,
30 videoFileMetadataGetValidator, 49 videoFileMetadataGetValidator,
31 videosAddValidator, 50 videosAddValidator,
32 videosCustomGetValidator, 51 videosCustomGetValidator,
@@ -35,37 +54,18 @@ import {
35 videosSortValidator, 54 videosSortValidator,
36 videosUpdateValidator 55 videosUpdateValidator
37} from '../../../middlewares' 56} from '../../../middlewares'
57import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
38import { TagModel } from '../../../models/video/tag' 58import { TagModel } from '../../../models/video/tag'
39import { VideoModel } from '../../../models/video/video' 59import { VideoModel } from '../../../models/video/video'
40import { VideoFileModel } from '../../../models/video/video-file' 60import { VideoFileModel } from '../../../models/video/video-file'
41import { abuseVideoRouter } from './abuse' 61import { abuseVideoRouter } from './abuse'
42import { blacklistRouter } from './blacklist' 62import { blacklistRouter } from './blacklist'
43import { videoCommentRouter } from './comment'
44import { rateVideoRouter } from './rate'
45import { ownershipVideoRouter } from './ownership'
46import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
47import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
48import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
49import { videoCaptionsRouter } from './captions' 63import { videoCaptionsRouter } from './captions'
64import { videoCommentRouter } from './comment'
50import { videoImportsRouter } from './import' 65import { videoImportsRouter } from './import'
51import { resetSequelizeInstance } from '../../../helpers/database-utils' 66import { ownershipVideoRouter } from './ownership'
52import { move } from 'fs-extra' 67import { rateVideoRouter } from './rate'
53import { watchingRouter } from './watching' 68import { watchingRouter } from './watching'
54import { Notifier } from '../../../lib/notifier'
55import { sendView } from '../../../lib/activitypub/send/send-view'
56import { CONFIG } from '../../../initializers/config'
57import { sequelizeTypescript } from '../../../initializers/database'
58import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
59import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
60import { Hooks } from '../../../lib/plugins/hooks'
61import { MVideoDetails, MVideoFullLight } from '@server/types/models'
62import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
63import { getVideoFilePath } from '@server/lib/video-paths'
64import toInt from 'validator/lib/toInt'
65import { addOptimizeOrMergeAudioJob } from '@server/helpers/video'
66import { getServerActor } from '@server/models/application/application'
67import { changeVideoChannelShare } from '@server/lib/activitypub/share'
68import { getVideoActivityPubUrl } from '@server/lib/activitypub/url'
69 69
70const auditLogger = auditLoggerFactory('videos') 70const auditLogger = auditLoggerFactory('videos')
71const videosRouter = express.Router() 71const videosRouter = express.Router()
@@ -105,7 +105,7 @@ videosRouter.get('/privacies', listVideoPrivacies)
105videosRouter.get('/', 105videosRouter.get('/',
106 paginationValidator, 106 paginationValidator,
107 videosSortValidator, 107 videosSortValidator,
108 setDefaultSort, 108 setDefaultVideosSort,
109 setDefaultPagination, 109 setDefaultPagination,
110 optionalAuthenticate, 110 optionalAuthenticate,
111 commonVideosFiltersValidator, 111 commonVideosFiltersValidator,
@@ -414,7 +414,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
414 Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) 414 Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated)
415 } 415 }
416 416
417 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated }) 417 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body })
418 } catch (err) { 418 } catch (err) {
419 // Force fields we want to update 419 // Force fields we want to update
420 // If the transaction is retried, sequelize will think the object has not changed 420 // If the transaction is retried, sequelize will think the object has not changed
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index bfcd3fe36..f14c0d316 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -1,21 +1,21 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Feed from 'pfeed'
3import { buildNSFWFilter } from '../helpers/express-utils'
4import { CONFIG } from '../initializers/config'
2import { FEEDS, ROUTE_CACHE_LIFETIME, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants' 5import { FEEDS, ROUTE_CACHE_LIFETIME, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants'
3import { 6import {
4 asyncMiddleware, 7 asyncMiddleware,
5 commonVideosFiltersValidator, 8 commonVideosFiltersValidator,
6 setDefaultSort, 9 feedsFormatValidator,
10 setDefaultVideosSort,
11 setFeedFormatContentType,
7 videoCommentsFeedsValidator, 12 videoCommentsFeedsValidator,
8 videoFeedsValidator, 13 videoFeedsValidator,
9 videosSortValidator, 14 videosSortValidator
10 feedsFormatValidator,
11 setFeedFormatContentType
12} from '../middlewares' 15} from '../middlewares'
13import { VideoModel } from '../models/video/video'
14import * as Feed from 'pfeed'
15import { cacheRoute } from '../middlewares/cache' 16import { cacheRoute } from '../middlewares/cache'
17import { VideoModel } from '../models/video/video'
16import { VideoCommentModel } from '../models/video/video-comment' 18import { VideoCommentModel } from '../models/video/video-comment'
17import { buildNSFWFilter } from '../helpers/express-utils'
18import { CONFIG } from '../initializers/config'
19 19
20const feedsRouter = express.Router() 20const feedsRouter = express.Router()
21 21
@@ -34,7 +34,7 @@ feedsRouter.get('/feeds/video-comments.:format',
34 34
35feedsRouter.get('/feeds/videos.:format', 35feedsRouter.get('/feeds/videos.:format',
36 videosSortValidator, 36 videosSortValidator,
37 setDefaultSort, 37 setDefaultVideosSort,
38 feedsFormatValidator, 38 feedsFormatValidator,
39 setFeedFormatContentType, 39 setFeedFormatContentType,
40 asyncMiddleware(cacheRoute({ 40 asyncMiddleware(cacheRoute({
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 691d29283..c26c3a88c 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -23,7 +23,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
23 23
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26const LAST_MIGRATION_VERSION = 525 26const LAST_MIGRATION_VERSION = 530
27 27
28// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
29 29
@@ -417,7 +417,9 @@ const MIMETYPES = {
417 'audio/x-ms-wma': '.wma', 417 'audio/x-ms-wma': '.wma',
418 'audio/wav': '.wav', 418 'audio/wav': '.wav',
419 'audio/x-flac': '.flac', 419 'audio/x-flac': '.flac',
420 'audio/flac': '.flac' 420 'audio/flac': '.flac',
421 '‎audio/aac': '.aac',
422 'audio/ac3': '.ac3'
421 }, 423 },
422 EXT_MIMETYPE: null as { [ id: string ]: string } 424 EXT_MIMETYPE: null as { [ id: string ]: string }
423 }, 425 },
@@ -841,7 +843,7 @@ function buildVideoMimetypeExt () {
841 'video/x-matroska': '.mkv', 843 'video/x-matroska': '.mkv',
842 844
843 // Developed by Apple 845 // Developed by Apple
844 'video/quicktime': '.mov', // often used as output format by editing software 846 'video/quicktime': [ '.mov', '.qt', '.mqv' ], // often used as output format by editing software
845 'video/x-m4v': '.m4v', 847 'video/x-m4v': '.m4v',
846 'video/m4v': '.m4v', 848 'video/m4v': '.m4v',
847 849
@@ -856,8 +858,8 @@ function buildVideoMimetypeExt () {
856 858
857 // Developed by 3GPP 859 // Developed by 3GPP
858 // common video formats for cell phones 860 // common video formats for cell phones
859 'video/3gpp': '.3gp', 861 'video/3gpp': [ '.3gp', '.3gpp' ],
860 'video/3gpp2': '.3g2', 862 'video/3gpp2': [ '.3g2', '.3gpp2' ],
861 863
862 // Developed by FFmpeg/Mplayer 864 // Developed by FFmpeg/Mplayer
863 'application/x-nut': '.nut', 865 'application/x-nut': '.nut',
@@ -870,7 +872,8 @@ function buildVideoMimetypeExt () {
870 // Old formats reliant on MPEG-1/MPEG-2 872 // Old formats reliant on MPEG-1/MPEG-2
871 'video/mpv': '.mpv', 873 'video/mpv': '.mpv',
872 'video/mpeg2': '.m2v', 874 'video/mpeg2': '.m2v',
873 'video/mpeg': '.mpeg', 875 'video/mpeg': [ '.m1v', '.mpg', '.mpe', '.mpeg', '.vob' ],
876 'video/dvd': '.vob',
874 877
875 // Could be anything 878 // Could be anything
876 'application/octet-stream': null, 879 'application/octet-stream': null,
diff --git a/server/initializers/migrations/0530-playlist-multiple-video.ts b/server/initializers/migrations/0530-playlist-multiple-video.ts
new file mode 100644
index 000000000..51a8c06b0
--- /dev/null
+++ b/server/initializers/migrations/0530-playlist-multiple-video.ts
@@ -0,0 +1,46 @@
1import * as Sequelize from 'sequelize'
2import { WEBSERVER } from '../constants'
3
4async function up (utils: {
5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize
8}): Promise<void> {
9 {
10 const field = {
11 type: Sequelize.STRING,
12 allowNull: true
13 }
14 await utils.queryInterface.changeColumn('videoPlaylistElement', 'url', field)
15 }
16
17 {
18 await utils.sequelize.query('DROP INDEX IF EXISTS video_playlist_element_video_playlist_id_video_id;')
19 }
20
21 {
22 const selectPlaylistUUID = 'SELECT "uuid" FROM "videoPlaylist" WHERE "id" = "videoPlaylistElement"."videoPlaylistId"'
23 const url = `'${WEBSERVER.URL}' || '/video-playlists/' || (${selectPlaylistUUID}) || '/videos/' || "videoPlaylistElement"."id"`
24
25 const query = `
26 UPDATE "videoPlaylistElement" SET "url" = ${url} WHERE id IN (
27 SELECT "videoPlaylistElement"."id" FROM "videoPlaylistElement"
28 INNER JOIN "videoPlaylist" ON "videoPlaylist".id = "videoPlaylistElement"."videoPlaylistId"
29 INNER JOIN account ON account.id = "videoPlaylist"."ownerAccountId"
30 INNER JOIN actor ON actor.id = account."actorId"
31 WHERE actor."serverId" IS NULL
32 )`
33
34 await utils.sequelize.query(query)
35 }
36
37}
38
39function down (options) {
40 throw new Error('Not implemented.')
41}
42
43export {
44 up,
45 down
46}
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index b54e038a4..58030be2c 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -8,7 +8,8 @@ import {
8 MVideoId, 8 MVideoId,
9 MVideoUrl, 9 MVideoUrl,
10 MVideoUUID, 10 MVideoUUID,
11 MAbuseId 11 MAbuseId,
12 MVideoPlaylistElement
12} from '../../types/models' 13} from '../../types/models'
13import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' 14import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist'
14import { MVideoFileVideoUUID } from '../../types/models/video/video-file' 15import { MVideoFileVideoUUID } from '../../types/models/video/video-file'
@@ -22,8 +23,8 @@ function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) {
22 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid 23 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid
23} 24}
24 25
25function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) { 26function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, videoPlaylistElement: MVideoPlaylistElement) {
26 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid 27 return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/videos/' + videoPlaylistElement.id
27} 28}
28 29
29function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) { 30function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) {
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index fcbb2902c..609046a46 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -2,6 +2,7 @@ import * as express from 'express'
2import { SortType } from '../models/utils' 2import { SortType } from '../models/utils'
3 3
4const setDefaultSort = setDefaultSortFactory('-createdAt') 4const setDefaultSort = setDefaultSortFactory('-createdAt')
5const setDefaultVideosSort = setDefaultSortFactory('-publishedAt')
5 6
6const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name') 7const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name')
7 8
@@ -33,6 +34,7 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex
33export { 34export {
34 setDefaultSort, 35 setDefaultSort,
35 setDefaultSearchSort, 36 setDefaultSearchSort,
37 setDefaultVideosSort,
36 setDefaultVideoRedundanciesSort, 38 setDefaultVideoRedundanciesSort,
37 setBlacklistSort 39 setBlacklistSort
38} 40}
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 07fd8533c..4647eae44 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -199,16 +199,6 @@ const videoPlaylistsAddVideoValidator = [
199 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return 199 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
200 200
201 const videoPlaylist = getPlaylist(res) 201 const videoPlaylist = getPlaylist(res)
202 const video = res.locals.onlyVideo
203
204 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id)
205 if (videoPlaylistElement) {
206 res.status(409)
207 .json({ error: 'This video in this playlist already exists' })
208 .end()
209
210 return
211 }
212 202
213 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { 203 if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) {
214 return 204 return
@@ -258,15 +248,18 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [
258const videoPlaylistElementAPGetValidator = [ 248const videoPlaylistElementAPGetValidator = [
259 param('playlistId') 249 param('playlistId')
260 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), 250 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
261 param('videoId') 251 param('playlistElementId')
262 .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'), 252 .custom(isIdValid).withMessage('Should have an playlist element id'),
263 253
264 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 254 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
265 logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params }) 255 logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params })
266 256
267 if (areValidationErrors(req, res)) return 257 if (areValidationErrors(req, res)) return
268 258
269 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideoForAP(req.params.playlistId, req.params.videoId) 259 const playlistElementId = parseInt(req.params.playlistElementId + '', 10)
260 const playlistId = req.params.playlistId
261
262 const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId)
270 if (!videoPlaylistElement) { 263 if (!videoPlaylistElement) {
271 res.status(404) 264 res.status(404)
272 .json({ error: 'Video playlist element not found' }) 265 .json({ error: 'Video playlist element not found' })
diff --git a/server/models/utils.ts b/server/models/utils.ts
index d706d9ea8..6e5522346 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -129,6 +129,30 @@ function buildBlockedAccountSQL (blockerIds: number[]) {
129 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' 129 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
130} 130}
131 131
132function buildBlockedAccountSQLOptimized (columnNameJoin: string, blockerIds: number[]) {
133 const blockerIdsString = blockerIds.join(', ')
134
135 return [
136 literal(
137 `NOT EXISTS (` +
138 ` SELECT 1 FROM "accountBlocklist" ` +
139 ` WHERE "targetAccountId" = ${columnNameJoin} ` +
140 ` AND "accountId" IN (${blockerIdsString})` +
141 `)`
142 ),
143
144 literal(
145 `NOT EXISTS (` +
146 ` SELECT 1 FROM "account" ` +
147 ` INNER JOIN "actor" ON account."actorId" = actor.id ` +
148 ` INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ` +
149 ` WHERE "account"."id" = ${columnNameJoin} ` +
150 ` AND "serverBlocklist"."accountId" IN (${blockerIdsString})` +
151 `)`
152 )
153 ]
154}
155
132function buildServerIdsFollowedBy (actorId: any) { 156function buildServerIdsFollowedBy (actorId: any) {
133 const actorIdNumber = parseInt(actorId + '', 10) 157 const actorIdNumber = parseInt(actorId + '', 10)
134 158
@@ -201,6 +225,7 @@ function searchAttribute (sourceField?: string, targetField?: string) {
201 225
202export { 226export {
203 buildBlockedAccountSQL, 227 buildBlockedAccountSQL,
228 buildBlockedAccountSQLOptimized,
204 buildLocalActorIdsIn, 229 buildLocalActorIdsIn,
205 SortType, 230 SortType,
206 buildLocalAccountIdsIn, 231 buildLocalAccountIdsIn,
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index 1d5c7280d..de27b3d87 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -1,6 +1,6 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { uniq } from 'lodash' 2import { uniq } from 'lodash'
3import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' 3import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
4import { 4import {
5 AllowNull, 5 AllowNull,
6 BelongsTo, 6 BelongsTo,
@@ -40,7 +40,7 @@ import {
40import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' 40import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
41import { AccountModel } from '../account/account' 41import { AccountModel } from '../account/account'
42import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' 42import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor'
43import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' 43import { buildBlockedAccountSQL, buildBlockedAccountSQLOptimized, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils'
44import { VideoModel } from './video' 44import { VideoModel } from './video'
45import { VideoChannelModel } from './video-channel' 45import { VideoChannelModel } from './video-channel'
46 46
@@ -460,19 +460,20 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
460 const serverActor = await getServerActor() 460 const serverActor = await getServerActor()
461 const { start, count, videoId, accountId, videoChannelId } = parameters 461 const { start, count, videoId, accountId, videoChannelId } = parameters
462 462
463 const accountExclusion = { 463 const whereAnd: WhereOptions[] = buildBlockedAccountSQLOptimized(
464 [Op.notIn]: Sequelize.literal( 464 '"VideoCommentModel"."accountId"',
465 '(' + buildBlockedAccountSQL([ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]) + ')' 465 [ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]
466 ) 466 )
467
468 if (accountId) {
469 whereAnd.push({
470 [Op.eq]: accountId
471 })
472 }
473
474 const accountWhere = {
475 [Op.and]: whereAnd
467 } 476 }
468 const accountWhere = accountId
469 ? {
470 [Op.and]: {
471 ...accountExclusion,
472 [Op.eq]: accountId
473 }
474 }
475 : accountExclusion
476 477
477 const videoChannelWhere = videoChannelId ? { id: videoChannelId } : undefined 478 const videoChannelWhere = videoChannelId ? { id: videoChannelId } : undefined
478 479
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index 9b6509dfd..7a17c839f 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -78,7 +78,10 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor
78 78
79 userHistory: userHistory ? { 79 userHistory: userHistory ? {
80 currentTime: userHistory.currentTime 80 currentTime: userHistory.currentTime
81 } : undefined 81 } : undefined,
82
83 // Can be added by external plugins
84 pluginData: (video as any).pluginData
82 } 85 }
83 86
84 if (options) { 87 if (options) {
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index ba92e129a..d357766e9 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -44,10 +44,6 @@ import { MUserAccountId } from '@server/types/models'
44 fields: [ 'videoId' ] 44 fields: [ 'videoId' ]
45 }, 45 },
46 { 46 {
47 fields: [ 'videoPlaylistId', 'videoId' ],
48 unique: true
49 },
50 {
51 fields: [ 'url' ], 47 fields: [ 'url' ],
52 unique: true 48 unique: true
53 } 49 }
@@ -60,8 +56,8 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
60 @UpdatedAt 56 @UpdatedAt
61 updatedAt: Date 57 updatedAt: Date
62 58
63 @AllowNull(false) 59 @AllowNull(true)
64 @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) 60 @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url', true))
65 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max)) 61 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max))
66 url: string 62 url: string
67 63
@@ -185,12 +181,11 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
185 return VideoPlaylistElementModel.findByPk(playlistElementId) 181 return VideoPlaylistElementModel.findByPk(playlistElementId)
186 } 182 }
187 183
188 static loadByPlaylistAndVideoForAP ( 184 static loadByPlaylistAndElementIdForAP (
189 playlistId: number | string, 185 playlistId: number | string,
190 videoId: number | string 186 playlistElementId: number
191 ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> { 187 ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> {
192 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } 188 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId }
193 const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId }
194 189
195 const query = { 190 const query = {
196 include: [ 191 include: [
@@ -201,10 +196,12 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
201 }, 196 },
202 { 197 {
203 attributes: [ 'url' ], 198 attributes: [ 'url' ],
204 model: VideoModel.unscoped(), 199 model: VideoModel.unscoped()
205 where: videoWhere
206 } 200 }
207 ] 201 ],
202 where: {
203 id: playlistElementId
204 }
208 } 205 }
209 206
210 return VideoPlaylistElementModel.findOne(query) 207 return VideoPlaylistElementModel.findOne(query)
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts
index 46ec00d46..179ae9201 100644
--- a/server/tests/api/check-params/video-playlists.ts
+++ b/server/tests/api/check-params/video-playlists.ts
@@ -346,11 +346,6 @@ describe('Test video playlists API validator', function () {
346 const res = await addVideoInPlaylist(params) 346 const res = await addVideoInPlaylist(params)
347 playlistElementId = res.body.videoPlaylistElement.id 347 playlistElementId = res.body.videoPlaylistElement.id
348 }) 348 })
349
350 it('Should fail if the video was already added in the playlist', async function () {
351 const params = getBase({}, { expectedStatus: 409 })
352 await addVideoInPlaylist(params)
353 })
354 }) 349 })
355 350
356 describe('When updating an element in a playlist', function () { 351 describe('When updating an element in a playlist', function () {
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts
index af4ff42b0..edc95b069 100644
--- a/server/tests/api/notifications/user-notifications.ts
+++ b/server/tests/api/notifications/user-notifications.ts
@@ -65,7 +65,7 @@ describe('Test user notifications', function () {
65 }) 65 })
66 66
67 it('Should not send notifications if the user does not follow the video publisher', async function () { 67 it('Should not send notifications if the user does not follow the video publisher', async function () {
68 this.timeout(10000) 68 this.timeout(30000)
69 69
70 await uploadRandomVideoOnServers(servers, 1) 70 await uploadRandomVideoOnServers(servers, 1)
71 71
@@ -97,7 +97,7 @@ describe('Test user notifications', function () {
97 }) 97 })
98 98
99 it('Should send a new video notification on a scheduled publication', async function () { 99 it('Should send a new video notification on a scheduled publication', async function () {
100 this.timeout(20000) 100 this.timeout(30000)
101 101
102 // In 2 seconds 102 // In 2 seconds
103 const updateAt = new Date(new Date().getTime() + 2000) 103 const updateAt = new Date(new Date().getTime() + 2000)
@@ -136,7 +136,7 @@ describe('Test user notifications', function () {
136 }) 136 })
137 137
138 it('Should not send a notification before the video is published', async function () { 138 it('Should not send a notification before the video is published', async function () {
139 this.timeout(20000) 139 this.timeout(30000)
140 140
141 const updateAt = new Date(new Date().getTime() + 1000000) 141 const updateAt = new Date(new Date().getTime() + 1000000)
142 142
@@ -154,7 +154,7 @@ describe('Test user notifications', function () {
154 }) 154 })
155 155
156 it('Should send a new video notification when a video becomes public', async function () { 156 it('Should send a new video notification when a video becomes public', async function () {
157 this.timeout(10000) 157 this.timeout(30000)
158 158
159 const data = { privacy: VideoPrivacy.PRIVATE } 159 const data = { privacy: VideoPrivacy.PRIVATE }
160 const { name, uuid } = await uploadRandomVideoOnServers(servers, 1, data) 160 const { name, uuid } = await uploadRandomVideoOnServers(servers, 1, data)
@@ -168,7 +168,7 @@ describe('Test user notifications', function () {
168 }) 168 })
169 169
170 it('Should send a new video notification when a remote video becomes public', async function () { 170 it('Should send a new video notification when a remote video becomes public', async function () {
171 this.timeout(20000) 171 this.timeout(30000)
172 172
173 const data = { privacy: VideoPrivacy.PRIVATE } 173 const data = { privacy: VideoPrivacy.PRIVATE }
174 const { name, uuid } = await uploadRandomVideoOnServers(servers, 2, data) 174 const { name, uuid } = await uploadRandomVideoOnServers(servers, 2, data)
@@ -182,7 +182,7 @@ describe('Test user notifications', function () {
182 }) 182 })
183 183
184 it('Should not send a new video notification when a video becomes unlisted', async function () { 184 it('Should not send a new video notification when a video becomes unlisted', async function () {
185 this.timeout(20000) 185 this.timeout(30000)
186 186
187 const data = { privacy: VideoPrivacy.PRIVATE } 187 const data = { privacy: VideoPrivacy.PRIVATE }
188 const { name, uuid } = await uploadRandomVideoOnServers(servers, 1, data) 188 const { name, uuid } = await uploadRandomVideoOnServers(servers, 1, data)
@@ -193,7 +193,7 @@ describe('Test user notifications', function () {
193 }) 193 })
194 194
195 it('Should not send a new video notification when a remote video becomes unlisted', async function () { 195 it('Should not send a new video notification when a remote video becomes unlisted', async function () {
196 this.timeout(20000) 196 this.timeout(30000)
197 197
198 const data = { privacy: VideoPrivacy.PRIVATE } 198 const data = { privacy: VideoPrivacy.PRIVATE }
199 const { name, uuid } = await uploadRandomVideoOnServers(servers, 2, data) 199 const { name, uuid } = await uploadRandomVideoOnServers(servers, 2, data)
@@ -237,7 +237,7 @@ describe('Test user notifications', function () {
237 }) 237 })
238 238
239 it('Should not send a notification if transcoding is not enabled', async function () { 239 it('Should not send a notification if transcoding is not enabled', async function () {
240 this.timeout(10000) 240 this.timeout(30000)
241 241
242 const { name, uuid } = await uploadRandomVideoOnServers(servers, 1) 242 const { name, uuid } = await uploadRandomVideoOnServers(servers, 1)
243 await waitJobs(servers) 243 await waitJobs(servers)
@@ -416,7 +416,7 @@ describe('Test user notifications', function () {
416 }) 416 })
417 417
418 it('Should notify when a local channel is following one of our channel', async function () { 418 it('Should notify when a local channel is following one of our channel', async function () {
419 this.timeout(10000) 419 this.timeout(30000)
420 420
421 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port) 421 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
422 await waitJobs(servers) 422 await waitJobs(servers)
@@ -427,7 +427,7 @@ describe('Test user notifications', function () {
427 }) 427 })
428 428
429 it('Should notify when a remote channel is following one of our channel', async function () { 429 it('Should notify when a remote channel is following one of our channel', async function () {
430 this.timeout(10000) 430 this.timeout(30000)
431 431
432 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port) 432 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
433 await waitJobs(servers) 433 await waitJobs(servers)
@@ -439,7 +439,7 @@ describe('Test user notifications', function () {
439 439
440 // PeerTube does not support accout -> account follows 440 // PeerTube does not support accout -> account follows
441 // it('Should notify when a local account is following one of our channel', async function () { 441 // it('Should notify when a local account is following one of our channel', async function () {
442 // this.timeout(10000) 442 // this.timeout(30000)
443 // 443 //
444 // await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:' + servers[0].port) 444 // await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:' + servers[0].port)
445 // 445 //
@@ -449,7 +449,7 @@ describe('Test user notifications', function () {
449 // }) 449 // })
450 450
451 // it('Should notify when a remote account is following one of our channel', async function () { 451 // it('Should notify when a remote account is following one of our channel', async function () {
452 // this.timeout(10000) 452 // this.timeout(30000)
453 // 453 //
454 // await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:' + servers[0].port) 454 // await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:' + servers[0].port)
455 // 455 //
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index 52b32998d..0bfb5bcd4 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -552,6 +552,9 @@ describe('Test video playlists', function () {
552 { 552 {
553 const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) 553 const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 })
554 playlistElementNSFW = res.body.videoPlaylistElement.id 554 playlistElementNSFW = res.body.videoPlaylistElement.id
555
556 await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 })
557 await addVideo({ videoId: nsfwVideoServer1 })
555 } 558 }
556 559
557 await waitJobs(servers) 560 await waitJobs(servers)
@@ -563,10 +566,10 @@ describe('Test video playlists', function () {
563 for (const server of servers) { 566 for (const server of servers) {
564 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) 567 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
565 568
566 expect(res.body.total).to.equal(6) 569 expect(res.body.total).to.equal(8)
567 570
568 const videoElements: VideoPlaylistElement[] = res.body.data 571 const videoElements: VideoPlaylistElement[] = res.body.data
569 expect(videoElements).to.have.lengthOf(6) 572 expect(videoElements).to.have.lengthOf(8)
570 573
571 expect(videoElements[0].video.name).to.equal('video 0 server 1') 574 expect(videoElements[0].video.name).to.equal('video 0 server 1')
572 expect(videoElements[0].position).to.equal(1) 575 expect(videoElements[0].position).to.equal(1)
@@ -598,6 +601,16 @@ describe('Test video playlists', function () {
598 expect(videoElements[5].startTimestamp).to.equal(5) 601 expect(videoElements[5].startTimestamp).to.equal(5)
599 expect(videoElements[5].stopTimestamp).to.be.null 602 expect(videoElements[5].stopTimestamp).to.be.null
600 603
604 expect(videoElements[6].video.name).to.equal('NSFW video')
605 expect(videoElements[6].position).to.equal(7)
606 expect(videoElements[6].startTimestamp).to.equal(4)
607 expect(videoElements[6].stopTimestamp).to.be.null
608
609 expect(videoElements[7].video.name).to.equal('NSFW video')
610 expect(videoElements[7].position).to.equal(8)
611 expect(videoElements[7].startTimestamp).to.be.null
612 expect(videoElements[7].stopTimestamp).to.be.null
613
601 const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) 614 const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2)
602 expect(res3.body.data).to.have.lengthOf(2) 615 expect(res3.body.data).to.have.lengthOf(2)
603 } 616 }
@@ -807,6 +820,8 @@ describe('Test video playlists', function () {
807 'video 1 server 3', 820 'video 1 server 3',
808 'video 3 server 1', 821 'video 3 server 1',
809 'video 4 server 1', 822 'video 4 server 1',
823 'NSFW video',
824 'NSFW video',
810 'NSFW video' 825 'NSFW video'
811 ]) 826 ])
812 } 827 }
@@ -836,6 +851,8 @@ describe('Test video playlists', function () {
836 'video 2 server 3', 851 'video 2 server 3',
837 'video 1 server 3', 852 'video 1 server 3',
838 'video 4 server 1', 853 'video 4 server 1',
854 'NSFW video',
855 'NSFW video',
839 'NSFW video' 856 'NSFW video'
840 ]) 857 ])
841 } 858 }
@@ -865,7 +882,9 @@ describe('Test video playlists', function () {
865 'video 2 server 3', 882 'video 2 server 3',
866 'NSFW video', 883 'NSFW video',
867 'video 1 server 3', 884 'video 1 server 3',
868 'video 4 server 1' 885 'video 4 server 1',
886 'NSFW video',
887 'NSFW video'
869 ]) 888 ])
870 889
871 for (let i = 1; i <= elements.length; i++) { 890 for (let i = 1; i <= elements.length; i++) {
@@ -1023,10 +1042,10 @@ describe('Test video playlists', function () {
1023 for (const server of servers) { 1042 for (const server of servers) {
1024 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) 1043 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
1025 1044
1026 expect(res.body.total).to.equal(4) 1045 expect(res.body.total).to.equal(6)
1027 1046
1028 const elements: VideoPlaylistElement[] = res.body.data 1047 const elements: VideoPlaylistElement[] = res.body.data
1029 expect(elements).to.have.lengthOf(4) 1048 expect(elements).to.have.lengthOf(6)
1030 1049
1031 expect(elements[0].video.name).to.equal('video 0 server 1') 1050 expect(elements[0].video.name).to.equal('video 0 server 1')
1032 expect(elements[0].position).to.equal(1) 1051 expect(elements[0].position).to.equal(1)
@@ -1039,6 +1058,12 @@ describe('Test video playlists', function () {
1039 1058
1040 expect(elements[3].video.name).to.equal('video 4 server 1') 1059 expect(elements[3].video.name).to.equal('video 4 server 1')
1041 expect(elements[3].position).to.equal(4) 1060 expect(elements[3].position).to.equal(4)
1061
1062 expect(elements[4].video.name).to.equal('NSFW video')
1063 expect(elements[4].position).to.equal(5)
1064
1065 expect(elements[5].video.name).to.equal('NSFW video')
1066 expect(elements[5].position).to.equal(6)
1042 } 1067 }
1043 }) 1068 })
1044 1069